Compare commits

..

58 Commits

Author SHA1 Message Date
0c2d84e395 chore: release version v0.14.3
License: MIT
Signed-off-by: Jacob Heun <jacobheun@gmail.com>
2019-12-20 09:02:38 -08:00
13fb6ba6e4 chore: update contributors 2019-12-20 09:02:37 -08:00
21f87476f6 fix: transport should not handle connection if upgradeInbound throws (#119) 2019-12-20 08:33:36 -08:00
657d3e6f4a chore: release version v0.14.2
License: MIT
Signed-off-by: Jacob Heun <jacobheun@gmail.com>
2019-12-06 10:09:04 +01:00
162c605bf5 chore: update contributors 2019-12-06 10:09:03 +01:00
d76a1f27e7 feat: add path multiaddr support (#118)
* chore: update deps
* test: skip paths on windows
2019-12-06 09:42:43 +01:00
7702646cfa fix(log): log the bound port and host (#117) 2019-10-02 16:49:13 +02:00
7c977d3cb1 chore: use libp2p utils for ip port to multiaddr (#116) 2019-09-24 16:35:40 +02:00
524acbe372 chore: release version v0.14.1
License: MIT
Signed-off-by: Jacob Heun <jacobheun@gmail.com>
2019-09-20 13:43:27 +02:00
1b210e71c0 chore: update contributors 2019-09-20 13:43:27 +02:00
a385f0eb34 docs: add upgrader to readme example (#114) 2019-09-20 13:42:11 +02:00
605ee279ae fix: ensure timeline.close is set (#113)
* fix(test): dns4 isnt a valid tcp multiaddr

It should be resolved first

* fix: ensure timeline.close is set on the multiaddrConn

* chore: add docs to gitignore

They are generated and uploaded on release
2019-09-19 15:20:54 +02:00
872e15af46 chore: release version v0.14.0
License: MIT
Signed-off-by: Jacob Heun <jacobheun@gmail.com>
2019-09-16 17:27:16 +02:00
0aba4f8611 chore: update contributors 2019-09-16 17:27:15 +02:00
cf7d1b8501 feat: change api to async / await (#112)
BREAKING CHANGE: All places in the API that used callbacks are now replaced with async/await. The API has also been updated according to the latest `interface-transport` version, https://github.com/libp2p/interface-transport/tree/v0.6.0#api.
2019-09-16 17:19:47 +02:00
49c7f33375 chore: release version v0.13.1
License: MIT
Signed-off-by: Jacob Heun <jacobheun@gmail.com>
2019-08-08 13:27:01 +02:00
89bceb461f chore: update contributors 2019-08-08 13:27:01 +02:00
44f9e32d07 chore: update deps (#110)
* chore: add node 12 to travis
* test: just use spec files
2019-08-08 12:48:13 +02:00
c26cc70c65 chore: add discourse badge (#106) 2019-04-26 17:44:28 +02:00
3ab43a3604 chore: prefer const over let (#99)
Prefer const over let when the binding is static, in order to comply with an upcoming Standard rule.
2018-10-31 08:35:55 +00:00
3aad2ed243 chore: release version v0.13.0
License: MIT
Signed-off-by: Jacob Heun <jacobheun@gmail.com>
2018-09-12 19:40:49 +02:00
01cfbda2e7 chore: update contributors 2018-09-12 19:40:49 +02:00
eba0b48744 feat: add support for dialing over dns
License: MIT
Signed-off-by: Jacob Heun <jacobheun@gmail.com>
2018-09-12 19:38:02 +02:00
a0c23e49f7 chore: release version v0.12.1
License: MIT
Signed-off-by: Jacob Heun <jacobheun@gmail.com>
2018-07-31 14:13:11 +02:00
66ab208182 chore: update contributors 2018-07-31 14:13:10 +02:00
168d111158 chore: update deps and fix test runner
License: MIT
Signed-off-by: Jacob Heun <jacobheun@gmail.com>
2018-07-31 14:08:31 +02:00
4b04b17dfa fix: invalid ip address and daemon can be crashed by remote user
Per the nodeJS documentation, a Net socket.remoteAddress value may
be undefined if the socket is destroyed, as by a client disconnect.
A multiaddr cannot be created for an invalid IP address (such as
the undefined remote address of a destroyed socket). Currently
the attempt results in a crash that can be triggered remotely. This
commit catches the exception in get-multiaddr and returns an
undefined value to listener rather than throwing an exception when
trying to process defective or destroyed socket data. Listener then
terminates processing of the incoming p2p connections that generate
this error condition.

fixes: https://github.com/libp2p/js-libp2p-tcp/issues/93
fixes: https://github.com/ipfs/js-ipfs/issues/1447
2018-07-31 13:51:27 +02:00
6c36a46831 test: fixes listen-dial test "dial and destroy on listener" (#97) 2018-07-31 13:46:12 +02:00
d39ec2db40 chore: add lead maintainer (#94)
* chore: add lead maintainer

License: MIT
Signed-off-by: Jacob Heun <jacobheun@gmail.com>

* Update package.json
2018-06-26 17:58:30 +02:00
79428f3e62 chore: release version v0.12.0 2018-04-05 17:00:33 +01:00
8a394b5286 chore: update contributors 2018-04-05 17:00:33 +01:00
b7f73bcda1 test: fix dial error test 2018-04-05 17:00:19 +01:00
8b44aa28ee chore: update deps 2018-04-05 16:56:23 +01:00
ded1f6831c feat: add class-is module 2018-04-05 16:55:54 +01:00
5ef24695fc docs: fixing the broken example in README (#91)
* Working example fix

* Char fix

* Corrected output, and add yarn to gitignore
2018-04-05 16:55:25 +01:00
df0aa059ca chore: release version v0.11.6 2018-02-20 10:27:27 +00:00
bf74e9acad chore: update contributors 2018-02-20 10:27:26 +00:00
83c4617e8d chore: update deps 2018-02-20 10:27:17 +00:00
3b7e68bc8a docs: update and polish 2018-02-20 10:25:18 +00:00
de1d7fe75c chore: tiny refactor 2018-02-07 06:40:00 +00:00
9c77a69ae3 chore: tiny refactor 2018-02-07 06:26:02 +00:00
50840a8067 test: refactor 2018-02-07 06:06:15 +00:00
d194ff0a3b chore: release version v0.11.5 2018-02-07 05:59:57 +00:00
41ca37e9ce chore: update contributors 2018-02-07 05:59:56 +00:00
71a28bb177 test: refactor 2018-02-07 05:59:34 +00:00
0d1861a1f8 chore: release version v0.11.4 2018-02-07 05:58:11 +00:00
929408eb38 chore: update contributors 2018-02-07 05:58:11 +00:00
b94f9c5d51 test: refactor 2018-02-07 05:56:55 +00:00
4b0851b4a4 chore: release version v0.11.3 2018-02-07 05:45:42 +00:00
74771b9504 chore: update contributors 2018-02-07 05:45:42 +00:00
48eefa1b40 chore: update deps 2018-02-07 05:45:30 +00:00
f8f52665f7 fix: clearing timeout when closes (#87) 2018-02-07 05:43:15 +00:00
d804244239 chore: release version v0.11.2 2018-01-12 12:30:45 +00:00
31c720f285 chore: update contributors 2018-01-12 12:30:45 +00:00
c553e0448f chore: fix release script 2018-01-12 12:30:38 +00:00
533835d22f test: do not run IPv6 tests on CI (#86)
* test: do not run IPv6 tests on CI

* Updating CI files (#83)

This commit updates all CI scripts to the latest version

* chore: use env variable instead

* skip the other IPv6 tests
2018-01-12 12:27:39 +00:00
74a88f6868 fix: missing dependency debug, fixes #84 2018-01-12 12:01:54 +00:00
1de9f18894 chore: update gitignore 2017-10-13 17:17:54 +02:00
17 changed files with 971 additions and 873 deletions

43
.gitignore vendored
View File

@ -1,42 +1,5 @@
**/node_modules/
**/*.log
test/repo-tests*
**/bundle.js
# Logs
logs
*.log
coverage
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
build
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
lib
dist
test/test-data/go-ipfs-repo/LOCK
test/test-data/go-ipfs-repo/LOG
test/test-data/go-ipfs-repo/LOG.old
# while testing npm5
package-lock.json
coverage
.nyc_output
docs

View File

@ -1,32 +1,35 @@
sudo: false
language: node_js
cache: npm
sudo: false
matrix:
include:
- node_js: 6
env: CXX=g++-4.8
- node_js: 8
env: CXX=g++-4.8
# - node_js: stable
# env: CXX=g++-4.8
stages:
- check
- test
- cov
script:
- npm run lint
- npm run test
- npm run coverage
- make test
node_js:
- '10'
- '12'
os:
- linux
- osx
- windows
before_script:
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6'; fi
after_success:
- npm run coverage-publish
script: npx nyc -s npm run test:node -- --bail
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov
addons:
firefox: 'latest'
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
jobs:
include:
- stage: check
os: linux
script:
- npx aegir build --bundlesize
- npx aegir dep-check
- npm run lint
notifications:
email: false

View File

@ -1,3 +1,123 @@
<a name="0.14.3"></a>
## [0.14.3](https://github.com/libp2p/js-libp2p-tcp/compare/v0.14.2...v0.14.3) (2019-12-20)
### Bug Fixes
* transport should not handle connection if upgradeInbound throws ([#119](https://github.com/libp2p/js-libp2p-tcp/issues/119)) ([21f8747](https://github.com/libp2p/js-libp2p-tcp/commit/21f8747))
<a name="0.14.2"></a>
## [0.14.2](https://github.com/libp2p/js-libp2p-tcp/compare/v0.14.1...v0.14.2) (2019-12-06)
### Bug Fixes
* **log:** log the bound port and host ([#117](https://github.com/libp2p/js-libp2p-tcp/issues/117)) ([7702646](https://github.com/libp2p/js-libp2p-tcp/commit/7702646))
### Features
* add path multiaddr support ([#118](https://github.com/libp2p/js-libp2p-tcp/issues/118)) ([d76a1f2](https://github.com/libp2p/js-libp2p-tcp/commit/d76a1f2))
<a name="0.14.1"></a>
## [0.14.1](https://github.com/libp2p/js-libp2p-tcp/compare/v0.14.0...v0.14.1) (2019-09-20)
### Bug Fixes
* ensure timeline.close is set ([#113](https://github.com/libp2p/js-libp2p-tcp/issues/113)) ([605ee27](https://github.com/libp2p/js-libp2p-tcp/commit/605ee27))
<a name="0.14.0"></a>
# [0.14.0](https://github.com/libp2p/js-libp2p-tcp/compare/v0.13.1...v0.14.0) (2019-09-16)
### Features
* change api to async / await ([#112](https://github.com/libp2p/js-libp2p-tcp/issues/112)) ([cf7d1b8](https://github.com/libp2p/js-libp2p-tcp/commit/cf7d1b8))
### BREAKING CHANGES
* All places in the API that used callbacks are now replaced with async/await. The API has also been updated according to the latest `interface-transport` version, https://github.com/libp2p/interface-transport/tree/v0.6.0#api.
<a name="0.13.1"></a>
## [0.13.1](https://github.com/libp2p/js-libp2p-tcp/compare/v0.13.0...v0.13.1) (2019-08-08)
<a name="0.13.0"></a>
# [0.13.0](https://github.com/libp2p/js-libp2p-tcp/compare/v0.12.1...v0.13.0) (2018-09-12)
### Features
* add support for dialing over dns ([eba0b48](https://github.com/libp2p/js-libp2p-tcp/commit/eba0b48))
<a name="0.12.1"></a>
## [0.12.1](https://github.com/libp2p/js-libp2p-tcp/compare/v0.12.0...v0.12.1) (2018-07-31)
### Bug Fixes
* invalid ip address and daemon can be crashed by remote user ([4b04b17](https://github.com/libp2p/js-libp2p-tcp/commit/4b04b17))
<a name="0.12.0"></a>
# [0.12.0](https://github.com/libp2p/js-libp2p-tcp/compare/v0.11.6...v0.12.0) (2018-04-05)
### Features
* add class-is module ([ded1f68](https://github.com/libp2p/js-libp2p-tcp/commit/ded1f68))
<a name="0.11.6"></a>
## [0.11.6](https://github.com/libp2p/js-libp2p-tcp/compare/v0.11.5...v0.11.6) (2018-02-20)
<a name="0.11.5"></a>
## [0.11.5](https://github.com/libp2p/js-libp2p-tcp/compare/v0.11.4...v0.11.5) (2018-02-07)
<a name="0.11.4"></a>
## [0.11.4](https://github.com/libp2p/js-libp2p-tcp/compare/v0.11.3...v0.11.4) (2018-02-07)
<a name="0.11.3"></a>
## [0.11.3](https://github.com/libp2p/js-libp2p-tcp/compare/v0.11.2...v0.11.3) (2018-02-07)
### Bug Fixes
* clearing timeout when closes ([#87](https://github.com/libp2p/js-libp2p-tcp/issues/87)) ([f8f5266](https://github.com/libp2p/js-libp2p-tcp/commit/f8f5266))
<a name="0.11.2"></a>
## [0.11.2](https://github.com/libp2p/js-libp2p-tcp/compare/v0.11.1...v0.11.2) (2018-01-12)
### Bug Fixes
* missing dependency debug, fixes [#84](https://github.com/libp2p/js-libp2p-tcp/issues/84) ([74a88f6](https://github.com/libp2p/js-libp2p-tcp/commit/74a88f6))
<a name="0.11.1"></a>
## [0.11.1](https://github.com/libp2p/js-libp2p-tcp/compare/v0.11.0...v0.11.1) (2017-10-13)

106
README.md
View File

@ -1,31 +1,29 @@
# js-libp2p-tcp
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io)
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
[![Build Status](https://travis-ci.org/libp2p/js-libp2p-tcp.svg?style=flat-square)](https://travis-ci.org/libp2p/js-libp2p-tcp)
[![Coverage Status](https://coveralls.io/repos/github/libp2p/js-libp2p-tcp/badge.svg?branch=master)](https://coveralls.io/github/libp2p/js-libp2p-tcp?branch=master)
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://protocol.ai)
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
[![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p)
[![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io)
[![](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-tcp.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-tcp)
[![](https://img.shields.io/travis/libp2p/js-libp2p-tcp.svg?style=flat-square)](https://travis-ci.com/libp2p/js-libp2p-tcp)
[![Dependency Status](https://david-dm.org/libp2p/js-libp2p-tcp.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-tcp)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
![](https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square)
![](https://img.shields.io/badge/Node.js-%3E%3D6.0.0-orange.svg?style=flat-square)
![](https://raw.githubusercontent.com/libp2p/interface-connection/master/img/badge.png)
![](https://raw.githubusercontent.com/libp2p/interface-transport/master/img/badge.png)
[![](https://raw.githubusercontent.com/libp2p/interface-transport/master/img/badge.png)](https://github.com/libp2p/interface-transport)
[![](https://raw.githubusercontent.com/libp2p/interface-connection/master/img/badge.png)](https://github.com/libp2p/interface-connection)
> Node.js implementation of the TCP module that libp2p uses, which implements the [interface-connection](https://github.com/libp2p/interface-connection) interface for dial/listen.
`libp2p-tcp` in Node.js is a very thin shim that adds support for dialing to a `multiaddr`. This small shim will enable libp2p to use other different transports.
> JavaScript implementation of the TCP module for libp2p. It exposes the [interface-transport](https://github.com/libp2p/interface-connection) for dial/listen. `libp2p-tcp` is a very thin shim that adds support for dialing to a `multiaddr`. This small shim will enable libp2p to use other transports.
**Note:** This module uses [pull-streams](https://pull-stream.github.io) for all stream based interfaces.
## Lead Maintainer
[Jacob Heun](https://github.com/jacobheun)
## Table of Contents
- [Install](#install)
- [npm](#npm)
- [Usage](#usage)
- [Example](#example)
- [This module uses `pull-streams`](#this-module-uses-pull-streams)
- [Converting `pull-streams` to Node.js Streams](#converting-pull-streams-to-nodejs-streams)
- [API](#api)
- [Contribute](#contribute)
- [License](#license)
@ -35,42 +33,46 @@
### npm
```sh
> npm i libp2p-tcp
> npm install libp2p-tcp
```
## Usage
### Example
```js
const TCP = require('libp2p-tcp')
const multiaddr = require('multiaddr')
const pull = require('pull-stream')
const pipe = require('it-pipe')
const { collect } = require('streaming-iterables')
const mh1 = multiaddr('/ip4/127.0.0.1/tcp/9090')
const mh2 = multiaddr('/ip6/::/tcp/9092')
// A simple upgrader that just returns the MultiaddrConnection
const upgrader = {
upgradeInbound: maConn => maConn,
upgradeOutbound: maConn => maConn
}
const tcp = new TCP()
const tcp = new TCP({ upgrader })
const listener = tcp.createListener(mh1, (socket) => {
const listener = tcp.createListener((socket) => {
console.log('new connection opened')
pull(
pull.values(['hello']),
pipe(
['hello'],
socket
)
})
listener.listen(() => {
console.log('listening')
const addr = multiaddr('/ip4/127.0.0.1/tcp/9090')
await listener.listen(addr)
console.log('listening')
pull(
tcp.dial(mh1),
pull.log,
pull.onEnd(() => {
tcp.close()
})
)
})
const socket = await tcp.dial(addr)
const values = await pipe(
socket,
collect
)
console.log(`Value: ${values.toString()}`)
// Close connection after reading
await listener.close()
```
Outputs:
@ -78,43 +80,25 @@ Outputs:
```sh
listening
new connection opened
hello
Value: hello
```
### This module uses `pull-streams`
We expose a streaming interface based on `pull-streams`, rather then on the Node.js core streams implementation (aka Node.js streams). `pull-streams` offers us a better mechanism for error handling and flow control guarantees. If you would like to know more about why we did this, see the discussion at this [issue](https://github.com/ipfs/js-ipfs/issues/362).
You can learn more about pull-streams at:
- [The history of Node.js streams, nodebp April 2014](https://www.youtube.com/watch?v=g5ewQEuXjsQ)
- [The history of streams, 2016](http://dominictarr.com/post/145135293917/history-of-streams)
- [pull-streams, the simple streaming primitive](http://dominictarr.com/post/149248845122/pull-streams-pull-streams-are-a-very-simple)
- [pull-streams documentation](https://pull-stream.github.io/)
#### Converting `pull-streams` to Node.js Streams
If you are a Node.js streams user, you can convert a pull-stream to a Node.js stream using the module [`pull-stream-to-stream`](https://github.com/dominictarr/pull-stream-to-stream), giving you an instance of a Node.js stream that is linked to the pull-stream. For example:
```js
const pullToStream = require('pull-stream-to-stream')
const nodeStreamInstance = pullToStream(pullStreamInstance)
// nodeStreamInstance is an instance of a Node.js Stream
```
To learn more about this utility, visit https://pull-stream.github.io/#pull-stream-to-stream.
## API
### Transport
[![](https://raw.githubusercontent.com/libp2p/interface-transport/master/img/badge.png)](https://github.com/libp2p/interface-transport)
`libp2p-tcp` accepts TCP addresses both IPFS and non IPFS encapsulated addresses, i.e:
`libp2p-tcp` accepts TCP addresses as both IPFS and non IPFS encapsulated addresses, i.e:
`/ip4/127.0.0.1/tcp/4001`
`/ip4/127.0.0.1/tcp/4001/ipfs/QmHash`
Both for dialing and listening.
(both for dialing and listening)
### Connection
[![](https://raw.githubusercontent.com/libp2p/interface-connection/master/img/badge.png)](https://github.com/libp2p/interface-connection)
## Contribute

View File

@ -1,18 +0,0 @@
machine:
node:
version: stable
dependencies:
pre:
- google-chrome --version
- curl -L -o google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
- for v in $(curl http://archive.ubuntu.com/ubuntu/pool/main/n/nss/ | grep "href=" | grep "libnss3.*deb\"" -o | grep -o "libnss3.*deb" | grep "3.28" | grep "14.04"); do curl -L -o $v http://archive.ubuntu.com/ubuntu/pool/main/n/nss/$v; done && rm libnss3-tools*_i386.deb libnss3-dev*_i386.deb
- sudo dpkg -i google-chrome.deb || true
- sudo dpkg -i libnss3*.deb || true
- sudo apt-get update
- sudo apt-get install -f || true
- sudo dpkg -i libnss3*.deb
- sudo apt-get install -f
- sudo apt-get install --only-upgrade lsb-base
- sudo dpkg -i google-chrome.deb
- google-chrome --version

View File

@ -1,20 +1,20 @@
{
"name": "libp2p-tcp",
"version": "0.11.1",
"version": "0.14.3",
"description": "Node.js implementation of the TCP module that libp2p uses, which implements the interface-connection and interface-transport interfaces",
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
"main": "src/index.js",
"scripts": {
"lint": "aegir lint",
"test": "aegir test --target node",
"release": "aegir release --no-build",
"release-minor": "aegir release --type minor --no-build",
"release-major": "aegir-release --type major --no-build",
"coverage": "aegir coverage",
"coverage-publish": "aegir coverage --provider coveralls"
"test": "aegir test -t node",
"test:node": "aegir test -t node",
"release": "aegir release -t node --no-build",
"release-minor": "aegir release -t node --type minor --no-build",
"release-major": "aegir-release -t node --type major --no-build",
"coverage": "nyc --reporter=text --reporter=lcov npm run test:node"
},
"pre-commit": [
"lint",
"test"
"pre-push": [
"lint"
],
"repository": {
"type": "git",
@ -23,7 +23,6 @@
"keywords": [
"IPFS"
],
"author": "David Dias <daviddias@ipfs.io>",
"license": "MIT",
"bugs": {
"url": "https://github.com/libp2p/js-libp2p-tcp/issues"
@ -34,33 +33,43 @@
"npm": ">=3.0.0"
},
"devDependencies": {
"aegir": "^12.0.8",
"chai": "^4.1.2",
"aegir": "^20.4.1",
"chai": "^4.2.0",
"dirty-chai": "^2.0.1",
"interface-transport": "~0.3.5",
"lodash.isfunction": "^3.0.8",
"pre-commit": "^1.2.2",
"pull-stream": "^3.6.1"
"libp2p-interfaces": "^0.2.0",
"it-pipe": "^1.1.0",
"sinon": "^7.5.0",
"streaming-iterables": "^4.1.1"
},
"dependencies": {
"interface-connection": "~0.3.2",
"ip-address": "^5.8.8",
"lodash.includes": "^4.3.0",
"lodash.isfunction": "^3.0.8",
"mafmt": "^3.0.1",
"multiaddr": "^3.0.1",
"once": "^1.4.0",
"stream-to-pull-stream": "^1.7.2"
"abortable-iterator": "^3.0.0",
"class-is": "^1.1.0",
"debug": "^4.1.1",
"err-code": "^2.0.0",
"libp2p-utils": "~0.1.0",
"mafmt": "^7.0.0",
"multiaddr": "^7.2.1",
"stream-to-it": "^0.2.0"
},
"contributors": [
"Alan Shaw <alan.shaw@protocol.ai>",
"Alan Shaw <alan@tableflip.io>",
"David Dias <daviddias.p@gmail.com>",
"Diogo Silva <fsdiogo@gmail.com>",
"Dmitriy Ryajov <dryajov@gmail.com>",
"Drew Stone <drewstone329@gmail.com>",
"Evan Schwartz <evan.mark.schwartz@gmail.com>",
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
"Greenkeeper <support@greenkeeper.io>",
"Jacob Heun <jacobheun@gmail.com>",
"Jacob Heun <jake@andyet.net>",
"João Antunes <j.goncalo.antunes@gmail.com>",
"Linus Unnebäck <linus@folkdatorn.se>",
"Pedro Teixeira <i@pgte.me>",
"Prashanth Chandra <coolshanth94@gmail.com>",
"Richard Littauer <richard.littauer@gmail.com>",
"Stephen Whitmore <stephen.whitmore@gmail.com>"
"Stephen Whitmore <stephen.whitmore@gmail.com>",
"TomCoded <tomtinkerer@gmail.com>",
"Vasco Santos <vasco.santos@moxy.studio>"
]
}

8
src/constants.js Normal file
View File

@ -0,0 +1,8 @@
'use strict'
// p2p multi-address code
exports.CODE_P2P = 421
exports.CODE_CIRCUIT = 290
// Time to wait for a connection to close gracefully before destroying it manually
exports.CLOSE_TIMEOUT = 2000

View File

@ -1,22 +0,0 @@
'use strict'
const multiaddr = require('multiaddr')
const Address6 = require('ip-address').Address6
module.exports = (socket) => {
let mh
if (socket.remoteFamily === 'IPv6') {
var addr = new Address6(socket.remoteAddress)
if (addr.v4) {
var ip4 = addr.to4().correctForm()
mh = multiaddr('/ip4/' + ip4 + '/tcp/' + socket.remotePort)
} else {
mh = multiaddr('/ip6/' + socket.remoteAddress + '/tcp/' + socket.remotePort)
}
} else {
mh = multiaddr('/ip4/' + socket.remoteAddress + '/tcp/' + socket.remotePort)
}
return mh
}

View File

@ -1,85 +1,140 @@
'use strict'
const net = require('net')
const toPull = require('stream-to-pull-stream')
const mafmt = require('mafmt')
const includes = require('lodash.includes')
const isFunction = require('lodash.isfunction')
const Connection = require('interface-connection').Connection
const once = require('once')
const debug = require('debug')
const log = debug('libp2p:tcp:dial')
const withIs = require('class-is')
const errCode = require('err-code')
const log = require('debug')('libp2p:tcp')
const toConnection = require('./socket-to-conn')
const createListener = require('./listener')
const { multiaddrToNetConfig } = require('./utils')
const { AbortError } = require('abortable-iterator')
const { CODE_CIRCUIT, CODE_P2P } = require('./constants')
const assert = require('assert')
function noop () {}
/**
* @class TCP
*/
class TCP {
dial (ma, options, callback) {
if (isFunction(options)) {
callback = options
options = {}
}
callback = callback || noop
callback = once(callback)
const cOpts = ma.toOptions()
log('Connecting to %s %s', cOpts.port, cOpts.host)
const rawSocket = net.connect(cOpts)
rawSocket.once('timeout', () => {
log('timeout')
rawSocket.emit('error', new Error('Timeout'))
})
rawSocket.once('error', callback)
rawSocket.once('connect', () => {
rawSocket.removeListener('error', callback)
callback()
})
const socket = toPull.duplex(rawSocket)
const conn = new Connection(socket)
conn.getObservedAddrs = (callback) => {
return callback(null, [ma])
}
/**
* @constructor
* @param {object} options
* @param {Upgrader} options.upgrader
*/
constructor ({ upgrader }) {
assert(upgrader, 'An upgrader must be provided. See https://github.com/libp2p/interface-transport#upgrader.')
this._upgrader = upgrader
}
/**
* @async
* @param {Multiaddr} ma
* @param {object} options
* @param {AbortSignal} options.signal Used to abort dial requests
* @returns {Connection} An upgraded Connection
*/
async dial (ma, options) {
options = options || {}
const socket = await this._connect(ma, options)
const maConn = toConnection(socket, { remoteAddr: ma, signal: options.signal })
log('new outbound connection %s', maConn.remoteAddr)
const conn = await this._upgrader.upgradeOutbound(maConn)
log('outbound connection %s upgraded', maConn.remoteAddr)
return conn
}
/**
* @private
* @param {Multiaddr} ma
* @param {object} options
* @param {AbortSignal} options.signal Used to abort dial requests
* @returns {Promise<Socket>} Resolves a TCP Socket
*/
_connect (ma, options = {}) {
if (options.signal && options.signal.aborted) {
throw new AbortError()
}
return new Promise((resolve, reject) => {
const start = Date.now()
const cOpts = multiaddrToNetConfig(ma)
log('dialing %j', cOpts)
const rawSocket = net.connect(cOpts)
const onError = err => {
err.message = `connection error ${cOpts.host}:${cOpts.port}: ${err.message}`
done(err)
}
const onTimeout = () => {
log('connnection timeout %s:%s', cOpts.host, cOpts.port)
const err = errCode(new Error(`connection timeout after ${Date.now() - start}ms`), 'ERR_CONNECT_TIMEOUT')
// Note: this will result in onError() being called
rawSocket.emit('error', err)
}
const onConnect = () => {
log('connection opened %j', cOpts)
done()
}
const onAbort = () => {
log('connection aborted %j', cOpts)
rawSocket.destroy()
done(new AbortError())
}
const done = err => {
rawSocket.removeListener('error', onError)
rawSocket.removeListener('timeout', onTimeout)
rawSocket.removeListener('connect', onConnect)
options.signal && options.signal.removeEventListener('abort', onAbort)
if (err) return reject(err)
resolve(rawSocket)
}
rawSocket.on('error', onError)
rawSocket.on('timeout', onTimeout)
rawSocket.on('connect', onConnect)
options.signal && options.signal.addEventListener('abort', onAbort)
})
}
/**
* Creates a TCP listener. The provided `handler` function will be called
* anytime a new incoming Connection has been successfully upgraded via
* `upgrader.upgradeInbound`.
* @param {*} [options]
* @param {function(Connection)} handler
* @returns {Listener} A TCP listener
*/
createListener (options, handler) {
if (isFunction(options)) {
if (typeof options === 'function') {
handler = options
options = {}
}
handler = handler || (() => {})
return createListener(handler)
options = options || {}
return createListener({ handler, upgrader: this._upgrader }, options)
}
/**
* Takes a list of `Multiaddr`s and returns only valid TCP addresses
* @param {Multiaddr[]} multiaddrs
* @returns {Multiaddr[]} Valid TCP multiaddrs
*/
filter (multiaddrs) {
if (!Array.isArray(multiaddrs)) {
multiaddrs = [multiaddrs]
}
multiaddrs = Array.isArray(multiaddrs) ? multiaddrs : [multiaddrs]
return multiaddrs.filter((ma) => {
if (includes(ma.protoNames(), 'p2p-circuit')) {
return multiaddrs.filter(ma => {
if (ma.protoCodes().includes(CODE_CIRCUIT)) {
return false
}
if (includes(ma.protoNames(), 'ipfs')) {
ma = ma.decapsulate('ipfs')
}
return mafmt.TCP.matches(ma)
return mafmt.TCP.matches(ma.decapsulateCode(CODE_P2P))
})
}
}
module.exports = TCP
module.exports = withIs(TCP, { className: 'TCP', symbolName: '@libp2p/js-libp2p-tcp/tcp' })

View File

@ -1,151 +1,109 @@
'use strict'
const multiaddr = require('multiaddr')
const Connection = require('interface-connection').Connection
const os = require('os')
const includes = require('lodash.includes')
const net = require('net')
const toPull = require('stream-to-pull-stream')
const EventEmitter = require('events').EventEmitter
const EventEmitter = require('events')
const debug = require('debug')
const log = debug('libp2p:tcp:listen')
const log = debug('libp2p:tcp:listener')
log.error = debug('libp2p:tcp:listener:error')
const getMultiaddr = require('./get-multiaddr')
const toConnection = require('./socket-to-conn')
const { CODE_P2P } = require('./constants')
const {
getMultiaddrs,
multiaddrToNetConfig
} = require('./utils')
const IPFS_CODE = 421
const CLOSE_TIMEOUT = 2000
function noop () {}
module.exports = (handler) => {
module.exports = ({ handler, upgrader }, options) => {
const listener = new EventEmitter()
const server = net.createServer((socket) => {
// Avoid uncaught errors cause by unstable connections
socket.on('error', noop)
const server = net.createServer(async socket => {
// Avoid uncaught errors caused by unstable connections
socket.on('error', err => log('socket error', err))
const addr = getMultiaddr(socket)
log('new connection', addr.toString())
const maConn = toConnection(socket, { listeningAddr })
log('new inbound connection %s', maConn.remoteAddr)
const s = toPull.duplex(socket)
s.getObservedAddrs = (cb) => {
cb(null, [addr])
let conn
try {
conn = await upgrader.upgradeInbound(maConn)
} catch (err) {
log.error('inbound connection failed to upgrade', err)
return maConn.close()
}
trackSocket(server, socket)
log('inbound connection %s upgraded', maConn.remoteAddr)
const conn = new Connection(s)
handler(conn)
trackConn(server, maConn)
if (handler) handler(conn)
listener.emit('connection', conn)
})
server.on('listening', () => listener.emit('listening'))
server.on('error', (err) => listener.emit('error', err))
server.on('close', () => listener.emit('close'))
server
.on('listening', () => listener.emit('listening'))
.on('error', err => listener.emit('error', err))
.on('close', () => listener.emit('close'))
// Keep track of open connections to destroy in case of timeout
server.__connections = {}
server.__connections = []
listener.close = (options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}
callback = callback || noop
options = options || {}
listener.close = () => {
if (!server.listening) return
let closed = false
server.close(callback)
server.once('close', () => {
closed = true
return new Promise((resolve, reject) => {
server.__connections.forEach(maConn => maConn.close())
server.close(err => err ? reject(err) : resolve())
})
setTimeout(() => {
if (closed) {
return
}
log('unable to close graciously, destroying conns')
Object.keys(server.__connections).forEach((key) => {
log('destroying %s', key)
server.__connections[key].destroy()
})
}, options.timeout || CLOSE_TIMEOUT)
}
let ipfsId
let listeningAddr
let peerId, listeningAddr
listener.listen = (ma, callback) => {
listener.listen = ma => {
listeningAddr = ma
if (includes(ma.protoNames(), 'ipfs')) {
ipfsId = getIpfsId(ma)
listeningAddr = ma.decapsulate('ipfs')
peerId = ma.getPeerId()
if (peerId) {
listeningAddr = ma.decapsulateCode(CODE_P2P)
}
const lOpts = listeningAddr.toOptions()
log('Listening on %s %s', lOpts.port, lOpts.host)
return server.listen(lOpts.port, lOpts.host, callback)
return new Promise((resolve, reject) => {
const options = multiaddrToNetConfig(listeningAddr)
server.listen(options, err => {
if (err) return reject(err)
log('Listening on %s', server.address())
resolve()
})
})
}
listener.getAddrs = (callback) => {
const multiaddrs = []
listener.getAddrs = () => {
let addrs = []
const address = server.address()
if (!address) {
return callback(new Error('Listener is not ready yet'))
throw new Error('Listener is not ready yet')
}
// Because TCP will only return the IPv6 version
// we need to capture from the passed multiaddr
if (listeningAddr.toString().indexOf('ip4') !== -1) {
let m = listeningAddr.decapsulate('tcp')
m = m.encapsulate('/tcp/' + address.port)
if (ipfsId) {
m = m.encapsulate('/ipfs/' + ipfsId)
}
if (m.toString().indexOf('0.0.0.0') !== -1) {
const netInterfaces = os.networkInterfaces()
Object.keys(netInterfaces).forEach((niKey) => {
netInterfaces[niKey].forEach((ni) => {
if (ni.family === 'IPv4') {
multiaddrs.push(multiaddr(m.toString().replace('0.0.0.0', ni.address)))
}
})
})
} else {
multiaddrs.push(m)
}
if (listeningAddr.toString().startsWith('/ip4')) {
addrs = addrs.concat(getMultiaddrs('ip4', address.address, address.port))
} else if (address.family === 'IPv6') {
addrs = addrs.concat(getMultiaddrs('ip6', address.address, address.port))
}
if (address.family === 'IPv6') {
let ma = multiaddr('/ip6/' + address.address + '/tcp/' + address.port)
if (ipfsId) {
ma = ma.encapsulate('/ipfs/' + ipfsId)
}
multiaddrs.push(ma)
}
callback(null, multiaddrs)
return addrs.map(ma => peerId ? ma.encapsulate(`/p2p/${peerId}`) : ma)
}
return listener
}
function getIpfsId (ma) {
return ma.stringTuples().filter((tuple) => {
return tuple[0] === IPFS_CODE
})[0][1]
}
function trackConn (server, maConn) {
server.__connections.push(maConn)
function trackSocket (server, socket) {
const key = `${socket.remoteAddress}:${socket.remotePort}`
server.__connections[key] = socket
const untrackConn = () => {
server.__connections = server.__connections.filter(c => c !== maConn)
}
socket.on('close', () => {
delete server.__connections[key]
})
maConn.conn.once('close', untrackConn)
}

101
src/socket-to-conn.js Normal file
View File

@ -0,0 +1,101 @@
'use strict'
const abortable = require('abortable-iterator')
const log = require('debug')('libp2p:tcp:socket')
const toIterable = require('stream-to-it')
const toMultiaddr = require('libp2p-utils/src/ip-port-to-multiaddr')
const { CLOSE_TIMEOUT } = require('./constants')
// Convert a socket into a MultiaddrConnection
// https://github.com/libp2p/interface-transport#multiaddrconnection
module.exports = (socket, options) => {
options = options || {}
// Check if we are connected on a unix path
if (options.listeningAddr && options.listeningAddr.getPath()) {
options.remoteAddr = options.listeningAddr
}
if (options.remoteAddr && options.remoteAddr.getPath()) {
options.localAddr = options.remoteAddr
}
const { sink, source } = toIterable.duplex(socket)
const maConn = {
async sink (source) {
if (options.signal) {
source = abortable(source, options.signal)
}
try {
await sink((async function * () {
for await (const chunk of source) {
// Convert BufferList to Buffer
yield Buffer.isBuffer(chunk) ? chunk : chunk.slice()
}
})())
} catch (err) {
// If aborted we can safely ignore
if (err.type !== 'aborted') {
// If the source errored the socket will already have been destroyed by
// toIterable.duplex(). If the socket errored it will already be
// destroyed. There's nothing to do here except log the error & return.
log(err)
}
}
},
source: options.signal ? abortable(source, options.signal) : source,
conn: socket,
localAddr: options.localAddr || toMultiaddr(socket.localAddress, socket.localPort),
// If the remote address was passed, use it - it may have the peer ID encapsulated
remoteAddr: options.remoteAddr || toMultiaddr(socket.remoteAddress, socket.remotePort),
timeline: { open: Date.now() },
close () {
if (socket.destroyed) return
return new Promise((resolve, reject) => {
const start = Date.now()
// Attempt to end the socket. If it takes longer to close than the
// timeout, destroy it manually.
const timeout = setTimeout(() => {
const { host, port } = maConn.remoteAddr.toOptions()
log('timeout closing socket to %s:%s after %dms, destroying it manually',
host, port, Date.now() - start)
if (socket.destroyed) {
log('%s:%s is already destroyed', host, port)
} else {
socket.destroy()
}
resolve()
}, CLOSE_TIMEOUT)
socket.once('close', () => clearTimeout(timeout))
socket.end(err => {
maConn.timeline.close = Date.now()
if (err) return reject(err)
resolve()
})
})
}
}
socket.once('close', () => {
// In instances where `close` was not explicitly called,
// such as an iterable stream ending, ensure we have set the close
// timeline
if (!maConn.timeline.close) {
maConn.timeline.close = Date.now()
}
})
return maConn
}

46
src/utils.js Normal file
View File

@ -0,0 +1,46 @@
'use strict'
const multiaddr = require('multiaddr')
const os = require('os')
const { resolve } = require('path')
const ProtoFamily = { ip4: 'IPv4', ip6: 'IPv6' }
function multiaddrToNetConfig (addr) {
const listenPath = addr.getPath()
// unix socket listening
if (listenPath) {
return resolve(listenPath)
}
// tcp listening
return addr.toOptions()
}
function getMultiaddrs (proto, ip, port) {
const toMa = ip => multiaddr(`/${proto}/${ip}/tcp/${port}`)
return (isAnyAddr(ip) ? getNetworkAddrs(ProtoFamily[proto]) : [ip]).map(toMa)
}
function isAnyAddr (ip) {
return ['0.0.0.0', '::'].includes(ip)
}
/**
* @private
* @param {string} family One of ['IPv6', 'IPv4']
* @returns {string[]} an array of ip address strings
*/
function getNetworkAddrs (family) {
return Object.values(os.networkInterfaces()).reduce((addresses, netAddrs) => {
netAddrs.forEach(netAddr => {
// Add the ip of each matching network interface
if (netAddr.family === family) addresses.push(netAddr.address)
})
return addresses
}, [])
}
module.exports = {
multiaddrToNetConfig,
isAnyAddr,
getMultiaddrs
}

View File

@ -1,23 +1,42 @@
/* eslint-env mocha */
'use strict'
const tests = require('interface-transport')
const sinon = require('sinon')
const tests = require('libp2p-interfaces/src/transport/tests')
const multiaddr = require('multiaddr')
const Tcp = require('../src')
const net = require('net')
const TCP = require('../src')
describe('interface-transport compliance', () => {
tests({
setup (cb) {
let tcp = new Tcp()
setup ({ upgrader }) {
const tcp = new TCP({ upgrader })
const addrs = [
multiaddr('/ip4/127.0.0.1/tcp/9091'),
multiaddr('/ip4/127.0.0.1/tcp/9092'),
multiaddr('/ip4/127.0.0.1/tcp/9093')
]
cb(null, tcp, addrs)
},
teardown (cb) {
cb()
// Used by the dial tests to simulate a delayed connect
const connector = {
delay (delayMs) {
const netConnect = net.connect
sinon.replace(net, 'connect', (opts) => {
const socket = netConnect(opts)
const socketEmit = socket.emit.bind(socket)
sinon.replace(socket, 'emit', (...args) => {
const time = args[0] === 'connect' ? delayMs : 0
setTimeout(() => socketEmit(...args), time)
})
return socket
})
},
restore () {
sinon.restore()
}
}
return { transport: tcp, addrs, connector }
}
})
})

56
test/connection.spec.js Normal file
View File

@ -0,0 +1,56 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const TCP = require('../src')
const multiaddr = require('multiaddr')
describe('valid localAddr and remoteAddr', () => {
let tcp
const mockUpgrader = {
upgradeInbound: maConn => maConn,
upgradeOutbound: maConn => maConn
}
beforeEach(() => {
tcp = new TCP({ upgrader: mockUpgrader })
})
const ma = multiaddr('/ip4/127.0.0.1/tcp/0')
it('should resolve port 0', async () => {
// Create a Promise that resolves when a connection is handled
let handled
const handlerPromise = new Promise(resolve => { handled = resolve })
const handler = conn => handled(conn)
// Create a listener with the handler
const listener = tcp.createListener(handler)
// Listen on the multi-address
await listener.listen(ma)
const localAddrs = listener.getAddrs()
expect(localAddrs.length).to.equal(1)
// Dial to that address
const dialerConn = await tcp.dial(localAddrs[0])
// Wait for the incoming dial to be handled
const listenerConn = await handlerPromise
// Close the listener
await listener.close()
expect(dialerConn.localAddr.toString())
.to.equal(listenerConn.remoteAddr.toString())
expect(dialerConn.remoteAddr.toString())
.to.equal(listenerConn.localAddr.toString())
})
})

44
test/filter.spec.js Normal file
View File

@ -0,0 +1,44 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const TCP = require('../src')
const multiaddr = require('multiaddr')
describe('filter addrs', () => {
const base = '/ip4/127.0.0.1'
const ipfs = '/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw'
let tcp
before(() => {
tcp = new TCP({ upgrader: {} })
})
it('filter valid addrs for this transport', () => {
const ma1 = multiaddr(base + '/tcp/9090')
const ma2 = multiaddr(base + '/udp/9090')
const ma3 = multiaddr(base + '/tcp/9090/http')
const ma4 = multiaddr(base + '/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw')
const ma5 = multiaddr(base + '/tcp/9090/http' + ipfs)
const ma6 = multiaddr('/ip4/127.0.0.1/tcp/9090/p2p-circuit' + ipfs)
const ma7 = multiaddr('/dns4/libp2p.io/tcp/9090')
const ma8 = multiaddr('/dnsaddr/libp2p.io/tcp/9090')
const valid = tcp.filter([ma1, ma2, ma3, ma4, ma5, ma6, ma7, ma8])
expect(valid.length).to.equal(4)
expect(valid[0]).to.deep.equal(ma1)
expect(valid[1]).to.deep.equal(ma4)
})
it('filter a single addr for this transport', () => {
const ma1 = multiaddr(base + '/tcp/9090')
const valid = tcp.filter(ma1)
expect(valid.length).to.equal(1)
expect(valid[0]).to.eql(ma1)
})
})

View File

@ -1,509 +0,0 @@
/* eslint-env mocha */
'use strict'
const pull = require('pull-stream')
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const TCP = require('../src')
const net = require('net')
const multiaddr = require('multiaddr')
const Connection = require('interface-connection').Connection
describe('instantiate the transport', () => {
it('create', () => {
const tcp = new TCP()
expect(tcp).to.exist()
})
})
describe('listen', () => {
let tcp
beforeEach(() => {
tcp = new TCP()
})
it('close listener with connections, through timeout', (done) => {
const mh = multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw')
const listener = tcp.createListener((conn) => {
pull(conn, conn)
})
listener.listen(mh, () => {
const socket1 = net.connect(9090)
const socket2 = net.connect(9090)
socket1.write('Some data that is never handled')
socket1.end()
socket1.on('error', () => {})
socket2.on('error', () => {})
socket1.on('connect', () => {
listener.close(done)
})
})
})
it('listen on port 0', (done) => {
const mh = multiaddr('/ip4/127.0.0.1/tcp/0')
const listener = tcp.createListener((conn) => {})
listener.listen(mh, () => {
listener.close(done)
})
})
it('listen on IPv6 addr', (done) => {
const mh = multiaddr('/ip6/::/tcp/9090')
const listener = tcp.createListener((conn) => {})
listener.listen(mh, () => {
listener.close(done)
})
})
it('listen on any Interface', (done) => {
const mh = multiaddr('/ip4/0.0.0.0/tcp/9090')
const listener = tcp.createListener((conn) => {})
listener.listen(mh, () => {
listener.close(done)
})
})
it('getAddrs', (done) => {
const mh = multiaddr('/ip4/127.0.0.1/tcp/9090')
const listener = tcp.createListener((conn) => {})
listener.listen(mh, () => {
listener.getAddrs((err, multiaddrs) => {
expect(err).to.not.exist()
expect(multiaddrs.length).to.equal(1)
expect(multiaddrs[0]).to.deep.equal(mh)
listener.close(done)
})
})
})
it('getAddrs on port 0 listen', (done) => {
const mh = multiaddr('/ip4/127.0.0.1/tcp/0')
const listener = tcp.createListener((conn) => {})
listener.listen(mh, () => {
listener.getAddrs((err, multiaddrs) => {
expect(err).to.not.exist()
expect(multiaddrs.length).to.equal(1)
listener.close(done)
})
})
})
it('getAddrs from listening on 0.0.0.0', (done) => {
const mh = multiaddr('/ip4/0.0.0.0/tcp/9090')
const listener = tcp.createListener((conn) => {})
listener.listen(mh, () => {
listener.getAddrs((err, multiaddrs) => {
expect(err).to.not.exist()
expect(multiaddrs.length > 0).to.equal(true)
expect(multiaddrs[0].toString().indexOf('0.0.0.0')).to.equal(-1)
listener.close(done)
})
})
})
it('getAddrs from listening on 0.0.0.0 and port 0', (done) => {
const mh = multiaddr('/ip4/0.0.0.0/tcp/0')
const listener = tcp.createListener((conn) => {})
listener.listen(mh, () => {
listener.getAddrs((err, multiaddrs) => {
expect(err).to.not.exist()
expect(multiaddrs.length > 0).to.equal(true)
expect(multiaddrs[0].toString().indexOf('0.0.0.0')).to.equal(-1)
listener.close(done)
})
})
})
it('getAddrs preserves IPFS Id', (done) => {
const mh = multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw')
const listener = tcp.createListener((conn) => {})
listener.listen(mh, () => {
listener.getAddrs((err, multiaddrs) => {
expect(err).to.not.exist()
expect(multiaddrs.length).to.equal(1)
expect(multiaddrs[0]).to.deep.equal(mh)
listener.close(done)
})
})
})
})
describe('dial', () => {
let tcp
let listener
const ma = multiaddr('/ip4/127.0.0.1/tcp/9090')
beforeEach((done) => {
tcp = new TCP()
listener = tcp.createListener((conn) => {
pull(
conn,
pull.map((x) => Buffer.from(x.toString() + '!')),
conn
)
})
listener.listen(ma, done)
})
afterEach((done) => {
listener.close(done)
})
it('dial on IPv4', (done) => {
pull(
pull.values(['hey']),
tcp.dial(ma),
pull.collect((err, values) => {
expect(err).to.not.exist()
expect(values).to.eql([Buffer.from('hey!')])
done()
})
)
})
it('dial to non existent listener', (done) => {
const ma = multiaddr('/ip4/127.0.0.1/tcp/8989')
pull(
tcp.dial(ma),
pull.onEnd((err) => {
expect(err).to.exist()
done()
})
)
})
it('dial on IPv6', (done) => {
const ma = multiaddr('/ip6/::/tcp/9066')
const listener = tcp.createListener((conn) => {
pull(conn, conn)
})
listener.listen(ma, () => {
pull(
pull.values(['hey']),
tcp.dial(ma),
pull.collect((err, values) => {
expect(err).to.not.exist()
expect(values).to.be.eql([Buffer.from('hey')])
listener.close(done)
})
)
})
})
it.skip('dial and destroy on listener', (done) => {
// TODO: why is this failing
let count = 0
const closed = ++count === 2 ? finish() : null
const ma = multiaddr('/ip6/::/tcp/9067')
const listener = tcp.createListener((conn) => {
pull(
pull.empty(),
conn,
pull.onEnd(closed)
)
})
listener.listen(ma, () => {
pull(tcp.dial(ma), pull.onEnd(closed))
})
function finish () {
listener.close(done)
}
})
it('dial and destroy on dialer', (done) => {
let count = 0
const destroyed = () => ++count === 2 ? finish() : null
const ma = multiaddr('/ip6/::/tcp/9068')
const listener = tcp.createListener((conn) => {
pull(conn, pull.onEnd(destroyed))
})
listener.listen(ma, () => {
pull(
pull.empty(),
tcp.dial(ma),
pull.onEnd(destroyed)
)
})
function finish () {
listener.close(done)
}
})
it('dial on IPv4 with IPFS Id', (done) => {
const ma = multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw')
const conn = tcp.dial(ma)
pull(
pull.values(['hey']),
conn,
pull.collect((err, res) => {
expect(err).to.not.exist()
expect(res).to.be.eql([Buffer.from('hey!')])
done()
})
)
})
})
describe('filter addrs', () => {
let tcp
before(() => {
tcp = new TCP()
})
it('filter valid addrs for this transport', () => {
const mh1 = multiaddr('/ip4/127.0.0.1/tcp/9090')
const mh2 = multiaddr('/ip4/127.0.0.1/udp/9090')
const mh3 = multiaddr('/ip4/127.0.0.1/tcp/9090/http')
const mh4 = multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw')
const mh5 = multiaddr('/ip4/127.0.0.1/tcp/9090/http/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw')
const mh6 = multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw' +
'/p2p-circuit/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw')
const valid = tcp.filter([mh1, mh2, mh3, mh4, mh5, mh6])
expect(valid.length).to.equal(2)
expect(valid[0]).to.deep.equal(mh1)
expect(valid[1]).to.deep.equal(mh4)
})
it('filter a single addr for this transport', () => {
const mh1 = multiaddr('/ip4/127.0.0.1/tcp/9090')
const valid = tcp.filter(mh1)
expect(valid.length).to.equal(1)
expect(valid[0]).to.deep.equal(mh1)
})
})
describe('valid Connection', () => {
let tcp
beforeEach(() => {
tcp = new TCP()
})
const ma = multiaddr('/ip4/127.0.0.1/tcp/9090')
it('get observed addrs', (done) => {
let dialerObsAddrs
const listener = tcp.createListener((conn) => {
expect(conn).to.exist()
conn.getObservedAddrs((err, addrs) => {
expect(err).to.not.exist()
dialerObsAddrs = addrs
pull(pull.empty(), conn)
})
})
listener.listen(ma, () => {
const conn = tcp.dial(ma)
pull(
conn,
pull.onEnd(endHandler)
)
function endHandler () {
conn.getObservedAddrs((err, addrs) => {
expect(err).to.not.exist()
pull(pull.empty(), conn)
closeAndAssert(listener, addrs)
})
}
function closeAndAssert (listener, addrs) {
listener.close(() => {
expect(addrs[0]).to.deep.equal(ma)
expect(dialerObsAddrs.length).to.equal(1)
done()
})
}
})
})
it('get Peer Info', (done) => {
const listener = tcp.createListener((conn) => {
expect(conn).to.exist()
conn.getPeerInfo((err, peerInfo) => {
expect(err).to.exist()
expect(peerInfo).to.not.exist()
pull(pull.empty(), conn)
})
})
listener.listen(ma, () => {
const conn = tcp.dial(ma)
pull(conn, pull.onEnd(endHandler))
function endHandler () {
conn.getPeerInfo((err, peerInfo) => {
expect(err).to.exist()
expect(peerInfo).to.not.exist()
listener.close(done)
})
}
})
})
it('set Peer Info', (done) => {
const listener = tcp.createListener((conn) => {
expect(conn).to.exist()
conn.setPeerInfo('batatas')
conn.getPeerInfo((err, peerInfo) => {
expect(err).to.not.exist()
expect(peerInfo).to.equal('batatas')
pull(pull.empty(), conn)
})
})
listener.listen(ma, () => {
const conn = tcp.dial(ma)
pull(conn, pull.onEnd(endHandler))
function endHandler () {
conn.setPeerInfo('arroz')
conn.getPeerInfo((err, peerInfo) => {
expect(err).to.not.exist()
expect(peerInfo).to.equal('arroz')
listener.close(done)
})
}
})
})
})
describe.skip('turbolence', () => {
it('dialer - emits error on the other end is terminated abruptly', (done) => {})
it('listener - emits error on the other end is terminated abruptly', (done) => {})
})
describe('Connection wrap', () => {
let tcp
let listener
const ma = multiaddr('/ip4/127.0.0.1/tcp/9090')
beforeEach((done) => {
tcp = new TCP()
listener = tcp.createListener((conn) => {
pull(conn, conn)
})
listener.on('listening', done)
listener.listen(ma)
})
afterEach((done) => {
listener.close(done)
})
it('simple wrap', (done) => {
const conn = tcp.dial(ma)
conn.setPeerInfo('peerInfo')
const connWrap = new Connection(conn)
pull(
pull.values(['hey']),
connWrap,
pull.collect((err, chunks) => {
expect(err).to.not.exist()
expect(chunks).to.be.eql([Buffer.from('hey')])
connWrap.getPeerInfo((err, peerInfo) => {
expect(err).to.not.exist()
expect(peerInfo).to.equal('peerInfo')
done()
})
})
)
})
it('buffer wrap', (done) => {
const conn = tcp.dial(ma)
const connWrap = new Connection()
pull(
pull.values(['hey']),
connWrap,
pull.collect((err, chunks) => {
expect(err).to.not.exist()
expect(chunks).to.be.eql([Buffer.from('hey')])
done()
})
)
connWrap.setInnerConn(conn)
})
it('overload wrap', (done) => {
const conn = tcp.dial(ma)
const connWrap = new Connection(conn)
connWrap.getPeerInfo = (callback) => {
callback(null, 'none')
}
conn.getPeerInfo((err, peerInfo) => {
expect(err).to.exist()
})
connWrap.getPeerInfo((err, peerInfo) => {
expect(err).to.not.exist()
expect(peerInfo).to.equal('none')
})
pull(
pull.values(['hey']),
connWrap,
pull.collect((err, chunks) => {
expect(err).to.not.exist()
expect(chunks).to.be.eql([Buffer.from('hey')])
done()
})
)
})
it('dial error', (done) => {
tcp.dial(multiaddr('/ip4/999.0.0.1/tcp/1234'), (err) => {
expect(err).to.exist()
done()
})
})
it('matryoshka wrap', (done) => {
const conn = tcp.dial(ma)
const connWrap1 = new Connection(conn)
const connWrap2 = new Connection(connWrap1)
const connWrap3 = new Connection(connWrap2)
conn.getPeerInfo = (callback) => {
callback(null, 'inner doll')
}
pull(
pull.values(['hey']),
connWrap3,
pull.collect((err, chunks) => {
expect(err).to.not.exist()
expect(chunks).to.be.eql([Buffer.from('hey')])
connWrap3.getPeerInfo((err, peerInfo) => {
expect(err).to.not.exist()
expect(peerInfo).to.equal('inner doll')
done()
})
})
)
})
})

281
test/listen-dial.spec.js Normal file
View File

@ -0,0 +1,281 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const TCP = require('../src')
const net = require('net')
const os = require('os')
const path = require('path')
const multiaddr = require('multiaddr')
const pipe = require('it-pipe')
const { collect, map } = require('streaming-iterables')
const isCI = process.env.CI
const isWindows = os.platform() === 'win32'
const skipOnWindows = isWindows ? it.skip : it
describe('construction', () => {
it('requires an upgrader', () => {
expect(() => new TCP()).to.throw()
})
})
describe('listen', () => {
let tcp
let listener
beforeEach(() => {
tcp = new TCP({
upgrader: {
upgradeOutbound: maConn => maConn,
upgradeInbound: maConn => maConn
}
})
})
afterEach(async () => {
listener && await listener.close()
})
it('close listener with connections, through timeout', async () => {
const mh = multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw')
listener = tcp.createListener((conn) => {
pipe(conn, conn)
})
await listener.listen(mh)
const socket1 = net.connect(9090)
const socket2 = net.connect(9090)
socket1.write('Some data that is never handled')
socket1.end()
socket1.on('error', () => {})
socket2.on('error', () => {})
await new Promise((resolve) => {
socket1.on('connect', async () => {
await listener.close({ timeout: 100 })
resolve()
})
})
})
// Windows doesn't support unix paths
skipOnWindows('listen on path', async () => {
const mh = multiaddr(`/unix${path.resolve(os.tmpdir(), '/tmp/p2pd.sock')}`)
listener = tcp.createListener((conn) => {})
await listener.listen(mh)
})
it('listen on port 0', async () => {
const mh = multiaddr('/ip4/127.0.0.1/tcp/0')
listener = tcp.createListener((conn) => {})
await listener.listen(mh)
})
it('listen on IPv6 addr', async () => {
if (isCI) {
return
}
const mh = multiaddr('/ip6/::/tcp/9090')
listener = tcp.createListener((conn) => {})
await listener.listen(mh)
})
it('listen on any Interface', async () => {
const mh = multiaddr('/ip4/0.0.0.0/tcp/9090')
listener = tcp.createListener((conn) => {})
await listener.listen(mh)
})
it('getAddrs', async () => {
const mh = multiaddr('/ip4/127.0.0.1/tcp/9090')
listener = tcp.createListener((conn) => {})
await listener.listen(mh)
const multiaddrs = listener.getAddrs()
expect(multiaddrs.length).to.equal(1)
expect(multiaddrs[0]).to.deep.equal(mh)
})
it('getAddrs on port 0 listen', async () => {
const mh = multiaddr('/ip4/127.0.0.1/tcp/0')
listener = tcp.createListener((conn) => {})
await listener.listen(mh)
const multiaddrs = listener.getAddrs()
expect(multiaddrs.length).to.equal(1)
})
it('getAddrs from listening on 0.0.0.0', async () => {
const mh = multiaddr('/ip4/0.0.0.0/tcp/9090')
listener = tcp.createListener((conn) => {})
await listener.listen(mh)
const multiaddrs = listener.getAddrs()
expect(multiaddrs.length > 0).to.equal(true)
expect(multiaddrs[0].toString().indexOf('0.0.0.0')).to.equal(-1)
})
it('getAddrs from listening on 0.0.0.0 and port 0', async () => {
const mh = multiaddr('/ip4/0.0.0.0/tcp/0')
listener = tcp.createListener((conn) => {})
await listener.listen(mh)
const multiaddrs = listener.getAddrs()
expect(multiaddrs.length > 0).to.equal(true)
expect(multiaddrs[0].toString().indexOf('0.0.0.0')).to.equal(-1)
})
it('getAddrs preserves IPFS Id', async () => {
const mh = multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw')
listener = tcp.createListener((conn) => {})
await listener.listen(mh)
const multiaddrs = listener.getAddrs()
expect(multiaddrs.length).to.equal(1)
expect(multiaddrs[0]).to.deep.equal(mh)
})
})
describe('dial', () => {
let tcp
let listener
const ma = multiaddr('/ip4/127.0.0.1/tcp/9090')
beforeEach(async () => {
tcp = new TCP({
upgrader: {
upgradeOutbound: maConn => maConn,
upgradeInbound: maConn => maConn
}
})
listener = tcp.createListener((conn) => {
pipe(
conn,
map((x) => Buffer.from(x.toString() + '!')),
conn
)
})
await listener.listen(ma)
})
afterEach(() => listener.close())
it('dial on IPv4', async () => {
const values = await pipe(
['hey'],
await tcp.dial(ma),
collect
)
expect(values).to.eql([Buffer.from('hey!')])
})
it('dial on IPv6', async () => {
if (isCI) {
return
}
const ma = multiaddr('/ip6/::/tcp/9066')
const listener = tcp.createListener((conn) => {
pipe(conn, conn)
})
await listener.listen(ma)
const values = await pipe(
['hey'],
await tcp.dial(ma),
collect
)
expect(values).to.be.eql([Buffer.from('hey')])
await listener.close()
})
// Windows doesn't support unix paths
skipOnWindows('dial on path', async () => {
const ma = multiaddr(`/unix${path.resolve(os.tmpdir(), '/tmp/p2pd.sock')}`)
const listener = tcp.createListener((conn) => {
pipe(conn, conn)
})
await listener.listen(ma)
const connection = await tcp.dial(ma)
const values = await pipe(
['hey'],
connection,
collect
)
expect(values).to.be.eql([Buffer.from('hey')])
await listener.close()
})
it('dial and destroy on listener', async () => {
let handled
const handledPromise = new Promise((resolve) => {
handled = resolve
})
const ma = multiaddr('/ip6/::/tcp/0')
const listener = tcp.createListener(async (conn) => {
await pipe(
[],
conn
)
handled()
})
await listener.listen(ma)
const addrs = listener.getAddrs()
await pipe(await tcp.dial(addrs[0]))
await handledPromise
await listener.close()
})
it('dial and destroy on dialer', async () => {
if (isCI) {
return
}
let handled
const handledPromise = new Promise((resolve) => {
handled = resolve
})
const ma = multiaddr('/ip6/::/tcp/0')
const listener = tcp.createListener(async (conn) => {
// pull(conn, pull.onEnd(destroyed))
await pipe(conn)
handled()
})
await listener.listen(ma)
const addrs = listener.getAddrs()
await pipe(await tcp.dial(addrs[0]))
await handledPromise
await listener.close()
})
it('dial on IPv4 with IPFS Id', async () => {
const ma = multiaddr('/ip4/127.0.0.1/tcp/9090/ipfs/Qmb6owHp6eaWArVbcJJbQSyifyJBttMMjYV76N2hMbf5Vw')
const conn = await tcp.dial(ma)
const res = await pipe(
['hey'],
conn,
collect
)
expect(res).to.be.eql([Buffer.from('hey!')])
})
})