mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-07-22 12:01:57 +00:00
Compare commits
10 Commits
v0.37.2
...
feat/circu
Author | SHA1 | Date | |
---|---|---|---|
|
6d83603541 | ||
|
f74e4d5d13 | ||
|
04297984d0 | ||
|
29a74c5c36 | ||
|
fa314ebfe0 | ||
|
4866b279e0 | ||
|
84e38d7e95 | ||
|
abe2b22af6 | ||
|
b2be4637e2 | ||
|
7815e44427 |
@@ -1,21 +1,21 @@
|
||||
import { WebSockets } from '@libp2p/websockets'
|
||||
import { Mplex } from '@libp2p/mplex'
|
||||
import { NOISE } from '@chainsafe/libp2p-noise'
|
||||
import { pipe } from 'it-pipe'
|
||||
import { createFromJSON } from '@libp2p/peer-id-factory'
|
||||
'use strict'
|
||||
|
||||
/** @type {import('aegir').PartialOptions} */
|
||||
export default {
|
||||
module.exports = {
|
||||
build: {
|
||||
bundlesizeMax: '147kB'
|
||||
bundlesizeMax: '253kB'
|
||||
},
|
||||
test: {
|
||||
before: async () => {
|
||||
// use dynamic import because we only want to reference these files during the test run, e.g. after building
|
||||
const { createLibp2p } = await import('./dist/src/index.js')
|
||||
const { MULTIADDRS_WEBSOCKETS } = await import('./dist/test/fixtures/browser.js')
|
||||
const { Plaintext } = await import('./dist/src/insecure/index.js')
|
||||
const { default: Peers } = await import('./dist/test/fixtures/peers.js')
|
||||
const { WebSockets } = await import('@libp2p/websockets')
|
||||
const { Mplex } = await import('@libp2p/mplex')
|
||||
const { NOISE } = await import('@chainsafe/libp2p-noise')
|
||||
const { Plaintext } = await import('./dist/src/insecure/index.js')
|
||||
const { pipe } = await import('it-pipe')
|
||||
const { createFromJSON } = await import('@libp2p/peer-id-factory')
|
||||
|
||||
// Use the last peer
|
||||
const peerId = await createFromJSON(Peers[Peers.length - 1])
|
43
CHANGELOG.md
43
CHANGELOG.md
@@ -10,49 +10,6 @@
|
||||
|
||||
|
||||
|
||||
### [0.37.2](https://www.github.com/libp2p/js-libp2p/compare/v0.37.1...v0.37.2) (2022-05-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* reduce identify message size limit ([#1230](https://www.github.com/libp2p/js-libp2p/issues/1230)) ([824720f](https://www.github.com/libp2p/js-libp2p/commit/824720fb8f21f868ed88e881fbc3ce6b9459600d))
|
||||
|
||||
### [0.37.1](https://www.github.com/libp2p/js-libp2p/compare/v0.37.0...v0.37.1) (2022-05-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do upnp hole punch after startup ([#1217](https://www.github.com/libp2p/js-libp2p/issues/1217)) ([d5386df](https://www.github.com/libp2p/js-libp2p/commit/d5386df68478a71ac269acb2d00d36a7a5c9ebc5))
|
||||
* explicitly close streams when connnections close ([#1221](https://www.github.com/libp2p/js-libp2p/issues/1221)) ([b09eb8f](https://www.github.com/libp2p/js-libp2p/commit/b09eb8fc53ec1d8f6280d681c9ca6a467ec259b5))
|
||||
* fix unintended aborts in dialer ([#1185](https://www.github.com/libp2p/js-libp2p/issues/1185)) ([35f9c0c](https://www.github.com/libp2p/js-libp2p/commit/35f9c0c79387232465848b450a47cafe841405e7))
|
||||
* time out slow reads ([#1227](https://www.github.com/libp2p/js-libp2p/issues/1227)) ([a1220d2](https://www.github.com/libp2p/js-libp2p/commit/a1220d22f5affb64e64dec0cd6a92cd8241b26df))
|
||||
|
||||
## [0.37.0](https://www.github.com/libp2p/js-libp2p/compare/v0.36.2...v0.37.0) (2022-05-16)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* types are no longer hand crafted, this module is now ESM only
|
||||
|
||||
### Features
|
||||
|
||||
* convert to typescript ([#1172](https://www.github.com/libp2p/js-libp2p/issues/1172)) ([199395d](https://www.github.com/libp2p/js-libp2p/commit/199395de4d8139cc77d0b408626f37c9b8520d28))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add transport manager to exports map and fix docs ([#1182](https://www.github.com/libp2p/js-libp2p/issues/1182)) ([cc60cfd](https://www.github.com/libp2p/js-libp2p/commit/cc60cfde1a0907ca68f658f6de5362a708189222))
|
||||
* emit peer:connect after all ([#1171](https://www.github.com/libp2p/js-libp2p/issues/1171)) ([d16817c](https://www.github.com/libp2p/js-libp2p/commit/d16817ca443443e88803ee8096d45debb14af91b))
|
||||
* encode enums correctly ([#1210](https://www.github.com/libp2p/js-libp2p/issues/1210)) ([4837430](https://www.github.com/libp2p/js-libp2p/commit/4837430d8bcdbee0865eeba6fe694bc71fc6c9bb))
|
||||
* expose getPublicKey ([#1188](https://www.github.com/libp2p/js-libp2p/issues/1188)) ([1473044](https://www.github.com/libp2p/js-libp2p/commit/147304449e5f8d3acb8b00bdd9588b56830667c6))
|
||||
* expose metrics and registrar, use dht for peer discovery ([#1183](https://www.github.com/libp2p/js-libp2p/issues/1183)) ([64bfcee](https://www.github.com/libp2p/js-libp2p/commit/64bfcee5093b368df0b381f78afc2ddff3d339a9))
|
||||
* simplify pnet exports ([#1213](https://www.github.com/libp2p/js-libp2p/issues/1213)) ([3148060](https://www.github.com/libp2p/js-libp2p/commit/31480603f3e17d838d2685573995218a1e678e7a))
|
||||
* update deps ([#1181](https://www.github.com/libp2p/js-libp2p/issues/1181)) ([8cca8e4](https://www.github.com/libp2p/js-libp2p/commit/8cca8e4bfc6a339e58b5a5efa8a84fd891aa08ee))
|
||||
* update interfaces ([#1207](https://www.github.com/libp2p/js-libp2p/issues/1207)) ([da3d19b](https://www.github.com/libp2p/js-libp2p/commit/da3d19b30977fd2c7e77d92aa8914b13e3179aaa))
|
||||
* update pubsub interfaces ([#1194](https://www.github.com/libp2p/js-libp2p/issues/1194)) ([fab4f13](https://www.github.com/libp2p/js-libp2p/commit/fab4f1385cf61b7b16719b9aacdfe03146a3f260))
|
||||
* update to new interfaces ([#1206](https://www.github.com/libp2p/js-libp2p/issues/1206)) ([a15254f](https://www.github.com/libp2p/js-libp2p/commit/a15254fdd478a336edf1e1196b721dc56888b2ea))
|
||||
* use placeholder dht/pubsub ([#1193](https://www.github.com/libp2p/js-libp2p/issues/1193)) ([5397137](https://www.github.com/libp2p/js-libp2p/commit/5397137c654dfdec431e0c9ba4b1ff9dee19abf1))
|
||||
|
||||
### [0.36.2](https://www.github.com/libp2p/js-libp2p/compare/v0.36.1...v0.36.2) (2022-01-26)
|
||||
|
||||
|
||||
|
@@ -97,9 +97,7 @@ Creates an instance of Libp2p.
|
||||
| options.modules | [`Array<object>`](./CONFIGURATION.md#modules) | libp2p [modules](./CONFIGURATION.md#modules) to use |
|
||||
| [options.addresses] | `{ listen: Array<string>, announce: Array<string>, announceFilter: (ma: Array<multiaddr>) => Array<multiaddr> }` | Addresses for transport listening and to advertise to the network |
|
||||
| [options.config] | `object` | libp2p modules configuration and core configuration |
|
||||
| [options.identify] | `{ protocolPrefix: string, host: { agentVersion: string }, timeout: number, maxIdentifyMessageSize: number }` | libp2p identify protocol options |
|
||||
| [options.ping] | `{ protocolPrefix: string }` | libp2p ping protocol options |
|
||||
| [options.fetch] | `{ protocolPrefix: string }` | libp2p fetch protocol options |
|
||||
| [options.host] | `{ agentVersion: string }` | libp2p host options |
|
||||
| [options.connectionManager] | [`object`](./CONFIGURATION.md#configuring-connection-manager) | libp2p Connection Manager [configuration](./CONFIGURATION.md#configuring-connection-manager) |
|
||||
| [options.transportManager] | [`object`](./CONFIGURATION.md#configuring-transport-manager) | libp2p transport manager [configuration](./CONFIGURATION.md#configuring-transport-manager) |
|
||||
| [options.datastore] | `object` | must implement [ipfs/interface-datastore](https://github.com/ipfs/interface-datastore) (in memory datastore will be used if not provided) |
|
||||
|
@@ -885,12 +885,7 @@ Changing the protocol name prefix can isolate default public network (IPFS) for
|
||||
|
||||
```js
|
||||
const node = await createLibp2p({
|
||||
identify: {
|
||||
protocolPrefix: 'ipfs' // default
|
||||
},
|
||||
ping: {
|
||||
protocolPrefix: 'ipfs' // default
|
||||
}
|
||||
protocolPrefix: 'ipfs' // default
|
||||
})
|
||||
/*
|
||||
protocols: [
|
||||
|
@@ -1,262 +0,0 @@
|
||||
<!--Specify versions for migration below-->
|
||||
# Migrating to libp2p@37 <!-- omit in toc -->
|
||||
|
||||
A migration guide for refactoring your application code from libp2p v0.36.x to v0.37.0.
|
||||
|
||||
## Table of Contents <!-- omit in toc -->
|
||||
|
||||
- [ESM](#esm)
|
||||
- [TypeScript](#typescript)
|
||||
- [Config](#config)
|
||||
- [Bundled modules](#bundled-modules)
|
||||
- [Events](#events)
|
||||
- [Pubsub](#pubsub)
|
||||
|
||||
## ESM
|
||||
|
||||
The biggest change to `libp2p@0.37.0` is that the module is now [ESM-only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).
|
||||
|
||||
ESM is the module system for JavaScript, it allows us to structure our code in separate files without polluting a global namespace.
|
||||
|
||||
Other systems have tried to fill this gap, notably CommonJS, AMD, RequireJS and others, but ESM is [the official standard format](https://tc39.es/ecma262/#sec-modules) to package JavaScript code for reuse.
|
||||
|
||||
## TypeScript
|
||||
|
||||
The core `libp2p` module and all supporting modules have now been ported to TypeScript in a complete ground-up rewrite. This will not have a huge impact on most application code, but those that are type-aware, either by being written in TypeScript themselves or using JSDoc comments will notice full type completion and better error message when coding against the libp2p API.
|
||||
|
||||
To reflect the updated nature of these modules, all ecosystem modules have been moved to the `@libp2p` org on npm, so `libp2p-tcp` has become `@libp2p/tcp`, `libp2p-mplex` has become `@libp2p/mplex` and so on. `@chainsafe/libp2p-noise` and `libp2p-gossipsub` are unaffected.
|
||||
|
||||
## Config
|
||||
|
||||
Because libp2p is now fully typed it was necessary to refactor the configuration object passed to the libp2p constructor. The reason being, it previously accepted config objects to pass to the constructors of the various modules - to type those we'd need to know the types of all possible modules in advance which isn't possible.
|
||||
|
||||
The following changes have been made to the configuration object:
|
||||
|
||||
1. It now takes instances of modules rather than their classes
|
||||
2. Keys from the `config` and `modules` objects have been migrated to the root of the object
|
||||
3. Use of the `enabled` flag has been removed - if you don't want a particular feature enabled, don't pass a module implementing that feature
|
||||
4. Some keys have been renamed = `transport` -> `transports`, `streamMuxer` -> `streamMuxers`, `connEncryption` -> `connectionEncryption`, etc
|
||||
5. Keys from `config.dialer` have been moved to `config.connectionManager` as the connection manager is now responsible for managing connections
|
||||
6. The `protocolPrefix` configuration option is now passed on a per-protocol basis for `identify`, `fetch` and `ping`
|
||||
|
||||
**Before**
|
||||
|
||||
```js
|
||||
import Libp2p from 'libp2p'
|
||||
import TCP from 'libp2p-tcp'
|
||||
import Mplex from 'libp2p-mplex'
|
||||
import { NOISE } from '@chainsafe/libp2p-noise'
|
||||
import Gossipsub from 'libp2p-gossipsub'
|
||||
import KadDHT from 'libp2p-kad-dht'
|
||||
import Bootstrap from 'libp2p-bootstrap'
|
||||
import MulticastDNS from 'libp2p-mdns'
|
||||
|
||||
const node = await Libp2p.create({
|
||||
addresses: {
|
||||
listen: ['/ip4/127.0.0.1/tcp/8000']
|
||||
},
|
||||
modules: {
|
||||
transport: [
|
||||
TCP
|
||||
],
|
||||
streamMuxer: [
|
||||
Mplex
|
||||
],
|
||||
connEncryption: [
|
||||
NOISE
|
||||
],
|
||||
dht: KadDHT,
|
||||
pubsub: Gossipsub,
|
||||
peerDiscovery: [
|
||||
Bootstrap,
|
||||
MulticastDNS
|
||||
]
|
||||
},
|
||||
protocolPrefix: 'ipfs',
|
||||
config: {
|
||||
peerDiscovery: {
|
||||
autoDial: true,
|
||||
[MulticastDNS.tag]: {
|
||||
interval: 1000,
|
||||
enabled: true
|
||||
},
|
||||
[Bootstrap.tag]: {
|
||||
list: [
|
||||
// .. multiaddrs here
|
||||
],
|
||||
interval: 2000,
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
dialer: {
|
||||
dialTimeout: 60000
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { TCP } from '@libp2p/tcp'
|
||||
import { Mplex } from '@libp2p/mplex'
|
||||
import { Noise } from '@chainsafe/libp2p-noise'
|
||||
import Gossipsub from '@chainsafe/libp2p-gossipsub'
|
||||
import { KadDHT } from '@libp2p/kad-dht'
|
||||
import { Bootstrap } from '@libp2p/bootstrap'
|
||||
import { MulticastDNS } from '@libp2p/mdns'
|
||||
|
||||
const node = await createLibp2p({
|
||||
addresses: {
|
||||
listen: ['/ip4/127.0.0.1/tcp/8000']
|
||||
},
|
||||
addressManager: {
|
||||
autoDial: true
|
||||
},
|
||||
connectionManager: {
|
||||
dialTimeout: 60000
|
||||
},
|
||||
transports: [
|
||||
new TCP()
|
||||
],
|
||||
streamMuxers: [
|
||||
new Mplex()
|
||||
],
|
||||
connectionEncryption: [
|
||||
new Noise()
|
||||
],
|
||||
dht: new KadDHT(),
|
||||
pubsub: new Gossipsub(),
|
||||
peerDiscovery: [
|
||||
new Bootstrap({
|
||||
list: [
|
||||
// .. multiaddrs here
|
||||
],
|
||||
interval: 2000
|
||||
}),
|
||||
new MulticastDNS({
|
||||
interval: 1000
|
||||
})
|
||||
],
|
||||
identify: {
|
||||
protocolPrefix: 'ipfs'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Bundled modules
|
||||
|
||||
Previously you'd have to use deep import paths to get at bundled modules such as the private network module.
|
||||
|
||||
Access to these modules is now controlled by the package.json export map so your import paths will need to be updated:
|
||||
|
||||
**Before**
|
||||
|
||||
```js
|
||||
import plaintext from 'libp2p/src/insecure/plaintext.js'
|
||||
import Protector from 'libp2p/src/pnet/index.js'
|
||||
import generateKey from 'libp2p/src/pnet/key-generator.js'
|
||||
import TransportManager from 'libp2p/src/transport-manager.js'
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
import { Plaintext } from 'libp2p/insecure'
|
||||
import { PreSharedKeyConnectionProtector, generateKey } from 'libp2p/pnet'
|
||||
import { TransportManager } from 'libp2p/transport-manager'
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
To reduce our dependency on Node.js internals, use of [EventEmitter](https://nodejs.org/api/events.html#class-eventemitter) has been replaced with the standard [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget).
|
||||
|
||||
The EventTarget API is very similar to [HTML DOM Events](https://developer.mozilla.org/en-US/docs/Web/API/Event) used by the browser.
|
||||
|
||||
All events are instances of the [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) class. Event-specific information can be accessed via the `.detail` property of the passed event.
|
||||
|
||||
They type of event emitted can be inferred from the types for each event emitter.
|
||||
|
||||
**Before**
|
||||
|
||||
```js
|
||||
const handler = (peerInfo) => {
|
||||
//...
|
||||
}
|
||||
|
||||
// listen for event
|
||||
libp2p.on('peer:discovery', handler)
|
||||
|
||||
// stop listening for event
|
||||
libp2p.removeListener('peer:discovery', handler)
|
||||
libp2p.off('peer:discovery', handler)
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
const handler = (event) => {
|
||||
const peerInfo = event.detail
|
||||
//...
|
||||
}
|
||||
|
||||
// listen for event
|
||||
libp2p.addEventListener('peer:discovery', handler)
|
||||
|
||||
// stop listening for event
|
||||
libp2p.removeEventListener('peer:discovery', handler)
|
||||
```
|
||||
|
||||
## Pubsub
|
||||
|
||||
Similar to the events refactor above, pubsub is now driven by the standard [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) API.
|
||||
|
||||
You can still subscribe to events without a listener with `.subscribe` but all other uses now use the standard API.
|
||||
|
||||
Similar to the other events emitted by libp2p the event type is [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). This is part of the js language but at the time of writing Node.js [does not support](https://github.com/nodejs/node/issues/40678) `CustomEvent`, so a polyfill is supplied as part of the `@libp2p/interfaces`
|
||||
|
||||
**Before**
|
||||
|
||||
```js
|
||||
const handler = (message: Message) => {
|
||||
const topic = message.topic
|
||||
|
||||
//...
|
||||
}
|
||||
|
||||
// listen for event
|
||||
libp2p.pubsub.subscribe('my-topic')
|
||||
libp2p.pubsub.on('my-topic', handler)
|
||||
|
||||
// send event
|
||||
libp2p.pubsub.emit('my-topic', Uint8Array.from([0, 1, 2, 3]))
|
||||
|
||||
// stop listening for event
|
||||
libp2p.unsubscribe('my-topic', handler)
|
||||
libp2p.pubsub.off('my-topic', handler)
|
||||
```
|
||||
|
||||
**After**
|
||||
|
||||
```js
|
||||
import type { Message } from '@libp2p/interfaces/pubsub'
|
||||
|
||||
const handler = (event: CustomEvent<Message>) => {
|
||||
const message = event.detail
|
||||
const topic = message.topic
|
||||
|
||||
//...
|
||||
}
|
||||
|
||||
// listen for event
|
||||
libp2p.pubsub.subscribe('my-topic')
|
||||
libp2p.pubsub.addEventListener('message', handler)
|
||||
|
||||
// send event
|
||||
libp2p.pubsub.publish('my-topic', Uint8Array.from([0, 1, 2, 3]))
|
||||
|
||||
// stop listening for event
|
||||
libp2p.pubsub.unsubscribe('my-topic')
|
||||
libp2p.pubsub.removeEventListener('message', handler)
|
||||
```
|
@@ -3,15 +3,15 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@chainsafe/libp2p-noise": "^6.2.0",
|
||||
"@chainsafe/libp2p-noise": "^6.0.1",
|
||||
"ipfs-core": "^0.14.1",
|
||||
"libp2p": "../../",
|
||||
"@libp2p/delegated-content-routing": "^1.0.1",
|
||||
"@libp2p/delegated-peer-routing": "^1.0.1",
|
||||
"@libp2p/kad-dht": "^1.0.9",
|
||||
"@libp2p/mplex": "^1.0.4",
|
||||
"@libp2p/webrtc-star": "^1.0.8",
|
||||
"@libp2p/websockets": "^1.0.7",
|
||||
"@libp2p/kad-dht": "^1.0.1",
|
||||
"@libp2p/mplex": "^1.0.2",
|
||||
"@libp2p/webrtc-star": "^1.0.6",
|
||||
"@libp2p/websockets": "^1.0.3",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "5.0.0"
|
||||
|
@@ -4,7 +4,7 @@ import { createLibp2p } from 'libp2p'
|
||||
import { TCP } from '@libp2p/tcp'
|
||||
import { Mplex } from '@libp2p/mplex'
|
||||
import { Noise } from '@chainsafe/libp2p-noise'
|
||||
import { FloodSub } from '@libp2p/floodsub'
|
||||
import { Gossipsub } from '@achingbrain/libp2p-gossipsub'
|
||||
import { Bootstrap } from '@libp2p/bootstrap'
|
||||
import { PubSubPeerDiscovery } from '@libp2p/pubsub-peer-discovery'
|
||||
|
||||
@@ -16,7 +16,7 @@ const createNode = async (bootstrappers) => {
|
||||
transports: [new TCP()],
|
||||
streamMuxers: [new Mplex()],
|
||||
connectionEncryption: [new Noise()],
|
||||
pubsub: new FloodSub(),
|
||||
pubsub: new Gossipsub(),
|
||||
peerDiscovery: [
|
||||
new Bootstrap({
|
||||
list: bootstrappers
|
||||
@@ -40,7 +40,7 @@ const createNode = async (bootstrappers) => {
|
||||
transports: [new TCP()],
|
||||
streamMuxers: [new Mplex()],
|
||||
connectionEncryption: [new Noise()],
|
||||
pubsub: new FloodSub(),
|
||||
pubsub: new Gossipsub(),
|
||||
peerDiscovery: [
|
||||
new PubSubPeerDiscovery({
|
||||
interval: 1000
|
||||
|
@@ -3,17 +3,20 @@
|
||||
"version": "1.0.0",
|
||||
"description": "A libp2p node running in the browser",
|
||||
"type": "module",
|
||||
"browserslist": [
|
||||
"last 2 Chrome versions"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "vite"
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@chainsafe/libp2p-noise": "^6.2.0",
|
||||
"@libp2p/bootstrap": "^1.0.4",
|
||||
"@libp2p/mplex": "^1.0.4",
|
||||
"@libp2p/webrtc-star": "^1.0.8",
|
||||
"@libp2p/websockets": "^1.0.7",
|
||||
"@chainsafe/libp2p-noise": "^6.0.1",
|
||||
"@libp2p/bootstrap": "^1.0.1",
|
||||
"@libp2p/mplex": "^1.0.2",
|
||||
"@libp2p/webrtc-star": "^1.0.6",
|
||||
"@libp2p/websockets": "^1.0.3",
|
||||
"libp2p": "../../"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@@ -1,5 +0,0 @@
|
||||
export default {
|
||||
build: {
|
||||
target: 'es2020'
|
||||
}
|
||||
}
|
@@ -9,9 +9,8 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@libp2p/pubsub-peer-discovery": "^5.0.2",
|
||||
"@libp2p/floodsub": "^1.0.6",
|
||||
"@nodeutils/defaults-deep": "^1.1.0",
|
||||
"@achingbrain/libp2p-gossipsub": "^0.13.5",
|
||||
"@libp2p/pubsub-peer-discovery": "^5.0.1",
|
||||
"execa": "^2.1.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"libp2p": "../",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint no-console: ["off"] */
|
||||
|
||||
import { generateKey } from 'libp2p/pnet'
|
||||
import { generate } from 'libp2p/pnet/generate'
|
||||
import { privateLibp2pNode } from './libp2p-node.js'
|
||||
import { pipe } from 'it-pipe'
|
||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
@@ -8,11 +8,11 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||
|
||||
// Create a Uint8Array and write the swarm key to it
|
||||
const swarmKey = new Uint8Array(95)
|
||||
generateKey(swarmKey)
|
||||
generate(swarmKey)
|
||||
|
||||
// This key is for testing a different key not working
|
||||
const otherSwarmKey = new Uint8Array(95)
|
||||
generateKey(otherSwarmKey)
|
||||
generate(otherSwarmKey)
|
||||
|
||||
;(async () => {
|
||||
const node1 = await privateLibp2pNode(swarmKey)
|
||||
|
@@ -167,105 +167,10 @@ There is one last trick on _protocol and stream multiplexing_ that libp2p uses t
|
||||
|
||||
With the aid of both mechanisms, we can reuse an incomming connection to dial streams out too, this is specially useful when you are behind tricky NAT, firewalls or if you are running in a browser, where you can't have listening addrs, but you can dial out. By dialing out, you enable other peers to talk with you in Protocols that they want, simply by opening a new multiplexed stream.
|
||||
|
||||
You can see this working on example [3.js](./3.js).
|
||||
|
||||
As we've seen earlier, we can create our node with this createNode function.
|
||||
```js
|
||||
const createNode = async () => {
|
||||
const node = await Libp2p.create({
|
||||
addresses: {
|
||||
listen: ['/ip4/0.0.0.0/tcp/0']
|
||||
},
|
||||
modules: {
|
||||
transport: [TCP],
|
||||
streamMuxer: [MPLEX],
|
||||
connEncryption: [NOISE]
|
||||
}
|
||||
})
|
||||
|
||||
await node.start()
|
||||
|
||||
return node
|
||||
}
|
||||
```
|
||||
|
||||
We can now create our two nodes for this example.
|
||||
```js
|
||||
const [node1, node2] = await Promise.all([
|
||||
createNode(),
|
||||
createNode()
|
||||
])
|
||||
```
|
||||
|
||||
Since, we want to connect these nodes `node1` & `node2`, we add our `node2` multiaddr in key-value pair in `node1` peer store.
|
||||
```js
|
||||
await node1.peerStore.addressBook.set(node2.peerId, node2.multiaddrs)
|
||||
```
|
||||
|
||||
You may notice that we are only adding `node2` to `node1` peer store. This is because we want to dial up a bidirectional connection between these two nodes.
|
||||
|
||||
Finally, let's create protocols for `node1` & `node2` and dial those protocols.
|
||||
```js
|
||||
node1.handle('/node-1', ({ stream }) => {
|
||||
pipe(
|
||||
stream,
|
||||
async function (source) {
|
||||
for await (const msg of source) {
|
||||
console.log(msg.toString())
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
node2.handle('/node-2', ({ stream }) => {
|
||||
pipe(
|
||||
stream,
|
||||
async function (source) {
|
||||
for await (const msg of source) {
|
||||
console.log(msg.toString())
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// Dialing node2 from node1
|
||||
const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2'])
|
||||
await pipe(
|
||||
['from 1 to 2'],
|
||||
stream1
|
||||
)
|
||||
|
||||
// Dialing node1 from node2
|
||||
const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1'])
|
||||
await pipe(
|
||||
['from 2 to 1'],
|
||||
stream2
|
||||
)
|
||||
```
|
||||
|
||||
If we run this code, the result should look like the following:
|
||||
You can see this working on example [3.js](./3.js). The result should look like the following:
|
||||
|
||||
```Bash
|
||||
> node 3.js
|
||||
from 1 to 2
|
||||
from 2 to 1
|
||||
```
|
||||
|
||||
So, we have successfully set up a bidirectional connection with protocol muxing. But you should be aware that we were able to dial from `node2` to `node1` even we haven't added the `node1` peerId to node2 address book is because we dialed node2 from node1 first. Then, we just dialed back our stream out from `node2` to `node1`. So, if we dial from `node2` to `node1` before dialing from `node1` to `node2` we will get an error.
|
||||
|
||||
The code below will result into an error as `the dial address is not valid`.
|
||||
```js
|
||||
// Dialing from node2 to node1
|
||||
const { stream: stream2 } = await node2.dialProtocol(node1.peerId, ['/node-1'])
|
||||
await pipe(
|
||||
['from 2 to 1'],
|
||||
stream2
|
||||
)
|
||||
|
||||
// Dialing from node1 to node2
|
||||
const { stream: stream1 } = await node1.dialProtocol(node2.peerId, ['/node-2'])
|
||||
await pipe(
|
||||
['from 1 to 2'],
|
||||
stream1
|
||||
)
|
||||
```
|
@@ -4,9 +4,10 @@ import { createLibp2p } from 'libp2p'
|
||||
import { TCP } from '@libp2p/tcp'
|
||||
import { Mplex } from '@libp2p/mplex'
|
||||
import { Noise } from '@chainsafe/libp2p-noise'
|
||||
import { FloodSub } from '@libp2p/floodsub'
|
||||
import { Gossipsub } from '@achingbrain/libp2p-gossipsub'
|
||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||
import { CustomEvent } from '@libp2p/interfaces'
|
||||
|
||||
const createNode = async () => {
|
||||
const node = await createLibp2p({
|
||||
@@ -16,7 +17,7 @@ const createNode = async () => {
|
||||
transports: [new TCP()],
|
||||
streamMuxers: [new Mplex()],
|
||||
connectionEncryption: [new Noise()],
|
||||
pubsub: new FloodSub()
|
||||
pubsub: new Gossipsub()
|
||||
})
|
||||
|
||||
await node.start()
|
||||
@@ -35,21 +36,17 @@ const createNode = async () => {
|
||||
await node1.peerStore.addressBook.set(node2.peerId, node2.getMultiaddrs())
|
||||
await node1.dial(node2.peerId)
|
||||
|
||||
node1.pubsub.subscribe(topic)
|
||||
node1.pubsub.addEventListener('message', (evt) => {
|
||||
console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`)
|
||||
node1.pubsub.addEventListener(topic, (evt) => {
|
||||
console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`)
|
||||
})
|
||||
|
||||
// Will not receive own published messages by default
|
||||
node2.pubsub.subscribe(topic)
|
||||
node2.pubsub.addEventListener('message', (evt) => {
|
||||
console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`)
|
||||
node2.pubsub.addEventListener(topic, (evt) => {
|
||||
console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)}`)
|
||||
})
|
||||
|
||||
// node2 publishes "news" every second
|
||||
setInterval(() => {
|
||||
node2.pubsub.publish(topic, uint8ArrayFromString('Bird bird bird, bird is the word!')).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
node2.pubsub.dispatchEvent(new CustomEvent(topic, { detail: uint8ArrayFromString('Bird bird bird, bird is the word!') }))
|
||||
}, 1000)
|
||||
})()
|
||||
|
@@ -69,9 +69,7 @@ await node2.pubsub.subscribe(topic)
|
||||
|
||||
// node2 publishes "news" every second
|
||||
setInterval(() => {
|
||||
node2.pubsub.publish(topic, fromString('Bird bird bird, bird is the word!')).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
node2.pubsub.publish(topic, fromString('Bird bird bird, bird is the word!'))
|
||||
}, 1000)
|
||||
```
|
||||
|
||||
|
@@ -4,9 +4,10 @@ import { createLibp2p } from 'libp2p'
|
||||
import { TCP } from '@libp2p/tcp'
|
||||
import { Mplex } from '@libp2p/mplex'
|
||||
import { Noise } from '@chainsafe/libp2p-noise'
|
||||
import { FloodSub } from '@libp2p/floodsub'
|
||||
import { Gossipsub } from '@achingbrain/libp2p-gossipsub'
|
||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||
import { CustomEvent } from '@libp2p/interfaces'
|
||||
|
||||
const createNode = async () => {
|
||||
const node = await createLibp2p({
|
||||
@@ -16,7 +17,7 @@ const createNode = async () => {
|
||||
transports: [new TCP()],
|
||||
streamMuxers: [new Mplex()],
|
||||
connectionEncryption: [new Noise()],
|
||||
pubsub: new FloodSub()
|
||||
pubsub: new Gossipsub()
|
||||
})
|
||||
|
||||
await node.start()
|
||||
@@ -44,7 +45,7 @@ const createNode = async () => {
|
||||
// Will not receive own published messages by default
|
||||
console.log(`node1 received: ${uint8ArrayToString(evt.detail.data)}`)
|
||||
})
|
||||
node1.pubsub.subscribe(topic)
|
||||
await node1.pubsub.subscribe(topic)
|
||||
|
||||
node2.pubsub.addEventListener(topic, (evt) => {
|
||||
console.log(`node2 received: ${uint8ArrayToString(evt.detail.data)}`)
|
||||
@@ -74,9 +75,7 @@ const createNode = async () => {
|
||||
// car is not a fruit !
|
||||
setInterval(() => {
|
||||
console.log('############## fruit ' + myFruits[count] + ' ##############')
|
||||
node1.pubsub.publish(topic, uint8ArrayFromString(myFruits[count])).catch(err => {
|
||||
console.info(err)
|
||||
})
|
||||
node1.pubsub.dispatchEvent(new CustomEvent<Uint8Array>(topic, { detail: uint8ArrayFromString(myFruits[count]) }))
|
||||
count++
|
||||
if (count == myFruits.length) {
|
||||
count = 0
|
||||
|
@@ -88,9 +88,7 @@ const myFruits = ['banana', 'apple', 'car', 'orange'];
|
||||
|
||||
setInterval(() => {
|
||||
console.log('############## fruit ' + myFruits[count] + ' ##############')
|
||||
node1.pubsub.publish(topic, new TextEncoder().encode(myFruits[count])).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
node1.pubsub.publish(topic, new TextEncoder().encode(myFruits[count]))
|
||||
count++
|
||||
if (count == myFruits.length) {
|
||||
count = 0
|
||||
|
@@ -3,16 +3,19 @@
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"browserslist": [
|
||||
"last 2 Chrome versions"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "vite"
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@libp2p/webrtc-direct": "^1.0.1",
|
||||
"@chainsafe/libp2p-noise": "^6.2.0",
|
||||
"@libp2p/bootstrap": "^1.0.4",
|
||||
"@libp2p/mplex": "^1.0.4",
|
||||
"@libp2p/webrtc-direct": "^1.0.0",
|
||||
"@chainsafe/libp2p-noise": "^6.0.1",
|
||||
"@libp2p/bootstrap": "^1.0.1",
|
||||
"@libp2p/mplex": "^1.0.2",
|
||||
"libp2p": "../../",
|
||||
"wrtc": "^0.4.7"
|
||||
},
|
||||
|
@@ -1,5 +0,0 @@
|
||||
export default {
|
||||
build: {
|
||||
target: 'es2020'
|
||||
}
|
||||
}
|
113
package.json
113
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "libp2p",
|
||||
"version": "0.37.2",
|
||||
"version": "0.36.2",
|
||||
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"homepage": "https://github.com/libp2p/js-libp2p#readme",
|
||||
@@ -57,6 +57,9 @@
|
||||
"./pnet": {
|
||||
"import": "./dist/src/pnet/index.js"
|
||||
},
|
||||
"./pnet/generate": {
|
||||
"import": "./dist/src/pnet/key-generator.js"
|
||||
},
|
||||
"./transport-manager": {
|
||||
"import": "./dist/src/transport-manager.js"
|
||||
}
|
||||
@@ -73,43 +76,48 @@
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "aegir clean",
|
||||
"lint": "aegir lint",
|
||||
"dep-check": "aegir dep-check",
|
||||
"build": "aegir build",
|
||||
"generate": "run-s generate:proto:*",
|
||||
"generate:proto:circuit": "protons ./src/circuit/pb/index.proto",
|
||||
"generate:proto:fetch": "protons ./src/fetch/pb/proto.proto",
|
||||
"generate:proto:identify": "protons ./src/identify/pb/message.proto",
|
||||
"generate:proto:plaintext": "protons ./src/insecure/pb/proto.proto",
|
||||
"build": "tsc",
|
||||
"postbuild": "mkdirp dist/src/circuit/v1/pb dist/src/circuit/v2/pb dist/src/fetch/pb dist/src/identify/pb dist/src/insecure/pb && cp src/circuit/v1/pb/*.js src/circuit/v1/pb/*.d.ts dist/src/circuit/v1/pb && cp src/circuit/v2/pb/*.js src/circuit/v2/pb/*.d.ts dist/src/circuit/v2/pb && cp src/fetch/pb/*.js src/fetch/pb/*.d.ts dist/src/fetch/pb && cp src/identify/pb/*.js src/identify/pb/*.d.ts dist/src/identify/pb && cp src/insecure/pb/*.js src/insecure/pb/*.d.ts dist/src/insecure/pb",
|
||||
"generate": "run-s generate:proto:* generate:proto-types:*",
|
||||
"generate:proto:fetch": "pbjs -t static-module -w es6 -r libp2p-fetch --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/fetch/pb/proto.js ./src/fetch/pb/proto.proto",
|
||||
"generate:proto:identify": "pbjs -t static-module -w es6 -r libp2p-identify --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/identify/pb/message.js ./src/identify/pb/message.proto",
|
||||
"generate:proto:plaintext": "pbjs -t static-module -w es6 -r libp2p-plaintext --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/insecure/pb/proto.js ./src/insecure/pb/proto.proto",
|
||||
"generate:proto:circuit": "pbjs -t static-module -w es6 -r libp2p-circuit --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/circuit/v1/pb/index.js ./src/circuit/v1/pb/index.proto",
|
||||
"generate:proto:circuitv2": "pbjs -t static-module -w es6 -r libp2p-circuitv2 --force-number --no-verify --no-delimited --no-create --no-beautify --no-defaults --lint eslint-disable -o src/circuit/v2/pb/index.js ./src/circuit/v2/pb/index.proto && pbts --out src/circuit/v2/pb/index.d.ts src/circuit/v2/pb/index.js",
|
||||
"generate:proto-types:circuit": "pbts -o src/circuit/v1/pb/index.d.ts src/circuit/v1/pb/index.js",
|
||||
"generate:proto-types:fetch": "pbts -o src/fetch/pb/proto.d.ts src/fetch/pb/proto.js",
|
||||
"generate:proto-types:identify": "pbts -o src/identify/pb/message.d.ts src/identify/pb/message.js",
|
||||
"generate:proto-types:plaintext": "pbts -o src/insecure/pb/proto.d.ts src/insecure/pb/proto.js",
|
||||
"pretest": "npm run build",
|
||||
"test": "aegir test",
|
||||
"test:node": "aegir test -t node -f \"./dist/test/**/*.{node,spec}.js\" --cov",
|
||||
"test:chrome": "aegir test -t browser -f \"./dist/test/**/*.spec.js\" --cov",
|
||||
"test:chrome-webworker": "aegir test -t webworker -f \"./dist/test/**/*.spec.js\"",
|
||||
"test:firefox": "aegir test -t browser -f \"./dist/test/**/*.spec.js\" -- --browser firefox",
|
||||
"test:firefox-webworker": "aegir test -t webworker -f \"./dist/test/**/*.spec.js\" -- --browser firefox",
|
||||
"test:node": "npm run test -- -t node -f \"./dist/test/**/*.{node,spec}.js\" --cov",
|
||||
"test:chrome": "npm run test -- -t browser -f \"./dist/test/**/*.spec.js\" --cov",
|
||||
"test:chrome-webworker": "npm run test -- -t webworker -f \"./dist/test/**/*.spec.js\"",
|
||||
"test:firefox": "npm run test -- -t browser -f \"./dist/test/**/*.spec.js\" -- --browser firefox",
|
||||
"test:firefox-webworker": "npm run test -- -t webworker -f \"./dist/test/**/*.spec.js\" -- --browser firefox",
|
||||
"test:examples": "cd examples && npm run test:all",
|
||||
"test:interop": "aegir test -t node -f dist/test/interop.js"
|
||||
"test:interop": "npm run test -- -t node -f dist/test/interop.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@achingbrain/nat-port-mapper": "^1.0.3",
|
||||
"@libp2p/connection": "^2.0.2",
|
||||
"@libp2p/crypto": "^0.22.11",
|
||||
"@libp2p/interfaces": "^2.0.2",
|
||||
"@libp2p/logger": "^1.1.4",
|
||||
"@libp2p/multistream-select": "^1.0.4",
|
||||
"@libp2p/peer-collections": "^1.0.2",
|
||||
"@libp2p/peer-id": "^1.1.10",
|
||||
"@libp2p/peer-id-factory": "^1.0.9",
|
||||
"@libp2p/peer-record": "^1.0.8",
|
||||
"@libp2p/peer-store": "^1.0.10",
|
||||
"@libp2p/tracked-map": "^1.0.5",
|
||||
"@libp2p/utils": "^1.0.10",
|
||||
"@achingbrain/nat-port-mapper": "^1.0.0",
|
||||
"@libp2p/connection": "^1.1.4",
|
||||
"@libp2p/crypto": "^0.22.9",
|
||||
"@libp2p/interfaces": "^1.3.17",
|
||||
"@libp2p/multistream-select": "^1.0.3",
|
||||
"@libp2p/peer-id": "^1.1.8",
|
||||
"@libp2p/peer-id-factory": "^1.0.8",
|
||||
"@libp2p/peer-store": "^1.0.6",
|
||||
"@libp2p/utils": "^1.0.9",
|
||||
"@multiformats/mafmt": "^11.0.2",
|
||||
"@multiformats/multiaddr": "^10.1.8",
|
||||
"abortable-iterator": "^4.0.2",
|
||||
"aggregate-error": "^4.0.0",
|
||||
"any-signal": "^3.0.0",
|
||||
"bignumber.js": "^9.0.1",
|
||||
"class-is": "^1.1.0",
|
||||
"datastore-core": "^7.0.0",
|
||||
"debug": "^4.3.3",
|
||||
"err-code": "^3.0.1",
|
||||
"events": "^3.3.0",
|
||||
"hashlru": "^2.3.0",
|
||||
@@ -123,11 +131,13 @@
|
||||
"it-length-prefixed": "^7.0.1",
|
||||
"it-map": "^1.0.6",
|
||||
"it-merge": "^1.0.3",
|
||||
"it-pair": "^2.0.2",
|
||||
"it-pipe": "^2.0.3",
|
||||
"it-sort": "^1.0.1",
|
||||
"it-stream-types": "^1.0.4",
|
||||
"it-take": "^1.0.2",
|
||||
"it-to-buffer": "^2.0.2",
|
||||
"merge-options": "^3.0.4",
|
||||
"mortice": "^3.0.0",
|
||||
"multiformats": "^9.6.3",
|
||||
"mutable-proxy": "^1.0.0",
|
||||
"node-forge": "^1.2.1",
|
||||
@@ -135,55 +145,58 @@
|
||||
"p-retry": "^5.0.0",
|
||||
"p-settle": "^5.0.0",
|
||||
"private-ip": "^2.3.3",
|
||||
"protons-runtime": "^1.0.4",
|
||||
"protobufjs": "^6.11.2",
|
||||
"retimer": "^3.0.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"set-delayed-interval": "^1.0.0",
|
||||
"streaming-iterables": "^6.0.0",
|
||||
"timeout-abort-controller": "^3.0.0",
|
||||
"uint8arrays": "^3.0.0",
|
||||
"varint": "^6.0.0",
|
||||
"wherearewe": "^1.0.0",
|
||||
"xsalsa20": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chainsafe/libp2p-noise": "^6.2.0",
|
||||
"@libp2p/bootstrap": "^1.0.4",
|
||||
"@libp2p/daemon-client": "^1.0.2",
|
||||
"@libp2p/daemon-server": "^1.0.2",
|
||||
"@achingbrain/libp2p-gossipsub": "^0.13.5",
|
||||
"@chainsafe/libp2p-noise": "^6.0.1",
|
||||
"@libp2p/bootstrap": "^1.0.2",
|
||||
"@libp2p/daemon-client": "^1.0.0",
|
||||
"@libp2p/daemon-server": "^1.0.0",
|
||||
"@libp2p/delegated-content-routing": "^1.0.2",
|
||||
"@libp2p/delegated-peer-routing": "^1.0.2",
|
||||
"@libp2p/floodsub": "^1.0.6",
|
||||
"@libp2p/interface-compliance-tests": "^2.0.3",
|
||||
"@libp2p/interop": "^1.0.3",
|
||||
"@libp2p/kad-dht": "^1.0.9",
|
||||
"@libp2p/mdns": "^1.0.5",
|
||||
"@libp2p/mplex": "^1.1.0",
|
||||
"@libp2p/pubsub": "^1.2.18",
|
||||
"@libp2p/tcp": "^1.0.9",
|
||||
"@libp2p/topology": "^1.1.7",
|
||||
"@libp2p/webrtc-star": "^1.0.8",
|
||||
"@libp2p/websockets": "^1.0.7",
|
||||
"@libp2p/floodsub": "^1.0.2",
|
||||
"@libp2p/interface-compliance-tests": "^1.1.20",
|
||||
"@libp2p/interop": "^1.0.0",
|
||||
"@libp2p/kad-dht": "^1.0.3",
|
||||
"@libp2p/mdns": "^1.0.3",
|
||||
"@libp2p/mplex": "^1.0.1",
|
||||
"@libp2p/tcp": "^1.0.6",
|
||||
"@libp2p/tracked-map": "^1.0.4",
|
||||
"@libp2p/webrtc-star": "^1.0.3",
|
||||
"@libp2p/websockets": "^1.0.3",
|
||||
"@nodeutils/defaults-deep": "^1.1.0",
|
||||
"@types/node": "^16.11.26",
|
||||
"@types/node-forge": "^1.0.0",
|
||||
"@types/p-fifo": "^1.0.0",
|
||||
"@types/varint": "^6.0.0",
|
||||
"@types/xsalsa20": "^1.1.0",
|
||||
"aegir": "^37.0.9",
|
||||
"aegir": "^36.1.3",
|
||||
"buffer": "^6.0.3",
|
||||
"cborg": "^1.8.1",
|
||||
"delay": "^5.0.0",
|
||||
"execa": "^6.1.0",
|
||||
"go-libp2p": "^0.0.6",
|
||||
"into-stream": "^7.0.0",
|
||||
"ipfs-http-client": "^56.0.1",
|
||||
"it-pair": "^2.0.2",
|
||||
"it-pushable": "^2.0.1",
|
||||
"it-to-buffer": "^2.0.2",
|
||||
"nock": "^13.0.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"p-defer": "^4.0.0",
|
||||
"p-event": "^5.0.1",
|
||||
"p-times": "^4.0.0",
|
||||
"p-wait-for": "^4.1.0",
|
||||
"protons": "^3.0.4",
|
||||
"rimraf": "^3.0.2",
|
||||
"sinon": "^14.0.0",
|
||||
"sinon": "^13.0.1",
|
||||
"ts-sinon": "^2.0.2"
|
||||
},
|
||||
"browser": {
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import type { AddressManagerEvents } from '@libp2p/interfaces/address-manager'
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
||||
import { AddressManagerEvents, CustomEvent, EventEmitter } from '@libp2p/interfaces'
|
||||
import { Multiaddr } from '@multiformats/multiaddr'
|
||||
import { peerIdFromString } from '@libp2p/peer-id'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { logger } from '@libp2p/logger'
|
||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||
import { RELAY_CODEC } from './multicodec.js'
|
||||
import { canHop } from './circuit/hop.js'
|
||||
import { RELAY_V1_CODEC } from './multicodec.js'
|
||||
import { canHop } from './v1/hop.js'
|
||||
import { namespaceToCid } from './utils.js'
|
||||
import {
|
||||
CIRCUIT_PROTO_CODE,
|
||||
@@ -68,7 +68,7 @@ export class AutoRelay {
|
||||
const id = peerId.toString()
|
||||
|
||||
// Check if it has the protocol
|
||||
const hasProtocol = protocols.find(protocol => protocol === RELAY_CODEC)
|
||||
const hasProtocol = protocols.find(protocol => protocol === RELAY_V1_CODEC)
|
||||
|
||||
// If no protocol, check if we were keeping the peer before as a listenRelay
|
||||
if (hasProtocol == null) {
|
||||
@@ -85,14 +85,12 @@ export class AutoRelay {
|
||||
|
||||
// If protocol, check if can hop, store info in the metadataBook and listen on it
|
||||
try {
|
||||
const connections = this.components.getConnectionManager().getConnections(peerId)
|
||||
const connection = this.components.getConnectionManager().getConnection(peerId)
|
||||
|
||||
if (connections.length === 0) {
|
||||
if (connection == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const connection = connections[0]
|
||||
|
||||
// Do not hop on a relayed connection
|
||||
if (connection.remoteAddr.protoCodes().includes(CIRCUIT_PROTO_CODE)) {
|
||||
log(`relayed connection to ${id} will not be used to hop on`)
|
||||
@@ -225,15 +223,15 @@ export class AutoRelay {
|
||||
continue
|
||||
}
|
||||
|
||||
const connections = this.components.getConnectionManager().getConnections(id)
|
||||
const connection = this.components.getConnectionManager().getConnection(id)
|
||||
|
||||
// If not connected, store for possible later use.
|
||||
if (connections.length === 0) {
|
||||
if (connection == null) {
|
||||
knownHopsToDial.push(id)
|
||||
continue
|
||||
}
|
||||
|
||||
await this._addListenRelay(connections[0], idStr)
|
||||
await this._addListenRelay(connection, idStr)
|
||||
|
||||
// Check if already listening on enough relays
|
||||
if (this.listenRelays.size >= this.maxListeners) {
|
||||
@@ -276,7 +274,7 @@ export class AutoRelay {
|
||||
|
||||
async _tryToListenOnRelay (peerId: PeerId) {
|
||||
try {
|
||||
const connection = await this.components.getConnectionManager().openConnection(peerId)
|
||||
const connection = await this.components.getDialer().dial(peerId)
|
||||
await this._addListenRelay(connection, peerId.toString())
|
||||
} catch (err: any) {
|
||||
log.error('Could not use %p as relay', peerId, err)
|
||||
|
@@ -11,7 +11,7 @@ import {
|
||||
RELAY_RENDEZVOUS_NS
|
||||
} from './constants.js'
|
||||
import type { AddressSorter } from '@libp2p/interfaces/peer-store'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import type { Startable } from '@libp2p/interfaces'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
|
||||
const log = logger('libp2p:relay')
|
||||
|
@@ -1,12 +1,11 @@
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
||||
import type { ConnectionManager } from '@libp2p/interfaces/connection-manager'
|
||||
import type { PeerStore } from '@libp2p/interfaces/peer-store'
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces'
|
||||
import type { ConnectionManager } from '@libp2p/interfaces/registrar'
|
||||
import type { Dialer } from '@libp2p/interfaces/dialer'
|
||||
import type { Listener } from '@libp2p/interfaces/transport'
|
||||
import { peerIdFromString } from '@libp2p/peer-id'
|
||||
import { Multiaddr } from '@multiformats/multiaddr'
|
||||
|
||||
export interface ListenerOptions {
|
||||
peerStore: PeerStore
|
||||
dialer: Dialer
|
||||
connectionManager: ConnectionManager
|
||||
}
|
||||
|
||||
@@ -18,19 +17,7 @@ export function createListener (options: ListenerOptions): Listener {
|
||||
*/
|
||||
async function listen (addr: Multiaddr): Promise<void> {
|
||||
const addrString = addr.toString().split('/p2p-circuit').find(a => a !== '')
|
||||
const ma = new Multiaddr(addrString)
|
||||
|
||||
const relayPeerStr = ma.getPeerId()
|
||||
|
||||
if (relayPeerStr == null) {
|
||||
throw new Error('Could not determine relay peer from multiaddr')
|
||||
}
|
||||
|
||||
const relayPeerId = peerIdFromString(relayPeerStr)
|
||||
|
||||
await options.peerStore.addressBook.add(relayPeerId, [ma])
|
||||
|
||||
const relayConn = await options.connectionManager.openConnection(relayPeerId)
|
||||
const relayConn = await options.dialer.dial(new Multiaddr(addrString))
|
||||
const relayedAddr = relayConn.remoteAddr.encapsulate('/p2p-circuit')
|
||||
|
||||
listeningAddrs.set(relayConn.remotePeer.toString(), relayedAddr)
|
||||
|
@@ -1,2 +1,4 @@
|
||||
|
||||
export const RELAY_CODEC = '/libp2p/circuit/relay/0.1.0'
|
||||
export const RELAY_V1_CODEC = '/libp2p/circuit/relay/0.1.0'
|
||||
export const protocolIDv2Hop = '/libp2p/circuit/relay/0.2.0/hop'
|
||||
export const protocolIDv2Stop = '/libp2p/circuit/relay/0.2.0/stop'
|
||||
|
@@ -1,117 +0,0 @@
|
||||
/* eslint-disable import/export */
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
|
||||
import { enumeration, encodeMessage, decodeMessage, message, bytes } from 'protons-runtime'
|
||||
import type { Codec } from 'protons-runtime'
|
||||
|
||||
export interface CircuitRelay {
|
||||
type?: CircuitRelay.Type
|
||||
srcPeer?: CircuitRelay.Peer
|
||||
dstPeer?: CircuitRelay.Peer
|
||||
code?: CircuitRelay.Status
|
||||
}
|
||||
|
||||
export namespace CircuitRelay {
|
||||
export enum Status {
|
||||
SUCCESS = 'SUCCESS',
|
||||
HOP_SRC_ADDR_TOO_LONG = 'HOP_SRC_ADDR_TOO_LONG',
|
||||
HOP_DST_ADDR_TOO_LONG = 'HOP_DST_ADDR_TOO_LONG',
|
||||
HOP_SRC_MULTIADDR_INVALID = 'HOP_SRC_MULTIADDR_INVALID',
|
||||
HOP_DST_MULTIADDR_INVALID = 'HOP_DST_MULTIADDR_INVALID',
|
||||
HOP_NO_CONN_TO_DST = 'HOP_NO_CONN_TO_DST',
|
||||
HOP_CANT_DIAL_DST = 'HOP_CANT_DIAL_DST',
|
||||
HOP_CANT_OPEN_DST_STREAM = 'HOP_CANT_OPEN_DST_STREAM',
|
||||
HOP_CANT_SPEAK_RELAY = 'HOP_CANT_SPEAK_RELAY',
|
||||
HOP_CANT_RELAY_TO_SELF = 'HOP_CANT_RELAY_TO_SELF',
|
||||
STOP_SRC_ADDR_TOO_LONG = 'STOP_SRC_ADDR_TOO_LONG',
|
||||
STOP_DST_ADDR_TOO_LONG = 'STOP_DST_ADDR_TOO_LONG',
|
||||
STOP_SRC_MULTIADDR_INVALID = 'STOP_SRC_MULTIADDR_INVALID',
|
||||
STOP_DST_MULTIADDR_INVALID = 'STOP_DST_MULTIADDR_INVALID',
|
||||
STOP_RELAY_REFUSED = 'STOP_RELAY_REFUSED',
|
||||
MALFORMED_MESSAGE = 'MALFORMED_MESSAGE'
|
||||
}
|
||||
|
||||
enum __StatusValues {
|
||||
SUCCESS = 100,
|
||||
HOP_SRC_ADDR_TOO_LONG = 220,
|
||||
HOP_DST_ADDR_TOO_LONG = 221,
|
||||
HOP_SRC_MULTIADDR_INVALID = 250,
|
||||
HOP_DST_MULTIADDR_INVALID = 251,
|
||||
HOP_NO_CONN_TO_DST = 260,
|
||||
HOP_CANT_DIAL_DST = 261,
|
||||
HOP_CANT_OPEN_DST_STREAM = 262,
|
||||
HOP_CANT_SPEAK_RELAY = 270,
|
||||
HOP_CANT_RELAY_TO_SELF = 280,
|
||||
STOP_SRC_ADDR_TOO_LONG = 320,
|
||||
STOP_DST_ADDR_TOO_LONG = 321,
|
||||
STOP_SRC_MULTIADDR_INVALID = 350,
|
||||
STOP_DST_MULTIADDR_INVALID = 351,
|
||||
STOP_RELAY_REFUSED = 390,
|
||||
MALFORMED_MESSAGE = 400
|
||||
}
|
||||
|
||||
export namespace Status {
|
||||
export const codec = () => {
|
||||
return enumeration<typeof Status>(__StatusValues)
|
||||
}
|
||||
}
|
||||
|
||||
export enum Type {
|
||||
HOP = 'HOP',
|
||||
STOP = 'STOP',
|
||||
STATUS = 'STATUS',
|
||||
CAN_HOP = 'CAN_HOP'
|
||||
}
|
||||
|
||||
enum __TypeValues {
|
||||
HOP = 1,
|
||||
STOP = 2,
|
||||
STATUS = 3,
|
||||
CAN_HOP = 4
|
||||
}
|
||||
|
||||
export namespace Type {
|
||||
export const codec = () => {
|
||||
return enumeration<typeof Type>(__TypeValues)
|
||||
}
|
||||
}
|
||||
|
||||
export interface Peer {
|
||||
id: Uint8Array
|
||||
addrs: Uint8Array[]
|
||||
}
|
||||
|
||||
export namespace Peer {
|
||||
export const codec = (): Codec<Peer> => {
|
||||
return message<Peer>({
|
||||
1: { name: 'id', codec: bytes },
|
||||
2: { name: 'addrs', codec: bytes, repeats: true }
|
||||
})
|
||||
}
|
||||
|
||||
export const encode = (obj: Peer): Uint8Array => {
|
||||
return encodeMessage(obj, Peer.codec())
|
||||
}
|
||||
|
||||
export const decode = (buf: Uint8Array): Peer => {
|
||||
return decodeMessage(buf, Peer.codec())
|
||||
}
|
||||
}
|
||||
|
||||
export const codec = (): Codec<CircuitRelay> => {
|
||||
return message<CircuitRelay>({
|
||||
1: { name: 'type', codec: CircuitRelay.Type.codec(), optional: true },
|
||||
2: { name: 'srcPeer', codec: CircuitRelay.Peer.codec(), optional: true },
|
||||
3: { name: 'dstPeer', codec: CircuitRelay.Peer.codec(), optional: true },
|
||||
4: { name: 'code', codec: CircuitRelay.Status.codec(), optional: true }
|
||||
})
|
||||
}
|
||||
|
||||
export const encode = (obj: CircuitRelay): Uint8Array => {
|
||||
return encodeMessage(obj, CircuitRelay.codec())
|
||||
}
|
||||
|
||||
export const decode = (buf: Uint8Array): CircuitRelay => {
|
||||
return decodeMessage(buf, CircuitRelay.codec())
|
||||
}
|
||||
}
|
@@ -1,33 +1,56 @@
|
||||
import * as CircuitV1 from './v1/pb/index.js'
|
||||
import * as CircuitV2 from './v2/pb/index.js'
|
||||
import { ReservationStore } from './v2/reservation-store.js'
|
||||
import { logger } from '@libp2p/logger'
|
||||
import errCode from 'err-code'
|
||||
import createError from 'err-code'
|
||||
import * as mafmt from '@multiformats/mafmt'
|
||||
import { Multiaddr } from '@multiformats/multiaddr'
|
||||
import { CircuitRelay as CircuitPB } from './pb/index.js'
|
||||
import { codes } from '../errors.js'
|
||||
import { streamToMaConnection } from '@libp2p/utils/stream-to-ma-conn'
|
||||
import { RELAY_CODEC } from './multicodec.js'
|
||||
import { protocolIDv2Hop, RELAY_V1_CODEC } from './multicodec.js'
|
||||
import { createListener } from './listener.js'
|
||||
import { handleCanHop, handleHop, hop } from './circuit/hop.js'
|
||||
import { handleStop } from './circuit/stop.js'
|
||||
import { StreamHandler } from './circuit/stream-handler.js'
|
||||
import { symbol } from '@libp2p/interfaces/transport'
|
||||
import { peerIdFromString } from '@libp2p/peer-id'
|
||||
import { Components, Initializable } from '@libp2p/interfaces/components'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
import type { IncomingStreamData } from '@libp2p/interfaces/registrar'
|
||||
import type { Listener, Transport, CreateListenerOptions, ConnectionHandler } from '@libp2p/interfaces/transport'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import type { Connection, Stream } from '@libp2p/interfaces/connection'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import { StreamHandlerV2 } from './v2/stream-handler.js'
|
||||
import { StreamHandlerV1 } from './v1/stream-handler.js'
|
||||
import * as CircuitV1Handler from './v1/index.js'
|
||||
import * as CircuitV2Handler from './v2/index.js'
|
||||
|
||||
const log = logger('libp2p:circuit')
|
||||
|
||||
export interface CircuitOptions {
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface ConnectOptions {
|
||||
stream: Stream
|
||||
connection: Connection
|
||||
destinationPeer: PeerId
|
||||
destinationAddr: Multiaddr
|
||||
relayAddr: Multiaddr
|
||||
ma: Multiaddr
|
||||
disconnectOnFailure: boolean
|
||||
}
|
||||
export class Circuit implements Transport, Initializable {
|
||||
private handler?: ConnectionHandler
|
||||
private components: Components = new Components()
|
||||
private readonly reservationStore: ReservationStore
|
||||
|
||||
constructor (options: CircuitOptions) {
|
||||
this.reservationStore = new ReservationStore(options.limit)
|
||||
}
|
||||
|
||||
init (components: Components): void {
|
||||
this.components = components
|
||||
void this.components.getRegistrar().handle(RELAY_CODEC, (data) => {
|
||||
void this._onProtocol(data).catch(err => {
|
||||
|
||||
void this.components.getRegistrar().handle(RELAY_V1_CODEC, (data) => {
|
||||
void this._onProtocolV1(data).catch(err => {
|
||||
log.error(err)
|
||||
})
|
||||
})
|
||||
@@ -49,19 +72,23 @@ export class Circuit implements Transport, Initializable {
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return 'libp2p/circuit-relay-v1'
|
||||
return this.constructor.name
|
||||
}
|
||||
|
||||
async _onProtocol (data: IncomingStreamData) {
|
||||
getPeerConnection (dstPeer: PeerId) {
|
||||
return this.components.getConnectionManager().getConnection(dstPeer)
|
||||
}
|
||||
|
||||
async _onProtocolV1 (data: IncomingStreamData) {
|
||||
const { connection, stream } = data
|
||||
const streamHandler = new StreamHandler({ stream })
|
||||
const streamHandler = new StreamHandlerV1({ stream })
|
||||
const request = await streamHandler.read()
|
||||
|
||||
if (request == null) {
|
||||
log('request was invalid, could not read from stream')
|
||||
streamHandler.write({
|
||||
type: CircuitPB.Type.STATUS,
|
||||
code: CircuitPB.Status.MALFORMED_MESSAGE
|
||||
type: CircuitV1.CircuitRelay.Type.STATUS,
|
||||
code: CircuitV1.CircuitRelay.Status.MALFORMED_MESSAGE
|
||||
})
|
||||
streamHandler.close()
|
||||
return
|
||||
@@ -70,14 +97,14 @@ export class Circuit implements Transport, Initializable {
|
||||
let virtualConnection
|
||||
|
||||
switch (request.type) {
|
||||
case CircuitPB.Type.CAN_HOP: {
|
||||
case CircuitV1.CircuitRelay.Type.CAN_HOP: {
|
||||
log('received CAN_HOP request from %p', connection.remotePeer)
|
||||
await handleCanHop({ circuit: this, connection, streamHandler })
|
||||
await CircuitV1Handler.handleCanHop({ circuit: this, connection, streamHandler })
|
||||
break
|
||||
}
|
||||
case CircuitPB.Type.HOP: {
|
||||
case CircuitV1.CircuitRelay.Type.HOP: {
|
||||
log('received HOP request from %p', connection.remotePeer)
|
||||
virtualConnection = await handleHop({
|
||||
virtualConnection = await CircuitV1Handler.handleHop({
|
||||
connection,
|
||||
request,
|
||||
streamHandler,
|
||||
@@ -86,9 +113,9 @@ export class Circuit implements Transport, Initializable {
|
||||
})
|
||||
break
|
||||
}
|
||||
case CircuitPB.Type.STOP: {
|
||||
case CircuitV1.CircuitRelay.Type.STOP: {
|
||||
log('received STOP request from %p', connection.remotePeer)
|
||||
virtualConnection = await handleStop({
|
||||
virtualConnection = await CircuitV1Handler.handleStop({
|
||||
connection,
|
||||
request,
|
||||
streamHandler
|
||||
@@ -98,8 +125,8 @@ export class Circuit implements Transport, Initializable {
|
||||
default: {
|
||||
log('Request of type %s not supported', request.type)
|
||||
streamHandler.write({
|
||||
type: CircuitPB.Type.STATUS,
|
||||
code: CircuitPB.Status.MALFORMED_MESSAGE
|
||||
type: CircuitV1.CircuitRelay.Type.STATUS,
|
||||
code: CircuitV1.CircuitRelay.Status.MALFORMED_MESSAGE
|
||||
})
|
||||
streamHandler.close()
|
||||
return
|
||||
@@ -107,16 +134,14 @@ export class Circuit implements Transport, Initializable {
|
||||
}
|
||||
|
||||
if (virtualConnection != null) {
|
||||
// @ts-expect-error dst peer will not be undefined
|
||||
const remoteAddr = new Multiaddr(request.dstPeer.addrs[0])
|
||||
// @ts-expect-error dst peer will not be undefined
|
||||
const localAddr = new Multiaddr(request.srcPeer.addrs[0])
|
||||
const remoteAddr = new Multiaddr(request.dstPeer?.addrs?.[0] ?? '')
|
||||
const localAddr = new Multiaddr(request.srcPeer?.addrs?.[0] ?? '')
|
||||
const maConn = streamToMaConnection({
|
||||
stream: virtualConnection,
|
||||
remoteAddr,
|
||||
localAddr
|
||||
})
|
||||
const type = request.type === CircuitPB.Type.HOP ? 'relay' : 'inbound'
|
||||
const type = request.type === CircuitV1.CircuitRelay.Type.HOP ? 'relay' : 'inbound'
|
||||
log('new %s connection %s', type, maConn.remoteAddr)
|
||||
|
||||
const conn = await this.components.getUpgrader().upgradeInbound(maConn)
|
||||
@@ -128,6 +153,55 @@ export class Circuit implements Transport, Initializable {
|
||||
}
|
||||
}
|
||||
|
||||
async _onV2ProtocolHop ({ connection, stream }: IncomingStreamData) {
|
||||
log('received circuit v2 hop protocol stream from %s', connection.remotePeer)
|
||||
const streamHandler = new StreamHandlerV2({ stream })
|
||||
const request = CircuitV2.HopMessage.decode(await streamHandler.read())
|
||||
|
||||
if (request?.type === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
await CircuitV2Handler.handleHopProtocol({
|
||||
connection,
|
||||
streamHandler,
|
||||
circuit: this,
|
||||
relayPeer: this.components.getPeerId(),
|
||||
relayAddrs: this.components.getAddressManager().getListenAddrs(),
|
||||
reservationStore: this.reservationStore,
|
||||
request
|
||||
})
|
||||
}
|
||||
|
||||
async _onV2ProtocolStop ({ connection, stream }: IncomingStreamData) {
|
||||
const streamHandler = new StreamHandlerV2({ stream })
|
||||
const request = CircuitV2.StopMessage.decode(await streamHandler.read())
|
||||
log('received circuit v2 stop protocol request from %s', connection.remotePeer)
|
||||
if (request?.type === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const mStream = await CircuitV2Handler.handleStop({
|
||||
connection,
|
||||
streamHandler,
|
||||
request
|
||||
})
|
||||
|
||||
if (mStream !== null && mStream !== undefined) {
|
||||
const remoteAddr = new Multiaddr(request.peer?.addrs?.[0])
|
||||
const localAddr = this.components.getTransportManager().getAddrs()[0]
|
||||
const maConn = streamToMaConnection({
|
||||
stream: mStream,
|
||||
remoteAddr,
|
||||
localAddr
|
||||
})
|
||||
log('new inbound connection %s', maConn.remoteAddr)
|
||||
const conn = await this.components.getUpgrader().upgradeInbound(maConn)
|
||||
log('%s connection %s upgraded', 'inbound', maConn.remoteAddr)
|
||||
this.handler?.(conn)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dial a peer over a relay
|
||||
*/
|
||||
@@ -142,50 +216,121 @@ export class Circuit implements Transport, Initializable {
|
||||
if (relayId == null || destinationId == null) {
|
||||
const errMsg = 'Circuit relay dial failed as addresses did not have peer id'
|
||||
log.error(errMsg)
|
||||
throw errCode(new Error(errMsg), codes.ERR_RELAYED_DIAL)
|
||||
throw createError(new Error(errMsg), codes.ERR_RELAYED_DIAL)
|
||||
}
|
||||
|
||||
const relayPeer = peerIdFromString(relayId)
|
||||
const destinationPeer = peerIdFromString(destinationId)
|
||||
|
||||
let disconnectOnFailure = false
|
||||
const relayConnections = this.components.getConnectionManager().getConnections(relayPeer)
|
||||
let relayConnection = relayConnections[0]
|
||||
|
||||
let relayConnection = this.components.getConnectionManager().getConnection(relayPeer)
|
||||
if (relayConnection == null) {
|
||||
await this.components.getPeerStore().addressBook.add(relayPeer, [relayAddr])
|
||||
relayConnection = await this.components.getConnectionManager().openConnection(relayPeer, options)
|
||||
relayConnection = await this.components.getDialer().dial(relayAddr, options)
|
||||
disconnectOnFailure = true
|
||||
}
|
||||
|
||||
try {
|
||||
const virtualConnection = await hop({
|
||||
connection: relayConnection,
|
||||
request: {
|
||||
type: CircuitPB.Type.HOP,
|
||||
srcPeer: {
|
||||
id: this.components.getPeerId().toBytes(),
|
||||
addrs: this.components.getAddressManager().getAddresses().map(addr => addr.bytes)
|
||||
},
|
||||
dstPeer: {
|
||||
id: destinationPeer.toBytes(),
|
||||
addrs: [new Multiaddr(destinationAddr).bytes]
|
||||
}
|
||||
}
|
||||
})
|
||||
const stream = await relayConnection.newStream([protocolIDv2Hop, RELAY_V1_CODEC])
|
||||
|
||||
const localAddr = relayAddr.encapsulate(`/p2p-circuit/p2p/${this.components.getPeerId().toString()}`)
|
||||
switch (stream.protocol) {
|
||||
case RELAY_V1_CODEC: return await this.connectV1({
|
||||
stream: stream.stream,
|
||||
connection: relayConnection,
|
||||
destinationPeer,
|
||||
destinationAddr,
|
||||
relayAddr,
|
||||
ma,
|
||||
disconnectOnFailure
|
||||
})
|
||||
case protocolIDv2Hop: return await this.connectV2({
|
||||
stream: stream.stream,
|
||||
connection: relayConnection,
|
||||
destinationPeer,
|
||||
destinationAddr,
|
||||
relayAddr,
|
||||
ma,
|
||||
disconnectOnFailure
|
||||
})
|
||||
default:
|
||||
stream.stream.reset()
|
||||
throw new Error('Unexpected stream protocol')
|
||||
}
|
||||
} catch (err: any) {
|
||||
log.error('Circuit relay dial failed', err)
|
||||
disconnectOnFailure && await relayConnection.close()
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async connectV1 ({
|
||||
stream, connection, destinationPeer,
|
||||
destinationAddr, relayAddr, ma,
|
||||
disconnectOnFailure
|
||||
}: ConnectOptions
|
||||
) {
|
||||
const virtualConnection = await CircuitV1Handler.hop({
|
||||
stream,
|
||||
request: {
|
||||
type: CircuitV1.CircuitRelay.Type.HOP,
|
||||
srcPeer: {
|
||||
id: this.components.getPeerId().toBytes(),
|
||||
addrs: this.components.getAddressManager().getListenAddrs().map(addr => addr.bytes)
|
||||
},
|
||||
dstPeer: {
|
||||
id: destinationPeer.toBytes(),
|
||||
addrs: [new Multiaddr(destinationAddr).bytes]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const localAddr = relayAddr.encapsulate(`/p2p-circuit/p2p/${this.components.getPeerId().toString()}`)
|
||||
const maConn = streamToMaConnection({
|
||||
stream: virtualConnection,
|
||||
remoteAddr: ma,
|
||||
localAddr
|
||||
})
|
||||
log('new outbound connection %s', maConn.remoteAddr)
|
||||
|
||||
return await this.components.getUpgrader().upgradeOutbound(maConn)
|
||||
}
|
||||
|
||||
async connectV2 (
|
||||
{
|
||||
stream, connection, destinationPeer,
|
||||
destinationAddr, relayAddr, ma,
|
||||
disconnectOnFailure
|
||||
}: ConnectOptions
|
||||
) {
|
||||
try {
|
||||
const streamHandler = new StreamHandlerV2({ stream })
|
||||
streamHandler.write(CircuitV2.HopMessage.encode({
|
||||
type: CircuitV2.HopMessage.Type.CONNECT,
|
||||
peer: {
|
||||
id: destinationPeer.toBytes(),
|
||||
addrs: [new Multiaddr(destinationAddr).bytes]
|
||||
}
|
||||
}).finish())
|
||||
|
||||
const status = CircuitV2.HopMessage.decode(await streamHandler.read())
|
||||
if (status.status !== CircuitV2.Status.OK) {
|
||||
throw createError(new Error('failed to connect via relay with status ' + status.status.toString()), codes.ERR_HOP_REQUEST_FAILED)
|
||||
}
|
||||
|
||||
// TODO: do something with limit and transient connection
|
||||
|
||||
let localAddr = relayAddr
|
||||
localAddr = localAddr.encapsulate(`/p2p-circuit/p2p/${this.components.getPeerId().toString()}`)
|
||||
const maConn = streamToMaConnection({
|
||||
stream: virtualConnection,
|
||||
stream: streamHandler.rest(),
|
||||
remoteAddr: ma,
|
||||
localAddr
|
||||
})
|
||||
log('new outbound connection %s', maConn.remoteAddr)
|
||||
|
||||
return await this.components.getUpgrader().upgradeOutbound(maConn)
|
||||
} catch (err: any) {
|
||||
const conn = await this.components.getUpgrader().upgradeOutbound(maConn)
|
||||
return conn
|
||||
} catch (/** @type {any} */ err) {
|
||||
log.error('Circuit relay dial failed', err)
|
||||
disconnectOnFailure && await relayConnection.close()
|
||||
disconnectOnFailure && await connection.close()
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -198,8 +343,8 @@ export class Circuit implements Transport, Initializable {
|
||||
this.handler = options.handler
|
||||
|
||||
return createListener({
|
||||
connectionManager: this.components.getConnectionManager(),
|
||||
peerStore: this.components.getPeerStore()
|
||||
dialer: this.components.getDialer(),
|
||||
connectionManager: this.components.getConnectionManager()
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -1,24 +1,24 @@
|
||||
import { logger } from '@libp2p/logger'
|
||||
import errCode from 'err-code'
|
||||
import { validateAddrs } from './utils.js'
|
||||
import { StreamHandler } from './stream-handler.js'
|
||||
import { CircuitRelay as CircuitPB } from '../pb/index.js'
|
||||
import { StreamHandlerV1 } from './stream-handler.js'
|
||||
import { CircuitRelay as CircuitPB, ICircuitRelay } from './pb/index.js'
|
||||
import { pipe } from 'it-pipe'
|
||||
import { codes as Errors } from '../../errors.js'
|
||||
import { stop } from './stop.js'
|
||||
import { RELAY_CODEC } from '../multicodec.js'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import { RELAY_V1_CODEC } from '../multicodec.js'
|
||||
import type { Connection, Stream } from '@libp2p/interfaces/connection'
|
||||
import { peerIdFromBytes } from '@libp2p/peer-id'
|
||||
import type { Duplex } from 'it-stream-types'
|
||||
import type { Circuit } from '../transport.js'
|
||||
import type { ConnectionManager } from '@libp2p/interfaces/connection-manager'
|
||||
import type { ConnectionManager } from '@libp2p/interfaces/registrar'
|
||||
|
||||
const log = logger('libp2p:circuit:hop')
|
||||
|
||||
export interface HopRequest {
|
||||
connection: Connection
|
||||
request: CircuitPB
|
||||
streamHandler: StreamHandler
|
||||
request: ICircuitRelay
|
||||
streamHandler: StreamHandlerV1
|
||||
circuit: Circuit
|
||||
connectionManager: ConnectionManager
|
||||
}
|
||||
@@ -58,8 +58,8 @@ export async function handleHop (hopRequest: HopRequest) {
|
||||
// Get the connection to the destination (stop) peer
|
||||
const destinationPeer = peerIdFromBytes(request.dstPeer.id)
|
||||
|
||||
const destinationConnections = connectionManager.getConnections(destinationPeer)
|
||||
if (destinationConnections.length === 0 && !circuit.hopActive()) {
|
||||
const destinationConnection = connectionManager.getConnection(destinationPeer)
|
||||
if (destinationConnection == null && !circuit.hopActive()) {
|
||||
log('HOP request received but we are not connected to the destination peer')
|
||||
return streamHandler.end({
|
||||
type: CircuitPB.Type.STATUS,
|
||||
@@ -68,7 +68,7 @@ export async function handleHop (hopRequest: HopRequest) {
|
||||
}
|
||||
|
||||
// TODO: Handle being an active relay
|
||||
if (destinationConnections.length === 0) {
|
||||
if (destinationConnection == null) {
|
||||
log('did not have connection to remote peer')
|
||||
return streamHandler.end({
|
||||
type: CircuitPB.Type.STATUS,
|
||||
@@ -87,7 +87,7 @@ export async function handleHop (hopRequest: HopRequest) {
|
||||
try {
|
||||
log('performing STOP request')
|
||||
const result = await stop({
|
||||
connection: destinationConnections[0],
|
||||
connection: destinationConnection,
|
||||
request: stopRequest
|
||||
})
|
||||
|
||||
@@ -119,8 +119,8 @@ export async function handleHop (hopRequest: HopRequest) {
|
||||
}
|
||||
|
||||
export interface HopConfig {
|
||||
connection: Connection
|
||||
request: CircuitPB
|
||||
stream: Stream
|
||||
request: ICircuitRelay
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,14 +129,12 @@ export interface HopConfig {
|
||||
*/
|
||||
export async function hop (options: HopConfig): Promise<Duplex<Uint8Array>> {
|
||||
const {
|
||||
connection,
|
||||
stream,
|
||||
request
|
||||
} = options
|
||||
|
||||
// Create a new stream to the relay
|
||||
const { stream } = await connection.newStream(RELAY_CODEC)
|
||||
// Send the HOP request
|
||||
const streamHandler = new StreamHandler({ stream })
|
||||
const streamHandler = new StreamHandlerV1({ stream })
|
||||
streamHandler.write(request)
|
||||
|
||||
const response = await streamHandler.read()
|
||||
@@ -153,7 +151,7 @@ export async function hop (options: HopConfig): Promise<Duplex<Uint8Array>> {
|
||||
log('hop request failed with code %d, closing stream', response.code)
|
||||
streamHandler.close()
|
||||
|
||||
throw errCode(new Error(`HOP request failed with code "${response.code ?? 'unknown'}"`), Errors.ERR_HOP_REQUEST_FAILED)
|
||||
throw errCode(new Error(`HOP request failed with code ${response.code}`), Errors.ERR_HOP_REQUEST_FAILED)
|
||||
}
|
||||
|
||||
export interface CanHopOptions {
|
||||
@@ -169,10 +167,10 @@ export async function canHop (options: CanHopOptions) {
|
||||
} = options
|
||||
|
||||
// Create a new stream to the relay
|
||||
const { stream } = await connection.newStream(RELAY_CODEC)
|
||||
const { stream } = await connection.newStream(RELAY_V1_CODEC)
|
||||
|
||||
// Send the HOP request
|
||||
const streamHandler = new StreamHandler({ stream })
|
||||
const streamHandler = new StreamHandlerV1({ stream })
|
||||
streamHandler.write({
|
||||
type: CircuitPB.Type.CAN_HOP
|
||||
})
|
||||
@@ -189,7 +187,7 @@ export async function canHop (options: CanHopOptions) {
|
||||
|
||||
export interface HandleCanHopOptions {
|
||||
connection: Connection
|
||||
streamHandler: StreamHandler
|
||||
streamHandler: StreamHandlerV1
|
||||
circuit: Circuit
|
||||
}
|
||||
|
2
src/circuit/v1/index.ts
Normal file
2
src/circuit/v1/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './hop.js'
|
||||
export * from './stop.js'
|
173
src/circuit/v1/pb/index.d.ts
vendored
Normal file
173
src/circuit/v1/pb/index.d.ts
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
import * as $protobuf from "protobufjs";
|
||||
/** Properties of a CircuitRelay. */
|
||||
export interface ICircuitRelay {
|
||||
|
||||
/** CircuitRelay type */
|
||||
type?: (CircuitRelay.Type|null);
|
||||
|
||||
/** CircuitRelay srcPeer */
|
||||
srcPeer?: (CircuitRelay.IPeer|null);
|
||||
|
||||
/** CircuitRelay dstPeer */
|
||||
dstPeer?: (CircuitRelay.IPeer|null);
|
||||
|
||||
/** CircuitRelay code */
|
||||
code?: (CircuitRelay.Status|null);
|
||||
}
|
||||
|
||||
/** Represents a CircuitRelay. */
|
||||
export class CircuitRelay implements ICircuitRelay {
|
||||
|
||||
/**
|
||||
* Constructs a new CircuitRelay.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: ICircuitRelay);
|
||||
|
||||
/** CircuitRelay type. */
|
||||
public type: CircuitRelay.Type;
|
||||
|
||||
/** CircuitRelay srcPeer. */
|
||||
public srcPeer?: (CircuitRelay.IPeer|null);
|
||||
|
||||
/** CircuitRelay dstPeer. */
|
||||
public dstPeer?: (CircuitRelay.IPeer|null);
|
||||
|
||||
/** CircuitRelay code. */
|
||||
public code: CircuitRelay.Status;
|
||||
|
||||
/**
|
||||
* Encodes the specified CircuitRelay message. Does not implicitly {@link CircuitRelay.verify|verify} messages.
|
||||
* @param m CircuitRelay message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: ICircuitRelay, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes a CircuitRelay message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns CircuitRelay
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): CircuitRelay;
|
||||
|
||||
/**
|
||||
* Creates a CircuitRelay message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns CircuitRelay
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): CircuitRelay;
|
||||
|
||||
/**
|
||||
* Creates a plain object from a CircuitRelay message. Also converts values to other types if specified.
|
||||
* @param m CircuitRelay
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: CircuitRelay, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this CircuitRelay to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
||||
|
||||
export namespace CircuitRelay {
|
||||
|
||||
/** Status enum. */
|
||||
enum Status {
|
||||
SUCCESS = 100,
|
||||
HOP_SRC_ADDR_TOO_LONG = 220,
|
||||
HOP_DST_ADDR_TOO_LONG = 221,
|
||||
HOP_SRC_MULTIADDR_INVALID = 250,
|
||||
HOP_DST_MULTIADDR_INVALID = 251,
|
||||
HOP_NO_CONN_TO_DST = 260,
|
||||
HOP_CANT_DIAL_DST = 261,
|
||||
HOP_CANT_OPEN_DST_STREAM = 262,
|
||||
HOP_CANT_SPEAK_RELAY = 270,
|
||||
HOP_CANT_RELAY_TO_SELF = 280,
|
||||
STOP_SRC_ADDR_TOO_LONG = 320,
|
||||
STOP_DST_ADDR_TOO_LONG = 321,
|
||||
STOP_SRC_MULTIADDR_INVALID = 350,
|
||||
STOP_DST_MULTIADDR_INVALID = 351,
|
||||
STOP_RELAY_REFUSED = 390,
|
||||
MALFORMED_MESSAGE = 400
|
||||
}
|
||||
|
||||
/** Type enum. */
|
||||
enum Type {
|
||||
HOP = 1,
|
||||
STOP = 2,
|
||||
STATUS = 3,
|
||||
CAN_HOP = 4
|
||||
}
|
||||
|
||||
/** Properties of a Peer. */
|
||||
interface IPeer {
|
||||
|
||||
/** Peer id */
|
||||
id: Uint8Array;
|
||||
|
||||
/** Peer addrs */
|
||||
addrs?: (Uint8Array[]|null);
|
||||
}
|
||||
|
||||
/** Represents a Peer. */
|
||||
class Peer implements IPeer {
|
||||
|
||||
/**
|
||||
* Constructs a new Peer.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: CircuitRelay.IPeer);
|
||||
|
||||
/** Peer id. */
|
||||
public id: Uint8Array;
|
||||
|
||||
/** Peer addrs. */
|
||||
public addrs: Uint8Array[];
|
||||
|
||||
/**
|
||||
* Encodes the specified Peer message. Does not implicitly {@link CircuitRelay.Peer.verify|verify} messages.
|
||||
* @param m Peer message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: CircuitRelay.IPeer, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes a Peer message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns Peer
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): CircuitRelay.Peer;
|
||||
|
||||
/**
|
||||
* Creates a Peer message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns Peer
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): CircuitRelay.Peer;
|
||||
|
||||
/**
|
||||
* Creates a plain object from a Peer message. Also converts values to other types if specified.
|
||||
* @param m Peer
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: CircuitRelay.Peer, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this Peer to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
||||
}
|
528
src/circuit/v1/pb/index.js
Normal file
528
src/circuit/v1/pb/index.js
Normal file
@@ -0,0 +1,528 @@
|
||||
/*eslint-disable*/
|
||||
import $protobuf from "protobufjs/minimal.js";
|
||||
|
||||
// Common aliases
|
||||
const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
|
||||
|
||||
// Exported root namespace
|
||||
const $root = $protobuf.roots["libp2p-circuit"] || ($protobuf.roots["libp2p-circuit"] = {});
|
||||
|
||||
export const CircuitRelay = $root.CircuitRelay = (() => {
|
||||
|
||||
/**
|
||||
* Properties of a CircuitRelay.
|
||||
* @exports ICircuitRelay
|
||||
* @interface ICircuitRelay
|
||||
* @property {CircuitRelay.Type|null} [type] CircuitRelay type
|
||||
* @property {CircuitRelay.IPeer|null} [srcPeer] CircuitRelay srcPeer
|
||||
* @property {CircuitRelay.IPeer|null} [dstPeer] CircuitRelay dstPeer
|
||||
* @property {CircuitRelay.Status|null} [code] CircuitRelay code
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a new CircuitRelay.
|
||||
* @exports CircuitRelay
|
||||
* @classdesc Represents a CircuitRelay.
|
||||
* @implements ICircuitRelay
|
||||
* @constructor
|
||||
* @param {ICircuitRelay=} [p] Properties to set
|
||||
*/
|
||||
function CircuitRelay(p) {
|
||||
if (p)
|
||||
for (var ks = Object.keys(p), i = 0; i < ks.length; ++i)
|
||||
if (p[ks[i]] != null)
|
||||
this[ks[i]] = p[ks[i]];
|
||||
}
|
||||
|
||||
/**
|
||||
* CircuitRelay type.
|
||||
* @member {CircuitRelay.Type} type
|
||||
* @memberof CircuitRelay
|
||||
* @instance
|
||||
*/
|
||||
CircuitRelay.prototype.type = 1;
|
||||
|
||||
/**
|
||||
* CircuitRelay srcPeer.
|
||||
* @member {CircuitRelay.IPeer|null|undefined} srcPeer
|
||||
* @memberof CircuitRelay
|
||||
* @instance
|
||||
*/
|
||||
CircuitRelay.prototype.srcPeer = null;
|
||||
|
||||
/**
|
||||
* CircuitRelay dstPeer.
|
||||
* @member {CircuitRelay.IPeer|null|undefined} dstPeer
|
||||
* @memberof CircuitRelay
|
||||
* @instance
|
||||
*/
|
||||
CircuitRelay.prototype.dstPeer = null;
|
||||
|
||||
/**
|
||||
* CircuitRelay code.
|
||||
* @member {CircuitRelay.Status} code
|
||||
* @memberof CircuitRelay
|
||||
* @instance
|
||||
*/
|
||||
CircuitRelay.prototype.code = 100;
|
||||
|
||||
/**
|
||||
* Encodes the specified CircuitRelay message. Does not implicitly {@link CircuitRelay.verify|verify} messages.
|
||||
* @function encode
|
||||
* @memberof CircuitRelay
|
||||
* @static
|
||||
* @param {ICircuitRelay} m CircuitRelay message or plain object to encode
|
||||
* @param {$protobuf.Writer} [w] Writer to encode to
|
||||
* @returns {$protobuf.Writer} Writer
|
||||
*/
|
||||
CircuitRelay.encode = function encode(m, w) {
|
||||
if (!w)
|
||||
w = $Writer.create();
|
||||
if (m.type != null && Object.hasOwnProperty.call(m, "type"))
|
||||
w.uint32(8).int32(m.type);
|
||||
if (m.srcPeer != null && Object.hasOwnProperty.call(m, "srcPeer"))
|
||||
$root.CircuitRelay.Peer.encode(m.srcPeer, w.uint32(18).fork()).ldelim();
|
||||
if (m.dstPeer != null && Object.hasOwnProperty.call(m, "dstPeer"))
|
||||
$root.CircuitRelay.Peer.encode(m.dstPeer, w.uint32(26).fork()).ldelim();
|
||||
if (m.code != null && Object.hasOwnProperty.call(m, "code"))
|
||||
w.uint32(32).int32(m.code);
|
||||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes a CircuitRelay message from the specified reader or buffer.
|
||||
* @function decode
|
||||
* @memberof CircuitRelay
|
||||
* @static
|
||||
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
|
||||
* @param {number} [l] Message length if known beforehand
|
||||
* @returns {CircuitRelay} CircuitRelay
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
CircuitRelay.decode = function decode(r, l) {
|
||||
if (!(r instanceof $Reader))
|
||||
r = $Reader.create(r);
|
||||
var c = l === undefined ? r.len : r.pos + l, m = new $root.CircuitRelay();
|
||||
while (r.pos < c) {
|
||||
var t = r.uint32();
|
||||
switch (t >>> 3) {
|
||||
case 1:
|
||||
m.type = r.int32();
|
||||
break;
|
||||
case 2:
|
||||
m.srcPeer = $root.CircuitRelay.Peer.decode(r, r.uint32());
|
||||
break;
|
||||
case 3:
|
||||
m.dstPeer = $root.CircuitRelay.Peer.decode(r, r.uint32());
|
||||
break;
|
||||
case 4:
|
||||
m.code = r.int32();
|
||||
break;
|
||||
default:
|
||||
r.skipType(t & 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a CircuitRelay message from a plain object. Also converts values to their respective internal types.
|
||||
* @function fromObject
|
||||
* @memberof CircuitRelay
|
||||
* @static
|
||||
* @param {Object.<string,*>} d Plain object
|
||||
* @returns {CircuitRelay} CircuitRelay
|
||||
*/
|
||||
CircuitRelay.fromObject = function fromObject(d) {
|
||||
if (d instanceof $root.CircuitRelay)
|
||||
return d;
|
||||
var m = new $root.CircuitRelay();
|
||||
switch (d.type) {
|
||||
case "HOP":
|
||||
case 1:
|
||||
m.type = 1;
|
||||
break;
|
||||
case "STOP":
|
||||
case 2:
|
||||
m.type = 2;
|
||||
break;
|
||||
case "STATUS":
|
||||
case 3:
|
||||
m.type = 3;
|
||||
break;
|
||||
case "CAN_HOP":
|
||||
case 4:
|
||||
m.type = 4;
|
||||
break;
|
||||
}
|
||||
if (d.srcPeer != null) {
|
||||
if (typeof d.srcPeer !== "object")
|
||||
throw TypeError(".CircuitRelay.srcPeer: object expected");
|
||||
m.srcPeer = $root.CircuitRelay.Peer.fromObject(d.srcPeer);
|
||||
}
|
||||
if (d.dstPeer != null) {
|
||||
if (typeof d.dstPeer !== "object")
|
||||
throw TypeError(".CircuitRelay.dstPeer: object expected");
|
||||
m.dstPeer = $root.CircuitRelay.Peer.fromObject(d.dstPeer);
|
||||
}
|
||||
switch (d.code) {
|
||||
case "SUCCESS":
|
||||
case 100:
|
||||
m.code = 100;
|
||||
break;
|
||||
case "HOP_SRC_ADDR_TOO_LONG":
|
||||
case 220:
|
||||
m.code = 220;
|
||||
break;
|
||||
case "HOP_DST_ADDR_TOO_LONG":
|
||||
case 221:
|
||||
m.code = 221;
|
||||
break;
|
||||
case "HOP_SRC_MULTIADDR_INVALID":
|
||||
case 250:
|
||||
m.code = 250;
|
||||
break;
|
||||
case "HOP_DST_MULTIADDR_INVALID":
|
||||
case 251:
|
||||
m.code = 251;
|
||||
break;
|
||||
case "HOP_NO_CONN_TO_DST":
|
||||
case 260:
|
||||
m.code = 260;
|
||||
break;
|
||||
case "HOP_CANT_DIAL_DST":
|
||||
case 261:
|
||||
m.code = 261;
|
||||
break;
|
||||
case "HOP_CANT_OPEN_DST_STREAM":
|
||||
case 262:
|
||||
m.code = 262;
|
||||
break;
|
||||
case "HOP_CANT_SPEAK_RELAY":
|
||||
case 270:
|
||||
m.code = 270;
|
||||
break;
|
||||
case "HOP_CANT_RELAY_TO_SELF":
|
||||
case 280:
|
||||
m.code = 280;
|
||||
break;
|
||||
case "STOP_SRC_ADDR_TOO_LONG":
|
||||
case 320:
|
||||
m.code = 320;
|
||||
break;
|
||||
case "STOP_DST_ADDR_TOO_LONG":
|
||||
case 321:
|
||||
m.code = 321;
|
||||
break;
|
||||
case "STOP_SRC_MULTIADDR_INVALID":
|
||||
case 350:
|
||||
m.code = 350;
|
||||
break;
|
||||
case "STOP_DST_MULTIADDR_INVALID":
|
||||
case 351:
|
||||
m.code = 351;
|
||||
break;
|
||||
case "STOP_RELAY_REFUSED":
|
||||
case 390:
|
||||
m.code = 390;
|
||||
break;
|
||||
case "MALFORMED_MESSAGE":
|
||||
case 400:
|
||||
m.code = 400;
|
||||
break;
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a plain object from a CircuitRelay message. Also converts values to other types if specified.
|
||||
* @function toObject
|
||||
* @memberof CircuitRelay
|
||||
* @static
|
||||
* @param {CircuitRelay} m CircuitRelay
|
||||
* @param {$protobuf.IConversionOptions} [o] Conversion options
|
||||
* @returns {Object.<string,*>} Plain object
|
||||
*/
|
||||
CircuitRelay.toObject = function toObject(m, o) {
|
||||
if (!o)
|
||||
o = {};
|
||||
var d = {};
|
||||
if (o.defaults) {
|
||||
d.type = o.enums === String ? "HOP" : 1;
|
||||
d.srcPeer = null;
|
||||
d.dstPeer = null;
|
||||
d.code = o.enums === String ? "SUCCESS" : 100;
|
||||
}
|
||||
if (m.type != null && m.hasOwnProperty("type")) {
|
||||
d.type = o.enums === String ? $root.CircuitRelay.Type[m.type] : m.type;
|
||||
}
|
||||
if (m.srcPeer != null && m.hasOwnProperty("srcPeer")) {
|
||||
d.srcPeer = $root.CircuitRelay.Peer.toObject(m.srcPeer, o);
|
||||
}
|
||||
if (m.dstPeer != null && m.hasOwnProperty("dstPeer")) {
|
||||
d.dstPeer = $root.CircuitRelay.Peer.toObject(m.dstPeer, o);
|
||||
}
|
||||
if (m.code != null && m.hasOwnProperty("code")) {
|
||||
d.code = o.enums === String ? $root.CircuitRelay.Status[m.code] : m.code;
|
||||
}
|
||||
return d;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts this CircuitRelay to JSON.
|
||||
* @function toJSON
|
||||
* @memberof CircuitRelay
|
||||
* @instance
|
||||
* @returns {Object.<string,*>} JSON object
|
||||
*/
|
||||
CircuitRelay.prototype.toJSON = function toJSON() {
|
||||
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Status enum.
|
||||
* @name CircuitRelay.Status
|
||||
* @enum {number}
|
||||
* @property {number} SUCCESS=100 SUCCESS value
|
||||
* @property {number} HOP_SRC_ADDR_TOO_LONG=220 HOP_SRC_ADDR_TOO_LONG value
|
||||
* @property {number} HOP_DST_ADDR_TOO_LONG=221 HOP_DST_ADDR_TOO_LONG value
|
||||
* @property {number} HOP_SRC_MULTIADDR_INVALID=250 HOP_SRC_MULTIADDR_INVALID value
|
||||
* @property {number} HOP_DST_MULTIADDR_INVALID=251 HOP_DST_MULTIADDR_INVALID value
|
||||
* @property {number} HOP_NO_CONN_TO_DST=260 HOP_NO_CONN_TO_DST value
|
||||
* @property {number} HOP_CANT_DIAL_DST=261 HOP_CANT_DIAL_DST value
|
||||
* @property {number} HOP_CANT_OPEN_DST_STREAM=262 HOP_CANT_OPEN_DST_STREAM value
|
||||
* @property {number} HOP_CANT_SPEAK_RELAY=270 HOP_CANT_SPEAK_RELAY value
|
||||
* @property {number} HOP_CANT_RELAY_TO_SELF=280 HOP_CANT_RELAY_TO_SELF value
|
||||
* @property {number} STOP_SRC_ADDR_TOO_LONG=320 STOP_SRC_ADDR_TOO_LONG value
|
||||
* @property {number} STOP_DST_ADDR_TOO_LONG=321 STOP_DST_ADDR_TOO_LONG value
|
||||
* @property {number} STOP_SRC_MULTIADDR_INVALID=350 STOP_SRC_MULTIADDR_INVALID value
|
||||
* @property {number} STOP_DST_MULTIADDR_INVALID=351 STOP_DST_MULTIADDR_INVALID value
|
||||
* @property {number} STOP_RELAY_REFUSED=390 STOP_RELAY_REFUSED value
|
||||
* @property {number} MALFORMED_MESSAGE=400 MALFORMED_MESSAGE value
|
||||
*/
|
||||
CircuitRelay.Status = (function() {
|
||||
const valuesById = {}, values = Object.create(valuesById);
|
||||
values[valuesById[100] = "SUCCESS"] = 100;
|
||||
values[valuesById[220] = "HOP_SRC_ADDR_TOO_LONG"] = 220;
|
||||
values[valuesById[221] = "HOP_DST_ADDR_TOO_LONG"] = 221;
|
||||
values[valuesById[250] = "HOP_SRC_MULTIADDR_INVALID"] = 250;
|
||||
values[valuesById[251] = "HOP_DST_MULTIADDR_INVALID"] = 251;
|
||||
values[valuesById[260] = "HOP_NO_CONN_TO_DST"] = 260;
|
||||
values[valuesById[261] = "HOP_CANT_DIAL_DST"] = 261;
|
||||
values[valuesById[262] = "HOP_CANT_OPEN_DST_STREAM"] = 262;
|
||||
values[valuesById[270] = "HOP_CANT_SPEAK_RELAY"] = 270;
|
||||
values[valuesById[280] = "HOP_CANT_RELAY_TO_SELF"] = 280;
|
||||
values[valuesById[320] = "STOP_SRC_ADDR_TOO_LONG"] = 320;
|
||||
values[valuesById[321] = "STOP_DST_ADDR_TOO_LONG"] = 321;
|
||||
values[valuesById[350] = "STOP_SRC_MULTIADDR_INVALID"] = 350;
|
||||
values[valuesById[351] = "STOP_DST_MULTIADDR_INVALID"] = 351;
|
||||
values[valuesById[390] = "STOP_RELAY_REFUSED"] = 390;
|
||||
values[valuesById[400] = "MALFORMED_MESSAGE"] = 400;
|
||||
return values;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Type enum.
|
||||
* @name CircuitRelay.Type
|
||||
* @enum {number}
|
||||
* @property {number} HOP=1 HOP value
|
||||
* @property {number} STOP=2 STOP value
|
||||
* @property {number} STATUS=3 STATUS value
|
||||
* @property {number} CAN_HOP=4 CAN_HOP value
|
||||
*/
|
||||
CircuitRelay.Type = (function() {
|
||||
const valuesById = {}, values = Object.create(valuesById);
|
||||
values[valuesById[1] = "HOP"] = 1;
|
||||
values[valuesById[2] = "STOP"] = 2;
|
||||
values[valuesById[3] = "STATUS"] = 3;
|
||||
values[valuesById[4] = "CAN_HOP"] = 4;
|
||||
return values;
|
||||
})();
|
||||
|
||||
CircuitRelay.Peer = (function() {
|
||||
|
||||
/**
|
||||
* Properties of a Peer.
|
||||
* @memberof CircuitRelay
|
||||
* @interface IPeer
|
||||
* @property {Uint8Array} id Peer id
|
||||
* @property {Array.<Uint8Array>|null} [addrs] Peer addrs
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a new Peer.
|
||||
* @memberof CircuitRelay
|
||||
* @classdesc Represents a Peer.
|
||||
* @implements IPeer
|
||||
* @constructor
|
||||
* @param {CircuitRelay.IPeer=} [p] Properties to set
|
||||
*/
|
||||
function Peer(p) {
|
||||
this.addrs = [];
|
||||
if (p)
|
||||
for (var ks = Object.keys(p), i = 0; i < ks.length; ++i)
|
||||
if (p[ks[i]] != null)
|
||||
this[ks[i]] = p[ks[i]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Peer id.
|
||||
* @member {Uint8Array} id
|
||||
* @memberof CircuitRelay.Peer
|
||||
* @instance
|
||||
*/
|
||||
Peer.prototype.id = $util.newBuffer([]);
|
||||
|
||||
/**
|
||||
* Peer addrs.
|
||||
* @member {Array.<Uint8Array>} addrs
|
||||
* @memberof CircuitRelay.Peer
|
||||
* @instance
|
||||
*/
|
||||
Peer.prototype.addrs = $util.emptyArray;
|
||||
|
||||
/**
|
||||
* Encodes the specified Peer message. Does not implicitly {@link CircuitRelay.Peer.verify|verify} messages.
|
||||
* @function encode
|
||||
* @memberof CircuitRelay.Peer
|
||||
* @static
|
||||
* @param {CircuitRelay.IPeer} m Peer message or plain object to encode
|
||||
* @param {$protobuf.Writer} [w] Writer to encode to
|
||||
* @returns {$protobuf.Writer} Writer
|
||||
*/
|
||||
Peer.encode = function encode(m, w) {
|
||||
if (!w)
|
||||
w = $Writer.create();
|
||||
w.uint32(10).bytes(m.id);
|
||||
if (m.addrs != null && m.addrs.length) {
|
||||
for (var i = 0; i < m.addrs.length; ++i)
|
||||
w.uint32(18).bytes(m.addrs[i]);
|
||||
}
|
||||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes a Peer message from the specified reader or buffer.
|
||||
* @function decode
|
||||
* @memberof CircuitRelay.Peer
|
||||
* @static
|
||||
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
|
||||
* @param {number} [l] Message length if known beforehand
|
||||
* @returns {CircuitRelay.Peer} Peer
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
Peer.decode = function decode(r, l) {
|
||||
if (!(r instanceof $Reader))
|
||||
r = $Reader.create(r);
|
||||
var c = l === undefined ? r.len : r.pos + l, m = new $root.CircuitRelay.Peer();
|
||||
while (r.pos < c) {
|
||||
var t = r.uint32();
|
||||
switch (t >>> 3) {
|
||||
case 1:
|
||||
m.id = r.bytes();
|
||||
break;
|
||||
case 2:
|
||||
if (!(m.addrs && m.addrs.length))
|
||||
m.addrs = [];
|
||||
m.addrs.push(r.bytes());
|
||||
break;
|
||||
default:
|
||||
r.skipType(t & 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!m.hasOwnProperty("id"))
|
||||
throw $util.ProtocolError("missing required 'id'", { instance: m });
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a Peer message from a plain object. Also converts values to their respective internal types.
|
||||
* @function fromObject
|
||||
* @memberof CircuitRelay.Peer
|
||||
* @static
|
||||
* @param {Object.<string,*>} d Plain object
|
||||
* @returns {CircuitRelay.Peer} Peer
|
||||
*/
|
||||
Peer.fromObject = function fromObject(d) {
|
||||
if (d instanceof $root.CircuitRelay.Peer)
|
||||
return d;
|
||||
var m = new $root.CircuitRelay.Peer();
|
||||
if (d.id != null) {
|
||||
if (typeof d.id === "string")
|
||||
$util.base64.decode(d.id, m.id = $util.newBuffer($util.base64.length(d.id)), 0);
|
||||
else if (d.id.length)
|
||||
m.id = d.id;
|
||||
}
|
||||
if (d.addrs) {
|
||||
if (!Array.isArray(d.addrs))
|
||||
throw TypeError(".CircuitRelay.Peer.addrs: array expected");
|
||||
m.addrs = [];
|
||||
for (var i = 0; i < d.addrs.length; ++i) {
|
||||
if (typeof d.addrs[i] === "string")
|
||||
$util.base64.decode(d.addrs[i], m.addrs[i] = $util.newBuffer($util.base64.length(d.addrs[i])), 0);
|
||||
else if (d.addrs[i].length)
|
||||
m.addrs[i] = d.addrs[i];
|
||||
}
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a plain object from a Peer message. Also converts values to other types if specified.
|
||||
* @function toObject
|
||||
* @memberof CircuitRelay.Peer
|
||||
* @static
|
||||
* @param {CircuitRelay.Peer} m Peer
|
||||
* @param {$protobuf.IConversionOptions} [o] Conversion options
|
||||
* @returns {Object.<string,*>} Plain object
|
||||
*/
|
||||
Peer.toObject = function toObject(m, o) {
|
||||
if (!o)
|
||||
o = {};
|
||||
var d = {};
|
||||
if (o.arrays || o.defaults) {
|
||||
d.addrs = [];
|
||||
}
|
||||
if (o.defaults) {
|
||||
if (o.bytes === String)
|
||||
d.id = "";
|
||||
else {
|
||||
d.id = [];
|
||||
if (o.bytes !== Array)
|
||||
d.id = $util.newBuffer(d.id);
|
||||
}
|
||||
}
|
||||
if (m.id != null && m.hasOwnProperty("id")) {
|
||||
d.id = o.bytes === String ? $util.base64.encode(m.id, 0, m.id.length) : o.bytes === Array ? Array.prototype.slice.call(m.id) : m.id;
|
||||
}
|
||||
if (m.addrs && m.addrs.length) {
|
||||
d.addrs = [];
|
||||
for (var j = 0; j < m.addrs.length; ++j) {
|
||||
d.addrs[j] = o.bytes === String ? $util.base64.encode(m.addrs[j], 0, m.addrs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.addrs[j]) : m.addrs[j];
|
||||
}
|
||||
}
|
||||
return d;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts this Peer to JSON.
|
||||
* @function toJSON
|
||||
* @memberof CircuitRelay.Peer
|
||||
* @instance
|
||||
* @returns {Object.<string,*>} JSON object
|
||||
*/
|
||||
Peer.prototype.toJSON = function toJSON() {
|
||||
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
|
||||
};
|
||||
|
||||
return Peer;
|
||||
})();
|
||||
|
||||
return CircuitRelay;
|
||||
})();
|
||||
|
||||
export { $root as default };
|
@@ -1,4 +1,4 @@
|
||||
syntax = "proto3";
|
||||
syntax = "proto2";
|
||||
|
||||
message CircuitRelay {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { logger } from '@libp2p/logger'
|
||||
import { CircuitRelay as CircuitPB } from '../pb/index.js'
|
||||
import { RELAY_CODEC } from '../multicodec.js'
|
||||
import { StreamHandler } from './stream-handler.js'
|
||||
import { CircuitRelay as CircuitPB, ICircuitRelay } from './pb/index.js'
|
||||
import { RELAY_V1_CODEC } from '../multicodec.js'
|
||||
import { StreamHandlerV1 } from './stream-handler.js'
|
||||
import { validateAddrs } from './utils.js'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import type { Duplex } from 'it-stream-types'
|
||||
@@ -10,8 +10,8 @@ const log = logger('libp2p:circuit:stop')
|
||||
|
||||
export interface HandleStopOptions {
|
||||
connection: Connection
|
||||
request: CircuitPB
|
||||
streamHandler: StreamHandler
|
||||
request: ICircuitRelay
|
||||
streamHandler: StreamHandlerV1
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,7 +44,7 @@ export function handleStop (options: HandleStopOptions): Duplex<Uint8Array> | un
|
||||
|
||||
export interface StopOptions {
|
||||
connection: Connection
|
||||
request: CircuitPB
|
||||
request: ICircuitRelay
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,9 +56,9 @@ export async function stop (options: StopOptions) {
|
||||
request
|
||||
} = options
|
||||
|
||||
const { stream } = await connection.newStream([RELAY_CODEC])
|
||||
const { stream } = await connection.newStream([RELAY_V1_CODEC])
|
||||
log('starting stop request to %p', connection.remotePeer)
|
||||
const streamHandler = new StreamHandler({ stream })
|
||||
const streamHandler = new StreamHandlerV1({ stream })
|
||||
|
||||
streamHandler.write(request)
|
||||
const response = await streamHandler.read()
|
@@ -1,11 +1,11 @@
|
||||
import { logger } from '@libp2p/logger'
|
||||
import * as lp from 'it-length-prefixed'
|
||||
import { Handshake, handshake } from 'it-handshake'
|
||||
import { CircuitRelay } from '../pb/index.js'
|
||||
import { CircuitRelay, ICircuitRelay } from './pb/index.js'
|
||||
import type { Stream } from '@libp2p/interfaces/connection'
|
||||
import type { Source } from 'it-stream-types'
|
||||
|
||||
const log = logger('libp2p:circuit:stream-handler')
|
||||
const log = logger('libp2p:circuitv1:stream-handler')
|
||||
|
||||
export interface StreamHandlerOptions {
|
||||
/**
|
||||
@@ -19,7 +19,7 @@ export interface StreamHandlerOptions {
|
||||
maxLength?: number
|
||||
}
|
||||
|
||||
export class StreamHandler {
|
||||
export class StreamHandlerV1 {
|
||||
private readonly stream: Stream
|
||||
private readonly shake: Handshake
|
||||
private readonly decoder: Source<Uint8Array>
|
||||
@@ -53,9 +53,10 @@ export class StreamHandler {
|
||||
/**
|
||||
* Encode and write array of buffers
|
||||
*/
|
||||
write (msg: CircuitRelay) {
|
||||
write (msg: ICircuitRelay) {
|
||||
log('write message type %s', msg.type)
|
||||
this.shake.write(lp.encode.single(CircuitRelay.encode(msg)).slice())
|
||||
// @ts-expect-error lp.encode expects type type 'Buffer | BufferList', not 'Uint8Array'
|
||||
this.shake.write(lp.encode.single(CircuitRelay.encode(msg).finish()))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,9 +68,9 @@ export class StreamHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {CircuitRelay} msg - An unencoded CircuitRelay protobuf message
|
||||
* @param {ICircuitRelay} msg - An unencoded CircuitRelay protobuf message
|
||||
*/
|
||||
end (msg: CircuitRelay) {
|
||||
end (msg: ICircuitRelay) {
|
||||
this.write(msg)
|
||||
this.close()
|
||||
}
|
@@ -1,11 +1,11 @@
|
||||
import { Multiaddr } from '@multiformats/multiaddr'
|
||||
import { CircuitRelay } from '../pb/index.js'
|
||||
import type { StreamHandler } from './stream-handler.js'
|
||||
import { CircuitRelay, ICircuitRelay } from './pb/index.js'
|
||||
import type { StreamHandlerV1 } from './stream-handler.js'
|
||||
|
||||
/**
|
||||
* Write a response
|
||||
*/
|
||||
function writeResponse (streamHandler: StreamHandler, status: CircuitRelay.Status) {
|
||||
function writeResponse (streamHandler: StreamHandlerV1, status: CircuitRelay.Status) {
|
||||
streamHandler.write({
|
||||
type: CircuitRelay.Type.STATUS,
|
||||
code: status
|
||||
@@ -15,7 +15,7 @@ function writeResponse (streamHandler: StreamHandler, status: CircuitRelay.Statu
|
||||
/**
|
||||
* Validate incomming HOP/STOP message
|
||||
*/
|
||||
export function validateAddrs (msg: CircuitRelay, streamHandler: StreamHandler) {
|
||||
export function validateAddrs (msg: ICircuitRelay, streamHandler: StreamHandlerV1) {
|
||||
try {
|
||||
if (msg.dstPeer?.addrs != null) {
|
||||
msg.dstPeer.addrs.forEach((addr) => {
|
220
src/circuit/v2/hop.ts
Normal file
220
src/circuit/v2/hop.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import { RecordEnvelope } from '@libp2p/peer-record'
|
||||
import { logger } from '@libp2p/logger'
|
||||
import { pipe } from 'it-pipe'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import { HopMessage, IHopMessage, IReservation, ILimit, Status, StopMessage } from './pb/index.js'
|
||||
import { StreamHandlerV2 } from './stream-handler.js'
|
||||
import type { Circuit } from '../transport.js'
|
||||
import { Multiaddr } from '@multiformats/multiaddr'
|
||||
import type { Acl, ReservationStore } from './interfaces.js'
|
||||
import { protocolIDv2Hop } from '../multicodec.js'
|
||||
import { validateHopConnectRequest } from './validation.js'
|
||||
import { stop } from './stop.js'
|
||||
import { ReservationVoucherRecord } from './reservation-voucher.js'
|
||||
import { peerIdFromBytes } from '@libp2p/peer-id'
|
||||
|
||||
const log = logger('libp2p:circuitv2:hop')
|
||||
|
||||
export interface HopProtocolOptions {
|
||||
connection: Connection
|
||||
request: IHopMessage
|
||||
streamHandler: StreamHandlerV2
|
||||
circuit: Circuit
|
||||
relayPeer: PeerId
|
||||
relayAddrs: Multiaddr[]
|
||||
limit?: ILimit
|
||||
acl?: Acl
|
||||
reservationStore: ReservationStore
|
||||
}
|
||||
|
||||
export async function handleHopProtocol (options: HopProtocolOptions) {
|
||||
switch (options.request.type) {
|
||||
case HopMessage.Type.RESERVE: await handleReserve(options); break
|
||||
case HopMessage.Type.CONNECT: await handleConnect(options); break
|
||||
default: {
|
||||
log.error('invalid hop request type %s via peer %s', options.request.type, options.connection.remotePeer)
|
||||
writeErrorResponse(options.streamHandler, Status.MALFORMED_MESSAGE)
|
||||
options.streamHandler.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function reserve (connection: Connection) {
|
||||
log('requesting reservation from %s', connection.remotePeer)
|
||||
const { stream } = await connection.newStream([protocolIDv2Hop])
|
||||
const streamHandler = new StreamHandlerV2({ stream })
|
||||
streamHandler.write(HopMessage.encode({
|
||||
type: HopMessage.Type.RESERVE
|
||||
}).finish())
|
||||
|
||||
let response
|
||||
try {
|
||||
response = HopMessage.decode(await streamHandler.read())
|
||||
} catch (e: any) {
|
||||
log.error('error passing reserve message response from %s because', connection.remotePeer, e.message)
|
||||
streamHandler.close()
|
||||
throw e
|
||||
}
|
||||
|
||||
if (response.status === Status.OK && response.reservation !== null) {
|
||||
return response.reservation
|
||||
}
|
||||
const errMsg = `reservation failed with status ${response.status}`
|
||||
log.error(errMsg)
|
||||
throw new Error(errMsg)
|
||||
}
|
||||
|
||||
async function handleReserve ({ connection, streamHandler, relayPeer, relayAddrs, limit, acl, reservationStore }: HopProtocolOptions) {
|
||||
log('hop reserve request from %s', connection.remotePeer)
|
||||
|
||||
// TODO: prevent reservation over relay address
|
||||
|
||||
if ((await acl?.allowReserve?.(connection.remotePeer, connection.remoteAddr)) === false) {
|
||||
log.error('acl denied reservation to %s', connection.remotePeer)
|
||||
writeErrorResponse(streamHandler, Status.PERMISSION_DENIED)
|
||||
streamHandler.close()
|
||||
return
|
||||
}
|
||||
|
||||
const result = await reservationStore.reserve(connection.remotePeer, connection.remoteAddr)
|
||||
|
||||
if (result.status !== Status.OK) {
|
||||
writeErrorResponse(streamHandler, result.status)
|
||||
streamHandler.close()
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
writeResponse(
|
||||
streamHandler,
|
||||
{
|
||||
type: HopMessage.Type.STATUS,
|
||||
reservation: await makeReservation(relayAddrs, relayPeer, connection.remotePeer, result.expire ?? 0),
|
||||
limit
|
||||
})
|
||||
log('sent confirmation response to %s', connection.remotePeer)
|
||||
} catch (err) {
|
||||
log.error('failed to send confirmation response to %s', connection.remotePeer)
|
||||
await reservationStore.removeReservation(connection.remotePeer)
|
||||
}
|
||||
// TODO: how to ensure connection manager doesn't close reserved relay conn
|
||||
}
|
||||
|
||||
type HopConnectOptions = Pick<
|
||||
HopProtocolOptions,
|
||||
'connection' | 'streamHandler' | 'request' | 'reservationStore' |'circuit' |'acl'
|
||||
>
|
||||
|
||||
async function handleConnect (options: HopConnectOptions) {
|
||||
const { connection, streamHandler, request, reservationStore, circuit, acl } = options
|
||||
log('hop connect request from %s', connection.remotePeer)
|
||||
// Validate the HOP connect request has the required input
|
||||
try {
|
||||
validateHopConnectRequest(request, streamHandler)
|
||||
} catch (/** @type {any} */ err) {
|
||||
log.error('invalid hop connect request via peer %s', connection.remotePeer, err)
|
||||
writeErrorResponse(streamHandler, Status.MALFORMED_MESSAGE)
|
||||
return
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
const dstPeer = peerIdFromBytes(request.peer.id)
|
||||
|
||||
if (acl?.allowConnect !== undefined) {
|
||||
const status = await acl.allowConnect(connection.remotePeer, connection.remoteAddr, dstPeer)
|
||||
if (status !== Status.OK) {
|
||||
log.error('hop connect denied for %s with status %s', connection.remotePeer, status)
|
||||
writeErrorResponse(streamHandler, status)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!await reservationStore.hasReservation(dstPeer)) {
|
||||
log.error('hop connect denied for %s with status %s', connection.remotePeer, Status.NO_RESERVATION)
|
||||
writeErrorResponse(streamHandler, Status.NO_RESERVATION)
|
||||
return
|
||||
}
|
||||
|
||||
const destinationConnection = circuit.getPeerConnection(dstPeer)
|
||||
if (destinationConnection === undefined || destinationConnection === null) {
|
||||
log('hop connect denied for %s as there is no destination connection', connection.remotePeer)
|
||||
writeErrorResponse(streamHandler, Status.NO_RESERVATION)
|
||||
return
|
||||
}
|
||||
|
||||
log('hop connect request from %s to %s is valid', connection.remotePeer, dstPeer)
|
||||
|
||||
const destinationStream = await stop({
|
||||
connection: destinationConnection,
|
||||
request: {
|
||||
type: StopMessage.Type.CONNECT,
|
||||
peer: {
|
||||
id: connection.remotePeer.toBytes(),
|
||||
addrs: [new Multiaddr('/p2p/' + connection.remotePeer.toString()).bytes]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (destinationStream === undefined || destinationStream === null) {
|
||||
log.error('failed to open stream to destination peer %s', destinationConnection?.remotePeer)
|
||||
writeErrorResponse(streamHandler, Status.CONNECTION_FAILED)
|
||||
return
|
||||
}
|
||||
|
||||
writeResponse(streamHandler, { type: HopMessage.Type.STATUS, status: Status.OK })
|
||||
|
||||
const sourceStream = streamHandler.rest()
|
||||
log('connection to destination established, short circuiting streams...')
|
||||
// Short circuit the two streams to create the relayed connection
|
||||
return await pipe(
|
||||
sourceStream,
|
||||
destinationStream,
|
||||
sourceStream
|
||||
)
|
||||
}
|
||||
|
||||
async function makeReservation (
|
||||
relayAddrs: Multiaddr[],
|
||||
relayPeerId: PeerId,
|
||||
remotePeer: PeerId,
|
||||
expire: number
|
||||
): Promise<IReservation> {
|
||||
const addrs = []
|
||||
|
||||
for (const relayAddr of relayAddrs) {
|
||||
addrs.push(relayAddr.bytes)
|
||||
}
|
||||
|
||||
const voucher = await RecordEnvelope.seal(new ReservationVoucherRecord({
|
||||
peer: remotePeer,
|
||||
relay: relayPeerId,
|
||||
expiration: expire
|
||||
}), relayPeerId)
|
||||
|
||||
return {
|
||||
addrs,
|
||||
expire,
|
||||
voucher: voucher.marshal()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write an error response and closes stream
|
||||
*
|
||||
*/
|
||||
function writeErrorResponse (streamHandler: StreamHandlerV2, status: Status) {
|
||||
writeResponse(streamHandler, {
|
||||
type: HopMessage.Type.STATUS,
|
||||
status
|
||||
})
|
||||
streamHandler.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a response
|
||||
*
|
||||
*/
|
||||
function writeResponse (streamHandler: StreamHandlerV2, msg: IHopMessage) {
|
||||
streamHandler.write(HopMessage.encode(msg).finish())
|
||||
}
|
2
src/circuit/v2/index.ts
Normal file
2
src/circuit/v2/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './hop.js'
|
||||
export * from './stop.js'
|
21
src/circuit/v2/interfaces.ts
Normal file
21
src/circuit/v2/interfaces.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import type { Multiaddr } from '@multiformats/multiaddr'
|
||||
import type { Status } from './pb/index.js'
|
||||
|
||||
export type ReservationStatus = Status.OK | Status.PERMISSION_DENIED | Status.RESERVATION_REFUSED
|
||||
|
||||
export interface ReservationStore {
|
||||
reserve: (peer: PeerId, addr: Multiaddr) => Promise<{status: ReservationStatus, expire?: number}>
|
||||
removeReservation: (peer: PeerId) => Promise<void>
|
||||
hasReservation: (dst: PeerId) => Promise<boolean>
|
||||
}
|
||||
|
||||
type AclStatus = Status.OK | Status.RESOURCE_LIMIT_EXCEEDED | Status.PERMISSION_DENIED
|
||||
|
||||
export interface Acl {
|
||||
allowReserve: (peer: PeerId, addr: Multiaddr) => Promise<boolean>
|
||||
/**
|
||||
* Checks if connection should be allowed
|
||||
*/
|
||||
allowConnect: (src: PeerId, addr: Multiaddr, dst: PeerId) => Promise<AclStatus>
|
||||
}
|
450
src/circuit/v2/pb/index.d.ts
vendored
Normal file
450
src/circuit/v2/pb/index.d.ts
vendored
Normal file
@@ -0,0 +1,450 @@
|
||||
import * as $protobuf from "protobufjs";
|
||||
/** Properties of a HopMessage. */
|
||||
export interface IHopMessage {
|
||||
|
||||
/** HopMessage type */
|
||||
type: HopMessage.Type;
|
||||
|
||||
/** HopMessage peer */
|
||||
peer?: (IPeer|null);
|
||||
|
||||
/** HopMessage reservation */
|
||||
reservation?: (IReservation|null);
|
||||
|
||||
/** HopMessage limit */
|
||||
limit?: (ILimit|null);
|
||||
|
||||
/** HopMessage status */
|
||||
status?: (Status|null);
|
||||
}
|
||||
|
||||
/** Represents a HopMessage. */
|
||||
export class HopMessage implements IHopMessage {
|
||||
|
||||
/**
|
||||
* Constructs a new HopMessage.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: IHopMessage);
|
||||
|
||||
/** HopMessage type. */
|
||||
public type: HopMessage.Type;
|
||||
|
||||
/** HopMessage peer. */
|
||||
public peer?: (IPeer|null);
|
||||
|
||||
/** HopMessage reservation. */
|
||||
public reservation?: (IReservation|null);
|
||||
|
||||
/** HopMessage limit. */
|
||||
public limit?: (ILimit|null);
|
||||
|
||||
/** HopMessage status. */
|
||||
public status: Status;
|
||||
|
||||
/**
|
||||
* Encodes the specified HopMessage message. Does not implicitly {@link HopMessage.verify|verify} messages.
|
||||
* @param m HopMessage message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: IHopMessage, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes a HopMessage message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns HopMessage
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): HopMessage;
|
||||
|
||||
/**
|
||||
* Creates a HopMessage message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns HopMessage
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): HopMessage;
|
||||
|
||||
/**
|
||||
* Creates a plain object from a HopMessage message. Also converts values to other types if specified.
|
||||
* @param m HopMessage
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: HopMessage, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this HopMessage to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
||||
|
||||
export namespace HopMessage {
|
||||
|
||||
/** Type enum. */
|
||||
enum Type {
|
||||
RESERVE = 0,
|
||||
CONNECT = 1,
|
||||
STATUS = 2
|
||||
}
|
||||
}
|
||||
|
||||
/** Properties of a StopMessage. */
|
||||
export interface IStopMessage {
|
||||
|
||||
/** StopMessage type */
|
||||
type: StopMessage.Type;
|
||||
|
||||
/** StopMessage peer */
|
||||
peer?: (IPeer|null);
|
||||
|
||||
/** StopMessage limit */
|
||||
limit?: (ILimit|null);
|
||||
|
||||
/** StopMessage status */
|
||||
status?: (Status|null);
|
||||
}
|
||||
|
||||
/** Represents a StopMessage. */
|
||||
export class StopMessage implements IStopMessage {
|
||||
|
||||
/**
|
||||
* Constructs a new StopMessage.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: IStopMessage);
|
||||
|
||||
/** StopMessage type. */
|
||||
public type: StopMessage.Type;
|
||||
|
||||
/** StopMessage peer. */
|
||||
public peer?: (IPeer|null);
|
||||
|
||||
/** StopMessage limit. */
|
||||
public limit?: (ILimit|null);
|
||||
|
||||
/** StopMessage status. */
|
||||
public status: Status;
|
||||
|
||||
/**
|
||||
* Encodes the specified StopMessage message. Does not implicitly {@link StopMessage.verify|verify} messages.
|
||||
* @param m StopMessage message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: IStopMessage, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes a StopMessage message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns StopMessage
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): StopMessage;
|
||||
|
||||
/**
|
||||
* Creates a StopMessage message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns StopMessage
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): StopMessage;
|
||||
|
||||
/**
|
||||
* Creates a plain object from a StopMessage message. Also converts values to other types if specified.
|
||||
* @param m StopMessage
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: StopMessage, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this StopMessage to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
||||
|
||||
export namespace StopMessage {
|
||||
|
||||
/** Type enum. */
|
||||
enum Type {
|
||||
CONNECT = 0,
|
||||
STATUS = 1
|
||||
}
|
||||
}
|
||||
|
||||
/** Properties of a Peer. */
|
||||
export interface IPeer {
|
||||
|
||||
/** Peer id */
|
||||
id: Uint8Array;
|
||||
|
||||
/** Peer addrs */
|
||||
addrs?: (Uint8Array[]|null);
|
||||
}
|
||||
|
||||
/** Represents a Peer. */
|
||||
export class Peer implements IPeer {
|
||||
|
||||
/**
|
||||
* Constructs a new Peer.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: IPeer);
|
||||
|
||||
/** Peer id. */
|
||||
public id: Uint8Array;
|
||||
|
||||
/** Peer addrs. */
|
||||
public addrs: Uint8Array[];
|
||||
|
||||
/**
|
||||
* Encodes the specified Peer message. Does not implicitly {@link Peer.verify|verify} messages.
|
||||
* @param m Peer message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: IPeer, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes a Peer message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns Peer
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Peer;
|
||||
|
||||
/**
|
||||
* Creates a Peer message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns Peer
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): Peer;
|
||||
|
||||
/**
|
||||
* Creates a plain object from a Peer message. Also converts values to other types if specified.
|
||||
* @param m Peer
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: Peer, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this Peer to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
||||
|
||||
/** Properties of a Reservation. */
|
||||
export interface IReservation {
|
||||
|
||||
/** Reservation expire */
|
||||
expire: number;
|
||||
|
||||
/** Reservation addrs */
|
||||
addrs?: (Uint8Array[]|null);
|
||||
|
||||
/** Reservation voucher */
|
||||
voucher?: (Uint8Array|null);
|
||||
}
|
||||
|
||||
/** Represents a Reservation. */
|
||||
export class Reservation implements IReservation {
|
||||
|
||||
/**
|
||||
* Constructs a new Reservation.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: IReservation);
|
||||
|
||||
/** Reservation expire. */
|
||||
public expire: number;
|
||||
|
||||
/** Reservation addrs. */
|
||||
public addrs: Uint8Array[];
|
||||
|
||||
/** Reservation voucher. */
|
||||
public voucher: Uint8Array;
|
||||
|
||||
/**
|
||||
* Encodes the specified Reservation message. Does not implicitly {@link Reservation.verify|verify} messages.
|
||||
* @param m Reservation message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: IReservation, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes a Reservation message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns Reservation
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Reservation;
|
||||
|
||||
/**
|
||||
* Creates a Reservation message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns Reservation
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): Reservation;
|
||||
|
||||
/**
|
||||
* Creates a plain object from a Reservation message. Also converts values to other types if specified.
|
||||
* @param m Reservation
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: Reservation, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this Reservation to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
||||
|
||||
/** Properties of a Limit. */
|
||||
export interface ILimit {
|
||||
|
||||
/** Limit duration */
|
||||
duration?: (number|null);
|
||||
|
||||
/** Limit data */
|
||||
data?: (number|null);
|
||||
}
|
||||
|
||||
/** Represents a Limit. */
|
||||
export class Limit implements ILimit {
|
||||
|
||||
/**
|
||||
* Constructs a new Limit.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: ILimit);
|
||||
|
||||
/** Limit duration. */
|
||||
public duration: number;
|
||||
|
||||
/** Limit data. */
|
||||
public data: number;
|
||||
|
||||
/**
|
||||
* Encodes the specified Limit message. Does not implicitly {@link Limit.verify|verify} messages.
|
||||
* @param m Limit message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: ILimit, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes a Limit message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns Limit
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Limit;
|
||||
|
||||
/**
|
||||
* Creates a Limit message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns Limit
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): Limit;
|
||||
|
||||
/**
|
||||
* Creates a plain object from a Limit message. Also converts values to other types if specified.
|
||||
* @param m Limit
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: Limit, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this Limit to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
||||
|
||||
/** Status enum. */
|
||||
export enum Status {
|
||||
OK = 100,
|
||||
RESERVATION_REFUSED = 200,
|
||||
RESOURCE_LIMIT_EXCEEDED = 201,
|
||||
PERMISSION_DENIED = 202,
|
||||
CONNECTION_FAILED = 203,
|
||||
NO_RESERVATION = 204,
|
||||
MALFORMED_MESSAGE = 400,
|
||||
UNEXPECTED_MESSAGE = 401
|
||||
}
|
||||
|
||||
/** Represents a ReservationVoucher. */
|
||||
export class ReservationVoucher implements IReservationVoucher {
|
||||
|
||||
/**
|
||||
* Constructs a new ReservationVoucher.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: IReservationVoucher);
|
||||
|
||||
/** ReservationVoucher relay. */
|
||||
public relay: Uint8Array;
|
||||
|
||||
/** ReservationVoucher peer. */
|
||||
public peer: Uint8Array;
|
||||
|
||||
/** ReservationVoucher expiration. */
|
||||
public expiration: number;
|
||||
|
||||
/**
|
||||
* Encodes the specified ReservationVoucher message. Does not implicitly {@link ReservationVoucher.verify|verify} messages.
|
||||
* @param m ReservationVoucher message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: IReservationVoucher, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes a ReservationVoucher message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns ReservationVoucher
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): ReservationVoucher;
|
||||
|
||||
/**
|
||||
* Creates a ReservationVoucher message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns ReservationVoucher
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): ReservationVoucher;
|
||||
|
||||
/**
|
||||
* Creates a plain object from a ReservationVoucher message. Also converts values to other types if specified.
|
||||
* @param m ReservationVoucher
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: ReservationVoucher, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this ReservationVoucher to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
1346
src/circuit/v2/pb/index.js
Normal file
1346
src/circuit/v2/pb/index.js
Normal file
File diff suppressed because it is too large
Load Diff
64
src/circuit/v2/pb/index.proto
Normal file
64
src/circuit/v2/pb/index.proto
Normal file
@@ -0,0 +1,64 @@
|
||||
syntax = "proto2";
|
||||
|
||||
message HopMessage {
|
||||
enum Type {
|
||||
RESERVE = 0;
|
||||
CONNECT = 1;
|
||||
STATUS = 2;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional Peer peer = 2;
|
||||
optional Reservation reservation = 3;
|
||||
optional Limit limit = 4;
|
||||
|
||||
optional Status status = 5;
|
||||
}
|
||||
|
||||
message StopMessage {
|
||||
enum Type {
|
||||
CONNECT = 0;
|
||||
STATUS = 1;
|
||||
}
|
||||
|
||||
required Type type = 1;
|
||||
|
||||
optional Peer peer = 2;
|
||||
optional Limit limit = 3;
|
||||
|
||||
optional Status status = 4;
|
||||
}
|
||||
|
||||
message Peer {
|
||||
required bytes id = 1;
|
||||
repeated bytes addrs = 2;
|
||||
}
|
||||
|
||||
message Reservation {
|
||||
required uint64 expire = 1; // Unix expiration time (UTC)
|
||||
repeated bytes addrs = 2; // relay addrs for reserving peer
|
||||
optional bytes voucher = 3; // reservation voucher
|
||||
}
|
||||
|
||||
message Limit {
|
||||
optional uint32 duration = 1; // seconds
|
||||
optional uint64 data = 2; // bytes
|
||||
}
|
||||
|
||||
enum Status {
|
||||
OK = 100;
|
||||
RESERVATION_REFUSED = 200;
|
||||
RESOURCE_LIMIT_EXCEEDED = 201;
|
||||
PERMISSION_DENIED = 202;
|
||||
CONNECTION_FAILED = 203;
|
||||
NO_RESERVATION = 204;
|
||||
MALFORMED_MESSAGE = 400;
|
||||
UNEXPECTED_MESSAGE = 401;
|
||||
}
|
||||
|
||||
message ReservationVoucher {
|
||||
required bytes relay = 1;
|
||||
required bytes peer = 2;
|
||||
required uint64 expiration = 3;
|
||||
}
|
33
src/circuit/v2/reservation-store.ts
Normal file
33
src/circuit/v2/reservation-store.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Status } from './pb/index.js'
|
||||
import type { ReservationStore as IReservationStore, ReservationStatus } from './interfaces.js'
|
||||
import type { Multiaddr } from '@multiformats/multiaddr'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
|
||||
interface Reservation {
|
||||
addr: Multiaddr
|
||||
expire: Date
|
||||
}
|
||||
|
||||
export class ReservationStore implements IReservationStore {
|
||||
private readonly reservations = new Map<string, Reservation>()
|
||||
|
||||
constructor (private readonly limit = 15) {
|
||||
}
|
||||
|
||||
async reserve (peer: PeerId, addr: Multiaddr): Promise<{status: ReservationStatus, expire?: number}> {
|
||||
if (this.reservations.size >= this.limit && !this.reservations.has(peer.toString())) {
|
||||
return { status: Status.RESERVATION_REFUSED, expire: undefined }
|
||||
}
|
||||
const expire = new Date()
|
||||
this.reservations.set(peer.toString(), { addr, expire })
|
||||
return { status: Status.OK, expire: expire.getTime() }
|
||||
}
|
||||
|
||||
async removeReservation (peer: PeerId) {
|
||||
this.reservations.delete(peer.toString())
|
||||
}
|
||||
|
||||
async hasReservation (dst: PeerId) {
|
||||
return this.reservations.has(dst.toString())
|
||||
}
|
||||
}
|
51
src/circuit/v2/reservation-voucher.ts
Normal file
51
src/circuit/v2/reservation-voucher.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import type { Record } from '@libp2p/interfaces/record'
|
||||
import { ReservationVoucher } from './pb/index.js'
|
||||
|
||||
export interface ReservationVoucherOptions {
|
||||
relay: PeerId
|
||||
peer: PeerId
|
||||
expiration: number
|
||||
}
|
||||
|
||||
export class ReservationVoucherRecord implements Record {
|
||||
public readonly domain = 'libp2p-relay-rsvp'
|
||||
public readonly codec = new Uint8Array([0x03, 0x02])
|
||||
|
||||
private readonly relay: PeerId
|
||||
private readonly peer: PeerId
|
||||
private readonly expiration: number
|
||||
|
||||
constructor ({ relay, peer, expiration }: ReservationVoucherOptions) {
|
||||
this.relay = relay
|
||||
this.peer = peer
|
||||
this.expiration = expiration
|
||||
}
|
||||
|
||||
marshal () {
|
||||
return ReservationVoucher.encode({
|
||||
relay: this.relay.toBytes(),
|
||||
peer: this.peer.toBytes(),
|
||||
expiration: this.expiration
|
||||
}).finish()
|
||||
}
|
||||
|
||||
equals (other: Record) {
|
||||
if (!(other instanceof ReservationVoucherRecord)) {
|
||||
return false
|
||||
}
|
||||
if (!this.peer.equals(other.peer)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!this.relay.equals(other.relay)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.expiration !== other.expiration) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
79
src/circuit/v2/stop.ts
Normal file
79
src/circuit/v2/stop.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
|
||||
import { IStopMessage, Status, StopMessage } from './pb/index.js'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
|
||||
import { logger } from '@libp2p/logger'
|
||||
import { StreamHandlerV2 } from './stream-handler.js'
|
||||
import { protocolIDv2Stop } from '../multicodec.js'
|
||||
import { validateStopConnectRequest } from './validation.js'
|
||||
|
||||
const log = logger('libp2p:circuitv2:stop')
|
||||
|
||||
export interface HandleStopOptions {
|
||||
connection: Connection
|
||||
request: IStopMessage
|
||||
streamHandler: StreamHandlerV2
|
||||
}
|
||||
|
||||
export async function handleStop ({
|
||||
connection,
|
||||
request,
|
||||
streamHandler
|
||||
}: HandleStopOptions) {
|
||||
log('new circuit relay v2 stop stream from %s', connection.remotePeer)
|
||||
// Validate the STOP request has the required input
|
||||
try {
|
||||
validateStopConnectRequest(request, streamHandler)
|
||||
} catch (/** @type {any} */ err) {
|
||||
return log.error('invalid stop connect request via peer %s', connection.remotePeer, err)
|
||||
}
|
||||
log('stop request is valid')
|
||||
|
||||
// TODO: go-libp2p marks connection transient if there is limit field present in request.
|
||||
// Cannot find any reference to transient connections in js-libp2p
|
||||
|
||||
streamHandler.write(StopMessage.encode(
|
||||
{
|
||||
type: StopMessage.Type.STATUS,
|
||||
status: Status.OK
|
||||
}
|
||||
).finish())
|
||||
return streamHandler.rest()
|
||||
}
|
||||
|
||||
export interface StopOptions {
|
||||
connection: Connection
|
||||
request: IStopMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a STOP request
|
||||
*
|
||||
*/
|
||||
export async function stop ({
|
||||
connection,
|
||||
request
|
||||
}: StopOptions) {
|
||||
const { stream } = await connection.newStream([protocolIDv2Stop])
|
||||
log('starting circuit relay v2 stop request to %s', connection.remotePeer)
|
||||
const streamHandler = new StreamHandlerV2({ stream })
|
||||
streamHandler.write(StopMessage.encode(request).finish())
|
||||
let response
|
||||
try {
|
||||
response = StopMessage.decode(await streamHandler.read())
|
||||
} catch (/** @type {any} */ err) {
|
||||
log.error('error parsing stop message response from %s', connection.remotePeer)
|
||||
}
|
||||
|
||||
if (response == null) {
|
||||
return streamHandler.close()
|
||||
}
|
||||
|
||||
if (response.status === Status.OK) {
|
||||
log('stop request to %s was successful', connection.remotePeer)
|
||||
return streamHandler.rest()
|
||||
}
|
||||
|
||||
log('stop request failed with code %d', response.status)
|
||||
streamHandler.close()
|
||||
}
|
81
src/circuit/v2/stream-handler.ts
Normal file
81
src/circuit/v2/stream-handler.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { logger } from '@libp2p/logger'
|
||||
import * as lp from 'it-length-prefixed'
|
||||
import { Handshake, handshake } from 'it-handshake'
|
||||
import type { Stream } from '@libp2p/interfaces/connection'
|
||||
import type { Source } from 'it-stream-types'
|
||||
|
||||
const log = logger('libp2p:circuitv2:stream-handler')
|
||||
|
||||
export interface StreamHandlerOptions {
|
||||
/**
|
||||
* A duplex iterable
|
||||
*/
|
||||
stream: Stream
|
||||
|
||||
/**
|
||||
* max bytes length of message
|
||||
*/
|
||||
maxLength?: number
|
||||
}
|
||||
|
||||
export class StreamHandlerV2 {
|
||||
private readonly stream: Stream
|
||||
private readonly shake: Handshake
|
||||
private readonly decoder: Source<Uint8Array>
|
||||
|
||||
constructor (options: StreamHandlerOptions) {
|
||||
const { stream, maxLength = 4096 } = options
|
||||
|
||||
this.stream = stream
|
||||
this.shake = handshake(this.stream)
|
||||
this.decoder = lp.decode.fromReader(this.shake.reader, { maxDataLength: maxLength })
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and decode message
|
||||
*
|
||||
* @async
|
||||
*/
|
||||
async read () {
|
||||
// @ts-expect-error FIXME is a source, needs to be a generator
|
||||
const msg = await this.decoder.next()
|
||||
if (msg.value !== null) {
|
||||
return msg.value.slice()
|
||||
}
|
||||
|
||||
log('read received no value, closing stream')
|
||||
// End the stream, we didn't get data
|
||||
this.close()
|
||||
}
|
||||
|
||||
write (msg: Uint8Array) {
|
||||
this.shake.write(lp.encode.single(msg).slice())
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the handshake rest stream and invalidate handler
|
||||
*/
|
||||
rest () {
|
||||
this.shake.rest()
|
||||
return this.shake.stream
|
||||
}
|
||||
|
||||
/**
|
||||
* @param msg - An encoded Uint8Array protobuf message
|
||||
*/
|
||||
end (msg: Uint8Array) {
|
||||
this.write(msg)
|
||||
this.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the stream
|
||||
*
|
||||
*/
|
||||
close () {
|
||||
log('closing the stream')
|
||||
void this.rest().sink([]).catch(err => {
|
||||
log.error(err)
|
||||
})
|
||||
}
|
||||
}
|
67
src/circuit/v2/validation.ts
Normal file
67
src/circuit/v2/validation.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Multiaddr } from '@multiformats/multiaddr'
|
||||
import { Status, StopMessage, IHopMessage, IStopMessage, HopMessage } from './pb/index.js'
|
||||
import type { StreamHandlerV2 } from './stream-handler.js'
|
||||
|
||||
export function validateStopConnectRequest (request: IStopMessage, streamHandler: StreamHandlerV2) {
|
||||
if (request.type !== StopMessage.Type.CONNECT) {
|
||||
writeStopMessageResponse(streamHandler, Status.UNEXPECTED_MESSAGE)
|
||||
throw new Error('Received unexpected stop status msg')
|
||||
}
|
||||
try {
|
||||
if (request.peer?.addrs !== null && request.peer?.addrs !== undefined) {
|
||||
request.peer.addrs.forEach((addr) => {
|
||||
return new Multiaddr(addr)
|
||||
})
|
||||
} else {
|
||||
throw new Error('Missing peer info in stop request')
|
||||
}
|
||||
} catch (/** @type {any} */ err) {
|
||||
writeStopMessageResponse(streamHandler, Status.MALFORMED_MESSAGE)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
export function validateHopConnectRequest (request: IHopMessage, streamHandler: StreamHandlerV2) {
|
||||
// TODO: check if relay connection
|
||||
|
||||
try {
|
||||
if (request.peer?.addrs !== null && request.peer?.addrs !== undefined) {
|
||||
request.peer.addrs.forEach((addr) => {
|
||||
return new Multiaddr(addr)
|
||||
})
|
||||
} else {
|
||||
throw new Error('Missing peer info in hop connect request')
|
||||
}
|
||||
} catch (/** @type {any} */ err) {
|
||||
writeHopMessageResponse(streamHandler, Status.MALFORMED_MESSAGE)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a response
|
||||
*
|
||||
*/
|
||||
function writeStopMessageResponse (streamHandler: StreamHandlerV2, status: Status) {
|
||||
streamHandler.write(StopMessage.encode(
|
||||
{
|
||||
type: StopMessage.Type.STATUS,
|
||||
status: status
|
||||
}
|
||||
).finish())
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a response
|
||||
*
|
||||
* @param {StreamHandler} streamHandler
|
||||
* @param {import('./pb').Status} status
|
||||
*/
|
||||
function writeHopMessageResponse (streamHandler: StreamHandlerV2, status: Status) {
|
||||
streamHandler.write(HopMessage.encode(
|
||||
{
|
||||
type: HopMessage.Type.STATUS,
|
||||
status: status
|
||||
}
|
||||
).finish())
|
||||
}
|
@@ -21,8 +21,14 @@ const DefaultConfig: Partial<Libp2pInit> = {
|
||||
connectionManager: {
|
||||
maxConnections: 300,
|
||||
minConnections: 50,
|
||||
autoDial: true,
|
||||
autoDialInterval: 10000,
|
||||
autoDial: true
|
||||
},
|
||||
connectionGater: {},
|
||||
transportManager: {
|
||||
faultTolerance: FaultTolerance.FATAL_ALL
|
||||
},
|
||||
dialer: {
|
||||
maxParallelDials: Constants.MAX_PARALLEL_DIALS,
|
||||
maxDialsPerPeer: Constants.MAX_PER_PEER_DIALS,
|
||||
dialTimeout: Constants.DIAL_TIMEOUT,
|
||||
@@ -31,9 +37,8 @@ const DefaultConfig: Partial<Libp2pInit> = {
|
||||
},
|
||||
addressSorter: publicAddressesFirst
|
||||
},
|
||||
connectionGater: {},
|
||||
transportManager: {
|
||||
faultTolerance: FaultTolerance.FATAL_ALL
|
||||
host: {
|
||||
agentVersion: AGENT_VERSION
|
||||
},
|
||||
metrics: {
|
||||
enabled: false,
|
||||
@@ -53,6 +58,7 @@ const DefaultConfig: Partial<Libp2pInit> = {
|
||||
bootDelay: 10e3
|
||||
}
|
||||
},
|
||||
protocolPrefix: 'ipfs',
|
||||
nat: {
|
||||
enabled: true,
|
||||
ttl: 7200,
|
||||
@@ -60,6 +66,7 @@ const DefaultConfig: Partial<Libp2pInit> = {
|
||||
},
|
||||
relay: {
|
||||
enabled: true,
|
||||
limit: 15,
|
||||
advertise: {
|
||||
bootDelay: RelayConstants.ADVERTISE_BOOT_DELAY,
|
||||
enabled: false,
|
||||
@@ -73,19 +80,6 @@ const DefaultConfig: Partial<Libp2pInit> = {
|
||||
enabled: false,
|
||||
maxListeners: 2
|
||||
}
|
||||
},
|
||||
identify: {
|
||||
protocolPrefix: 'ipfs',
|
||||
host: {
|
||||
agentVersion: AGENT_VERSION
|
||||
},
|
||||
timeout: 30000
|
||||
},
|
||||
ping: {
|
||||
protocolPrefix: 'ipfs'
|
||||
},
|
||||
fetch: {
|
||||
protocolPrefix: 'libp2p'
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,7 @@ import all from 'it-all'
|
||||
import { pipe } from 'it-pipe'
|
||||
import filter from 'it-filter'
|
||||
import sort from 'it-sort'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import type { Startable } from '@libp2p/interfaces'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
|
||||
const log = logger('libp2p:connection-manager:auto-dialler')
|
||||
@@ -102,7 +102,7 @@ export class AutoDialler implements Startable {
|
||||
const minConnections = this.options.minConnections
|
||||
|
||||
// Already has enough connections
|
||||
if (this.components.getConnectionManager().getConnections().length >= minConnections) {
|
||||
if (this.components.getConnectionManager().getConnectionList().length >= minConnections) {
|
||||
this.autoDialTimeout = retimer(this._autoDial, this.options.autoDialInterval)
|
||||
|
||||
return
|
||||
@@ -126,7 +126,7 @@ export class AutoDialler implements Startable {
|
||||
async (source) => await all(source)
|
||||
)
|
||||
|
||||
for (let i = 0; this.running && i < peers.length && this.components.getConnectionManager().getConnections().length < minConnections; i++) {
|
||||
for (let i = 0; this.running && i < peers.length && this.components.getConnectionManager().getConnectionList().length < minConnections; i++) {
|
||||
// Connection Manager was stopped during async dial
|
||||
if (!this.running) {
|
||||
return
|
||||
@@ -134,10 +134,10 @@ export class AutoDialler implements Startable {
|
||||
|
||||
const peer = peers[i]
|
||||
|
||||
if (this.components.getConnectionManager().getConnections(peer.id).length === 0) {
|
||||
if (this.components.getConnectionManager().getConnection(peer.id) == null) {
|
||||
log('connecting to a peerStore stored peer %p', peer.id)
|
||||
try {
|
||||
await this.components.getConnectionManager().openConnection(peer.id)
|
||||
await this.components.getDialer().dial(peer.id)
|
||||
} catch (err: any) {
|
||||
log.error('could not connect to peerStore stored peer', err)
|
||||
}
|
||||
|
@@ -4,20 +4,16 @@ import mergeOptions from 'merge-options'
|
||||
import { LatencyMonitor, SummaryObject } from './latency-monitor.js'
|
||||
// @ts-expect-error retimer does not have types
|
||||
import retimer from 'retimer'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import { CustomEvent, EventEmitter, Startable } from '@libp2p/interfaces'
|
||||
import { trackedMap } from '@libp2p/tracked-map'
|
||||
import { codes } from '../errors.js'
|
||||
import { isPeerId, PeerId } from '@libp2p/interfaces/peer-id'
|
||||
// @ts-expect-error setMaxListeners is missing from the node 16 types
|
||||
import { setMaxListeners } from 'events'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import type { ConnectionManager } from '@libp2p/interfaces/connection-manager'
|
||||
import { Components, Initializable } from '@libp2p/interfaces/components'
|
||||
import type { ConnectionManager } from '@libp2p/interfaces/registrar'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
import * as STATUS from '@libp2p/interfaces/connection/status'
|
||||
import { Dialer } from './dialer/index.js'
|
||||
import type { AddressSorter } from '@libp2p/interfaces/peer-store'
|
||||
import type { Resolver } from '@multiformats/multiaddr'
|
||||
|
||||
const log = logger('libp2p:connection-manager')
|
||||
|
||||
@@ -38,16 +34,21 @@ const METRICS_COMPONENT = 'connection-manager'
|
||||
const METRICS_PEER_CONNECTIONS = 'peer-connections'
|
||||
const METRICS_PEER_VALUES = 'peer-values'
|
||||
|
||||
export interface ConnectionManagerEvents {
|
||||
'peer:connect': CustomEvent<PeerId>
|
||||
'peer:disconnect': CustomEvent<PeerId>
|
||||
}
|
||||
|
||||
export interface ConnectionManagerInit {
|
||||
/**
|
||||
* The maximum number of connections to keep open
|
||||
* The maximum number of connections allowed
|
||||
*/
|
||||
maxConnections: number
|
||||
maxConnections?: number
|
||||
|
||||
/**
|
||||
* The minimum number of connections to keep open
|
||||
* The minimum number of connections to avoid pruning
|
||||
*/
|
||||
minConnections: number
|
||||
minConnections?: number
|
||||
|
||||
/**
|
||||
* The max data (in and out), per average interval to allow
|
||||
@@ -85,75 +86,39 @@ export interface ConnectionManagerInit {
|
||||
defaultPeerValue?: number
|
||||
|
||||
/**
|
||||
* If true, try to connect to all discovered peers up to the connection manager limit
|
||||
* Should preemptively guarantee connections are above the low watermark
|
||||
*/
|
||||
autoDial?: boolean
|
||||
|
||||
/**
|
||||
* How long to wait between attempting to keep our number of concurrent connections
|
||||
* above minConnections
|
||||
* How often, in milliseconds, it should preemptively guarantee connections are above the low watermark
|
||||
*/
|
||||
autoDialInterval: number
|
||||
|
||||
/**
|
||||
* Sort the known addresses of a peer before trying to dial
|
||||
*/
|
||||
addressSorter?: AddressSorter
|
||||
|
||||
/**
|
||||
* Number of max concurrent dials
|
||||
*/
|
||||
maxParallelDials?: number
|
||||
|
||||
/**
|
||||
* Number of max addresses to dial for a given peer
|
||||
*/
|
||||
maxAddrsToDial?: number
|
||||
|
||||
/**
|
||||
* How long a dial attempt is allowed to take
|
||||
*/
|
||||
dialTimeout?: number
|
||||
|
||||
/**
|
||||
* Number of max concurrent dials per peer
|
||||
*/
|
||||
maxDialsPerPeer?: number
|
||||
|
||||
/**
|
||||
* Multiaddr resolvers to use when dialing
|
||||
*/
|
||||
resolvers?: Record<string, Resolver>
|
||||
}
|
||||
|
||||
export interface ConnectionManagerEvents {
|
||||
'peer:connect': CustomEvent<PeerId>
|
||||
'peer:disconnect': CustomEvent<PeerId>
|
||||
autoDialInterval?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for managing known connections.
|
||||
*/
|
||||
export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEvents> implements ConnectionManager, Startable, Initializable {
|
||||
public readonly dialer: Dialer
|
||||
private components = new Components()
|
||||
private readonly opts: Required<ConnectionManagerInit>
|
||||
export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEvents> implements ConnectionManager, Startable {
|
||||
private readonly components: Components
|
||||
private readonly init: Required<ConnectionManagerInit>
|
||||
private readonly peerValues: Map<string, number>
|
||||
private readonly connections: Map<string, Connection[]>
|
||||
private started: boolean
|
||||
private timer?: ReturnType<retimer>
|
||||
private readonly latencyMonitor: LatencyMonitor
|
||||
|
||||
constructor (init: ConnectionManagerInit) {
|
||||
constructor (components: Components, init: ConnectionManagerInit = {}) {
|
||||
super()
|
||||
|
||||
this.opts = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, init)
|
||||
this.components = components
|
||||
this.init = mergeOptions.call({ ignoreUndefined: true }, defaultOptions, init)
|
||||
|
||||
if (this.opts.maxConnections < this.opts.minConnections) {
|
||||
if (this.init.maxConnections < this.init.minConnections) {
|
||||
throw errCode(new Error('Connection Manager maxConnections must be greater than minConnections'), codes.ERR_INVALID_PARAMETERS)
|
||||
}
|
||||
|
||||
log('options: %o', this.opts)
|
||||
log('options: %o', this.init)
|
||||
|
||||
/**
|
||||
* Map of peer identifiers to their peer value for pruning connections.
|
||||
@@ -188,16 +153,12 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
||||
setMaxListeners?.(Infinity, this)
|
||||
} catch {}
|
||||
|
||||
this.dialer = new Dialer(this.opts)
|
||||
|
||||
this.onConnect = this.onConnect.bind(this)
|
||||
this.onDisconnect = this.onDisconnect.bind(this)
|
||||
}
|
||||
|
||||
init (components: Components): void {
|
||||
this.components = components
|
||||
|
||||
this.dialer.init(components)
|
||||
this.components.getUpgrader().addEventListener('connection', (evt) => {
|
||||
void this.onConnect(evt).catch(err => {
|
||||
log.error(err)
|
||||
})
|
||||
})
|
||||
this.components.getUpgrader().addEventListener('connectionEnd', this.onDisconnect.bind(this))
|
||||
}
|
||||
|
||||
isStarted () {
|
||||
@@ -210,29 +171,18 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
||||
*/
|
||||
async start () {
|
||||
if (this.components.getMetrics() != null) {
|
||||
this.timer = this.timer ?? retimer(this._checkMetrics, this.opts.pollInterval)
|
||||
this.timer = this.timer ?? retimer(this._checkMetrics, this.init.pollInterval)
|
||||
}
|
||||
|
||||
// latency monitor
|
||||
this.latencyMonitor.start()
|
||||
this._onLatencyMeasure = this._onLatencyMeasure.bind(this)
|
||||
this.latencyMonitor.addEventListener('data', this._onLatencyMeasure)
|
||||
await this.dialer.start()
|
||||
|
||||
this.started = true
|
||||
log('started')
|
||||
}
|
||||
|
||||
async afterStart () {
|
||||
this.components.getUpgrader().addEventListener('connection', this.onConnect)
|
||||
this.components.getUpgrader().addEventListener('connectionEnd', this.onDisconnect)
|
||||
}
|
||||
|
||||
async beforeStop () {
|
||||
this.components.getUpgrader().removeEventListener('connection', this.onConnect)
|
||||
this.components.getUpgrader().removeEventListener('connectionEnd', this.onDisconnect)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the Connection Manager
|
||||
*/
|
||||
@@ -241,7 +191,6 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
||||
|
||||
this.latencyMonitor.removeEventListener('data', this._onLatencyMeasure)
|
||||
this.latencyMonitor.stop()
|
||||
await this.dialer.stop()
|
||||
|
||||
this.started = false
|
||||
await this._close()
|
||||
@@ -253,16 +202,10 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
||||
*/
|
||||
async _close () {
|
||||
// Close all connections we're tracking
|
||||
const tasks: Array<Promise<void>> = []
|
||||
const tasks = []
|
||||
for (const connectionList of this.connections.values()) {
|
||||
for (const connection of connectionList) {
|
||||
tasks.push((async () => {
|
||||
try {
|
||||
await connection.close()
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
}
|
||||
})())
|
||||
tasks.push(connection.close())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,29 +238,23 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
||||
if (metrics != null) {
|
||||
try {
|
||||
const movingAverages = metrics.getGlobal().getMovingAverages()
|
||||
const received = movingAverages.dataReceived[this.opts.movingAverageInterval].movingAverage
|
||||
const received = movingAverages.dataReceived[this.init.movingAverageInterval].movingAverage
|
||||
await this._checkMaxLimit('maxReceivedData', received)
|
||||
const sent = movingAverages.dataSent[this.opts.movingAverageInterval].movingAverage
|
||||
const sent = movingAverages.dataSent[this.init.movingAverageInterval].movingAverage
|
||||
await this._checkMaxLimit('maxSentData', sent)
|
||||
const total = received + sent
|
||||
await this._checkMaxLimit('maxData', total)
|
||||
log.trace('metrics update', total)
|
||||
log('metrics update', total)
|
||||
} finally {
|
||||
this.timer = retimer(this._checkMetrics, this.opts.pollInterval)
|
||||
this.timer = retimer(this._checkMetrics, this.init.pollInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onConnect (evt: CustomEvent<Connection>) {
|
||||
void this._onConnect(evt).catch(err => {
|
||||
log.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the incoming connection and check the connection limit
|
||||
*/
|
||||
async _onConnect (evt: CustomEvent<Connection>) {
|
||||
async onConnect (evt: CustomEvent<Connection>) {
|
||||
const { detail: connection } = evt
|
||||
|
||||
if (!this.started) {
|
||||
@@ -330,6 +267,8 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
||||
const peerIdStr = peerId.toString()
|
||||
const storedConns = this.connections.get(peerIdStr)
|
||||
|
||||
this.dispatchEvent(new CustomEvent<Connection>('peer:connect', { detail: connection }))
|
||||
|
||||
if (storedConns != null) {
|
||||
storedConns.push(connection)
|
||||
} else {
|
||||
@@ -341,11 +280,10 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
||||
}
|
||||
|
||||
if (!this.peerValues.has(peerIdStr)) {
|
||||
this.peerValues.set(peerIdStr, this.opts.defaultPeerValue)
|
||||
this.peerValues.set(peerIdStr, this.init.defaultPeerValue)
|
||||
}
|
||||
|
||||
await this._checkMaxLimit('maxConnections', this.getConnections().length)
|
||||
this.dispatchEvent(new CustomEvent<Connection>('peer:connect', { detail: connection }))
|
||||
await this._checkMaxLimit('maxConnections', this.getConnectionList().length)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,64 +312,35 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
||||
}
|
||||
}
|
||||
|
||||
getConnections (peerId?: PeerId): Connection[] {
|
||||
if (peerId != null) {
|
||||
return this.connections.get(peerId.toString()) ?? []
|
||||
}
|
||||
|
||||
let conns: Connection[] = []
|
||||
|
||||
for (const c of this.connections.values()) {
|
||||
conns = conns.concat(c)
|
||||
}
|
||||
|
||||
return conns
|
||||
getConnectionMap (): Map<string, Connection[]> {
|
||||
return this.connections
|
||||
}
|
||||
|
||||
async openConnection (peerId: PeerId, options?: AbortOptions): Promise<Connection> {
|
||||
log('dial to %p', peerId)
|
||||
const existingConnections = this.getConnections(peerId)
|
||||
getConnectionList (): Connection[] {
|
||||
let output: Connection[] = []
|
||||
|
||||
if (existingConnections.length > 0) {
|
||||
log('had an existing connection to %p', peerId)
|
||||
|
||||
return existingConnections[0]
|
||||
for (const connections of this.connections.values()) {
|
||||
output = output.concat(connections)
|
||||
}
|
||||
|
||||
const connection = await this.dialer.dial(peerId, options)
|
||||
let peerConnections = this.connections.get(peerId.toString())
|
||||
|
||||
if (peerConnections == null) {
|
||||
peerConnections = []
|
||||
this.connections.set(peerId.toString(), peerConnections)
|
||||
}
|
||||
|
||||
// we get notified of connections via the Upgrader emitting "connection"
|
||||
// events, double check we aren't already tracking this connection before
|
||||
// storing it
|
||||
let trackedConnection = false
|
||||
|
||||
for (const conn of peerConnections) {
|
||||
if (conn.id === connection.id) {
|
||||
trackedConnection = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!trackedConnection) {
|
||||
peerConnections.push(connection)
|
||||
}
|
||||
|
||||
return connection
|
||||
return output
|
||||
}
|
||||
|
||||
async closeConnections (peerId: PeerId): Promise<void> {
|
||||
const connections = this.connections.get(peerId.toString()) ?? []
|
||||
getConnections (peerId: PeerId): Connection[] {
|
||||
return this.connections.get(peerId.toString()) ?? []
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
connections.map(async connection => {
|
||||
return await connection.close()
|
||||
})
|
||||
)
|
||||
/**
|
||||
* Get a connection with a peer
|
||||
*/
|
||||
getConnection (peerId: PeerId): Connection | undefined {
|
||||
const connections = this.getAll(peerId)
|
||||
|
||||
if (connections.length > 0) {
|
||||
return connections[0]
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -469,7 +378,7 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
||||
* If the `value` of `name` has exceeded its limit, maybe close a connection
|
||||
*/
|
||||
async _checkMaxLimit (name: keyof ConnectionManagerInit, value: number) {
|
||||
const limit = this.opts[name]
|
||||
const limit = this.init[name]
|
||||
log.trace('checking limit of %s. current value: %d of %d', name, value, limit)
|
||||
if (value > limit) {
|
||||
log('%s: limit exceeded: %p, %d', this.components.getPeerId(), name, value)
|
||||
@@ -482,7 +391,7 @@ export class DefaultConnectionManager extends EventEmitter<ConnectionManagerEven
|
||||
* to the lowest valued peer.
|
||||
*/
|
||||
async _maybeDisconnectOne () {
|
||||
if (this.opts.minConnections < this.connections.size) {
|
||||
if (this.init.minConnections < this.connections.size) {
|
||||
const peerValues = Array.from(new Map([...this.peerValues.entries()].sort((a, b) => a[1] - b[1])))
|
||||
|
||||
log('%p: sorted peer values: %j', this.components.getPeerId(), peerValues)
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces'
|
||||
import { VisibilityChangeEmitter } from './visibility-change-emitter.js'
|
||||
import { logger } from '@libp2p/logger'
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
* This code is based on `latency-monitor` (https://github.com/mlucool/latency-monitor) by `mlucool` (https://github.com/mlucool), available under Apache License 2.0 (https://github.com/mlucool/latency-monitor/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces'
|
||||
import { logger } from '@libp2p/logger'
|
||||
|
||||
const log = logger('libp2p:connection-manager:latency-monitor:visibility-change-emitter')
|
||||
|
@@ -9,8 +9,7 @@ import drain from 'it-drain'
|
||||
import merge from 'it-merge'
|
||||
import { pipe } from 'it-pipe'
|
||||
import type { ContentRouting } from '@libp2p/interfaces/content-routing'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import type { AbortOptions, Startable } from '@libp2p/interfaces'
|
||||
import type { CID } from 'multiformats/cid'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
|
||||
|
@@ -1,60 +0,0 @@
|
||||
import type { DualDHT, QueryEvent, SingleDHT } from '@libp2p/interfaces/dht'
|
||||
import type { PeerDiscoveryEvents } from '@libp2p/interfaces/peer-discovery'
|
||||
import errCode from 'err-code'
|
||||
import { messages, codes } from '../errors.js'
|
||||
import { EventEmitter } from '@libp2p/interfaces/events'
|
||||
import { symbol } from '@libp2p/interfaces/peer-discovery'
|
||||
|
||||
export class DummyDHT extends EventEmitter<PeerDiscoveryEvents> implements DualDHT {
|
||||
get [symbol] (): true {
|
||||
return true
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag] () {
|
||||
return '@libp2p/dummy-dht'
|
||||
}
|
||||
|
||||
get wan (): SingleDHT {
|
||||
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
|
||||
}
|
||||
|
||||
get lan (): SingleDHT {
|
||||
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
|
||||
}
|
||||
|
||||
get (): AsyncIterable<QueryEvent> {
|
||||
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
|
||||
}
|
||||
|
||||
findProviders (): AsyncIterable<QueryEvent> {
|
||||
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
|
||||
}
|
||||
|
||||
findPeer (): AsyncIterable<QueryEvent> {
|
||||
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
|
||||
}
|
||||
|
||||
getClosestPeers (): AsyncIterable<QueryEvent> {
|
||||
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
|
||||
}
|
||||
|
||||
provide (): AsyncIterable<QueryEvent> {
|
||||
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
|
||||
}
|
||||
|
||||
put (): AsyncIterable<QueryEvent> {
|
||||
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
|
||||
}
|
||||
|
||||
async getMode (): Promise<'client' | 'server'> {
|
||||
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
|
||||
}
|
||||
|
||||
async setMode (): Promise<void> {
|
||||
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
|
||||
}
|
||||
|
||||
async refreshRoutingTable (): Promise<void> {
|
||||
throw errCode(new Error(messages.DHT_DISABLED), codes.DHT_DISABLED)
|
||||
}
|
||||
}
|
@@ -1,58 +1,39 @@
|
||||
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
||||
import { logger } from '@libp2p/logger'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
import { TimeoutController } from 'timeout-abort-controller'
|
||||
|
||||
const log = logger('libp2p:dialer:auto-dialer')
|
||||
|
||||
export interface AutoDialerInit {
|
||||
enabled: boolean
|
||||
minConnections: number
|
||||
dialTimeout: number
|
||||
}
|
||||
|
||||
export class AutoDialer {
|
||||
private readonly components: Components
|
||||
private readonly enabled: boolean
|
||||
private readonly minConnections: number
|
||||
private readonly dialTimeout: number
|
||||
|
||||
constructor (components: Components, init: AutoDialerInit) {
|
||||
this.components = components
|
||||
this.enabled = init.enabled
|
||||
this.minConnections = init.minConnections
|
||||
this.dialTimeout = init.dialTimeout
|
||||
}
|
||||
|
||||
public handle (evt: CustomEvent<PeerInfo>) {
|
||||
const { detail: peer } = evt
|
||||
|
||||
if (!this.enabled) {
|
||||
return
|
||||
}
|
||||
|
||||
const connections = this.components.getConnectionManager().getConnections(peer.id)
|
||||
|
||||
// If auto dialing is on and we have no connection to the peer, check if we should dial
|
||||
if (connections.length === 0) {
|
||||
if (this.enabled && this.components.getConnectionManager().getConnection(peer.id) == null) {
|
||||
const minConnections = this.minConnections ?? 0
|
||||
|
||||
const allConnections = this.components.getConnectionManager().getConnections()
|
||||
if (minConnections > this.components.getConnectionManager().getConnectionList().length) {
|
||||
log('auto-dialing discovered peer %p', peer.id)
|
||||
|
||||
if (minConnections > allConnections.length) {
|
||||
log('auto-dialing discovered peer %p with timeout %d', peer.id, this.dialTimeout)
|
||||
|
||||
const controller = new TimeoutController(this.dialTimeout)
|
||||
|
||||
void this.components.getConnectionManager().openConnection(peer.id, {
|
||||
signal: controller.signal
|
||||
})
|
||||
void this.components.getDialer().dial(peer.id)
|
||||
.catch(err => {
|
||||
log.error('could not connect to discovered peer %p with %o', peer.id, err)
|
||||
})
|
||||
.finally(() => {
|
||||
controller.clear()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,13 +1,14 @@
|
||||
import errCode from 'err-code'
|
||||
import { anySignal } from 'any-signal'
|
||||
import FIFO from 'p-fifo'
|
||||
// @ts-expect-error setMaxListeners is missing from the node 16 types
|
||||
import { setMaxListeners } from 'events'
|
||||
import { codes } from '../../errors.js'
|
||||
import { codes } from '../errors.js'
|
||||
import { logger } from '@libp2p/logger'
|
||||
import type { Multiaddr } from '@multiformats/multiaddr'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
import type { Dialer } from './index.js'
|
||||
import type { DefaultDialer } from './index.js'
|
||||
|
||||
const log = logger('libp2p:dialer:dial-request')
|
||||
|
||||
@@ -18,12 +19,12 @@ export interface DialAction {
|
||||
export interface DialRequestOptions {
|
||||
addrs: Multiaddr[]
|
||||
dialAction: DialAction
|
||||
dialer: Dialer
|
||||
dialer: DefaultDialer
|
||||
}
|
||||
|
||||
export class DialRequest {
|
||||
private readonly addrs: Multiaddr[]
|
||||
private readonly dialer: Dialer
|
||||
private readonly dialer: DefaultDialer
|
||||
private readonly dialAction: DialAction
|
||||
|
||||
/**
|
||||
@@ -61,7 +62,7 @@ export class DialRequest {
|
||||
})
|
||||
}
|
||||
|
||||
const dialAbortControllers: Array<(AbortController | undefined)> = this.addrs.map(() => {
|
||||
const dialAbortControllers = this.addrs.map(() => {
|
||||
const controller = new AbortController()
|
||||
try {
|
||||
// fails on node < 15.4
|
||||
@@ -79,27 +80,16 @@ export class DialRequest {
|
||||
}
|
||||
|
||||
let completedDials = 0
|
||||
let done = false
|
||||
|
||||
try {
|
||||
return await Promise.any(this.addrs.map(async (addr, i) => {
|
||||
const token = await tokenHolder.shift() // get token
|
||||
// End attempt once another attempt succeeded
|
||||
if (done) {
|
||||
this.dialer.releaseToken(tokens.splice(tokens.indexOf(token), 1)[0])
|
||||
throw errCode(new Error('dialAction already succeeded'), codes.ERR_ALREADY_SUCCEEDED)
|
||||
}
|
||||
|
||||
const controller = dialAbortControllers[i]
|
||||
if (controller == null) {
|
||||
throw errCode(new Error('dialAction did not come with an AbortController'), codes.ERR_INVALID_PARAMETERS)
|
||||
}
|
||||
let conn
|
||||
try {
|
||||
const signal = controller.signal
|
||||
const signal = dialAbortControllers[i].signal
|
||||
conn = await this.dialAction(addr, { ...options, signal: (options.signal != null) ? anySignal([signal, options.signal]) : signal })
|
||||
// Remove the successful AbortController so it is not aborted
|
||||
dialAbortControllers[i] = undefined
|
||||
dialAbortControllers.splice(i, 1)
|
||||
} finally {
|
||||
completedDials++
|
||||
// If we have more or equal dials remaining than tokens, recycle the token, otherwise release it
|
||||
@@ -112,25 +102,10 @@ export class DialRequest {
|
||||
}
|
||||
}
|
||||
|
||||
if (conn == null) {
|
||||
// Notify Promise.any that attempt was not successful
|
||||
// to prevent from returning undefined despite there
|
||||
// were successful dial attempts
|
||||
throw errCode(new Error('dialAction led to empty object'), codes.ERR_TRANSPORT_DIAL_FAILED)
|
||||
} else {
|
||||
// This dial succeeded, don't attempt anything else
|
||||
done = true
|
||||
}
|
||||
|
||||
return conn
|
||||
}))
|
||||
} finally {
|
||||
// success/failure happened, abort everything else
|
||||
dialAbortControllers.forEach(c => {
|
||||
if (c !== undefined) {
|
||||
c.abort()
|
||||
}
|
||||
})
|
||||
dialAbortControllers.map(c => c.abort()) // success/failure happened, abort everything else
|
||||
tokens.forEach(token => this.dialer.releaseToken(token)) // release tokens back to the dialer
|
||||
}
|
||||
}
|
@@ -3,31 +3,31 @@ import all from 'it-all'
|
||||
import filter from 'it-filter'
|
||||
import { pipe } from 'it-pipe'
|
||||
import errCode from 'err-code'
|
||||
import { Multiaddr, Resolver } from '@multiformats/multiaddr'
|
||||
import { Multiaddr } from '@multiformats/multiaddr'
|
||||
import { TimeoutController } from 'timeout-abort-controller'
|
||||
import { AbortError } from '@libp2p/interfaces/errors'
|
||||
import { anySignal } from 'any-signal'
|
||||
// @ts-expect-error setMaxListeners is missing from the node 16 types
|
||||
import { setMaxListeners } from 'events'
|
||||
import { DialAction, DialRequest } from './dial-request.js'
|
||||
import { publicAddressesFirst } from '@libp2p/utils/address-sort'
|
||||
import { trackedMap } from '@libp2p/tracked-map'
|
||||
import { codes } from '../../errors.js'
|
||||
import { codes } from '../errors.js'
|
||||
import {
|
||||
DIAL_TIMEOUT,
|
||||
MAX_PARALLEL_DIALS,
|
||||
MAX_PER_PEER_DIALS,
|
||||
MAX_ADDRS_TO_DIAL
|
||||
} from '../../constants.js'
|
||||
} from '../constants.js'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import type { AbortOptions, Startable } from '@libp2p/interfaces'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import { getPeer } from '../../get-peer.js'
|
||||
import { getPeer } from '../get-peer.js'
|
||||
import sort from 'it-sort'
|
||||
import { Components, Initializable } from '@libp2p/interfaces/components'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
import type { Dialer, DialerInit } from '@libp2p/interfaces/dialer'
|
||||
import map from 'it-map'
|
||||
import type { AddressSorter } from '@libp2p/interfaces/peer-store'
|
||||
import type { ComponentMetricsTracker } from '@libp2p/interfaces/metrics'
|
||||
|
||||
const log = logger('libp2p:dialer')
|
||||
|
||||
@@ -52,41 +52,8 @@ export interface PendingDialTarget {
|
||||
reject: (err: Error) => void
|
||||
}
|
||||
|
||||
export interface DialerInit {
|
||||
/**
|
||||
* Sort the known addresses of a peer before trying to dial
|
||||
*/
|
||||
addressSorter?: AddressSorter
|
||||
|
||||
/**
|
||||
* Number of max concurrent dials
|
||||
*/
|
||||
maxParallelDials?: number
|
||||
|
||||
/**
|
||||
* Number of max addresses to dial for a given peer
|
||||
*/
|
||||
maxAddrsToDial?: number
|
||||
|
||||
/**
|
||||
* How long a dial attempt is allowed to take
|
||||
*/
|
||||
dialTimeout?: number
|
||||
|
||||
/**
|
||||
* Number of max concurrent dials per peer
|
||||
*/
|
||||
maxDialsPerPeer?: number
|
||||
|
||||
/**
|
||||
* Multiaddr resolvers to use when dialing
|
||||
*/
|
||||
resolvers?: Record<string, Resolver>
|
||||
metrics?: ComponentMetricsTracker
|
||||
}
|
||||
|
||||
export class Dialer implements Startable, Initializable {
|
||||
private components: Components = new Components()
|
||||
export class DefaultDialer implements Dialer, Startable {
|
||||
private readonly components: Components
|
||||
private readonly addressSorter: AddressSorter
|
||||
private readonly maxAddrsToDial: number
|
||||
private readonly timeout: number
|
||||
@@ -96,7 +63,8 @@ export class Dialer implements Startable, Initializable {
|
||||
public pendingDialTargets: Map<string, PendingDialTarget>
|
||||
private started: boolean
|
||||
|
||||
constructor (init: DialerInit = {}) {
|
||||
constructor (components: Components, init: DialerInit = {}) {
|
||||
this.components = components
|
||||
this.started = false
|
||||
this.addressSorter = init.addressSorter ?? publicAddressesFirst
|
||||
this.maxAddrsToDial = init.maxAddrsToDial ?? MAX_ADDRS_TO_DIAL
|
||||
@@ -119,10 +87,6 @@ export class Dialer implements Startable, Initializable {
|
||||
}
|
||||
}
|
||||
|
||||
init (components: Components): void {
|
||||
this.components = components
|
||||
}
|
||||
|
||||
isStarted () {
|
||||
return this.started
|
||||
}
|
||||
@@ -175,6 +139,16 @@ export class Dialer implements Startable, Initializable {
|
||||
throw errCode(new Error('The dial request is blocked by gater.allowDialPeer'), codes.ERR_PEER_DIAL_INTERCEPTED)
|
||||
}
|
||||
|
||||
log('dial to %p', id)
|
||||
|
||||
const existingConnection = this.components.getConnectionManager().getConnection(id)
|
||||
|
||||
if (existingConnection != null) {
|
||||
log('had an existing connection to %p', id)
|
||||
|
||||
return existingConnection
|
||||
}
|
||||
|
||||
log('creating dial target for %p', id)
|
||||
|
||||
const dialTarget = await this._createCancellableDialTarget(id)
|
||||
@@ -202,6 +176,22 @@ export class Dialer implements Startable, Initializable {
|
||||
}
|
||||
}
|
||||
|
||||
async dialProtocol (peer: PeerId | Multiaddr, protocols: string | string[], options: AbortOptions = {}) {
|
||||
if (protocols == null) {
|
||||
throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM)
|
||||
}
|
||||
|
||||
protocols = Array.isArray(protocols) ? protocols : [protocols]
|
||||
|
||||
if (protocols.length === 0) {
|
||||
throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM)
|
||||
}
|
||||
|
||||
const connection = await this.dial(peer, options)
|
||||
|
||||
return await connection.newStream(protocols)
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to a given `peer` by dialing all of its known addresses.
|
||||
* The dial to the first address that is successfully able to upgrade a connection
|
||||
@@ -254,6 +244,9 @@ export class Dialer implements Startable, Initializable {
|
||||
const addrs: Multiaddr[] = []
|
||||
for (const a of knownAddrs) {
|
||||
const resolvedAddrs = await this._resolve(a)
|
||||
|
||||
log('resolved %s to %s', a, resolvedAddrs)
|
||||
|
||||
resolvedAddrs.forEach(ra => addrs.push(ra))
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
export enum messages {
|
||||
NOT_STARTED_YET = 'The libp2p node is not started yet',
|
||||
DHT_DISABLED = 'DHT is not available',
|
||||
PUBSUB_DISABLED = 'PubSub is not available',
|
||||
CONN_ENCRYPTION_REQUIRED = 'At least one connection encryption module is required',
|
||||
ERR_TRANSPORTS_REQUIRED = 'At least one transport module is required',
|
||||
ERR_PROTECTOR_REQUIRED = 'Private network is enforced, but no protector was provided',
|
||||
@@ -10,7 +9,6 @@ export enum messages {
|
||||
|
||||
export enum codes {
|
||||
DHT_DISABLED = 'ERR_DHT_DISABLED',
|
||||
ERR_PUBSUB_DISABLED = 'ERR_PUBSUB_DISABLED',
|
||||
PUBSUB_NOT_STARTED = 'ERR_PUBSUB_NOT_STARTED',
|
||||
DHT_NOT_STARTED = 'ERR_DHT_NOT_STARTED',
|
||||
CONN_ENCRYPTION_REQUIRED = 'ERR_CONN_ENCRYPTION_REQUIRED',
|
||||
@@ -69,6 +67,5 @@ export enum codes {
|
||||
ERR_INVALID_PASS_LENGTH = 'ERR_INVALID_PASS_LENGTH',
|
||||
ERR_NOT_IMPLEMENTED = 'ERR_NOT_IMPLEMENTED',
|
||||
ERR_WRONG_PING_ACK = 'ERR_WRONG_PING_ACK',
|
||||
ERR_INVALID_RECORD = 'ERR_INVALID_RECORD',
|
||||
ERR_ALREADY_SUCCEEDED = 'ERR_ALREADY_SUCCEEDED'
|
||||
ERR_INVALID_RECORD = 'ERR_INVALID_RECORD'
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
|
||||
// https://github.com/libp2p/specs/tree/master/fetch#wire-protocol
|
||||
export const PROTOCOL_VERSION = '0.0.1'
|
||||
export const PROTOCOL_NAME = 'fetch'
|
||||
export const PROTOCOL = '/libp2p/fetch/0.0.1'
|
||||
|
@@ -4,19 +4,16 @@ import { codes } from '../errors.js'
|
||||
import * as lp from 'it-length-prefixed'
|
||||
import { FetchRequest, FetchResponse } from './pb/proto.js'
|
||||
import { handshake } from 'it-handshake'
|
||||
import { PROTOCOL_NAME, PROTOCOL_VERSION } from './constants.js'
|
||||
import { PROTOCOL } from './constants.js'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import type { Startable } from '@libp2p/interfaces'
|
||||
import type { Stream } from '@libp2p/interfaces/connection'
|
||||
import type { IncomingStreamData } from '@libp2p/interfaces/registrar'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
import type { Duplex } from 'it-stream-types'
|
||||
import { abortableDuplex } from 'abortable-iterator'
|
||||
|
||||
const log = logger('libp2p:fetch')
|
||||
|
||||
export interface FetchServiceInit {
|
||||
export interface FetchInit {
|
||||
protocolPrefix: string
|
||||
}
|
||||
|
||||
@@ -36,15 +33,15 @@ export interface LookupFunction {
|
||||
* by a fixed prefix that all keys that should be routed to that lookup function will start with.
|
||||
*/
|
||||
export class FetchService implements Startable {
|
||||
public readonly protocol: string
|
||||
private readonly components: Components
|
||||
private readonly lookupFunctions: Map<string, LookupFunction>
|
||||
private readonly protocol: string
|
||||
private started: boolean
|
||||
|
||||
constructor (components: Components, init: FetchServiceInit) {
|
||||
constructor (components: Components, init: FetchInit) {
|
||||
this.started = false
|
||||
this.components = components
|
||||
this.protocol = `/${init.protocolPrefix ?? 'libp2p'}/${PROTOCOL_NAME}/${PROTOCOL_VERSION}`
|
||||
this.protocol = PROTOCOL
|
||||
this.lookupFunctions = new Map() // Maps key prefix to value lookup function
|
||||
this.handleMessage = this.handleMessage.bind(this)
|
||||
}
|
||||
@@ -70,22 +67,16 @@ export class FetchService implements Startable {
|
||||
/**
|
||||
* Sends a request to fetch the value associated with the given key from the given peer
|
||||
*/
|
||||
async fetch (peer: PeerId, key: string, options: AbortOptions = {}): Promise<Uint8Array | null> {
|
||||
async fetch (peer: PeerId, key: string): Promise<Uint8Array | null> {
|
||||
log('dialing %s to %p', this.protocol, peer)
|
||||
|
||||
const connection = await this.components.getConnectionManager().openConnection(peer, options)
|
||||
const { stream } = await connection.newStream([this.protocol], options)
|
||||
let source: Duplex<Uint8Array> = stream
|
||||
|
||||
// make stream abortable if AbortSignal passed
|
||||
if (options.signal != null) {
|
||||
source = abortableDuplex(stream, options.signal)
|
||||
}
|
||||
|
||||
const shake = handshake(source)
|
||||
const connection = await this.components.getDialer().dial(peer)
|
||||
const { stream } = await connection.newStream([this.protocol])
|
||||
const shake = handshake(stream)
|
||||
|
||||
// send message
|
||||
shake.write(lp.encode.single(FetchRequest.encode({ identifier: key })).slice())
|
||||
const request = new FetchRequest({ identifier: key })
|
||||
shake.write(lp.encode.single(FetchRequest.encode(request).finish()).slice())
|
||||
|
||||
// read response
|
||||
// @ts-expect-error fromReader returns a Source which has no .next method
|
||||
@@ -118,21 +109,21 @@ export class FetchService implements Startable {
|
||||
// @ts-expect-error fromReader returns a Source which has no .next method
|
||||
const request = FetchRequest.decode((await lp.decode.fromReader(shake.reader).next()).value.slice())
|
||||
|
||||
let response: FetchResponse
|
||||
let response
|
||||
const lookup = this._getLookupFunction(request.identifier)
|
||||
if (lookup != null) {
|
||||
const data = await lookup(request.identifier)
|
||||
if (data != null) {
|
||||
response = { status: FetchResponse.StatusCode.OK, data }
|
||||
response = new FetchResponse({ status: FetchResponse.StatusCode.OK, data })
|
||||
} else {
|
||||
response = { status: FetchResponse.StatusCode.NOT_FOUND, data: new Uint8Array(0) }
|
||||
response = new FetchResponse({ status: FetchResponse.StatusCode.NOT_FOUND })
|
||||
}
|
||||
} else {
|
||||
const errmsg = (new TextEncoder()).encode('No lookup function registered for key: ' + request.identifier)
|
||||
response = { status: FetchResponse.StatusCode.ERROR, data: errmsg }
|
||||
response = new FetchResponse({ status: FetchResponse.StatusCode.ERROR, data: errmsg })
|
||||
}
|
||||
|
||||
shake.write(lp.encode.single(FetchResponse.encode(response)).slice())
|
||||
shake.write(lp.encode.single(FetchResponse.encode(response).finish()).slice())
|
||||
}
|
||||
|
||||
/**
|
||||
|
134
src/fetch/pb/proto.d.ts
vendored
Normal file
134
src/fetch/pb/proto.d.ts
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
import * as $protobuf from "protobufjs";
|
||||
/** Properties of a FetchRequest. */
|
||||
export interface IFetchRequest {
|
||||
|
||||
/** FetchRequest identifier */
|
||||
identifier?: (string|null);
|
||||
}
|
||||
|
||||
/** Represents a FetchRequest. */
|
||||
export class FetchRequest implements IFetchRequest {
|
||||
|
||||
/**
|
||||
* Constructs a new FetchRequest.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: IFetchRequest);
|
||||
|
||||
/** FetchRequest identifier. */
|
||||
public identifier: string;
|
||||
|
||||
/**
|
||||
* Encodes the specified FetchRequest message. Does not implicitly {@link FetchRequest.verify|verify} messages.
|
||||
* @param m FetchRequest message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: IFetchRequest, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes a FetchRequest message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns FetchRequest
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): FetchRequest;
|
||||
|
||||
/**
|
||||
* Creates a FetchRequest message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns FetchRequest
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): FetchRequest;
|
||||
|
||||
/**
|
||||
* Creates a plain object from a FetchRequest message. Also converts values to other types if specified.
|
||||
* @param m FetchRequest
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: FetchRequest, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this FetchRequest to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
||||
|
||||
/** Properties of a FetchResponse. */
|
||||
export interface IFetchResponse {
|
||||
|
||||
/** FetchResponse status */
|
||||
status?: (FetchResponse.StatusCode|null);
|
||||
|
||||
/** FetchResponse data */
|
||||
data?: (Uint8Array|null);
|
||||
}
|
||||
|
||||
/** Represents a FetchResponse. */
|
||||
export class FetchResponse implements IFetchResponse {
|
||||
|
||||
/**
|
||||
* Constructs a new FetchResponse.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: IFetchResponse);
|
||||
|
||||
/** FetchResponse status. */
|
||||
public status: FetchResponse.StatusCode;
|
||||
|
||||
/** FetchResponse data. */
|
||||
public data: Uint8Array;
|
||||
|
||||
/**
|
||||
* Encodes the specified FetchResponse message. Does not implicitly {@link FetchResponse.verify|verify} messages.
|
||||
* @param m FetchResponse message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: IFetchResponse, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes a FetchResponse message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns FetchResponse
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): FetchResponse;
|
||||
|
||||
/**
|
||||
* Creates a FetchResponse message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns FetchResponse
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): FetchResponse;
|
||||
|
||||
/**
|
||||
* Creates a plain object from a FetchResponse message. Also converts values to other types if specified.
|
||||
* @param m FetchResponse
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: FetchResponse, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this FetchResponse to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
||||
|
||||
export namespace FetchResponse {
|
||||
|
||||
/** StatusCode enum. */
|
||||
enum StatusCode {
|
||||
OK = 0,
|
||||
NOT_FOUND = 1,
|
||||
ERROR = 2
|
||||
}
|
||||
}
|
331
src/fetch/pb/proto.js
Normal file
331
src/fetch/pb/proto.js
Normal file
@@ -0,0 +1,331 @@
|
||||
/*eslint-disable*/
|
||||
import $protobuf from "protobufjs/minimal.js";
|
||||
|
||||
// Common aliases
|
||||
const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
|
||||
|
||||
// Exported root namespace
|
||||
const $root = $protobuf.roots["libp2p-fetch"] || ($protobuf.roots["libp2p-fetch"] = {});
|
||||
|
||||
export const FetchRequest = $root.FetchRequest = (() => {
|
||||
|
||||
/**
|
||||
* Properties of a FetchRequest.
|
||||
* @exports IFetchRequest
|
||||
* @interface IFetchRequest
|
||||
* @property {string|null} [identifier] FetchRequest identifier
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a new FetchRequest.
|
||||
* @exports FetchRequest
|
||||
* @classdesc Represents a FetchRequest.
|
||||
* @implements IFetchRequest
|
||||
* @constructor
|
||||
* @param {IFetchRequest=} [p] Properties to set
|
||||
*/
|
||||
function FetchRequest(p) {
|
||||
if (p)
|
||||
for (var ks = Object.keys(p), i = 0; i < ks.length; ++i)
|
||||
if (p[ks[i]] != null)
|
||||
this[ks[i]] = p[ks[i]];
|
||||
}
|
||||
|
||||
/**
|
||||
* FetchRequest identifier.
|
||||
* @member {string} identifier
|
||||
* @memberof FetchRequest
|
||||
* @instance
|
||||
*/
|
||||
FetchRequest.prototype.identifier = "";
|
||||
|
||||
/**
|
||||
* Encodes the specified FetchRequest message. Does not implicitly {@link FetchRequest.verify|verify} messages.
|
||||
* @function encode
|
||||
* @memberof FetchRequest
|
||||
* @static
|
||||
* @param {IFetchRequest} m FetchRequest message or plain object to encode
|
||||
* @param {$protobuf.Writer} [w] Writer to encode to
|
||||
* @returns {$protobuf.Writer} Writer
|
||||
*/
|
||||
FetchRequest.encode = function encode(m, w) {
|
||||
if (!w)
|
||||
w = $Writer.create();
|
||||
if (m.identifier != null && Object.hasOwnProperty.call(m, "identifier"))
|
||||
w.uint32(10).string(m.identifier);
|
||||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes a FetchRequest message from the specified reader or buffer.
|
||||
* @function decode
|
||||
* @memberof FetchRequest
|
||||
* @static
|
||||
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
|
||||
* @param {number} [l] Message length if known beforehand
|
||||
* @returns {FetchRequest} FetchRequest
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
FetchRequest.decode = function decode(r, l) {
|
||||
if (!(r instanceof $Reader))
|
||||
r = $Reader.create(r);
|
||||
var c = l === undefined ? r.len : r.pos + l, m = new $root.FetchRequest();
|
||||
while (r.pos < c) {
|
||||
var t = r.uint32();
|
||||
switch (t >>> 3) {
|
||||
case 1:
|
||||
m.identifier = r.string();
|
||||
break;
|
||||
default:
|
||||
r.skipType(t & 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a FetchRequest message from a plain object. Also converts values to their respective internal types.
|
||||
* @function fromObject
|
||||
* @memberof FetchRequest
|
||||
* @static
|
||||
* @param {Object.<string,*>} d Plain object
|
||||
* @returns {FetchRequest} FetchRequest
|
||||
*/
|
||||
FetchRequest.fromObject = function fromObject(d) {
|
||||
if (d instanceof $root.FetchRequest)
|
||||
return d;
|
||||
var m = new $root.FetchRequest();
|
||||
if (d.identifier != null) {
|
||||
m.identifier = String(d.identifier);
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a plain object from a FetchRequest message. Also converts values to other types if specified.
|
||||
* @function toObject
|
||||
* @memberof FetchRequest
|
||||
* @static
|
||||
* @param {FetchRequest} m FetchRequest
|
||||
* @param {$protobuf.IConversionOptions} [o] Conversion options
|
||||
* @returns {Object.<string,*>} Plain object
|
||||
*/
|
||||
FetchRequest.toObject = function toObject(m, o) {
|
||||
if (!o)
|
||||
o = {};
|
||||
var d = {};
|
||||
if (o.defaults) {
|
||||
d.identifier = "";
|
||||
}
|
||||
if (m.identifier != null && m.hasOwnProperty("identifier")) {
|
||||
d.identifier = m.identifier;
|
||||
}
|
||||
return d;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts this FetchRequest to JSON.
|
||||
* @function toJSON
|
||||
* @memberof FetchRequest
|
||||
* @instance
|
||||
* @returns {Object.<string,*>} JSON object
|
||||
*/
|
||||
FetchRequest.prototype.toJSON = function toJSON() {
|
||||
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
|
||||
};
|
||||
|
||||
return FetchRequest;
|
||||
})();
|
||||
|
||||
export const FetchResponse = $root.FetchResponse = (() => {
|
||||
|
||||
/**
|
||||
* Properties of a FetchResponse.
|
||||
* @exports IFetchResponse
|
||||
* @interface IFetchResponse
|
||||
* @property {FetchResponse.StatusCode|null} [status] FetchResponse status
|
||||
* @property {Uint8Array|null} [data] FetchResponse data
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a new FetchResponse.
|
||||
* @exports FetchResponse
|
||||
* @classdesc Represents a FetchResponse.
|
||||
* @implements IFetchResponse
|
||||
* @constructor
|
||||
* @param {IFetchResponse=} [p] Properties to set
|
||||
*/
|
||||
function FetchResponse(p) {
|
||||
if (p)
|
||||
for (var ks = Object.keys(p), i = 0; i < ks.length; ++i)
|
||||
if (p[ks[i]] != null)
|
||||
this[ks[i]] = p[ks[i]];
|
||||
}
|
||||
|
||||
/**
|
||||
* FetchResponse status.
|
||||
* @member {FetchResponse.StatusCode} status
|
||||
* @memberof FetchResponse
|
||||
* @instance
|
||||
*/
|
||||
FetchResponse.prototype.status = 0;
|
||||
|
||||
/**
|
||||
* FetchResponse data.
|
||||
* @member {Uint8Array} data
|
||||
* @memberof FetchResponse
|
||||
* @instance
|
||||
*/
|
||||
FetchResponse.prototype.data = $util.newBuffer([]);
|
||||
|
||||
/**
|
||||
* Encodes the specified FetchResponse message. Does not implicitly {@link FetchResponse.verify|verify} messages.
|
||||
* @function encode
|
||||
* @memberof FetchResponse
|
||||
* @static
|
||||
* @param {IFetchResponse} m FetchResponse message or plain object to encode
|
||||
* @param {$protobuf.Writer} [w] Writer to encode to
|
||||
* @returns {$protobuf.Writer} Writer
|
||||
*/
|
||||
FetchResponse.encode = function encode(m, w) {
|
||||
if (!w)
|
||||
w = $Writer.create();
|
||||
if (m.status != null && Object.hasOwnProperty.call(m, "status"))
|
||||
w.uint32(8).int32(m.status);
|
||||
if (m.data != null && Object.hasOwnProperty.call(m, "data"))
|
||||
w.uint32(18).bytes(m.data);
|
||||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes a FetchResponse message from the specified reader or buffer.
|
||||
* @function decode
|
||||
* @memberof FetchResponse
|
||||
* @static
|
||||
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
|
||||
* @param {number} [l] Message length if known beforehand
|
||||
* @returns {FetchResponse} FetchResponse
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
FetchResponse.decode = function decode(r, l) {
|
||||
if (!(r instanceof $Reader))
|
||||
r = $Reader.create(r);
|
||||
var c = l === undefined ? r.len : r.pos + l, m = new $root.FetchResponse();
|
||||
while (r.pos < c) {
|
||||
var t = r.uint32();
|
||||
switch (t >>> 3) {
|
||||
case 1:
|
||||
m.status = r.int32();
|
||||
break;
|
||||
case 2:
|
||||
m.data = r.bytes();
|
||||
break;
|
||||
default:
|
||||
r.skipType(t & 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a FetchResponse message from a plain object. Also converts values to their respective internal types.
|
||||
* @function fromObject
|
||||
* @memberof FetchResponse
|
||||
* @static
|
||||
* @param {Object.<string,*>} d Plain object
|
||||
* @returns {FetchResponse} FetchResponse
|
||||
*/
|
||||
FetchResponse.fromObject = function fromObject(d) {
|
||||
if (d instanceof $root.FetchResponse)
|
||||
return d;
|
||||
var m = new $root.FetchResponse();
|
||||
switch (d.status) {
|
||||
case "OK":
|
||||
case 0:
|
||||
m.status = 0;
|
||||
break;
|
||||
case "NOT_FOUND":
|
||||
case 1:
|
||||
m.status = 1;
|
||||
break;
|
||||
case "ERROR":
|
||||
case 2:
|
||||
m.status = 2;
|
||||
break;
|
||||
}
|
||||
if (d.data != null) {
|
||||
if (typeof d.data === "string")
|
||||
$util.base64.decode(d.data, m.data = $util.newBuffer($util.base64.length(d.data)), 0);
|
||||
else if (d.data.length)
|
||||
m.data = d.data;
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a plain object from a FetchResponse message. Also converts values to other types if specified.
|
||||
* @function toObject
|
||||
* @memberof FetchResponse
|
||||
* @static
|
||||
* @param {FetchResponse} m FetchResponse
|
||||
* @param {$protobuf.IConversionOptions} [o] Conversion options
|
||||
* @returns {Object.<string,*>} Plain object
|
||||
*/
|
||||
FetchResponse.toObject = function toObject(m, o) {
|
||||
if (!o)
|
||||
o = {};
|
||||
var d = {};
|
||||
if (o.defaults) {
|
||||
d.status = o.enums === String ? "OK" : 0;
|
||||
if (o.bytes === String)
|
||||
d.data = "";
|
||||
else {
|
||||
d.data = [];
|
||||
if (o.bytes !== Array)
|
||||
d.data = $util.newBuffer(d.data);
|
||||
}
|
||||
}
|
||||
if (m.status != null && m.hasOwnProperty("status")) {
|
||||
d.status = o.enums === String ? $root.FetchResponse.StatusCode[m.status] : m.status;
|
||||
}
|
||||
if (m.data != null && m.hasOwnProperty("data")) {
|
||||
d.data = o.bytes === String ? $util.base64.encode(m.data, 0, m.data.length) : o.bytes === Array ? Array.prototype.slice.call(m.data) : m.data;
|
||||
}
|
||||
return d;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts this FetchResponse to JSON.
|
||||
* @function toJSON
|
||||
* @memberof FetchResponse
|
||||
* @instance
|
||||
* @returns {Object.<string,*>} JSON object
|
||||
*/
|
||||
FetchResponse.prototype.toJSON = function toJSON() {
|
||||
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
* StatusCode enum.
|
||||
* @name FetchResponse.StatusCode
|
||||
* @enum {number}
|
||||
* @property {number} OK=0 OK value
|
||||
* @property {number} NOT_FOUND=1 NOT_FOUND value
|
||||
* @property {number} ERROR=2 ERROR value
|
||||
*/
|
||||
FetchResponse.StatusCode = (function() {
|
||||
const valuesById = {}, values = Object.create(valuesById);
|
||||
values[valuesById[0] = "OK"] = 0;
|
||||
values[valuesById[1] = "NOT_FOUND"] = 1;
|
||||
values[valuesById[2] = "ERROR"] = 2;
|
||||
return values;
|
||||
})();
|
||||
|
||||
return FetchResponse;
|
||||
})();
|
||||
|
||||
export { $root as default };
|
@@ -1,65 +0,0 @@
|
||||
/* eslint-disable import/export */
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
|
||||
import { encodeMessage, decodeMessage, message, string, enumeration, bytes } from 'protons-runtime'
|
||||
import type { Codec } from 'protons-runtime'
|
||||
|
||||
export interface FetchRequest {
|
||||
identifier: string
|
||||
}
|
||||
|
||||
export namespace FetchRequest {
|
||||
export const codec = (): Codec<FetchRequest> => {
|
||||
return message<FetchRequest>({
|
||||
1: { name: 'identifier', codec: string }
|
||||
})
|
||||
}
|
||||
|
||||
export const encode = (obj: FetchRequest): Uint8Array => {
|
||||
return encodeMessage(obj, FetchRequest.codec())
|
||||
}
|
||||
|
||||
export const decode = (buf: Uint8Array): FetchRequest => {
|
||||
return decodeMessage(buf, FetchRequest.codec())
|
||||
}
|
||||
}
|
||||
|
||||
export interface FetchResponse {
|
||||
status: FetchResponse.StatusCode
|
||||
data: Uint8Array
|
||||
}
|
||||
|
||||
export namespace FetchResponse {
|
||||
export enum StatusCode {
|
||||
OK = 'OK',
|
||||
NOT_FOUND = 'NOT_FOUND',
|
||||
ERROR = 'ERROR'
|
||||
}
|
||||
|
||||
enum __StatusCodeValues {
|
||||
OK = 0,
|
||||
NOT_FOUND = 1,
|
||||
ERROR = 2
|
||||
}
|
||||
|
||||
export namespace StatusCode {
|
||||
export const codec = () => {
|
||||
return enumeration<typeof StatusCode>(__StatusCodeValues)
|
||||
}
|
||||
}
|
||||
|
||||
export const codec = (): Codec<FetchResponse> => {
|
||||
return message<FetchResponse>({
|
||||
1: { name: 'status', codec: FetchResponse.StatusCode.codec() },
|
||||
2: { name: 'data', codec: bytes }
|
||||
})
|
||||
}
|
||||
|
||||
export const encode = (obj: FetchResponse): Uint8Array => {
|
||||
return encodeMessage(obj, FetchResponse.codec())
|
||||
}
|
||||
|
||||
export const decode = (buf: Uint8Array): FetchResponse => {
|
||||
return decodeMessage(buf, FetchResponse.codec())
|
||||
}
|
||||
}
|
@@ -2,11 +2,13 @@ import { logger } from '@libp2p/logger'
|
||||
import errCode from 'err-code'
|
||||
import * as lp from 'it-length-prefixed'
|
||||
import { pipe } from 'it-pipe'
|
||||
import all from 'it-all'
|
||||
import take from 'it-take'
|
||||
import drain from 'it-drain'
|
||||
import first from 'it-first'
|
||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
import { Multiaddr, protocols } from '@multiformats/multiaddr'
|
||||
import { Identify } from './pb/message.js'
|
||||
import Message from './pb/message.js'
|
||||
import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
|
||||
import {
|
||||
MULTICODEC_IDENTIFY,
|
||||
@@ -19,47 +21,20 @@ import {
|
||||
} from './consts.js'
|
||||
import { codes } from '../errors.js'
|
||||
import type { IncomingStreamData } from '@libp2p/interfaces/registrar'
|
||||
import type { Connection, Stream } from '@libp2p/interfaces/connection'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import { peerIdFromKeys } from '@libp2p/peer-id'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import type { Startable } from '@libp2p/interfaces'
|
||||
import { peerIdFromKeys, peerIdFromString } from '@libp2p/peer-id'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
import { TimeoutController } from 'timeout-abort-controller'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
import { abortableDuplex } from 'abortable-iterator'
|
||||
import type { Duplex } from 'it-stream-types'
|
||||
|
||||
const log = logger('libp2p:identify')
|
||||
|
||||
// https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L48
|
||||
const IDENTIFY_TIMEOUT = 60000
|
||||
|
||||
// https://github.com/libp2p/go-libp2p/blob/8d2e54e1637041d5cf4fac1e531287560bd1f4ac/p2p/protocol/identify/id.go#L52
|
||||
const MAX_IDENTIFY_MESSAGE_SIZE = 1024 * 8
|
||||
|
||||
export interface HostProperties {
|
||||
agentVersion: string
|
||||
}
|
||||
|
||||
export interface IdentifyServiceInit {
|
||||
/**
|
||||
* The prefix to use for the protocol (default: 'ipfs')
|
||||
*/
|
||||
protocolPrefix: string
|
||||
|
||||
/**
|
||||
* What details we should send as part of an identify message
|
||||
*/
|
||||
host: HostProperties
|
||||
|
||||
/**
|
||||
* How long we should wait for a remote peer to send their identify response
|
||||
*/
|
||||
timeout?: number
|
||||
|
||||
/**
|
||||
* Identify responses larger than this in bytes will be rejected (default: 8192)
|
||||
*/
|
||||
maxIdentifyMessageSize?: number
|
||||
}
|
||||
|
||||
export class IdentifyService implements Startable {
|
||||
@@ -71,13 +46,11 @@ export class IdentifyService implements Startable {
|
||||
agentVersion: string
|
||||
}
|
||||
|
||||
private readonly init: IdentifyServiceInit
|
||||
private started: boolean
|
||||
|
||||
constructor (components: Components, init: IdentifyServiceInit) {
|
||||
this.components = components
|
||||
this.started = false
|
||||
this.init = init
|
||||
|
||||
this.handleMessage = this.handleMessage.bind(this)
|
||||
|
||||
@@ -155,37 +128,22 @@ export class IdentifyService implements Startable {
|
||||
const protocols = await this.components.getPeerStore().protoBook.get(this.components.getPeerId())
|
||||
|
||||
const pushes = connections.map(async connection => {
|
||||
const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT)
|
||||
let stream: Stream | undefined
|
||||
|
||||
try {
|
||||
const data = await connection.newStream([this.identifyPushProtocolStr], {
|
||||
signal: timeoutController.signal
|
||||
})
|
||||
stream = data.stream
|
||||
|
||||
// make stream abortable
|
||||
const source: Duplex<Uint8Array> = abortableDuplex(stream, timeoutController.signal)
|
||||
const { stream } = await connection.newStream([this.identifyPushProtocolStr])
|
||||
|
||||
await pipe(
|
||||
[Identify.encode({
|
||||
[Message.Identify.encode({
|
||||
listenAddrs,
|
||||
signedPeerRecord,
|
||||
protocols
|
||||
})],
|
||||
}).finish()],
|
||||
lp.encode(),
|
||||
source,
|
||||
stream,
|
||||
drain
|
||||
)
|
||||
} catch (err: any) {
|
||||
// Just log errors
|
||||
log.error('could not push identify update to peer', err)
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
stream.close()
|
||||
}
|
||||
|
||||
timeoutController.clear()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -203,70 +161,45 @@ export class IdentifyService implements Startable {
|
||||
|
||||
const connections: Connection[] = []
|
||||
|
||||
for (const conn of this.components.getConnectionManager().getConnections()) {
|
||||
const peerId = conn.remotePeer
|
||||
for (const [peerIdStr, conns] of this.components.getConnectionManager().getConnectionMap().entries()) {
|
||||
const peerId = peerIdFromString(peerIdStr)
|
||||
const peer = await this.components.getPeerStore().get(peerId)
|
||||
|
||||
if (!peer.protocols.includes(this.identifyPushProtocolStr)) {
|
||||
continue
|
||||
}
|
||||
|
||||
connections.push(conn)
|
||||
connections.push(...conns)
|
||||
}
|
||||
|
||||
await this.push(connections)
|
||||
}
|
||||
|
||||
async _identify (connection: Connection, options: AbortOptions = {}): Promise<Identify> {
|
||||
const { stream } = await connection.newStream([this.identifyProtocolStr], options)
|
||||
let source: Duplex<Uint8Array> = stream
|
||||
let timeoutController
|
||||
let signal = options.signal
|
||||
|
||||
// create a timeout if no abort signal passed
|
||||
if (signal == null) {
|
||||
timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT)
|
||||
signal = timeoutController.signal
|
||||
}
|
||||
|
||||
// make stream abortable if AbortSignal passed
|
||||
source = abortableDuplex(stream, signal)
|
||||
|
||||
try {
|
||||
const data = await pipe(
|
||||
[],
|
||||
source,
|
||||
lp.decode({
|
||||
maxDataLength: this.init.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE
|
||||
}),
|
||||
async (source) => await first(source)
|
||||
)
|
||||
|
||||
if (data == null) {
|
||||
throw errCode(new Error('No data could be retrieved'), codes.ERR_CONNECTION_ENDED)
|
||||
}
|
||||
|
||||
try {
|
||||
return Identify.decode(data)
|
||||
} catch (err: any) {
|
||||
throw errCode(err, codes.ERR_INVALID_MESSAGE)
|
||||
}
|
||||
} finally {
|
||||
if (timeoutController != null) {
|
||||
timeoutController.clear()
|
||||
}
|
||||
|
||||
stream.close()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests the `Identify` message from peer associated with the given `connection`.
|
||||
* If the identified peer does not match the `PeerId` associated with the connection,
|
||||
* an error will be thrown.
|
||||
*/
|
||||
async identify (connection: Connection, options: AbortOptions = {}): Promise<void> {
|
||||
const message = await this._identify(connection, options)
|
||||
async identify (connection: Connection): Promise<void> {
|
||||
const { stream } = await connection.newStream([this.identifyProtocolStr])
|
||||
const [data] = await pipe(
|
||||
[],
|
||||
stream,
|
||||
lp.decode(),
|
||||
(source) => take(source, 1),
|
||||
async (source) => await all(source)
|
||||
)
|
||||
|
||||
if (data == null) {
|
||||
throw errCode(new Error('No data could be retrieved'), codes.ERR_CONNECTION_ENDED)
|
||||
}
|
||||
|
||||
let message
|
||||
try {
|
||||
message = Message.Identify.decode(data)
|
||||
} catch (err: any) {
|
||||
throw errCode(err, codes.ERR_INVALID_MESSAGE)
|
||||
}
|
||||
|
||||
const {
|
||||
publicKey,
|
||||
@@ -375,8 +308,6 @@ export class IdentifyService implements Startable {
|
||||
*/
|
||||
async _handleIdentify (data: IncomingStreamData) {
|
||||
const { connection, stream } = data
|
||||
const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT)
|
||||
|
||||
try {
|
||||
const publicKey = this.components.getPeerId().publicKey ?? new Uint8Array(0)
|
||||
const peerData = await this.components.getPeerStore().get(this.components.getPeerId())
|
||||
@@ -394,7 +325,7 @@ export class IdentifyService implements Startable {
|
||||
signedPeerRecord = envelope.marshal()
|
||||
}
|
||||
|
||||
const message = Identify.encode({
|
||||
const message = Message.Identify.encode({
|
||||
protocolVersion: this.host.protocolVersion,
|
||||
agentVersion: this.host.agentVersion,
|
||||
publicKey,
|
||||
@@ -402,22 +333,16 @@ export class IdentifyService implements Startable {
|
||||
signedPeerRecord,
|
||||
observedAddr: connection.remoteAddr.bytes,
|
||||
protocols: peerData.protocols
|
||||
})
|
||||
|
||||
// make stream abortable
|
||||
const source: Duplex<Uint8Array> = abortableDuplex(stream, timeoutController.signal)
|
||||
}).finish()
|
||||
|
||||
await pipe(
|
||||
[message],
|
||||
lp.encode(),
|
||||
source,
|
||||
stream,
|
||||
drain
|
||||
)
|
||||
} catch (err: any) {
|
||||
log.error('could not respond to identify request', err)
|
||||
} finally {
|
||||
stream.close()
|
||||
timeoutController.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,30 +351,21 @@ export class IdentifyService implements Startable {
|
||||
*/
|
||||
async _handlePush (data: IncomingStreamData) {
|
||||
const { connection, stream } = data
|
||||
const timeoutController = new TimeoutController(this.init.timeout ?? IDENTIFY_TIMEOUT)
|
||||
|
||||
let message: Identify | undefined
|
||||
let message
|
||||
try {
|
||||
// make stream abortable
|
||||
const source: Duplex<Uint8Array> = abortableDuplex(stream, timeoutController.signal)
|
||||
|
||||
const data = await pipe(
|
||||
[],
|
||||
source,
|
||||
lp.decode({
|
||||
maxDataLength: this.init.maxIdentifyMessageSize ?? MAX_IDENTIFY_MESSAGE_SIZE
|
||||
}),
|
||||
stream,
|
||||
lp.decode(),
|
||||
async (source) => await first(source)
|
||||
)
|
||||
|
||||
if (data != null) {
|
||||
message = Identify.decode(data)
|
||||
message = Message.Identify.decode(data)
|
||||
}
|
||||
} catch (err: any) {
|
||||
return log.error('received invalid message', err)
|
||||
} finally {
|
||||
stream.close()
|
||||
timeoutController.clear()
|
||||
}
|
||||
|
||||
if (message == null) {
|
||||
@@ -526,4 +442,4 @@ export const multicodecs = {
|
||||
IDENTIFY_PUSH: MULTICODEC_IDENTIFY_PUSH
|
||||
}
|
||||
|
||||
export const Message = { Identify }
|
||||
export { Message }
|
||||
|
110
src/identify/pb/message.d.ts
vendored
Normal file
110
src/identify/pb/message.d.ts
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
import * as $protobuf from "protobufjs";
|
||||
/** Properties of an Identify. */
|
||||
export interface IIdentify {
|
||||
|
||||
/** Identify protocolVersion */
|
||||
protocolVersion?: (string|null);
|
||||
|
||||
/** Identify agentVersion */
|
||||
agentVersion?: (string|null);
|
||||
|
||||
/** Identify publicKey */
|
||||
publicKey?: (Uint8Array|null);
|
||||
|
||||
/** Identify listenAddrs */
|
||||
listenAddrs?: (Uint8Array[]|null);
|
||||
|
||||
/** Identify observedAddr */
|
||||
observedAddr?: (Uint8Array|null);
|
||||
|
||||
/** Identify protocols */
|
||||
protocols?: (string[]|null);
|
||||
|
||||
/** Identify signedPeerRecord */
|
||||
signedPeerRecord?: (Uint8Array|null);
|
||||
}
|
||||
|
||||
/** Represents an Identify. */
|
||||
export class Identify implements IIdentify {
|
||||
|
||||
/**
|
||||
* Constructs a new Identify.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: IIdentify);
|
||||
|
||||
/** Identify protocolVersion. */
|
||||
public protocolVersion?: (string|null);
|
||||
|
||||
/** Identify agentVersion. */
|
||||
public agentVersion?: (string|null);
|
||||
|
||||
/** Identify publicKey. */
|
||||
public publicKey?: (Uint8Array|null);
|
||||
|
||||
/** Identify listenAddrs. */
|
||||
public listenAddrs: Uint8Array[];
|
||||
|
||||
/** Identify observedAddr. */
|
||||
public observedAddr?: (Uint8Array|null);
|
||||
|
||||
/** Identify protocols. */
|
||||
public protocols: string[];
|
||||
|
||||
/** Identify signedPeerRecord. */
|
||||
public signedPeerRecord?: (Uint8Array|null);
|
||||
|
||||
/** Identify _protocolVersion. */
|
||||
public _protocolVersion?: "protocolVersion";
|
||||
|
||||
/** Identify _agentVersion. */
|
||||
public _agentVersion?: "agentVersion";
|
||||
|
||||
/** Identify _publicKey. */
|
||||
public _publicKey?: "publicKey";
|
||||
|
||||
/** Identify _observedAddr. */
|
||||
public _observedAddr?: "observedAddr";
|
||||
|
||||
/** Identify _signedPeerRecord. */
|
||||
public _signedPeerRecord?: "signedPeerRecord";
|
||||
|
||||
/**
|
||||
* Encodes the specified Identify message. Does not implicitly {@link Identify.verify|verify} messages.
|
||||
* @param m Identify message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: IIdentify, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes an Identify message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns Identify
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Identify;
|
||||
|
||||
/**
|
||||
* Creates an Identify message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns Identify
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): Identify;
|
||||
|
||||
/**
|
||||
* Creates a plain object from an Identify message. Also converts values to other types if specified.
|
||||
* @param m Identify
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: Identify, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this Identify to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
369
src/identify/pb/message.js
Normal file
369
src/identify/pb/message.js
Normal file
@@ -0,0 +1,369 @@
|
||||
/*eslint-disable*/
|
||||
import $protobuf from "protobufjs/minimal.js";
|
||||
|
||||
// Common aliases
|
||||
const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
|
||||
|
||||
// Exported root namespace
|
||||
const $root = $protobuf.roots["libp2p-identify"] || ($protobuf.roots["libp2p-identify"] = {});
|
||||
|
||||
export const Identify = $root.Identify = (() => {
|
||||
|
||||
/**
|
||||
* Properties of an Identify.
|
||||
* @exports IIdentify
|
||||
* @interface IIdentify
|
||||
* @property {string|null} [protocolVersion] Identify protocolVersion
|
||||
* @property {string|null} [agentVersion] Identify agentVersion
|
||||
* @property {Uint8Array|null} [publicKey] Identify publicKey
|
||||
* @property {Array.<Uint8Array>|null} [listenAddrs] Identify listenAddrs
|
||||
* @property {Uint8Array|null} [observedAddr] Identify observedAddr
|
||||
* @property {Array.<string>|null} [protocols] Identify protocols
|
||||
* @property {Uint8Array|null} [signedPeerRecord] Identify signedPeerRecord
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a new Identify.
|
||||
* @exports Identify
|
||||
* @classdesc Represents an Identify.
|
||||
* @implements IIdentify
|
||||
* @constructor
|
||||
* @param {IIdentify=} [p] Properties to set
|
||||
*/
|
||||
function Identify(p) {
|
||||
this.listenAddrs = [];
|
||||
this.protocols = [];
|
||||
if (p)
|
||||
for (var ks = Object.keys(p), i = 0; i < ks.length; ++i)
|
||||
if (p[ks[i]] != null)
|
||||
this[ks[i]] = p[ks[i]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify protocolVersion.
|
||||
* @member {string|null|undefined} protocolVersion
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Identify.prototype.protocolVersion = null;
|
||||
|
||||
/**
|
||||
* Identify agentVersion.
|
||||
* @member {string|null|undefined} agentVersion
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Identify.prototype.agentVersion = null;
|
||||
|
||||
/**
|
||||
* Identify publicKey.
|
||||
* @member {Uint8Array|null|undefined} publicKey
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Identify.prototype.publicKey = null;
|
||||
|
||||
/**
|
||||
* Identify listenAddrs.
|
||||
* @member {Array.<Uint8Array>} listenAddrs
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Identify.prototype.listenAddrs = $util.emptyArray;
|
||||
|
||||
/**
|
||||
* Identify observedAddr.
|
||||
* @member {Uint8Array|null|undefined} observedAddr
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Identify.prototype.observedAddr = null;
|
||||
|
||||
/**
|
||||
* Identify protocols.
|
||||
* @member {Array.<string>} protocols
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Identify.prototype.protocols = $util.emptyArray;
|
||||
|
||||
/**
|
||||
* Identify signedPeerRecord.
|
||||
* @member {Uint8Array|null|undefined} signedPeerRecord
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Identify.prototype.signedPeerRecord = null;
|
||||
|
||||
// OneOf field names bound to virtual getters and setters
|
||||
let $oneOfFields;
|
||||
|
||||
/**
|
||||
* Identify _protocolVersion.
|
||||
* @member {"protocolVersion"|undefined} _protocolVersion
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Object.defineProperty(Identify.prototype, "_protocolVersion", {
|
||||
get: $util.oneOfGetter($oneOfFields = ["protocolVersion"]),
|
||||
set: $util.oneOfSetter($oneOfFields)
|
||||
});
|
||||
|
||||
/**
|
||||
* Identify _agentVersion.
|
||||
* @member {"agentVersion"|undefined} _agentVersion
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Object.defineProperty(Identify.prototype, "_agentVersion", {
|
||||
get: $util.oneOfGetter($oneOfFields = ["agentVersion"]),
|
||||
set: $util.oneOfSetter($oneOfFields)
|
||||
});
|
||||
|
||||
/**
|
||||
* Identify _publicKey.
|
||||
* @member {"publicKey"|undefined} _publicKey
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Object.defineProperty(Identify.prototype, "_publicKey", {
|
||||
get: $util.oneOfGetter($oneOfFields = ["publicKey"]),
|
||||
set: $util.oneOfSetter($oneOfFields)
|
||||
});
|
||||
|
||||
/**
|
||||
* Identify _observedAddr.
|
||||
* @member {"observedAddr"|undefined} _observedAddr
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Object.defineProperty(Identify.prototype, "_observedAddr", {
|
||||
get: $util.oneOfGetter($oneOfFields = ["observedAddr"]),
|
||||
set: $util.oneOfSetter($oneOfFields)
|
||||
});
|
||||
|
||||
/**
|
||||
* Identify _signedPeerRecord.
|
||||
* @member {"signedPeerRecord"|undefined} _signedPeerRecord
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
*/
|
||||
Object.defineProperty(Identify.prototype, "_signedPeerRecord", {
|
||||
get: $util.oneOfGetter($oneOfFields = ["signedPeerRecord"]),
|
||||
set: $util.oneOfSetter($oneOfFields)
|
||||
});
|
||||
|
||||
/**
|
||||
* Encodes the specified Identify message. Does not implicitly {@link Identify.verify|verify} messages.
|
||||
* @function encode
|
||||
* @memberof Identify
|
||||
* @static
|
||||
* @param {IIdentify} m Identify message or plain object to encode
|
||||
* @param {$protobuf.Writer} [w] Writer to encode to
|
||||
* @returns {$protobuf.Writer} Writer
|
||||
*/
|
||||
Identify.encode = function encode(m, w) {
|
||||
if (!w)
|
||||
w = $Writer.create();
|
||||
if (m.publicKey != null && Object.hasOwnProperty.call(m, "publicKey"))
|
||||
w.uint32(10).bytes(m.publicKey);
|
||||
if (m.listenAddrs != null && m.listenAddrs.length) {
|
||||
for (var i = 0; i < m.listenAddrs.length; ++i)
|
||||
w.uint32(18).bytes(m.listenAddrs[i]);
|
||||
}
|
||||
if (m.protocols != null && m.protocols.length) {
|
||||
for (var i = 0; i < m.protocols.length; ++i)
|
||||
w.uint32(26).string(m.protocols[i]);
|
||||
}
|
||||
if (m.observedAddr != null && Object.hasOwnProperty.call(m, "observedAddr"))
|
||||
w.uint32(34).bytes(m.observedAddr);
|
||||
if (m.protocolVersion != null && Object.hasOwnProperty.call(m, "protocolVersion"))
|
||||
w.uint32(42).string(m.protocolVersion);
|
||||
if (m.agentVersion != null && Object.hasOwnProperty.call(m, "agentVersion"))
|
||||
w.uint32(50).string(m.agentVersion);
|
||||
if (m.signedPeerRecord != null && Object.hasOwnProperty.call(m, "signedPeerRecord"))
|
||||
w.uint32(66).bytes(m.signedPeerRecord);
|
||||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes an Identify message from the specified reader or buffer.
|
||||
* @function decode
|
||||
* @memberof Identify
|
||||
* @static
|
||||
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
|
||||
* @param {number} [l] Message length if known beforehand
|
||||
* @returns {Identify} Identify
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
Identify.decode = function decode(r, l) {
|
||||
if (!(r instanceof $Reader))
|
||||
r = $Reader.create(r);
|
||||
var c = l === undefined ? r.len : r.pos + l, m = new $root.Identify();
|
||||
while (r.pos < c) {
|
||||
var t = r.uint32();
|
||||
switch (t >>> 3) {
|
||||
case 5:
|
||||
m.protocolVersion = r.string();
|
||||
break;
|
||||
case 6:
|
||||
m.agentVersion = r.string();
|
||||
break;
|
||||
case 1:
|
||||
m.publicKey = r.bytes();
|
||||
break;
|
||||
case 2:
|
||||
if (!(m.listenAddrs && m.listenAddrs.length))
|
||||
m.listenAddrs = [];
|
||||
m.listenAddrs.push(r.bytes());
|
||||
break;
|
||||
case 4:
|
||||
m.observedAddr = r.bytes();
|
||||
break;
|
||||
case 3:
|
||||
if (!(m.protocols && m.protocols.length))
|
||||
m.protocols = [];
|
||||
m.protocols.push(r.string());
|
||||
break;
|
||||
case 8:
|
||||
m.signedPeerRecord = r.bytes();
|
||||
break;
|
||||
default:
|
||||
r.skipType(t & 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an Identify message from a plain object. Also converts values to their respective internal types.
|
||||
* @function fromObject
|
||||
* @memberof Identify
|
||||
* @static
|
||||
* @param {Object.<string,*>} d Plain object
|
||||
* @returns {Identify} Identify
|
||||
*/
|
||||
Identify.fromObject = function fromObject(d) {
|
||||
if (d instanceof $root.Identify)
|
||||
return d;
|
||||
var m = new $root.Identify();
|
||||
if (d.protocolVersion != null) {
|
||||
m.protocolVersion = String(d.protocolVersion);
|
||||
}
|
||||
if (d.agentVersion != null) {
|
||||
m.agentVersion = String(d.agentVersion);
|
||||
}
|
||||
if (d.publicKey != null) {
|
||||
if (typeof d.publicKey === "string")
|
||||
$util.base64.decode(d.publicKey, m.publicKey = $util.newBuffer($util.base64.length(d.publicKey)), 0);
|
||||
else if (d.publicKey.length)
|
||||
m.publicKey = d.publicKey;
|
||||
}
|
||||
if (d.listenAddrs) {
|
||||
if (!Array.isArray(d.listenAddrs))
|
||||
throw TypeError(".Identify.listenAddrs: array expected");
|
||||
m.listenAddrs = [];
|
||||
for (var i = 0; i < d.listenAddrs.length; ++i) {
|
||||
if (typeof d.listenAddrs[i] === "string")
|
||||
$util.base64.decode(d.listenAddrs[i], m.listenAddrs[i] = $util.newBuffer($util.base64.length(d.listenAddrs[i])), 0);
|
||||
else if (d.listenAddrs[i].length)
|
||||
m.listenAddrs[i] = d.listenAddrs[i];
|
||||
}
|
||||
}
|
||||
if (d.observedAddr != null) {
|
||||
if (typeof d.observedAddr === "string")
|
||||
$util.base64.decode(d.observedAddr, m.observedAddr = $util.newBuffer($util.base64.length(d.observedAddr)), 0);
|
||||
else if (d.observedAddr.length)
|
||||
m.observedAddr = d.observedAddr;
|
||||
}
|
||||
if (d.protocols) {
|
||||
if (!Array.isArray(d.protocols))
|
||||
throw TypeError(".Identify.protocols: array expected");
|
||||
m.protocols = [];
|
||||
for (var i = 0; i < d.protocols.length; ++i) {
|
||||
m.protocols[i] = String(d.protocols[i]);
|
||||
}
|
||||
}
|
||||
if (d.signedPeerRecord != null) {
|
||||
if (typeof d.signedPeerRecord === "string")
|
||||
$util.base64.decode(d.signedPeerRecord, m.signedPeerRecord = $util.newBuffer($util.base64.length(d.signedPeerRecord)), 0);
|
||||
else if (d.signedPeerRecord.length)
|
||||
m.signedPeerRecord = d.signedPeerRecord;
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a plain object from an Identify message. Also converts values to other types if specified.
|
||||
* @function toObject
|
||||
* @memberof Identify
|
||||
* @static
|
||||
* @param {Identify} m Identify
|
||||
* @param {$protobuf.IConversionOptions} [o] Conversion options
|
||||
* @returns {Object.<string,*>} Plain object
|
||||
*/
|
||||
Identify.toObject = function toObject(m, o) {
|
||||
if (!o)
|
||||
o = {};
|
||||
var d = {};
|
||||
if (o.arrays || o.defaults) {
|
||||
d.listenAddrs = [];
|
||||
d.protocols = [];
|
||||
}
|
||||
if (m.publicKey != null && m.hasOwnProperty("publicKey")) {
|
||||
d.publicKey = o.bytes === String ? $util.base64.encode(m.publicKey, 0, m.publicKey.length) : o.bytes === Array ? Array.prototype.slice.call(m.publicKey) : m.publicKey;
|
||||
if (o.oneofs)
|
||||
d._publicKey = "publicKey";
|
||||
}
|
||||
if (m.listenAddrs && m.listenAddrs.length) {
|
||||
d.listenAddrs = [];
|
||||
for (var j = 0; j < m.listenAddrs.length; ++j) {
|
||||
d.listenAddrs[j] = o.bytes === String ? $util.base64.encode(m.listenAddrs[j], 0, m.listenAddrs[j].length) : o.bytes === Array ? Array.prototype.slice.call(m.listenAddrs[j]) : m.listenAddrs[j];
|
||||
}
|
||||
}
|
||||
if (m.protocols && m.protocols.length) {
|
||||
d.protocols = [];
|
||||
for (var j = 0; j < m.protocols.length; ++j) {
|
||||
d.protocols[j] = m.protocols[j];
|
||||
}
|
||||
}
|
||||
if (m.observedAddr != null && m.hasOwnProperty("observedAddr")) {
|
||||
d.observedAddr = o.bytes === String ? $util.base64.encode(m.observedAddr, 0, m.observedAddr.length) : o.bytes === Array ? Array.prototype.slice.call(m.observedAddr) : m.observedAddr;
|
||||
if (o.oneofs)
|
||||
d._observedAddr = "observedAddr";
|
||||
}
|
||||
if (m.protocolVersion != null && m.hasOwnProperty("protocolVersion")) {
|
||||
d.protocolVersion = m.protocolVersion;
|
||||
if (o.oneofs)
|
||||
d._protocolVersion = "protocolVersion";
|
||||
}
|
||||
if (m.agentVersion != null && m.hasOwnProperty("agentVersion")) {
|
||||
d.agentVersion = m.agentVersion;
|
||||
if (o.oneofs)
|
||||
d._agentVersion = "agentVersion";
|
||||
}
|
||||
if (m.signedPeerRecord != null && m.hasOwnProperty("signedPeerRecord")) {
|
||||
d.signedPeerRecord = o.bytes === String ? $util.base64.encode(m.signedPeerRecord, 0, m.signedPeerRecord.length) : o.bytes === Array ? Array.prototype.slice.call(m.signedPeerRecord) : m.signedPeerRecord;
|
||||
if (o.oneofs)
|
||||
d._signedPeerRecord = "signedPeerRecord";
|
||||
}
|
||||
return d;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts this Identify to JSON.
|
||||
* @function toJSON
|
||||
* @memberof Identify
|
||||
* @instance
|
||||
* @returns {Object.<string,*>} JSON object
|
||||
*/
|
||||
Identify.prototype.toJSON = function toJSON() {
|
||||
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
|
||||
};
|
||||
|
||||
return Identify;
|
||||
})();
|
||||
|
||||
export { $root as default };
|
@@ -1,37 +0,0 @@
|
||||
/* eslint-disable import/export */
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
|
||||
import { encodeMessage, decodeMessage, message, string, bytes } from 'protons-runtime'
|
||||
import type { Codec } from 'protons-runtime'
|
||||
|
||||
export interface Identify {
|
||||
protocolVersion?: string
|
||||
agentVersion?: string
|
||||
publicKey?: Uint8Array
|
||||
listenAddrs: Uint8Array[]
|
||||
observedAddr?: Uint8Array
|
||||
protocols: string[]
|
||||
signedPeerRecord?: Uint8Array
|
||||
}
|
||||
|
||||
export namespace Identify {
|
||||
export const codec = (): Codec<Identify> => {
|
||||
return message<Identify>({
|
||||
5: { name: 'protocolVersion', codec: string, optional: true },
|
||||
6: { name: 'agentVersion', codec: string, optional: true },
|
||||
1: { name: 'publicKey', codec: bytes, optional: true },
|
||||
2: { name: 'listenAddrs', codec: bytes, repeats: true },
|
||||
4: { name: 'observedAddr', codec: bytes, optional: true },
|
||||
3: { name: 'protocols', codec: string, repeats: true },
|
||||
8: { name: 'signedPeerRecord', codec: bytes, optional: true }
|
||||
})
|
||||
}
|
||||
|
||||
export const encode = (obj: Identify): Uint8Array => {
|
||||
return encodeMessage(obj, Identify.codec())
|
||||
}
|
||||
|
||||
export const decode = (buf: Uint8Array): Identify => {
|
||||
return decodeMessage(buf, Identify.codec())
|
||||
}
|
||||
}
|
60
src/index.ts
60
src/index.ts
@@ -1,10 +1,8 @@
|
||||
import { createLibp2pNode } from './libp2p.js'
|
||||
import type { AbortOptions, RecursivePartial } from '@libp2p/interfaces'
|
||||
import type { EventEmitter } from '@libp2p/interfaces/events'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import type { AbortOptions, EventEmitter, RecursivePartial, Startable } from '@libp2p/interfaces'
|
||||
import type { Multiaddr } from '@multiformats/multiaddr'
|
||||
import type { FaultTolerance } from './transport-manager.js'
|
||||
import type { IdentifyServiceInit } from './identify/index.js'
|
||||
import type { HostProperties } from './identify/index.js'
|
||||
import type { DualDHT } from '@libp2p/interfaces/dht'
|
||||
import type { Datastore } from 'interface-datastore'
|
||||
import type { PeerStore, PeerStoreInit } from '@libp2p/interfaces/peer-store'
|
||||
@@ -18,14 +16,11 @@ import type { ConnectionEncrypter } from '@libp2p/interfaces/connection-encrypte
|
||||
import type { PeerRouting } from '@libp2p/interfaces/peer-routing'
|
||||
import type { ContentRouting } from '@libp2p/interfaces/content-routing'
|
||||
import type { PubSub } from '@libp2p/interfaces/pubsub'
|
||||
import type { Registrar, StreamHandler } from '@libp2p/interfaces/registrar'
|
||||
import type { ConnectionManager } from '@libp2p/interfaces/connection-manager'
|
||||
import type { ConnectionManager, Registrar, StreamHandler } from '@libp2p/interfaces/registrar'
|
||||
import type { Metrics, MetricsInit } from '@libp2p/interfaces/metrics'
|
||||
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
||||
import type { DialerInit } from '@libp2p/interfaces/dialer'
|
||||
import type { KeyChain } from './keychain/index.js'
|
||||
import type { ConnectionManagerInit } from './connection-manager/index.js'
|
||||
import type { PingServiceInit } from './ping/index.js'
|
||||
import type { FetchServiceInit } from './fetch/index.js'
|
||||
|
||||
export interface PersistentPeerStoreOptions {
|
||||
threshold?: number
|
||||
@@ -56,6 +51,7 @@ export interface RelayConfig {
|
||||
enabled: boolean
|
||||
advertise: RelayAdvertiseConfig
|
||||
hop: HopConfig
|
||||
limit: number
|
||||
autoRelay: AutoRelayConfig
|
||||
}
|
||||
|
||||
@@ -76,6 +72,29 @@ export interface AddressesConfig {
|
||||
announceFilter: (multiaddrs: Multiaddr[]) => Multiaddr[]
|
||||
}
|
||||
|
||||
export interface ConnectionManagerConfig {
|
||||
/**
|
||||
* If true, try to connect to all discovered peers up to the connection manager limit
|
||||
*/
|
||||
autoDial?: boolean
|
||||
|
||||
/**
|
||||
* The maximum number of connections to keep open
|
||||
*/
|
||||
maxConnections: number
|
||||
|
||||
/**
|
||||
* The minimum number of connections to keep open
|
||||
*/
|
||||
minConnections: number
|
||||
|
||||
/**
|
||||
* How long to wait between attempting to keep our number of concurrent connections
|
||||
* above minConnections
|
||||
*/
|
||||
autoDialInterval: number
|
||||
}
|
||||
|
||||
export interface TransportManagerConfig {
|
||||
faultTolerance?: FaultTolerance
|
||||
}
|
||||
@@ -97,20 +116,20 @@ export interface RefreshManagerConfig {
|
||||
|
||||
export interface Libp2pInit {
|
||||
peerId: PeerId
|
||||
host: HostProperties
|
||||
addresses: AddressesConfig
|
||||
connectionManager: ConnectionManagerInit
|
||||
connectionManager: ConnectionManagerConfig
|
||||
connectionGater: Partial<ConnectionGater>
|
||||
transportManager: TransportManagerConfig
|
||||
datastore: Datastore
|
||||
dialer: DialerInit
|
||||
metrics: MetricsInit
|
||||
peerStore: PeerStoreInit
|
||||
peerRouting: PeerRoutingConfig
|
||||
keychain: KeychainConfig
|
||||
protocolPrefix: string
|
||||
nat: NatManagerConfig
|
||||
relay: RelayConfig
|
||||
identify: IdentifyServiceInit
|
||||
ping: PingServiceInit
|
||||
fetch: FetchServiceInit
|
||||
|
||||
transports: Transport[]
|
||||
streamMuxers?: StreamMuxerFactory[]
|
||||
@@ -136,8 +155,9 @@ export interface Libp2p extends Startable, EventEmitter<Libp2pEvents> {
|
||||
connectionManager: ConnectionManager
|
||||
registrar: Registrar
|
||||
metrics?: Metrics
|
||||
pubsub: PubSub
|
||||
dht: DualDHT
|
||||
|
||||
pubsub?: PubSub
|
||||
dht?: DualDHT
|
||||
|
||||
/**
|
||||
* Load keychain keys from the datastore.
|
||||
@@ -198,18 +218,12 @@ export interface Libp2p extends Startable, EventEmitter<Libp2pEvents> {
|
||||
/**
|
||||
* Pings the given peer in order to obtain the operation latency
|
||||
*/
|
||||
ping: (peer: Multiaddr | PeerId, options?: AbortOptions) => Promise<number>
|
||||
ping: (peer: Multiaddr |PeerId) => Promise<number>
|
||||
|
||||
/**
|
||||
* Sends a request to fetch the value associated with the given key from the given peer.
|
||||
*/
|
||||
fetch: (peer: PeerId | Multiaddr | string, key: string, options?: AbortOptions) => Promise<Uint8Array | null>
|
||||
|
||||
/**
|
||||
* Returns the public key for the passed PeerId. If the PeerId is of the 'RSA' type
|
||||
* this may mean searching the DHT if the key is not present in the KeyStore.
|
||||
*/
|
||||
getPublicKey: (peer: PeerId, options?: AbortOptions) => Promise<Uint8Array>
|
||||
fetch: (peer: PeerId | Multiaddr | string, key: string) => Promise<Uint8Array | null>
|
||||
}
|
||||
|
||||
export type Libp2pOptions = RecursivePartial<Libp2pInit>
|
||||
|
@@ -2,7 +2,7 @@ import { logger } from '@libp2p/logger'
|
||||
import { handshake } from 'it-handshake'
|
||||
import * as lp from 'it-length-prefixed'
|
||||
import { UnexpectedPeerError, InvalidCryptoExchangeError } from '@libp2p/interfaces/connection-encrypter/errors'
|
||||
import { Exchange, KeyType } from './pb/proto.js'
|
||||
import { Exchange, IExchange, KeyType } from './pb/proto.js'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import { peerIdFromBytes, peerIdFromKeys } from '@libp2p/peer-id'
|
||||
import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interfaces/connection-encrypter'
|
||||
@@ -11,8 +11,8 @@ import type { Duplex } from 'it-stream-types'
|
||||
const log = logger('libp2p:plaintext')
|
||||
const PROTOCOL = '/plaintext/2.0.0'
|
||||
|
||||
function lpEncodeExchange (exchange: Exchange) {
|
||||
const pb = Exchange.encode(exchange)
|
||||
function lpEncodeExchange (exchange: IExchange) {
|
||||
const pb = Exchange.encode(exchange).finish()
|
||||
|
||||
return lp.encode.single(pb)
|
||||
}
|
||||
@@ -37,7 +37,7 @@ async function encrypt (localId: PeerId, conn: Duplex<Uint8Array>, remoteId?: Pe
|
||||
id: localId.toBytes(),
|
||||
pubkey: {
|
||||
Type: type,
|
||||
Data: localId.publicKey ?? new Uint8Array(0)
|
||||
Data: localId.publicKey
|
||||
}
|
||||
}).slice()
|
||||
)
|
||||
@@ -52,10 +52,6 @@ async function encrypt (localId: PeerId, conn: Duplex<Uint8Array>, remoteId?: Pe
|
||||
|
||||
let peerId
|
||||
try {
|
||||
if (id.pubkey == null) {
|
||||
throw new Error('Public key missing')
|
||||
}
|
||||
|
||||
if (id.pubkey.Data.length === 0) {
|
||||
throw new Error('Public key data too short')
|
||||
}
|
||||
|
134
src/insecure/pb/proto.d.ts
vendored
Normal file
134
src/insecure/pb/proto.d.ts
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
import * as $protobuf from "protobufjs";
|
||||
/** Properties of an Exchange. */
|
||||
export interface IExchange {
|
||||
|
||||
/** Exchange id */
|
||||
id?: (Uint8Array|null);
|
||||
|
||||
/** Exchange pubkey */
|
||||
pubkey?: (IPublicKey|null);
|
||||
}
|
||||
|
||||
/** Represents an Exchange. */
|
||||
export class Exchange implements IExchange {
|
||||
|
||||
/**
|
||||
* Constructs a new Exchange.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: IExchange);
|
||||
|
||||
/** Exchange id. */
|
||||
public id?: (Uint8Array|null);
|
||||
|
||||
/** Exchange pubkey. */
|
||||
public pubkey?: (IPublicKey|null);
|
||||
|
||||
/** Exchange _id. */
|
||||
public _id?: "id";
|
||||
|
||||
/** Exchange _pubkey. */
|
||||
public _pubkey?: "pubkey";
|
||||
|
||||
/**
|
||||
* Encodes the specified Exchange message. Does not implicitly {@link Exchange.verify|verify} messages.
|
||||
* @param m Exchange message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: IExchange, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes an Exchange message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns Exchange
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): Exchange;
|
||||
|
||||
/**
|
||||
* Creates an Exchange message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns Exchange
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): Exchange;
|
||||
|
||||
/**
|
||||
* Creates a plain object from an Exchange message. Also converts values to other types if specified.
|
||||
* @param m Exchange
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: Exchange, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this Exchange to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
||||
|
||||
/** KeyType enum. */
|
||||
export enum KeyType {
|
||||
RSA = 0,
|
||||
Ed25519 = 1,
|
||||
Secp256k1 = 2,
|
||||
ECDSA = 3
|
||||
}
|
||||
|
||||
/** Represents a PublicKey. */
|
||||
export class PublicKey implements IPublicKey {
|
||||
|
||||
/**
|
||||
* Constructs a new PublicKey.
|
||||
* @param [p] Properties to set
|
||||
*/
|
||||
constructor(p?: IPublicKey);
|
||||
|
||||
/** PublicKey Type. */
|
||||
public Type: KeyType;
|
||||
|
||||
/** PublicKey Data. */
|
||||
public Data: Uint8Array;
|
||||
|
||||
/**
|
||||
* Encodes the specified PublicKey message. Does not implicitly {@link PublicKey.verify|verify} messages.
|
||||
* @param m PublicKey message or plain object to encode
|
||||
* @param [w] Writer to encode to
|
||||
* @returns Writer
|
||||
*/
|
||||
public static encode(m: IPublicKey, w?: $protobuf.Writer): $protobuf.Writer;
|
||||
|
||||
/**
|
||||
* Decodes a PublicKey message from the specified reader or buffer.
|
||||
* @param r Reader or buffer to decode from
|
||||
* @param [l] Message length if known beforehand
|
||||
* @returns PublicKey
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
public static decode(r: ($protobuf.Reader|Uint8Array), l?: number): PublicKey;
|
||||
|
||||
/**
|
||||
* Creates a PublicKey message from a plain object. Also converts values to their respective internal types.
|
||||
* @param d Plain object
|
||||
* @returns PublicKey
|
||||
*/
|
||||
public static fromObject(d: { [k: string]: any }): PublicKey;
|
||||
|
||||
/**
|
||||
* Creates a plain object from a PublicKey message. Also converts values to other types if specified.
|
||||
* @param m PublicKey
|
||||
* @param [o] Conversion options
|
||||
* @returns Plain object
|
||||
*/
|
||||
public static toObject(m: PublicKey, o?: $protobuf.IConversionOptions): { [k: string]: any };
|
||||
|
||||
/**
|
||||
* Converts this PublicKey to JSON.
|
||||
* @returns JSON object
|
||||
*/
|
||||
public toJSON(): { [k: string]: any };
|
||||
}
|
388
src/insecure/pb/proto.js
Normal file
388
src/insecure/pb/proto.js
Normal file
@@ -0,0 +1,388 @@
|
||||
/*eslint-disable*/
|
||||
import $protobuf from "protobufjs/minimal.js";
|
||||
|
||||
// Common aliases
|
||||
const $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
|
||||
|
||||
// Exported root namespace
|
||||
const $root = $protobuf.roots["libp2p-plaintext"] || ($protobuf.roots["libp2p-plaintext"] = {});
|
||||
|
||||
export const Exchange = $root.Exchange = (() => {
|
||||
|
||||
/**
|
||||
* Properties of an Exchange.
|
||||
* @exports IExchange
|
||||
* @interface IExchange
|
||||
* @property {Uint8Array|null} [id] Exchange id
|
||||
* @property {IPublicKey|null} [pubkey] Exchange pubkey
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a new Exchange.
|
||||
* @exports Exchange
|
||||
* @classdesc Represents an Exchange.
|
||||
* @implements IExchange
|
||||
* @constructor
|
||||
* @param {IExchange=} [p] Properties to set
|
||||
*/
|
||||
function Exchange(p) {
|
||||
if (p)
|
||||
for (var ks = Object.keys(p), i = 0; i < ks.length; ++i)
|
||||
if (p[ks[i]] != null)
|
||||
this[ks[i]] = p[ks[i]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange id.
|
||||
* @member {Uint8Array|null|undefined} id
|
||||
* @memberof Exchange
|
||||
* @instance
|
||||
*/
|
||||
Exchange.prototype.id = null;
|
||||
|
||||
/**
|
||||
* Exchange pubkey.
|
||||
* @member {IPublicKey|null|undefined} pubkey
|
||||
* @memberof Exchange
|
||||
* @instance
|
||||
*/
|
||||
Exchange.prototype.pubkey = null;
|
||||
|
||||
// OneOf field names bound to virtual getters and setters
|
||||
let $oneOfFields;
|
||||
|
||||
/**
|
||||
* Exchange _id.
|
||||
* @member {"id"|undefined} _id
|
||||
* @memberof Exchange
|
||||
* @instance
|
||||
*/
|
||||
Object.defineProperty(Exchange.prototype, "_id", {
|
||||
get: $util.oneOfGetter($oneOfFields = ["id"]),
|
||||
set: $util.oneOfSetter($oneOfFields)
|
||||
});
|
||||
|
||||
/**
|
||||
* Exchange _pubkey.
|
||||
* @member {"pubkey"|undefined} _pubkey
|
||||
* @memberof Exchange
|
||||
* @instance
|
||||
*/
|
||||
Object.defineProperty(Exchange.prototype, "_pubkey", {
|
||||
get: $util.oneOfGetter($oneOfFields = ["pubkey"]),
|
||||
set: $util.oneOfSetter($oneOfFields)
|
||||
});
|
||||
|
||||
/**
|
||||
* Encodes the specified Exchange message. Does not implicitly {@link Exchange.verify|verify} messages.
|
||||
* @function encode
|
||||
* @memberof Exchange
|
||||
* @static
|
||||
* @param {IExchange} m Exchange message or plain object to encode
|
||||
* @param {$protobuf.Writer} [w] Writer to encode to
|
||||
* @returns {$protobuf.Writer} Writer
|
||||
*/
|
||||
Exchange.encode = function encode(m, w) {
|
||||
if (!w)
|
||||
w = $Writer.create();
|
||||
if (m.id != null && Object.hasOwnProperty.call(m, "id"))
|
||||
w.uint32(10).bytes(m.id);
|
||||
if (m.pubkey != null && Object.hasOwnProperty.call(m, "pubkey"))
|
||||
$root.PublicKey.encode(m.pubkey, w.uint32(18).fork()).ldelim();
|
||||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes an Exchange message from the specified reader or buffer.
|
||||
* @function decode
|
||||
* @memberof Exchange
|
||||
* @static
|
||||
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
|
||||
* @param {number} [l] Message length if known beforehand
|
||||
* @returns {Exchange} Exchange
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
Exchange.decode = function decode(r, l) {
|
||||
if (!(r instanceof $Reader))
|
||||
r = $Reader.create(r);
|
||||
var c = l === undefined ? r.len : r.pos + l, m = new $root.Exchange();
|
||||
while (r.pos < c) {
|
||||
var t = r.uint32();
|
||||
switch (t >>> 3) {
|
||||
case 1:
|
||||
m.id = r.bytes();
|
||||
break;
|
||||
case 2:
|
||||
m.pubkey = $root.PublicKey.decode(r, r.uint32());
|
||||
break;
|
||||
default:
|
||||
r.skipType(t & 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an Exchange message from a plain object. Also converts values to their respective internal types.
|
||||
* @function fromObject
|
||||
* @memberof Exchange
|
||||
* @static
|
||||
* @param {Object.<string,*>} d Plain object
|
||||
* @returns {Exchange} Exchange
|
||||
*/
|
||||
Exchange.fromObject = function fromObject(d) {
|
||||
if (d instanceof $root.Exchange)
|
||||
return d;
|
||||
var m = new $root.Exchange();
|
||||
if (d.id != null) {
|
||||
if (typeof d.id === "string")
|
||||
$util.base64.decode(d.id, m.id = $util.newBuffer($util.base64.length(d.id)), 0);
|
||||
else if (d.id.length)
|
||||
m.id = d.id;
|
||||
}
|
||||
if (d.pubkey != null) {
|
||||
if (typeof d.pubkey !== "object")
|
||||
throw TypeError(".Exchange.pubkey: object expected");
|
||||
m.pubkey = $root.PublicKey.fromObject(d.pubkey);
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a plain object from an Exchange message. Also converts values to other types if specified.
|
||||
* @function toObject
|
||||
* @memberof Exchange
|
||||
* @static
|
||||
* @param {Exchange} m Exchange
|
||||
* @param {$protobuf.IConversionOptions} [o] Conversion options
|
||||
* @returns {Object.<string,*>} Plain object
|
||||
*/
|
||||
Exchange.toObject = function toObject(m, o) {
|
||||
if (!o)
|
||||
o = {};
|
||||
var d = {};
|
||||
if (m.id != null && m.hasOwnProperty("id")) {
|
||||
d.id = o.bytes === String ? $util.base64.encode(m.id, 0, m.id.length) : o.bytes === Array ? Array.prototype.slice.call(m.id) : m.id;
|
||||
if (o.oneofs)
|
||||
d._id = "id";
|
||||
}
|
||||
if (m.pubkey != null && m.hasOwnProperty("pubkey")) {
|
||||
d.pubkey = $root.PublicKey.toObject(m.pubkey, o);
|
||||
if (o.oneofs)
|
||||
d._pubkey = "pubkey";
|
||||
}
|
||||
return d;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts this Exchange to JSON.
|
||||
* @function toJSON
|
||||
* @memberof Exchange
|
||||
* @instance
|
||||
* @returns {Object.<string,*>} JSON object
|
||||
*/
|
||||
Exchange.prototype.toJSON = function toJSON() {
|
||||
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
|
||||
};
|
||||
|
||||
return Exchange;
|
||||
})();
|
||||
|
||||
/**
|
||||
* KeyType enum.
|
||||
* @exports KeyType
|
||||
* @enum {number}
|
||||
* @property {number} RSA=0 RSA value
|
||||
* @property {number} Ed25519=1 Ed25519 value
|
||||
* @property {number} Secp256k1=2 Secp256k1 value
|
||||
* @property {number} ECDSA=3 ECDSA value
|
||||
*/
|
||||
export const KeyType = $root.KeyType = (() => {
|
||||
const valuesById = {}, values = Object.create(valuesById);
|
||||
values[valuesById[0] = "RSA"] = 0;
|
||||
values[valuesById[1] = "Ed25519"] = 1;
|
||||
values[valuesById[2] = "Secp256k1"] = 2;
|
||||
values[valuesById[3] = "ECDSA"] = 3;
|
||||
return values;
|
||||
})();
|
||||
|
||||
export const PublicKey = $root.PublicKey = (() => {
|
||||
|
||||
/**
|
||||
* Properties of a PublicKey.
|
||||
* @exports IPublicKey
|
||||
* @interface IPublicKey
|
||||
* @property {KeyType|null} [Type] PublicKey Type
|
||||
* @property {Uint8Array|null} [Data] PublicKey Data
|
||||
*/
|
||||
|
||||
/**
|
||||
* Constructs a new PublicKey.
|
||||
* @exports PublicKey
|
||||
* @classdesc Represents a PublicKey.
|
||||
* @implements IPublicKey
|
||||
* @constructor
|
||||
* @param {IPublicKey=} [p] Properties to set
|
||||
*/
|
||||
function PublicKey(p) {
|
||||
if (p)
|
||||
for (var ks = Object.keys(p), i = 0; i < ks.length; ++i)
|
||||
if (p[ks[i]] != null)
|
||||
this[ks[i]] = p[ks[i]];
|
||||
}
|
||||
|
||||
/**
|
||||
* PublicKey Type.
|
||||
* @member {KeyType} Type
|
||||
* @memberof PublicKey
|
||||
* @instance
|
||||
*/
|
||||
PublicKey.prototype.Type = 0;
|
||||
|
||||
/**
|
||||
* PublicKey Data.
|
||||
* @member {Uint8Array} Data
|
||||
* @memberof PublicKey
|
||||
* @instance
|
||||
*/
|
||||
PublicKey.prototype.Data = $util.newBuffer([]);
|
||||
|
||||
/**
|
||||
* Encodes the specified PublicKey message. Does not implicitly {@link PublicKey.verify|verify} messages.
|
||||
* @function encode
|
||||
* @memberof PublicKey
|
||||
* @static
|
||||
* @param {IPublicKey} m PublicKey message or plain object to encode
|
||||
* @param {$protobuf.Writer} [w] Writer to encode to
|
||||
* @returns {$protobuf.Writer} Writer
|
||||
*/
|
||||
PublicKey.encode = function encode(m, w) {
|
||||
if (!w)
|
||||
w = $Writer.create();
|
||||
if (m.Type != null && Object.hasOwnProperty.call(m, "Type"))
|
||||
w.uint32(8).int32(m.Type);
|
||||
if (m.Data != null && Object.hasOwnProperty.call(m, "Data"))
|
||||
w.uint32(18).bytes(m.Data);
|
||||
return w;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decodes a PublicKey message from the specified reader or buffer.
|
||||
* @function decode
|
||||
* @memberof PublicKey
|
||||
* @static
|
||||
* @param {$protobuf.Reader|Uint8Array} r Reader or buffer to decode from
|
||||
* @param {number} [l] Message length if known beforehand
|
||||
* @returns {PublicKey} PublicKey
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
PublicKey.decode = function decode(r, l) {
|
||||
if (!(r instanceof $Reader))
|
||||
r = $Reader.create(r);
|
||||
var c = l === undefined ? r.len : r.pos + l, m = new $root.PublicKey();
|
||||
while (r.pos < c) {
|
||||
var t = r.uint32();
|
||||
switch (t >>> 3) {
|
||||
case 1:
|
||||
m.Type = r.int32();
|
||||
break;
|
||||
case 2:
|
||||
m.Data = r.bytes();
|
||||
break;
|
||||
default:
|
||||
r.skipType(t & 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a PublicKey message from a plain object. Also converts values to their respective internal types.
|
||||
* @function fromObject
|
||||
* @memberof PublicKey
|
||||
* @static
|
||||
* @param {Object.<string,*>} d Plain object
|
||||
* @returns {PublicKey} PublicKey
|
||||
*/
|
||||
PublicKey.fromObject = function fromObject(d) {
|
||||
if (d instanceof $root.PublicKey)
|
||||
return d;
|
||||
var m = new $root.PublicKey();
|
||||
switch (d.Type) {
|
||||
case "RSA":
|
||||
case 0:
|
||||
m.Type = 0;
|
||||
break;
|
||||
case "Ed25519":
|
||||
case 1:
|
||||
m.Type = 1;
|
||||
break;
|
||||
case "Secp256k1":
|
||||
case 2:
|
||||
m.Type = 2;
|
||||
break;
|
||||
case "ECDSA":
|
||||
case 3:
|
||||
m.Type = 3;
|
||||
break;
|
||||
}
|
||||
if (d.Data != null) {
|
||||
if (typeof d.Data === "string")
|
||||
$util.base64.decode(d.Data, m.Data = $util.newBuffer($util.base64.length(d.Data)), 0);
|
||||
else if (d.Data.length)
|
||||
m.Data = d.Data;
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a plain object from a PublicKey message. Also converts values to other types if specified.
|
||||
* @function toObject
|
||||
* @memberof PublicKey
|
||||
* @static
|
||||
* @param {PublicKey} m PublicKey
|
||||
* @param {$protobuf.IConversionOptions} [o] Conversion options
|
||||
* @returns {Object.<string,*>} Plain object
|
||||
*/
|
||||
PublicKey.toObject = function toObject(m, o) {
|
||||
if (!o)
|
||||
o = {};
|
||||
var d = {};
|
||||
if (o.defaults) {
|
||||
d.Type = o.enums === String ? "RSA" : 0;
|
||||
if (o.bytes === String)
|
||||
d.Data = "";
|
||||
else {
|
||||
d.Data = [];
|
||||
if (o.bytes !== Array)
|
||||
d.Data = $util.newBuffer(d.Data);
|
||||
}
|
||||
}
|
||||
if (m.Type != null && m.hasOwnProperty("Type")) {
|
||||
d.Type = o.enums === String ? $root.KeyType[m.Type] : m.Type;
|
||||
}
|
||||
if (m.Data != null && m.hasOwnProperty("Data")) {
|
||||
d.Data = o.bytes === String ? $util.base64.encode(m.Data, 0, m.Data.length) : o.bytes === Array ? Array.prototype.slice.call(m.Data) : m.Data;
|
||||
}
|
||||
return d;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts this PublicKey to JSON.
|
||||
* @function toJSON
|
||||
* @memberof PublicKey
|
||||
* @instance
|
||||
* @returns {Object.<string,*>} JSON object
|
||||
*/
|
||||
PublicKey.prototype.toJSON = function toJSON() {
|
||||
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
|
||||
};
|
||||
|
||||
return PublicKey;
|
||||
})();
|
||||
|
||||
export { $root as default };
|
@@ -1,68 +0,0 @@
|
||||
/* eslint-disable import/export */
|
||||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
|
||||
import { encodeMessage, decodeMessage, message, bytes, enumeration } from 'protons-runtime'
|
||||
import type { Codec } from 'protons-runtime'
|
||||
|
||||
export interface Exchange {
|
||||
id?: Uint8Array
|
||||
pubkey?: PublicKey
|
||||
}
|
||||
|
||||
export namespace Exchange {
|
||||
export const codec = (): Codec<Exchange> => {
|
||||
return message<Exchange>({
|
||||
1: { name: 'id', codec: bytes, optional: true },
|
||||
2: { name: 'pubkey', codec: PublicKey.codec(), optional: true }
|
||||
})
|
||||
}
|
||||
|
||||
export const encode = (obj: Exchange): Uint8Array => {
|
||||
return encodeMessage(obj, Exchange.codec())
|
||||
}
|
||||
|
||||
export const decode = (buf: Uint8Array): Exchange => {
|
||||
return decodeMessage(buf, Exchange.codec())
|
||||
}
|
||||
}
|
||||
|
||||
export enum KeyType {
|
||||
RSA = 'RSA',
|
||||
Ed25519 = 'Ed25519',
|
||||
Secp256k1 = 'Secp256k1',
|
||||
ECDSA = 'ECDSA'
|
||||
}
|
||||
|
||||
enum __KeyTypeValues {
|
||||
RSA = 0,
|
||||
Ed25519 = 1,
|
||||
Secp256k1 = 2,
|
||||
ECDSA = 3
|
||||
}
|
||||
|
||||
export namespace KeyType {
|
||||
export const codec = () => {
|
||||
return enumeration<typeof KeyType>(__KeyTypeValues)
|
||||
}
|
||||
}
|
||||
export interface PublicKey {
|
||||
Type: KeyType
|
||||
Data: Uint8Array
|
||||
}
|
||||
|
||||
export namespace PublicKey {
|
||||
export const codec = (): Codec<PublicKey> => {
|
||||
return message<PublicKey>({
|
||||
1: { name: 'Type', codec: KeyType.codec() },
|
||||
2: { name: 'Data', codec: bytes }
|
||||
})
|
||||
}
|
||||
|
||||
export const encode = (obj: PublicKey): Uint8Array => {
|
||||
return encodeMessage(obj, PublicKey.codec())
|
||||
}
|
||||
|
||||
export const decode = (buf: Uint8Array): PublicKey => {
|
||||
return decodeMessage(buf, PublicKey.codec())
|
||||
}
|
||||
}
|
@@ -9,6 +9,7 @@ import errCode from 'err-code'
|
||||
import { codes } from '../errors.js'
|
||||
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
import 'node-forge/lib/sha512.js'
|
||||
import { generateKeyPair, importKey, unmarshalPrivateKey } from '@libp2p/crypto/keys'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
@@ -170,7 +171,7 @@ export class KeyChain {
|
||||
/**
|
||||
* Generates the options for a keychain. A random salt is produced.
|
||||
*
|
||||
* @returns {object}
|
||||
* @returns {Object}
|
||||
*/
|
||||
static generateOptions (): KeyChainInit {
|
||||
const options = Object.assign({}, defaultOptions)
|
||||
@@ -183,7 +184,7 @@ export class KeyChain {
|
||||
* Gets an object that can encrypt/decrypt protected data.
|
||||
* The default options for a keychain.
|
||||
*
|
||||
* @returns {object}
|
||||
* @returns {Object}
|
||||
*/
|
||||
static get options () {
|
||||
return defaultOptions
|
||||
|
158
src/libp2p.ts
158
src/libp2p.ts
@@ -1,7 +1,5 @@
|
||||
import { logger } from '@libp2p/logger'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
import { EventEmitter, CustomEvent } from '@libp2p/interfaces/events'
|
||||
import { Startable, isStartable } from '@libp2p/interfaces/startable'
|
||||
import { AbortOptions, EventEmitter, Startable, CustomEvent, isStartable } from '@libp2p/interfaces'
|
||||
import type { Multiaddr } from '@multiformats/multiaddr'
|
||||
import { MemoryDatastore } from 'datastore-core/memory'
|
||||
import { DefaultPeerRouting } from './peer-routing.js'
|
||||
@@ -13,6 +11,7 @@ import { DefaultConnectionManager } from './connection-manager/index.js'
|
||||
import { AutoDialler } from './connection-manager/auto-dialler.js'
|
||||
import { Circuit } from './circuit/transport.js'
|
||||
import { Relay } from './circuit/index.js'
|
||||
import { DefaultDialer } from './dialer/index.js'
|
||||
import { KeyChain } from './keychain/index.js'
|
||||
import { DefaultMetrics } from './metrics/index.js'
|
||||
import { DefaultTransportManager } from './transport-manager.js'
|
||||
@@ -26,15 +25,14 @@ import { PeerRecordUpdater } from './peer-record-updater.js'
|
||||
import { DHTPeerRouting } from './dht/dht-peer-routing.js'
|
||||
import { PersistentPeerStore } from '@libp2p/peer-store'
|
||||
import { DHTContentRouting } from './dht/dht-content-routing.js'
|
||||
import { AutoDialer } from './connection-manager/dialer/auto-dialer.js'
|
||||
import { AutoDialer } from './dialer/auto-dialer.js'
|
||||
import { Initializable, Components, isInitializable } from '@libp2p/interfaces/components'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import type { PeerRouting } from '@libp2p/interfaces/peer-routing'
|
||||
import type { ContentRouting } from '@libp2p/interfaces/content-routing'
|
||||
import type { PubSub } from '@libp2p/interfaces/pubsub'
|
||||
import type { Registrar, StreamHandler } from '@libp2p/interfaces/registrar'
|
||||
import type { ConnectionManager } from '@libp2p/interfaces/connection-manager'
|
||||
import type { ConnectionManager, Registrar, StreamHandler } from '@libp2p/interfaces/registrar'
|
||||
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
||||
import type { Libp2p, Libp2pEvents, Libp2pInit, Libp2pOptions } from './index.js'
|
||||
import { validateConfig } from './config.js'
|
||||
@@ -46,16 +44,13 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
import errCode from 'err-code'
|
||||
import { unmarshalPublicKey } from '@libp2p/crypto/keys'
|
||||
import type { Metrics } from '@libp2p/interfaces/metrics'
|
||||
import { DummyDHT } from './dht/dummy-dht.js'
|
||||
import { DummyPubSub } from './pubsub/dummy-pubsub.js'
|
||||
import { PeerSet } from '@libp2p/peer-collections'
|
||||
|
||||
const log = logger('libp2p')
|
||||
|
||||
export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
public peerId: PeerId
|
||||
public dht: DualDHT
|
||||
public pubsub: PubSub
|
||||
public dht?: DualDHT
|
||||
public pubsub?: PubSub
|
||||
public identifyService?: IdentifyService
|
||||
public fetchService: FetchService
|
||||
public pingService: PingService
|
||||
@@ -75,40 +70,34 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
constructor (init: Libp2pInit) {
|
||||
super()
|
||||
|
||||
this.services = []
|
||||
this.initializables = []
|
||||
this.started = false
|
||||
this.peerId = init.peerId
|
||||
this.components = new Components({
|
||||
peerId: init.peerId,
|
||||
datastore: init.datastore ?? new MemoryDatastore(),
|
||||
connectionGater: {
|
||||
denyDialPeer: async () => await Promise.resolve(false),
|
||||
denyDialMultiaddr: async () => await Promise.resolve(false),
|
||||
denyInboundConnection: async () => await Promise.resolve(false),
|
||||
denyOutboundConnection: async () => await Promise.resolve(false),
|
||||
denyInboundEncryptedConnection: async () => await Promise.resolve(false),
|
||||
denyOutboundEncryptedConnection: async () => await Promise.resolve(false),
|
||||
denyInboundUpgradedConnection: async () => await Promise.resolve(false),
|
||||
denyOutboundUpgradedConnection: async () => await Promise.resolve(false),
|
||||
filterMultiaddrForPeer: async () => await Promise.resolve(true),
|
||||
...init.connectionGater
|
||||
}
|
||||
datastore: init.datastore ?? new MemoryDatastore()
|
||||
})
|
||||
this.components.setPeerStore(new PersistentPeerStore({
|
||||
addressFilter: this.components.getConnectionGater().filterMultiaddrForPeer,
|
||||
...init.peerStore
|
||||
}))
|
||||
|
||||
this.services = [
|
||||
this.components
|
||||
]
|
||||
|
||||
// Create Metrics
|
||||
if (init.metrics.enabled) {
|
||||
this.metrics = this.components.setMetrics(new DefaultMetrics(init.metrics))
|
||||
this.metrics = this.components.setMetrics(this.configureComponent(new DefaultMetrics(init.metrics)))
|
||||
}
|
||||
|
||||
this.peerStore = this.components.getPeerStore()
|
||||
this.components.setConnectionGater(this.configureComponent({
|
||||
denyDialPeer: async () => await Promise.resolve(false),
|
||||
denyDialMultiaddr: async () => await Promise.resolve(false),
|
||||
denyInboundConnection: async () => await Promise.resolve(false),
|
||||
denyOutboundConnection: async () => await Promise.resolve(false),
|
||||
denyInboundEncryptedConnection: async () => await Promise.resolve(false),
|
||||
denyOutboundEncryptedConnection: async () => await Promise.resolve(false),
|
||||
denyInboundUpgradedConnection: async () => await Promise.resolve(false),
|
||||
denyOutboundUpgradedConnection: async () => await Promise.resolve(false),
|
||||
filterMultiaddrForPeer: async () => await Promise.resolve(true),
|
||||
...init.connectionGater
|
||||
}))
|
||||
|
||||
this.peerStore = this.components.setPeerStore(this.configureComponent(new PersistentPeerStore(this.components, init.peerStore)))
|
||||
|
||||
this.peerStore.addEventListener('peer', evt => {
|
||||
const { detail: peerData } = evt
|
||||
@@ -118,30 +107,32 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
|
||||
// Set up connection protector if configured
|
||||
if (init.connectionProtector != null) {
|
||||
this.components.setConnectionProtector(init.connectionProtector)
|
||||
this.components.setConnectionProtector(this.configureComponent(init.connectionProtector))
|
||||
}
|
||||
|
||||
// Set up the Upgrader
|
||||
this.components.setUpgrader(new DefaultUpgrader(this.components, {
|
||||
this.components.setUpgrader(this.configureComponent(new DefaultUpgrader(this.components, {
|
||||
connectionEncryption: (init.connectionEncryption ?? []).map(component => this.configureComponent(component)),
|
||||
muxers: (init.streamMuxers ?? []).map(component => this.configureComponent(component))
|
||||
}))
|
||||
})))
|
||||
|
||||
// Create the Connection Manager
|
||||
this.connectionManager = this.components.setConnectionManager(new DefaultConnectionManager(init.connectionManager))
|
||||
this.connectionManager = this.components.setConnectionManager(this.configureComponent(new DefaultConnectionManager(this.components, init.connectionManager)))
|
||||
|
||||
// Create the Registrar
|
||||
this.registrar = this.components.setRegistrar(new DefaultRegistrar(this.components))
|
||||
this.registrar = this.components.setRegistrar(this.configureComponent(new DefaultRegistrar(this.components)))
|
||||
|
||||
// Setup the transport manager
|
||||
this.components.setTransportManager(new DefaultTransportManager(this.components, init.transportManager))
|
||||
this.components.setTransportManager(this.configureComponent(new DefaultTransportManager(this.components, init.transportManager)))
|
||||
|
||||
// Addresses {listen, announce, noAnnounce}
|
||||
this.components.setAddressManager(new DefaultAddressManager(this.components, init.addresses))
|
||||
this.components.setAddressManager(this.configureComponent(new DefaultAddressManager(this.components, init.addresses)))
|
||||
|
||||
// update our peer record when addresses change
|
||||
this.configureComponent(new PeerRecordUpdater(this.components))
|
||||
|
||||
this.components.setDialer(this.configureComponent(new DefaultDialer(this.components, init.dialer)))
|
||||
|
||||
this.configureComponent(new AutoDialler(this.components, {
|
||||
enabled: init.connectionManager.autoDial,
|
||||
minConnections: init.connectionManager.minConnections,
|
||||
@@ -166,23 +157,22 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
if (init.streamMuxers != null && init.streamMuxers.length > 0) {
|
||||
// Add the identify service since we can multiplex
|
||||
this.identifyService = new IdentifyService(this.components, {
|
||||
...init.identify
|
||||
protocolPrefix: init.protocolPrefix,
|
||||
host: {
|
||||
agentVersion: init.host.agentVersion
|
||||
}
|
||||
})
|
||||
this.configureComponent(this.identifyService)
|
||||
}
|
||||
|
||||
// dht provided components (peerRouting, contentRouting, dht)
|
||||
if (init.dht != null) {
|
||||
this.dht = this.components.setDHT(init.dht)
|
||||
} else {
|
||||
this.dht = new DummyDHT()
|
||||
this.dht = this.components.setDHT(this.configureComponent(init.dht))
|
||||
}
|
||||
|
||||
// Create pubsub if provided
|
||||
if (init.pubsub != null) {
|
||||
this.pubsub = this.components.setPubSub(init.pubsub)
|
||||
} else {
|
||||
this.pubsub = new DummyPubSub()
|
||||
this.pubsub = this.components.setPubSub(this.configureComponent(init.pubsub))
|
||||
}
|
||||
|
||||
// Attach remaining APIs
|
||||
@@ -190,7 +180,7 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
|
||||
const peerRouters: PeerRouting[] = (init.peerRouters ?? []).map(component => this.configureComponent(component))
|
||||
|
||||
if (init.dht != null) {
|
||||
if (this.dht != null) {
|
||||
// add dht to routers
|
||||
peerRouters.push(this.configureComponent(new DHTPeerRouting(this.dht)))
|
||||
|
||||
@@ -207,7 +197,7 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
|
||||
const contentRouters: ContentRouting[] = (init.contentRouters ?? []).map(component => this.configureComponent(component))
|
||||
|
||||
if (init.dht != null) {
|
||||
if (this.dht != null) {
|
||||
// add dht to routers
|
||||
contentRouters.push(this.configureComponent(new DHTContentRouting(this.dht)))
|
||||
}
|
||||
@@ -217,26 +207,25 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
})))
|
||||
|
||||
if (init.relay.enabled) {
|
||||
this.components.getTransportManager().add(this.configureComponent(new Circuit()))
|
||||
this.components.getTransportManager().add(this.configureComponent(new Circuit(init.relay)))
|
||||
|
||||
this.configureComponent(new Relay(this.components, {
|
||||
addressSorter: init.connectionManager.addressSorter,
|
||||
addressSorter: init.dialer.addressSorter,
|
||||
...init.relay
|
||||
}))
|
||||
}
|
||||
|
||||
this.fetchService = this.configureComponent(new FetchService(this.components, {
|
||||
...init.fetch
|
||||
protocolPrefix: init.protocolPrefix
|
||||
}))
|
||||
|
||||
this.pingService = this.configureComponent(new PingService(this.components, {
|
||||
...init.ping
|
||||
protocolPrefix: init.protocolPrefix
|
||||
}))
|
||||
|
||||
const autoDialer = this.configureComponent(new AutoDialer(this.components, {
|
||||
enabled: init.connectionManager.autoDial !== false,
|
||||
minConnections: init.connectionManager.minConnections,
|
||||
dialTimeout: init.connectionManager.dialTimeout ?? 30000
|
||||
minConnections: init.connectionManager.minConnections ?? Infinity
|
||||
}))
|
||||
|
||||
this.addEventListener('peer:discovery', evt => {
|
||||
@@ -384,41 +373,24 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
}
|
||||
|
||||
getConnections (peerId?: PeerId): Connection[] {
|
||||
if (peerId == null) {
|
||||
return this.components.getConnectionManager().getConnectionList()
|
||||
}
|
||||
|
||||
return this.components.getConnectionManager().getConnections(peerId)
|
||||
}
|
||||
|
||||
getPeers (): PeerId[] {
|
||||
const peerSet = new PeerSet()
|
||||
|
||||
for (const conn of this.components.getConnectionManager().getConnections()) {
|
||||
peerSet.add(conn.remotePeer)
|
||||
}
|
||||
|
||||
return Array.from(peerSet)
|
||||
return this.components.getConnectionManager().getConnectionList()
|
||||
.map(conn => conn.remotePeer)
|
||||
}
|
||||
|
||||
async dial (peer: PeerId | Multiaddr, options: AbortOptions = {}): Promise<Connection> {
|
||||
const { id, multiaddrs } = getPeer(peer)
|
||||
|
||||
await this.components.getPeerStore().addressBook.add(id, multiaddrs)
|
||||
|
||||
return await this.components.getConnectionManager().openConnection(id, options)
|
||||
return await this.components.getDialer().dial(peer, options)
|
||||
}
|
||||
|
||||
async dialProtocol (peer: PeerId | Multiaddr, protocols: string | string[], options: AbortOptions = {}) {
|
||||
if (protocols == null) {
|
||||
throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM)
|
||||
}
|
||||
|
||||
protocols = Array.isArray(protocols) ? protocols : [protocols]
|
||||
|
||||
if (protocols.length === 0) {
|
||||
throw errCode(new Error('no protocols were provided to open a stream'), codes.ERR_INVALID_PROTOCOLS_FOR_STREAM)
|
||||
}
|
||||
|
||||
const connection = await this.dial(peer, options)
|
||||
|
||||
return await connection.newStream(protocols, options)
|
||||
return await this.components.getDialer().dialProtocol(peer, protocols, options)
|
||||
}
|
||||
|
||||
getMultiaddrs (): Multiaddr[] {
|
||||
@@ -428,19 +400,21 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
async hangUp (peer: PeerId | Multiaddr | string): Promise<void> {
|
||||
const { id } = getPeer(peer)
|
||||
|
||||
await this.components.getConnectionManager().closeConnections(id)
|
||||
const connections = this.components.getConnectionManager().getConnections(id)
|
||||
|
||||
await Promise.all(
|
||||
connections.map(async connection => {
|
||||
return await connection.close()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the public key for the given peer id
|
||||
*/
|
||||
async getPublicKey (peer: PeerId, options: AbortOptions = {}): Promise<Uint8Array> {
|
||||
async getPublicKey (peer: PeerId, options: AbortOptions = {}) {
|
||||
log('getPublicKey %p', peer)
|
||||
|
||||
if (peer.publicKey != null) {
|
||||
return peer.publicKey
|
||||
}
|
||||
|
||||
const peerInfo = await this.peerStore.get(peer)
|
||||
|
||||
if (peerInfo.pubKey != null) {
|
||||
@@ -463,31 +437,31 @@ export class Libp2pNode extends EventEmitter<Libp2pEvents> implements Libp2p {
|
||||
|
||||
await this.peerStore.keyBook.set(peer, event.value)
|
||||
|
||||
return key.bytes
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
throw errCode(new Error(`Node not responding with its public key: ${peer.toString()}`), codes.ERR_INVALID_RECORD)
|
||||
}
|
||||
|
||||
async fetch (peer: PeerId | Multiaddr | string, key: string, options: AbortOptions = {}): Promise<Uint8Array | null> {
|
||||
async fetch (peer: PeerId | Multiaddr | string, key: string): Promise<Uint8Array | null> {
|
||||
const { id, multiaddrs } = getPeer(peer)
|
||||
|
||||
if (multiaddrs != null) {
|
||||
await this.components.getPeerStore().addressBook.add(id, multiaddrs)
|
||||
}
|
||||
|
||||
return await this.fetchService.fetch(id, key, options)
|
||||
return await this.fetchService.fetch(id, key)
|
||||
}
|
||||
|
||||
async ping (peer: PeerId | Multiaddr | string, options: AbortOptions = {}): Promise<number> {
|
||||
async ping (peer: PeerId | Multiaddr | string): Promise<number> {
|
||||
const { id, multiaddrs } = getPeer(peer)
|
||||
|
||||
if (multiaddrs.length > 0) {
|
||||
await this.components.getPeerStore().addressBook.add(id, multiaddrs)
|
||||
}
|
||||
|
||||
return await this.pingService.ping(id, options)
|
||||
return await this.pingService.ping(id)
|
||||
}
|
||||
|
||||
async handle (protocols: string | string[], handler: StreamHandler): Promise<void> {
|
||||
|
@@ -5,7 +5,7 @@ import { METRICS as defaultOptions } from '../constants.js'
|
||||
import { DefaultStats, StatsInit } from './stats.js'
|
||||
import type { ComponentMetricsUpdate, Metrics, Stats, TrackStreamOptions } from '@libp2p/interfaces/metrics'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import type { Startable } from '@libp2p/interfaces'
|
||||
import type { Duplex } from 'it-stream-types'
|
||||
|
||||
const initialCounters: ['dataReceived', 'dataSent'] = [
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces'
|
||||
import { createMovingAverage } from './moving-average.js'
|
||||
// @ts-expect-error no types
|
||||
import retimer from 'retimer'
|
||||
import type { MovingAverages, Stats, TransferStats } from '@libp2p/interfaces/metrics'
|
||||
import type { MovingAverages, Stats } from '@libp2p/interfaces/metrics'
|
||||
|
||||
export interface StatsEvents {
|
||||
'update': CustomEvent<TransferStats>
|
||||
@@ -16,6 +16,11 @@ export interface StatsInit {
|
||||
computeThrottleTimeout: number
|
||||
}
|
||||
|
||||
export interface TransferStats {
|
||||
dataReceived: BigInt
|
||||
dataSent: BigInt
|
||||
}
|
||||
|
||||
export class DefaultStats extends EventEmitter<StatsEvents> implements Stats {
|
||||
private readonly enabled: boolean
|
||||
public queue: Array<[string, number, number]>
|
||||
|
@@ -7,7 +7,7 @@ import * as pkg from './version.js'
|
||||
import errCode from 'err-code'
|
||||
import { codes } from './errors.js'
|
||||
import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import type { Startable } from '@libp2p/interfaces'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
|
||||
const log = logger('libp2p:nat')
|
||||
@@ -94,14 +94,10 @@ export class NatManager implements Startable {
|
||||
return this.started
|
||||
}
|
||||
|
||||
start () {}
|
||||
|
||||
/**
|
||||
* Attempt to use uPnP to configure port mapping using the current gateway.
|
||||
*
|
||||
* Run after start to ensure the transport manager has all addresses configured.
|
||||
* Starts the NAT manager
|
||||
*/
|
||||
afterStart () {
|
||||
start () {
|
||||
if (isBrowser || !this.enabled || this.started) {
|
||||
return
|
||||
}
|
||||
@@ -109,7 +105,7 @@ export class NatManager implements Startable {
|
||||
this.started = true
|
||||
|
||||
// done async to not slow down startup
|
||||
void this._start().catch((err) => {
|
||||
this._start().catch((err) => {
|
||||
// hole punching errors are non-fatal
|
||||
log.error(err)
|
||||
})
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { RecordEnvelope, PeerRecord } from '@libp2p/peer-record'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import type { Startable } from '@libp2p/interfaces'
|
||||
import { logger } from '@libp2p/logger'
|
||||
import { protocols } from '@multiformats/multiaddr'
|
||||
|
||||
|
@@ -17,11 +17,11 @@ import {
|
||||
clearDelayedInterval
|
||||
// @ts-expect-error module with no types
|
||||
} from 'set-delayed-interval'
|
||||
// @ts-expect-error setMaxListeners is missing from the node 16 types
|
||||
import { setMaxListeners } from 'events'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import type { PeerRouting } from '@libp2p/interfaces/peer-routing'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import type { AbortOptions, Startable } from '@libp2p/interfaces'
|
||||
import type { PeerInfo } from '@libp2p/interfaces/peer-info'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
|
||||
|
@@ -8,11 +8,8 @@ import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
|
||||
import { PROTOCOL_NAME, PING_LENGTH, PROTOCOL_VERSION } from './constants.js'
|
||||
import type { IncomingStreamData } from '@libp2p/interfaces/registrar'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import type { Startable } from '@libp2p/interfaces'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
import type { Duplex } from 'it-stream-types'
|
||||
import { abortableDuplex } from 'abortable-iterator'
|
||||
|
||||
const log = logger('libp2p:ping')
|
||||
|
||||
@@ -21,8 +18,8 @@ export interface PingServiceInit {
|
||||
}
|
||||
|
||||
export class PingService implements Startable {
|
||||
public readonly protocol: string
|
||||
private readonly components: Components
|
||||
private readonly protocol: string
|
||||
private started: boolean
|
||||
|
||||
constructor (components: Components, init: PingServiceInit) {
|
||||
@@ -63,36 +60,24 @@ export class PingService implements Startable {
|
||||
* @param {PeerId|Multiaddr} peer
|
||||
* @returns {Promise<number>}
|
||||
*/
|
||||
async ping (peer: PeerId, options: AbortOptions = {}): Promise<number> {
|
||||
async ping (peer: PeerId): Promise<number> {
|
||||
log('dialing %s to %p', this.protocol, peer)
|
||||
|
||||
const connection = await this.components.getConnectionManager().openConnection(peer, options)
|
||||
const { stream } = await connection.newStream([this.protocol], options)
|
||||
const { stream } = await this.components.getDialer().dialProtocol(peer, this.protocol)
|
||||
const start = Date.now()
|
||||
const data = randomBytes(PING_LENGTH)
|
||||
|
||||
let source: Duplex<Uint8Array> = stream
|
||||
const result = await pipe(
|
||||
[data],
|
||||
stream,
|
||||
async (source) => await first(source)
|
||||
)
|
||||
const end = Date.now()
|
||||
|
||||
// make stream abortable if AbortSignal passed
|
||||
if (options.signal != null) {
|
||||
source = abortableDuplex(stream, options.signal)
|
||||
if (result == null || !uint8ArrayEquals(data, result)) {
|
||||
throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK)
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await pipe(
|
||||
[data],
|
||||
source,
|
||||
async (source) => await first(source)
|
||||
)
|
||||
const end = Date.now()
|
||||
|
||||
if (result == null || !uint8ArrayEquals(data, result)) {
|
||||
throw errCode(new Error('Received wrong ping ack'), codes.ERR_WRONG_PING_ACK)
|
||||
}
|
||||
|
||||
return end - start
|
||||
} finally {
|
||||
stream.close()
|
||||
}
|
||||
return end - start
|
||||
}
|
||||
}
|
||||
|
@@ -17,8 +17,6 @@ import type { ConnectionProtector } from '@libp2p/interfaces/connection'
|
||||
|
||||
const log = logger('libp2p:pnet')
|
||||
|
||||
export { generateKey } from './key-generator.js'
|
||||
|
||||
export interface ProtectorInit {
|
||||
enabled?: boolean
|
||||
psk: Uint8Array
|
||||
|
@@ -8,7 +8,7 @@ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
* @param {Uint8Array} bytes - An object to write the psk into
|
||||
* @returns {void}
|
||||
*/
|
||||
export function generateKey (bytes: Uint8Array) {
|
||||
export function generate (bytes: Uint8Array) {
|
||||
const psk = uint8ArrayToString(randomBytes(KEY_LENGTH), 'base16')
|
||||
const key = uint8ArrayFromString('/key/swarm/psk/1.0.0/\n/base16/\n' + psk)
|
||||
|
||||
|
@@ -1,51 +0,0 @@
|
||||
import { EventEmitter } from '@libp2p/interfaces/events'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import type { PublishResult, PubSub, PubSubEvents, StrictNoSign, StrictSign } from '@libp2p/interfaces/pubsub'
|
||||
import errCode from 'err-code'
|
||||
import { messages, codes } from '../errors.js'
|
||||
|
||||
export class DummyPubSub extends EventEmitter<PubSubEvents> implements PubSub {
|
||||
isStarted (): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
start (): void | Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
stop (): void | Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
get globalSignaturePolicy (): typeof StrictSign | typeof StrictNoSign {
|
||||
throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED)
|
||||
}
|
||||
|
||||
get multicodecs (): string[] {
|
||||
throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED)
|
||||
}
|
||||
|
||||
getPeers (): PeerId[] {
|
||||
throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED)
|
||||
}
|
||||
|
||||
getTopics (): string[] {
|
||||
throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED)
|
||||
}
|
||||
|
||||
subscribe (): void {
|
||||
throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED)
|
||||
}
|
||||
|
||||
unsubscribe (): void {
|
||||
throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED)
|
||||
}
|
||||
|
||||
getSubscribers (): PeerId[] {
|
||||
throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED)
|
||||
}
|
||||
|
||||
async publish (): Promise<PublishResult> {
|
||||
throw errCode(new Error(messages.PUBSUB_DISABLED), codes.ERR_PUBSUB_DISABLED)
|
||||
}
|
||||
}
|
@@ -33,11 +33,11 @@ export class DefaultRegistrar implements Registrar {
|
||||
this.components = components
|
||||
|
||||
this._onDisconnect = this._onDisconnect.bind(this)
|
||||
this._onConnect = this._onConnect.bind(this)
|
||||
this._onProtocolChange = this._onProtocolChange.bind(this)
|
||||
|
||||
this.components.getConnectionManager().addEventListener('peer:disconnect', this._onDisconnect)
|
||||
|
||||
// happens after identify
|
||||
this.components.getConnectionManager().addEventListener('peer:connect', this._onConnect)
|
||||
this.components.getPeerStore().addEventListener('change:protocols', this._onProtocolChange)
|
||||
}
|
||||
|
||||
@@ -159,6 +159,22 @@ export class DefaultRegistrar implements Registrar {
|
||||
})
|
||||
}
|
||||
|
||||
_onConnect (evt: CustomEvent<Connection>) {
|
||||
const connection = evt.detail
|
||||
|
||||
void this.components.getPeerStore().protoBook.get(connection.remotePeer)
|
||||
.then(peerProtocols => {
|
||||
for (const { topology, protocols } of this.topologies.values()) {
|
||||
if (supportsProtocol(peerProtocols, protocols)) {
|
||||
topology.onConnect(connection.remotePeer, connection)
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a new peer support the multicodecs for this topology
|
||||
*/
|
||||
@@ -176,7 +192,7 @@ export class DefaultRegistrar implements Registrar {
|
||||
|
||||
for (const { topology, protocols } of this.topologies.values()) {
|
||||
if (supportsProtocol(added, protocols)) {
|
||||
const connection = this.components.getConnectionManager().getConnections(peerId)[0]
|
||||
const connection = this.components.getConnectionManager().getConnection(peerId)
|
||||
|
||||
if (connection == null) {
|
||||
continue
|
||||
|
@@ -5,9 +5,7 @@ import errCode from 'err-code'
|
||||
import type { Listener, Transport, TransportManager, TransportManagerEvents } from '@libp2p/interfaces/transport'
|
||||
import type { Multiaddr } from '@multiformats/multiaddr'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
||||
import type { Startable } from '@libp2p/interfaces/startable'
|
||||
import { AbortOptions, CustomEvent, EventEmitter, Startable } from '@libp2p/interfaces'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
import { trackedMap } from '@libp2p/tracked-map'
|
||||
|
||||
|
@@ -6,7 +6,7 @@ import { pipe } from 'it-pipe'
|
||||
import mutableProxy from 'mutable-proxy'
|
||||
import { codes } from './errors.js'
|
||||
import { createConnection } from '@libp2p/connection'
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces/events'
|
||||
import { CustomEvent, EventEmitter } from '@libp2p/interfaces'
|
||||
import { peerIdFromString } from '@libp2p/peer-id'
|
||||
import type { Connection, ProtocolStream, Stream } from '@libp2p/interfaces/connection'
|
||||
import type { ConnectionEncrypter, SecuredConnection } from '@libp2p/interfaces/connection-encrypter'
|
||||
@@ -15,7 +15,6 @@ import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import type { MultiaddrConnection, Upgrader, UpgraderEvents } from '@libp2p/interfaces/transport'
|
||||
import type { Duplex } from 'it-stream-types'
|
||||
import type { Components } from '@libp2p/interfaces/components'
|
||||
import type { AbortOptions } from '@libp2p/interfaces'
|
||||
|
||||
const log = logger('libp2p:upgrader')
|
||||
|
||||
@@ -267,7 +266,7 @@ export class DefaultUpgrader extends EventEmitter<UpgraderEvents> implements Upg
|
||||
} = opts
|
||||
|
||||
let muxer: StreamMuxer | undefined
|
||||
let newStream: ((multicodecs: string[], options?: AbortOptions) => Promise<ProtocolStream>) | undefined
|
||||
let newStream: ((multicodecs: string[]) => Promise<ProtocolStream>) | undefined
|
||||
let connection: Connection // eslint-disable-line prefer-const
|
||||
|
||||
if (muxerFactory != null) {
|
||||
@@ -309,7 +308,7 @@ export class DefaultUpgrader extends EventEmitter<UpgraderEvents> implements Upg
|
||||
}
|
||||
})
|
||||
|
||||
newStream = async (protocols: string[], options: AbortOptions = {}): Promise<ProtocolStream> => {
|
||||
newStream = async (protocols: string[]): Promise<ProtocolStream> => {
|
||||
if (muxer == null) {
|
||||
throw errCode(new Error('Stream is not multiplexed'), codes.ERR_MUXER_UNAVAILABLE)
|
||||
}
|
||||
@@ -320,7 +319,7 @@ export class DefaultUpgrader extends EventEmitter<UpgraderEvents> implements Upg
|
||||
const metrics = this.components.getMetrics()
|
||||
|
||||
try {
|
||||
let { stream, protocol } = await mss.select(protocols, options)
|
||||
let { stream, protocol } = await mss.select(protocols)
|
||||
|
||||
if (metrics != null) {
|
||||
stream = metrics.trackStream({ stream, remotePeer, protocol })
|
||||
@@ -329,11 +328,6 @@ export class DefaultUpgrader extends EventEmitter<UpgraderEvents> implements Upg
|
||||
return { stream: { ...muxedStream, ...stream }, protocol }
|
||||
} catch (err: any) {
|
||||
log.error('could not create new stream', err)
|
||||
|
||||
if (err.code != null) {
|
||||
throw err
|
||||
}
|
||||
|
||||
throw errCode(err, codes.ERR_UNSUPPORTED_PROTOCOL)
|
||||
}
|
||||
}
|
||||
@@ -388,11 +382,9 @@ export class DefaultUpgrader extends EventEmitter<UpgraderEvents> implements Upg
|
||||
getStreams: () => muxer != null ? muxer.streams : errConnectionNotMultiplexed(),
|
||||
close: async () => {
|
||||
await maConn.close()
|
||||
// Ensure remaining streams are closed
|
||||
// Ensure remaining streams are aborted
|
||||
if (muxer != null) {
|
||||
await Promise.all(muxer.streams.map(async stream => {
|
||||
await stream.close()
|
||||
}))
|
||||
muxer.streams.map(stream => stream.abort())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import { Multiaddr, protocols } from '@multiformats/multiaddr'
|
||||
import { AddressFilter, DefaultAddressManager } from '../../src/address-manager/index.js'
|
||||
import { createNode } from '../utils/creators/peer.js'
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import sinon from 'sinon'
|
||||
import { Multiaddr, protocols } from '@multiformats/multiaddr'
|
||||
import { isLoopback } from '@libp2p/utils/multiaddr/is-loopback'
|
||||
|
278
test/circuit/v2/hop.spec.ts
Normal file
278
test/circuit/v2/hop.spec.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
import { protocolIDv2Hop } from './../../../src/circuit/multicodec.js'
|
||||
import { mockDuplex, mockConnection, mockMultiaddrConnection, mockStream } from '@libp2p/interface-compliance-tests/mocks'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import * as peerUtils from '../../utils/creators/peer.js'
|
||||
import { handleHopProtocol } from '../../../src/circuit/v2/hop.js'
|
||||
import { StreamHandlerV2 } from '../../../src/circuit/v2/stream-handler.js'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import { Status, HopMessage } from '../../../src/circuit/v2/pb/index.js'
|
||||
import { ReservationStore } from '../../../src/circuit/v2/reservation-store.js'
|
||||
import sinon from 'sinon'
|
||||
import { Circuit } from '../../../src/circuit/transport.js'
|
||||
import { Multiaddr } from '@multiformats/multiaddr'
|
||||
import { pair } from 'it-pair'
|
||||
|
||||
/* eslint-env mocha */
|
||||
|
||||
describe('Circuit v2 - hop protocol', function () {
|
||||
it('error on unknow message type', async function () {
|
||||
const streamHandler = new StreamHandlerV2({ stream: mockStream(pair<Uint8Array>()) })
|
||||
await handleHopProtocol({
|
||||
connection: mockConnection(mockMultiaddrConnection(mockDuplex(), await peerUtils.createPeerId())),
|
||||
streamHandler,
|
||||
request: {
|
||||
// @ts-expect-error
|
||||
type: 'not_existing'
|
||||
}
|
||||
})
|
||||
const msg = HopMessage.decode(await streamHandler.read())
|
||||
expect(msg.type).to.be.equal(HopMessage.Type.STATUS)
|
||||
expect(msg.status).to.be.equal(Status.MALFORMED_MESSAGE)
|
||||
})
|
||||
|
||||
describe('reserve', function () {
|
||||
let relayPeer: PeerId, conn: Connection, streamHandler: StreamHandlerV2, reservationStore: ReservationStore
|
||||
|
||||
beforeEach(async () => {
|
||||
[, relayPeer] = await peerUtils.createPeerIds(2)
|
||||
conn = await mockConnection(mockMultiaddrConnection(mockDuplex(), relayPeer))
|
||||
streamHandler = new StreamHandlerV2({ stream: mockStream(pair<Uint8Array>()) })
|
||||
reservationStore = new ReservationStore()
|
||||
})
|
||||
|
||||
this.afterEach(async function () {
|
||||
streamHandler.close()
|
||||
await conn.close()
|
||||
})
|
||||
|
||||
it('should reserve slot', async function () {
|
||||
const expire: number = 123
|
||||
const reserveStub = sinon.stub(reservationStore, 'reserve')
|
||||
reserveStub.resolves({ status: Status.OK, expire })
|
||||
await handleHopProtocol({
|
||||
request: {
|
||||
type: HopMessage.Type.RESERVE
|
||||
},
|
||||
connection: conn,
|
||||
streamHandler,
|
||||
relayPeer,
|
||||
circuit: sinon.stub() as any,
|
||||
relayAddrs: [new Multiaddr('/ip4/127.0.0.1/udp/1234')],
|
||||
reservationStore
|
||||
})
|
||||
expect(reserveStub.calledOnceWith(conn.remotePeer, conn.remoteAddr)).to.be.true()
|
||||
const response = HopMessage.decode(await streamHandler.read())
|
||||
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
|
||||
expect(response.limit).to.be.null()
|
||||
expect(response.status).to.be.equal(Status.OK)
|
||||
expect(response.reservation?.expire).to.be.equal(expire)
|
||||
expect(response.reservation?.voucher).to.not.be.null()
|
||||
expect(response.reservation?.addrs?.length).to.be.greaterThan(0)
|
||||
})
|
||||
|
||||
it('should fail to reserve slot - acl denied', async function () {
|
||||
const reserveStub = sinon.stub(reservationStore, 'reserve')
|
||||
await handleHopProtocol({
|
||||
request: {
|
||||
type: HopMessage.Type.RESERVE
|
||||
},
|
||||
connection: conn,
|
||||
streamHandler,
|
||||
relayPeer,
|
||||
circuit: sinon.stub() as any,
|
||||
relayAddrs: [new Multiaddr('/ip4/127.0.0.1/udp/1234')],
|
||||
reservationStore,
|
||||
acl: { allowReserve: async function () { return false }, allowConnect: sinon.stub() as any }
|
||||
})
|
||||
expect(reserveStub.notCalled).to.be.true()
|
||||
const response = HopMessage.decode(await streamHandler.read())
|
||||
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
|
||||
expect(response.limit).to.be.null()
|
||||
expect(response.status).to.be.equal(Status.PERMISSION_DENIED)
|
||||
})
|
||||
|
||||
it('should fail to reserve slot - resource exceeded', async function () {
|
||||
const reserveStub = sinon.stub(reservationStore, 'reserve')
|
||||
reserveStub.resolves({ status: Status.RESERVATION_REFUSED })
|
||||
await handleHopProtocol({
|
||||
request: {
|
||||
type: HopMessage.Type.RESERVE
|
||||
},
|
||||
connection: conn,
|
||||
streamHandler,
|
||||
relayPeer,
|
||||
circuit: sinon.stub() as any,
|
||||
relayAddrs: [new Multiaddr('/ip4/127.0.0.1/udp/1234')],
|
||||
reservationStore
|
||||
})
|
||||
expect(reserveStub.calledOnce).to.be.true()
|
||||
const response = HopMessage.decode(await streamHandler.read())
|
||||
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
|
||||
expect(response.limit).to.be.null()
|
||||
expect(response.status).to.be.equal(Status.RESERVATION_REFUSED)
|
||||
})
|
||||
|
||||
it('should fail to reserve slot - failed to write response', async function () {
|
||||
const reserveStub = sinon.stub(reservationStore, 'reserve')
|
||||
const removeReservationStub = sinon.stub(reservationStore, 'removeReservation')
|
||||
reserveStub.resolves({ status: Status.OK, expire: 123 })
|
||||
removeReservationStub.resolves()
|
||||
const backup = streamHandler.write
|
||||
streamHandler.write = function () { throw new Error('connection reset') }
|
||||
await handleHopProtocol({
|
||||
request: {
|
||||
type: HopMessage.Type.RESERVE
|
||||
},
|
||||
connection: conn,
|
||||
streamHandler,
|
||||
relayPeer,
|
||||
circuit: sinon.stub() as any,
|
||||
relayAddrs: [new Multiaddr('/ip4/127.0.0.1/udp/1234')],
|
||||
reservationStore
|
||||
})
|
||||
expect(reserveStub.calledOnce).to.be.true()
|
||||
expect(removeReservationStub.calledOnce).to.be.true()
|
||||
streamHandler.write = backup
|
||||
})
|
||||
})
|
||||
|
||||
describe('connect', function () {
|
||||
let relayPeer: PeerId, dstPeer: PeerId, conn: Connection, streamHandler: StreamHandlerV2, reservationStore: ReservationStore,
|
||||
circuit: Circuit
|
||||
|
||||
beforeEach(async () => {
|
||||
[, relayPeer, dstPeer] = await peerUtils.createPeerIds(3)
|
||||
conn = await mockConnection(mockMultiaddrConnection(mockDuplex(), relayPeer))
|
||||
streamHandler = new StreamHandlerV2({ stream: mockStream(pair<Uint8Array>()) })
|
||||
reservationStore = new ReservationStore()
|
||||
circuit = new Circuit({})
|
||||
})
|
||||
|
||||
this.afterEach(async function () {
|
||||
streamHandler.close()
|
||||
await conn.close()
|
||||
})
|
||||
|
||||
it('should succeed to connect', async function () {
|
||||
const hasReservationStub = sinon.stub(reservationStore, 'hasReservation')
|
||||
hasReservationStub.resolves(true)
|
||||
const dstConn = await mockConnection(
|
||||
mockMultiaddrConnection(pair<Uint8Array>(), dstPeer)
|
||||
)
|
||||
const streamStub = sinon.stub(dstConn, 'newStream')
|
||||
streamStub.resolves({ protocol: protocolIDv2Hop, stream: mockStream(pair<Uint8Array>()) })
|
||||
const stub = sinon.stub(circuit, 'getPeerConnection')
|
||||
stub.returns(dstConn)
|
||||
await handleHopProtocol({
|
||||
connection: conn,
|
||||
streamHandler,
|
||||
request: {
|
||||
type: HopMessage.Type.CONNECT,
|
||||
peer: {
|
||||
id: dstPeer.toBytes(),
|
||||
addrs: []
|
||||
}
|
||||
},
|
||||
relayPeer: relayPeer,
|
||||
relayAddrs: [],
|
||||
reservationStore,
|
||||
circuit
|
||||
})
|
||||
const response = HopMessage.decode(await streamHandler.read())
|
||||
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
|
||||
expect(response.status).to.be.equal(Status.OK)
|
||||
})
|
||||
|
||||
it('should fail to connect - invalid request', async function () {
|
||||
await handleHopProtocol({
|
||||
connection: conn,
|
||||
streamHandler,
|
||||
request: {
|
||||
type: HopMessage.Type.CONNECT,
|
||||
// @ts-expect-error
|
||||
peer: {
|
||||
}
|
||||
},
|
||||
reservationStore,
|
||||
circuit
|
||||
})
|
||||
const response = HopMessage.decode(await streamHandler.read())
|
||||
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
|
||||
expect(response.status).to.be.equal(Status.MALFORMED_MESSAGE)
|
||||
})
|
||||
|
||||
it('should failed to connect - acl denied', async function () {
|
||||
const acl = {
|
||||
allowConnect: function () { return Status.PERMISSION_DENIED }
|
||||
}
|
||||
await handleHopProtocol({
|
||||
connection: conn,
|
||||
streamHandler,
|
||||
request: {
|
||||
type: HopMessage.Type.CONNECT,
|
||||
peer: {
|
||||
id: dstPeer.toBytes(),
|
||||
addrs: []
|
||||
}
|
||||
},
|
||||
reservationStore,
|
||||
circuit,
|
||||
// @ts-expect-error
|
||||
acl
|
||||
})
|
||||
const response = HopMessage.decode(await streamHandler.read())
|
||||
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
|
||||
expect(response.status).to.be.equal(Status.PERMISSION_DENIED)
|
||||
})
|
||||
|
||||
it('should fail to connect - no reservation', async function () {
|
||||
const hasReservationStub = sinon.stub(reservationStore, 'hasReservation')
|
||||
hasReservationStub.resolves(false)
|
||||
await handleHopProtocol({
|
||||
connection: conn,
|
||||
streamHandler,
|
||||
request: {
|
||||
type: HopMessage.Type.CONNECT,
|
||||
peer: {
|
||||
id: dstPeer.toBytes(),
|
||||
addrs: []
|
||||
}
|
||||
},
|
||||
relayPeer: relayPeer,
|
||||
relayAddrs: [],
|
||||
reservationStore,
|
||||
circuit
|
||||
})
|
||||
const response = HopMessage.decode(await streamHandler.read())
|
||||
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
|
||||
expect(response.status).to.be.equal(Status.NO_RESERVATION)
|
||||
})
|
||||
|
||||
it('should fail to connect - no connection', async function () {
|
||||
const hasReservationStub = sinon.stub(reservationStore, 'hasReservation')
|
||||
hasReservationStub.resolves(true)
|
||||
const stub = sinon.stub(circuit, 'getPeerConnection')
|
||||
stub.returns(undefined)
|
||||
await handleHopProtocol({
|
||||
connection: conn,
|
||||
streamHandler,
|
||||
request: {
|
||||
type: HopMessage.Type.CONNECT,
|
||||
peer: {
|
||||
id: dstPeer.toBytes(),
|
||||
addrs: []
|
||||
}
|
||||
},
|
||||
relayPeer: relayPeer,
|
||||
relayAddrs: [],
|
||||
reservationStore,
|
||||
circuit
|
||||
})
|
||||
const response = HopMessage.decode(await streamHandler.read())
|
||||
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
|
||||
expect(response.status).to.be.equal(Status.NO_RESERVATION)
|
||||
expect(stub.calledOnce).to.be.true()
|
||||
})
|
||||
})
|
||||
})
|
45
test/circuit/v2/reservation-store.spec.ts
Normal file
45
test/circuit/v2/reservation-store.spec.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Multiaddr } from '@multiformats/multiaddr'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import { Status } from '../../../src/circuit/v2/pb/index.js'
|
||||
import { ReservationStore } from '../../../src/circuit/v2/reservation-store.js'
|
||||
import { createPeerId } from '../../utils/creators/peer.js'
|
||||
|
||||
/* eslint-env mocha */
|
||||
|
||||
describe('Circuit v2 - reservation store', function () {
|
||||
it('should add reservation', async function () {
|
||||
const store = new ReservationStore(2)
|
||||
const peer = await createPeerId()
|
||||
const result = await store.reserve(peer, new Multiaddr())
|
||||
expect(result.status).to.equal(Status.OK)
|
||||
expect(result.expire).to.not.be.undefined()
|
||||
expect(await store.hasReservation(peer)).to.be.true()
|
||||
})
|
||||
it('should add reservation if peer already has reservation', async function () {
|
||||
const store = new ReservationStore(1)
|
||||
const peer = await createPeerId()
|
||||
await store.reserve(peer, new Multiaddr())
|
||||
const result = await store.reserve(peer, new Multiaddr())
|
||||
expect(result.status).to.equal(Status.OK)
|
||||
expect(result.expire).to.not.be.undefined()
|
||||
expect(await store.hasReservation(peer)).to.be.true()
|
||||
})
|
||||
|
||||
it('should fail to add reservation on exceeding limit', async function () {
|
||||
const store = new ReservationStore(0)
|
||||
const peer = await createPeerId()
|
||||
const result = await store.reserve(peer, new Multiaddr())
|
||||
expect(result.status).to.equal(Status.RESERVATION_REFUSED)
|
||||
})
|
||||
|
||||
it('should remove reservation', async function () {
|
||||
const store = new ReservationStore(10)
|
||||
const peer = await createPeerId()
|
||||
const result = await store.reserve(peer, new Multiaddr())
|
||||
expect(result.status).to.equal(Status.OK)
|
||||
expect(await store.hasReservation(peer)).to.be.true()
|
||||
await store.removeReservation(peer)
|
||||
expect(await store.hasReservation(peer)).to.be.false()
|
||||
await store.removeReservation(peer)
|
||||
})
|
||||
})
|
69
test/circuit/v2/stop.spec.ts
Normal file
69
test/circuit/v2/stop.spec.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { protocolIDv2Stop } from './../../../src/circuit/multicodec.js'
|
||||
import { pair } from 'it-pair'
|
||||
import { StreamHandlerV2 } from './../../../src/circuit/v2/stream-handler.js'
|
||||
import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import { createPeerIds } from '../../utils/creators/peer.js'
|
||||
import { mockConnection, mockMultiaddrConnection, mockStream } from '@libp2p/interface-compliance-tests/mocks'
|
||||
import { handleStop, stop } from '../../../src/circuit/v2/stop.js'
|
||||
import { Status, StopMessage } from '../../../src/circuit/v2/pb/index.js'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import sinon from 'sinon'
|
||||
|
||||
/* eslint-env mocha */
|
||||
|
||||
describe('Circuit v2 - stop protocol', function () {
|
||||
let srcPeer: PeerId, relayPeer: PeerId, conn: Connection, streamHandler: StreamHandlerV2
|
||||
|
||||
beforeEach(async () => {
|
||||
[srcPeer, relayPeer] = await createPeerIds(2)
|
||||
conn = await mockConnection(mockMultiaddrConnection(pair<Uint8Array>(), relayPeer))
|
||||
streamHandler = new StreamHandlerV2({ stream: mockStream(pair<Uint8Array>()) })
|
||||
})
|
||||
|
||||
this.afterEach(async function () {
|
||||
streamHandler.close()
|
||||
await conn.close()
|
||||
})
|
||||
|
||||
it('handle stop - success', async function () {
|
||||
await handleStop({ connection: conn, request: { type: StopMessage.Type.CONNECT, peer: { id: srcPeer.toBytes(), addrs: [] } }, streamHandler })
|
||||
const response = StopMessage.decode(await streamHandler.read())
|
||||
expect(response.status).to.be.equal(Status.OK)
|
||||
})
|
||||
|
||||
it('handle stop error - invalid request - wrong type', async function () {
|
||||
await handleStop({ connection: conn, request: { type: StopMessage.Type.STATUS, peer: { id: srcPeer.toBytes(), addrs: [] } }, streamHandler })
|
||||
const response = StopMessage.decode(await streamHandler.read())
|
||||
expect(response.status).to.be.equal(Status.UNEXPECTED_MESSAGE)
|
||||
})
|
||||
|
||||
it('handle stop error - invalid request - missing peer', async function () {
|
||||
await handleStop({ connection: conn, request: { type: StopMessage.Type.CONNECT }, streamHandler })
|
||||
const response = StopMessage.decode(await streamHandler.read())
|
||||
expect(response.status).to.be.equal(Status.MALFORMED_MESSAGE)
|
||||
})
|
||||
|
||||
it('handle stop error - invalid request - invalid peer addr', async function () {
|
||||
await handleStop({ connection: conn, request: { type: StopMessage.Type.CONNECT, peer: { id: srcPeer.toBytes(), addrs: [new Uint8Array(32)] } }, streamHandler })
|
||||
const response = StopMessage.decode(await streamHandler.read())
|
||||
expect(response.status).to.be.equal(Status.MALFORMED_MESSAGE)
|
||||
})
|
||||
|
||||
it('send stop - success', async function () {
|
||||
const streamStub = sinon.stub(conn, 'newStream')
|
||||
streamStub.resolves({ protocol: protocolIDv2Stop, stream: mockStream(pair<Uint8Array>()) })
|
||||
await stop({ connection: conn, request: { type: StopMessage.Type.CONNECT, peer: { id: srcPeer.toBytes(), addrs: [] } } })
|
||||
streamHandler.write(StopMessage.encode({
|
||||
type: StopMessage.Type.STATUS,
|
||||
status: Status.OK
|
||||
}).finish())
|
||||
})
|
||||
|
||||
it('send stop - should not fall apart with invalid status response', async function () {
|
||||
const streamStub = sinon.stub(conn, 'newStream')
|
||||
streamStub.resolves({ protocol: protocolIDv2Stop, stream: mockStream(pair<Uint8Array>()) })
|
||||
await stop({ connection: conn, request: { type: StopMessage.Type.CONNECT, peer: { id: srcPeer.toBytes(), addrs: [] } } })
|
||||
streamHandler.write(new Uint8Array(10))
|
||||
})
|
||||
})
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import mergeOptions from 'merge-options'
|
||||
import { validateConfig } from '../../src/config.js'
|
||||
import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js'
|
||||
@@ -18,21 +18,13 @@ describe('Protocol prefix is configurable', () => {
|
||||
it('protocolPrefix is provided', async () => {
|
||||
const testProtocol = 'test-protocol'
|
||||
libp2p = await createLibp2pNode(mergeOptions(baseOptions, {
|
||||
identify: {
|
||||
protocolPrefix: testProtocol
|
||||
},
|
||||
ping: {
|
||||
protocolPrefix: testProtocol
|
||||
},
|
||||
fetch: {
|
||||
protocolPrefix: testProtocol
|
||||
}
|
||||
protocolPrefix: testProtocol
|
||||
}))
|
||||
await libp2p.start()
|
||||
|
||||
const protocols = await libp2p.peerStore.protoBook.get(libp2p.peerId)
|
||||
expect(protocols).to.include.members([
|
||||
`/${testProtocol}/fetch/0.0.1`,
|
||||
'/libp2p/fetch/0.0.1',
|
||||
'/libp2p/circuit/relay/0.1.0',
|
||||
`/${testProtocol}/id/1.0.0`,
|
||||
`/${testProtocol}/id/push/1.0.0`,
|
||||
@@ -49,8 +41,7 @@ describe('Protocol prefix is configurable', () => {
|
||||
'/libp2p/circuit/relay/0.1.0',
|
||||
'/ipfs/id/1.0.0',
|
||||
'/ipfs/id/push/1.0.0',
|
||||
'/ipfs/ping/1.0.0',
|
||||
'/libp2p/fetch/0.0.1'
|
||||
'/ipfs/ping/1.0.0'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
@@ -1,12 +1,13 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import mergeOptions from 'merge-options'
|
||||
import pDefer from 'p-defer'
|
||||
import delay from 'delay'
|
||||
import { createLibp2p, Libp2p } from '../../src/index.js'
|
||||
import { baseOptions, pubsubSubsystemOptions } from './utils.js'
|
||||
import { createPeerId } from '../utils/creators/peer.js'
|
||||
import { CustomEvent } from '@libp2p/interfaces'
|
||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
import { FloodSub } from '@libp2p/floodsub'
|
||||
import type { PubSub } from '@libp2p/interfaces/pubsub'
|
||||
@@ -20,16 +21,14 @@ describe('Pubsub subsystem is configurable', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw if no module is provided', async () => {
|
||||
it('should not exist if no module is provided', async () => {
|
||||
libp2p = await createLibp2p(baseOptions)
|
||||
await libp2p.start()
|
||||
expect(() => libp2p.pubsub.getTopics()).to.throw()
|
||||
expect(libp2p.pubsub).to.not.exist()
|
||||
})
|
||||
|
||||
it('should not throw if the module is provided', async () => {
|
||||
it('should exist if the module is provided', async () => {
|
||||
libp2p = await createLibp2p(pubsubSubsystemOptions)
|
||||
await libp2p.start()
|
||||
expect(libp2p.pubsub.getTopics()).to.be.empty()
|
||||
expect(libp2p.pubsub).to.exist()
|
||||
})
|
||||
|
||||
it('should start and stop by default once libp2p starts', async () => {
|
||||
@@ -40,16 +39,13 @@ describe('Pubsub subsystem is configurable', () => {
|
||||
})
|
||||
|
||||
libp2p = await createLibp2p(customOptions)
|
||||
// @ts-expect-error not part of interface
|
||||
expect(libp2p.pubsub.isStarted()).to.equal(false)
|
||||
expect(libp2p.pubsub?.isStarted()).to.equal(false)
|
||||
|
||||
await libp2p.start()
|
||||
// @ts-expect-error not part of interface
|
||||
expect(libp2p.pubsub.isStarted()).to.equal(true)
|
||||
expect(libp2p.pubsub?.isStarted()).to.equal(true)
|
||||
|
||||
await libp2p.stop()
|
||||
// @ts-expect-error not part of interface
|
||||
expect(libp2p.pubsub.isStarted()).to.equal(false)
|
||||
expect(libp2p.pubsub?.isStarted()).to.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -91,14 +87,16 @@ describe('Pubsub subscription handlers adapter', () => {
|
||||
throw new Error('Pubsub was not enabled')
|
||||
}
|
||||
|
||||
pubsub.subscribe(topic)
|
||||
pubsub.addEventListener('message', handler)
|
||||
await pubsub.publish(topic, uint8ArrayFromString('useless-data'))
|
||||
pubsub.addEventListener(topic, handler)
|
||||
pubsub.dispatchEvent(new CustomEvent<Uint8Array>(topic, {
|
||||
detail: uint8ArrayFromString('useless-data')
|
||||
}))
|
||||
await defer.promise
|
||||
|
||||
pubsub.unsubscribe(topic)
|
||||
pubsub.removeEventListener('message', handler)
|
||||
await pubsub.publish(topic, uint8ArrayFromString('useless-data'))
|
||||
pubsub.removeEventListener(topic, handler)
|
||||
pubsub.dispatchEvent(new CustomEvent<Uint8Array>(topic, {
|
||||
detail: uint8ArrayFromString('useless-data')
|
||||
}))
|
||||
|
||||
// wait to guarantee that the handler is not called twice
|
||||
await delay(100)
|
||||
|
@@ -5,7 +5,7 @@ import { WebSockets } from '@libp2p/websockets'
|
||||
import * as filters from '@libp2p/websockets/filters'
|
||||
import { MULTIADDRS_WEBSOCKETS } from '../fixtures/browser.js'
|
||||
import mergeOptions from 'merge-options'
|
||||
import type { Message, PublishResult, PubSubInit, PubSubRPC, PubSubRPCMessage } from '@libp2p/interfaces/pubsub'
|
||||
import type { Message, PubSubInit, PubSubRPC, PubSubRPCMessage } from '@libp2p/interfaces/pubsub'
|
||||
import type { Libp2pInit, Libp2pOptions } from '../../src/index.js'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import * as cborg from 'cborg'
|
||||
@@ -44,12 +44,11 @@ class MockPubSub extends PubSubBaseProtocol {
|
||||
return cborg.encode(rpc)
|
||||
}
|
||||
|
||||
async publishMessage (from: PeerId, message: Message): Promise<PublishResult> {
|
||||
async publishMessage (from: PeerId, message: Message): Promise<void> {
|
||||
const peers = this.getSubscribers(message.topic)
|
||||
const recipients: PeerId[] = []
|
||||
|
||||
if (peers == null || peers.length === 0) {
|
||||
return { recipients }
|
||||
return
|
||||
}
|
||||
|
||||
peers.forEach(id => {
|
||||
@@ -61,11 +60,8 @@ class MockPubSub extends PubSubBaseProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
recipients.push(id)
|
||||
this.send(id, { messages: [message] })
|
||||
})
|
||||
|
||||
return { recipients }
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,14 +1,15 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import { AutoDialler } from '../../src/connection-manager/auto-dialler.js'
|
||||
import pWaitFor from 'p-wait-for'
|
||||
import delay from 'delay'
|
||||
import { createEd25519PeerId } from '@libp2p/peer-id-factory'
|
||||
import { Components } from '@libp2p/interfaces/components'
|
||||
import { stubInterface } from 'ts-sinon'
|
||||
import type { ConnectionManager } from '@libp2p/interfaces/connection-manager'
|
||||
import type { ConnectionManager } from '@libp2p/interfaces/registrar'
|
||||
import type { PeerStore, Peer } from '@libp2p/interfaces/peer-store'
|
||||
import type { Dialer } from '@libp2p/interfaces/dialer'
|
||||
|
||||
describe('Auto-dialler', () => {
|
||||
it('should not dial self', async () => {
|
||||
@@ -35,24 +36,26 @@ describe('Auto-dialler', () => {
|
||||
]))
|
||||
|
||||
const connectionManager = stubInterface<ConnectionManager>()
|
||||
connectionManager.getConnections.returns([])
|
||||
connectionManager.getConnectionList.returns([])
|
||||
const dialer = stubInterface<Dialer>()
|
||||
|
||||
const autoDialler = new AutoDialler(new Components({
|
||||
peerId: self.id,
|
||||
peerStore,
|
||||
connectionManager
|
||||
connectionManager,
|
||||
dialer
|
||||
}), {
|
||||
minConnections: 10
|
||||
})
|
||||
|
||||
await autoDialler.start()
|
||||
|
||||
await pWaitFor(() => connectionManager.openConnection.callCount === 1)
|
||||
await pWaitFor(() => dialer.dial.callCount === 1)
|
||||
await delay(1000)
|
||||
|
||||
await autoDialler.stop()
|
||||
|
||||
expect(connectionManager.openConnection.callCount).to.equal(1)
|
||||
expect(connectionManager.openConnection.calledWith(self.id)).to.be.false()
|
||||
expect(dialer.dial.callCount).to.equal(1)
|
||||
expect(dialer.dial.calledWith(self.id)).to.be.false()
|
||||
})
|
||||
})
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import { createNode, createPeerId } from '../utils/creators/peer.js'
|
||||
import { mockConnection, mockDuplex, mockMultiaddrConnection, mockUpgrader } from '@libp2p/interface-compliance-tests/mocks'
|
||||
import { createBaseOptions } from '../utils/base-options.browser.js'
|
||||
@@ -8,7 +8,7 @@ import type { Libp2p } from '../../src/index.js'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import { DefaultConnectionManager } from '../../src/connection-manager/index.js'
|
||||
import { Components } from '@libp2p/interfaces/components'
|
||||
import { CustomEvent } from '@libp2p/interfaces/events'
|
||||
import { CustomEvent } from '@libp2p/interfaces'
|
||||
import * as STATUS from '@libp2p/interfaces/connection/status'
|
||||
import { stubInterface } from 'ts-sinon'
|
||||
import type { KeyBook, PeerStore } from '@libp2p/interfaces/peer-store'
|
||||
@@ -18,7 +18,6 @@ import type { Connection } from '@libp2p/interfaces/connection'
|
||||
import delay from 'delay'
|
||||
import type { Libp2pNode } from '../../src/libp2p.js'
|
||||
import { codes } from '../../src/errors.js'
|
||||
import { start } from '@libp2p/interfaces/startable'
|
||||
|
||||
describe('Connection Manager', () => {
|
||||
let libp2p: Libp2p
|
||||
@@ -51,14 +50,9 @@ describe('Connection Manager', () => {
|
||||
const peerStore = stubInterface<PeerStore>()
|
||||
peerStore.keyBook = stubInterface<KeyBook>()
|
||||
|
||||
const connectionManager = new DefaultConnectionManager({
|
||||
maxConnections: 1000,
|
||||
minConnections: 50,
|
||||
autoDialInterval: 1000
|
||||
})
|
||||
connectionManager.init(new Components({ upgrader, peerStore }))
|
||||
const connectionManager = new DefaultConnectionManager(new Components({ upgrader, peerStore }))
|
||||
|
||||
await start(connectionManager)
|
||||
await connectionManager.start()
|
||||
|
||||
const conn1 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1]))
|
||||
const conn2 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1]))
|
||||
@@ -86,14 +80,9 @@ describe('Connection Manager', () => {
|
||||
const peerStore = stubInterface<PeerStore>()
|
||||
peerStore.keyBook = stubInterface<KeyBook>()
|
||||
|
||||
const connectionManager = new DefaultConnectionManager({
|
||||
maxConnections: 1000,
|
||||
minConnections: 50,
|
||||
autoDialInterval: 1000
|
||||
})
|
||||
connectionManager.init(new Components({ upgrader, peerStore }))
|
||||
const connectionManager = new DefaultConnectionManager(new Components({ upgrader, peerStore }))
|
||||
|
||||
await start(connectionManager)
|
||||
await connectionManager.start()
|
||||
|
||||
const conn1 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1]))
|
||||
const conn2 = await mockConnection(mockMultiaddrConnection(mockDuplex(), peerIds[1]))
|
||||
@@ -235,11 +224,11 @@ describe('libp2p.connections', () => {
|
||||
await libp2p.start()
|
||||
|
||||
// Wait for peer to connect
|
||||
await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === minConnections)
|
||||
await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().size === minConnections)
|
||||
|
||||
// Wait more time to guarantee no other connection happened
|
||||
await delay(200)
|
||||
expect(libp2p.components.getConnectionManager().getConnections().length).to.eql(minConnections)
|
||||
expect(libp2p.components.getConnectionManager().getConnectionMap().size).to.eql(minConnections)
|
||||
|
||||
await libp2p.stop()
|
||||
})
|
||||
@@ -268,11 +257,11 @@ describe('libp2p.connections', () => {
|
||||
await libp2p.start()
|
||||
|
||||
// Wait for peer to connect
|
||||
await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === minConnections)
|
||||
await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().size === minConnections)
|
||||
|
||||
// Should have connected to the peer with protocols
|
||||
expect(libp2p.components.getConnectionManager().getConnections(nodes[0].peerId)).to.be.empty()
|
||||
expect(libp2p.components.getConnectionManager().getConnections(nodes[1].peerId)).to.not.be.empty()
|
||||
expect(libp2p.components.getConnectionManager().getConnection(nodes[0].peerId)).to.not.exist()
|
||||
expect(libp2p.components.getConnectionManager().getConnection(nodes[1].peerId)).to.exist()
|
||||
|
||||
await libp2p.stop()
|
||||
})
|
||||
@@ -298,15 +287,15 @@ describe('libp2p.connections', () => {
|
||||
|
||||
// Wait for peer to connect
|
||||
const conn = await libp2p.dial(nodes[0].peerId)
|
||||
expect(libp2p.components.getConnectionManager().getConnections(nodes[0].peerId)).to.not.be.empty()
|
||||
expect(libp2p.components.getConnectionManager().getConnection(nodes[0].peerId)).to.exist()
|
||||
|
||||
await conn.close()
|
||||
// Closed
|
||||
await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === 0)
|
||||
await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().size === 0)
|
||||
// Connected
|
||||
await pWaitFor(() => libp2p.components.getConnectionManager().getConnections().length === 1)
|
||||
await pWaitFor(() => libp2p.components.getConnectionManager().getConnectionMap().size === 1)
|
||||
|
||||
expect(libp2p.components.getConnectionManager().getConnections(nodes[0].peerId)).to.not.be.empty()
|
||||
expect(libp2p.components.getConnectionManager().getConnection(nodes[0].peerId)).to.exist()
|
||||
|
||||
await libp2p.stop()
|
||||
})
|
||||
@@ -332,7 +321,9 @@ describe('libp2p.connections', () => {
|
||||
await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs())
|
||||
await libp2p.dial(remoteLibp2p.peerId)
|
||||
|
||||
const conns = libp2p.components.getConnectionManager().getConnections()
|
||||
const totalConns = Array.from(libp2p.components.getConnectionManager().getConnectionMap().values())
|
||||
expect(totalConns.length).to.eql(1)
|
||||
const conns = totalConns[0]
|
||||
expect(conns.length).to.eql(1)
|
||||
const conn = conns[0]
|
||||
|
||||
@@ -403,7 +394,7 @@ describe('libp2p.connections', () => {
|
||||
})
|
||||
})
|
||||
await libp2p.peerStore.addressBook.set(remoteLibp2p.peerId, remoteLibp2p.getMultiaddrs())
|
||||
await libp2p.components.getConnectionManager().openConnection(remoteLibp2p.peerId)
|
||||
await libp2p.components.getDialer().dial(remoteLibp2p.peerId)
|
||||
|
||||
for (const multiaddr of remoteLibp2p.getMultiaddrs()) {
|
||||
expect(denyDialMultiaddr.calledWith(remoteLibp2p.peerId, multiaddr)).to.be.true()
|
||||
@@ -427,7 +418,7 @@ describe('libp2p.connections', () => {
|
||||
|
||||
const fullMultiaddr = remoteLibp2p.getMultiaddrs()[0]
|
||||
|
||||
await libp2p.dial(fullMultiaddr)
|
||||
await libp2p.components.getDialer().dial(fullMultiaddr)
|
||||
|
||||
expect(filterMultiaddrForPeer.callCount).to.equal(2)
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import sinon from 'sinon'
|
||||
import { createNode } from '../utils/creators/peer.js'
|
||||
import { createBaseOptions } from '../utils/base-options.browser.js'
|
||||
@@ -8,7 +8,7 @@ import type { Libp2pNode } from '../../src/libp2p.js'
|
||||
import type { DefaultConnectionManager } from '../../src/connection-manager/index.js'
|
||||
import { mockConnection, mockDuplex, mockMultiaddrConnection } from '@libp2p/interface-compliance-tests/mocks'
|
||||
import { createEd25519PeerId } from '@libp2p/peer-id-factory'
|
||||
import { CustomEvent } from '@libp2p/interfaces/events'
|
||||
import { CustomEvent } from '@libp2p/interfaces'
|
||||
|
||||
describe('Connection Manager', () => {
|
||||
let libp2p: Libp2pNode
|
||||
@@ -79,7 +79,7 @@ describe('Connection Manager', () => {
|
||||
const value = Math.random()
|
||||
spies.set(value, spy)
|
||||
connectionManager.setPeerValue(connection.remotePeer, value)
|
||||
await connectionManager._onConnect(new CustomEvent('connection', { detail: connection }))
|
||||
await connectionManager.onConnect(new CustomEvent('connection', { detail: connection }))
|
||||
}))
|
||||
|
||||
// get the lowest value
|
||||
@@ -122,7 +122,7 @@ describe('Connection Manager', () => {
|
||||
await Promise.all([...new Array(max + 1)].map(async () => {
|
||||
const connection = mockConnection(mockMultiaddrConnection(mockDuplex(), await createEd25519PeerId()))
|
||||
sinon.stub(connection, 'close').callsFake(async () => spy()) // eslint-disable-line
|
||||
await connectionManager._onConnect(new CustomEvent('connection', { detail: connection }))
|
||||
await connectionManager.onConnect(new CustomEvent('connection', { detail: connection }))
|
||||
}))
|
||||
|
||||
expect(connectionManagerMaybeDisconnectOneSpy.callCount).to.equal(1)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import nock from 'nock'
|
||||
import sinon from 'sinon'
|
||||
import pDefer from 'p-defer'
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import { createLibp2p, Libp2p } from '../../../src/index.js'
|
||||
import { createSubsystemOptions } from './utils.js'
|
||||
|
||||
@@ -13,17 +13,15 @@ describe('DHT subsystem is configurable', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw if no module is provided', async () => {
|
||||
it('should not exist if no module is provided', async () => {
|
||||
libp2p = await createLibp2p(createSubsystemOptions({
|
||||
dht: undefined
|
||||
}))
|
||||
await libp2p.start()
|
||||
await expect(libp2p.dht.getMode()).to.eventually.be.rejected()
|
||||
expect(libp2p.dht).to.not.exist()
|
||||
})
|
||||
|
||||
it('should not throw if the module is provided', async () => {
|
||||
it('should exist if the module is provided', async () => {
|
||||
libp2p = await createLibp2p(createSubsystemOptions())
|
||||
await libp2p.start()
|
||||
await expect(libp2p.dht.getMode()).to.eventually.equal('client')
|
||||
expect(libp2p.dht).to.exist()
|
||||
})
|
||||
})
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import { Multiaddr } from '@multiformats/multiaddr'
|
||||
import pWaitFor from 'p-wait-for'
|
||||
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
||||
@@ -8,7 +8,7 @@ import { subsystemMulticodecs, createSubsystemOptions } from './utils.js'
|
||||
import { createPeerId } from '../../utils/creators/peer.js'
|
||||
import type { PeerId } from '@libp2p/interfaces/peer-id'
|
||||
import { createLibp2pNode, Libp2pNode } from '../../../src/libp2p.js'
|
||||
import { start } from '@libp2p/interfaces/startable'
|
||||
import { isStartable } from '@libp2p/interfaces'
|
||||
|
||||
const listenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8000')
|
||||
const remoteListenAddr = new Multiaddr('/ip4/127.0.0.1/tcp/8001')
|
||||
@@ -78,8 +78,8 @@ describe('DHT subsystem operates correctly', () => {
|
||||
expect(connection).to.exist()
|
||||
|
||||
return await Promise.all([
|
||||
pWaitFor(() => libp2p.dht.lan.routingTable.size === 1),
|
||||
pWaitFor(() => remoteLibp2p.dht.lan.routingTable.size === 1)
|
||||
pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1),
|
||||
pWaitFor(() => remoteLibp2p.dht?.lan.routingTable.size === 1)
|
||||
])
|
||||
})
|
||||
|
||||
@@ -89,8 +89,8 @@ describe('DHT subsystem operates correctly', () => {
|
||||
|
||||
await libp2p.dialProtocol(remAddr, subsystemMulticodecs)
|
||||
await Promise.all([
|
||||
pWaitFor(() => libp2p.dht.lan.routingTable.size === 1),
|
||||
pWaitFor(() => remoteLibp2p.dht.lan.routingTable.size === 1)
|
||||
pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1),
|
||||
pWaitFor(() => remoteLibp2p.dht?.lan.routingTable.size === 1)
|
||||
])
|
||||
|
||||
await libp2p.components.getContentRouting().put(key, value)
|
||||
@@ -141,17 +141,19 @@ describe('DHT subsystem operates correctly', () => {
|
||||
const connection = await libp2p.dial(remAddr)
|
||||
|
||||
expect(connection).to.exist()
|
||||
expect(libp2p.dht.lan.routingTable).to.be.empty()
|
||||
expect(libp2p.dht?.lan.routingTable).to.be.empty()
|
||||
|
||||
const dht = remoteLibp2p.dht
|
||||
|
||||
await start(dht)
|
||||
if (isStartable(dht)) {
|
||||
await dht.start()
|
||||
}
|
||||
|
||||
// should be 0 directly after start - TODO this may be susceptible to timing bugs, we should have
|
||||
// the ability to report stats on the DHT routing table instead of reaching into it's heart like this
|
||||
expect(remoteLibp2p.dht.lan.routingTable).to.be.empty()
|
||||
expect(remoteLibp2p.dht?.lan.routingTable).to.be.empty()
|
||||
|
||||
return await pWaitFor(() => libp2p.dht.lan.routingTable.size === 1)
|
||||
return await pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1)
|
||||
})
|
||||
|
||||
it('should put on a peer and get from the other', async () => {
|
||||
@@ -162,9 +164,11 @@ describe('DHT subsystem operates correctly', () => {
|
||||
|
||||
const dht = remoteLibp2p.dht
|
||||
|
||||
await start(dht)
|
||||
if (isStartable(dht)) {
|
||||
await dht.start()
|
||||
}
|
||||
|
||||
await pWaitFor(() => libp2p.dht.lan.routingTable.size === 1)
|
||||
await pWaitFor(() => libp2p.dht?.lan.routingTable.size === 1)
|
||||
await libp2p.components.getContentRouting().put(key, value)
|
||||
|
||||
const fetchedValue = await remoteLibp2p.components.getContentRouting().get(key)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { expect } from 'aegir/utils/chai.js'
|
||||
import { WebSockets } from '@libp2p/websockets'
|
||||
import { NOISE } from '@chainsafe/libp2p-noise'
|
||||
import { createLibp2p, Libp2pOptions } from '../../src/index.js'
|
||||
|
@@ -1,78 +0,0 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import { expect } from 'aegir/chai'
|
||||
import { WebSockets } from '@libp2p/websockets'
|
||||
import { NOISE } from '@chainsafe/libp2p-noise'
|
||||
import { createPeerId } from '../utils/creators/peer.js'
|
||||
import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js'
|
||||
import type { Libp2pOptions } from '../../src/index.js'
|
||||
import sinon from 'sinon'
|
||||
import { KadDHT } from '@libp2p/kad-dht'
|
||||
|
||||
describe('getPublicKey', () => {
|
||||
let libp2p: Libp2pNode
|
||||
|
||||
beforeEach(async () => {
|
||||
const peerId = await createPeerId()
|
||||
const config: Libp2pOptions = {
|
||||
peerId,
|
||||
transports: [
|
||||
new WebSockets()
|
||||
],
|
||||
connectionEncryption: [
|
||||
NOISE
|
||||
],
|
||||
dht: new KadDHT()
|
||||
}
|
||||
libp2p = await createLibp2pNode(config)
|
||||
|
||||
await libp2p.start()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await libp2p.stop()
|
||||
})
|
||||
|
||||
it('should extract embedded public key', async () => {
|
||||
const otherPeer = await createPeerId()
|
||||
|
||||
const key = await libp2p.getPublicKey(otherPeer)
|
||||
|
||||
expect(otherPeer.publicKey).to.equalBytes(key)
|
||||
})
|
||||
|
||||
it('should get key from the keystore', async () => {
|
||||
const otherPeer = await createPeerId({ opts: { type: 'rsa' } })
|
||||
|
||||
if (otherPeer.publicKey == null) {
|
||||
throw new Error('Public key was missing')
|
||||
}
|
||||
|
||||
await libp2p.peerStore.keyBook.set(otherPeer, otherPeer.publicKey)
|
||||
|
||||
const key = await libp2p.getPublicKey(otherPeer)
|
||||
|
||||
expect(otherPeer.publicKey).to.equalBytes(key)
|
||||
})
|
||||
|
||||
it('should query the DHT when the key is not in the keystore', async () => {
|
||||
const otherPeer = await createPeerId({ opts: { type: 'rsa' } })
|
||||
|
||||
if (otherPeer.publicKey == null) {
|
||||
throw new Error('Public key was missing')
|
||||
}
|
||||
|
||||
if (libp2p.dht == null) {
|
||||
throw new Error('DHT was not configured')
|
||||
}
|
||||
|
||||
libp2p.dht.get = sinon.stub().returns([{
|
||||
name: 'VALUE',
|
||||
value: otherPeer.publicKey
|
||||
}])
|
||||
|
||||
const key = await libp2p.getPublicKey(otherPeer)
|
||||
|
||||
expect(otherPeer.publicKey).to.equalBytes(key)
|
||||
})
|
||||
})
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user