Skip to content
This repository has been archived by the owner on Mar 11, 2020. It is now read-only.

Commit

Permalink
chore: add socket concept
Browse files Browse the repository at this point in the history
  • Loading branch information
vasco-santos committed May 28, 2019
1 parent 355e036 commit c7299d1
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 50 deletions.
122 changes: 77 additions & 45 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ This is a test suite and interface you can use to implement a connection. The co

The primary goal of this module is to enable developers to pick, swap or upgrade their connection without losing the same API expectations and mechanisms such as back pressure and the ability to half close a connection.

Publishing a test suite as a module lets multiple modules all ensure compatibility since they use the same test suite.
Publishing a test suite as a module lets multiple modules ensure compatibility since they use the same test suite.

The API is presented with both JS and Go primitives, however there is no actual limitations for it to be extended to any other language, pushing forward the cross compatibility and interop through diferent stacks.
The API is presented with both JS and Go primitives, however there is no actual limitations for it to be extended to any other language, pushing forward the cross compatibility and interop through different stacks.

## Lead Maintainer

[Jacob Heun](https://github.com/jacobheun/)

# Modules that implement the interface
## Modules that implement the interface

- [js-libp2p-tcp](https://github.com/libp2p/js-libp2p-tcp)
- [js-libp2p-webrtc-star](https://github.com/libp2p/js-libp2p-webrtc-star)
Expand All @@ -29,15 +29,56 @@ The API is presented with both JS and Go primitives, however there is no actual
- [js-libp2p-multiplex](https://github.com/libp2p/js-libp2p-multiplex)
- [js-libp2p-secio](https://github.com/libp2p/js-libp2p-secio)

# Badge
## Badge

Include this badge in your readme if you make a module that is compatible with the `interface-connection` API. You can validate this by running the tests.

![](https://raw.githubusercontent.com/diasdavid/interface-connection/master/img/badge.png)

# How to use the battery of tests
## Usage

## JS
### Connection

Before creating a connection from a transport compatible with `libp2p` it is important to understand some concepts:

- **socket**: the underlying raw duplex connection between two nodes. It is created by the transports during a dial/listen.
- **connection**: the abstract libp2p duplex connection between two nodes. The transport must pipe the `socket` through the connection.
- **stream**: a single duplex channel of the `connection`. Each connection may have many streams.

A connection stands for the libp2p communication duplex layer between two nodes. It is **not** the underlying raw transport duplex layer (socket), such as a TCP socket, but an abstracted duplex layer that sits on top of the raw socket.

When a libp2p transport creates its socket, a new `connection` instance should be created and the transport should **pipe** both input and output of the socket through this `connection`, i.e. `pipe(socket, connection, socket)`.

The transport must handle the translation of cleanup from the socket to the connection. That is, the errors, resets or closes on the socket **must** be passed to the connection. In the same way, the transport **must** map these actions from the `connection` to the socket. This helps ensuring that the transport is responsible for socket management, while also allowing the application layer to handle the connection management.

```js
const pipe = require('it-pipe')
const { Connection } = require('interface-connection')

class Transport {
async dial () {
// ...

// create the raw socket and the connection
const socket = await this._connect()
const conn = new Connection(remotePeerInfo, remoteMa)

// pipe the socket through the connection (if necessary include a conversion to iterable duplex streams)
pipe(socket, conn, socket)

// bind the necessary handlers to update state changes (error, close, reset, ...)
// ...

return conn
}

_connect () {}
}
```

### Test suite

#### JS

```js
describe('your connection', () => {
Expand All @@ -52,13 +93,13 @@ describe('your connection', () => {
})
```

## Go
#### Go

> WIP
# API
## API

## Connection
### Connection

A valid connection (one that follows this abstraction), must implement the following API:

Expand All @@ -76,7 +117,7 @@ It can be obtained as follows:
const { Connection } = require('interface-connection')
```

### Creating a connection instance
#### Creating a connection instance

- `JavaScript` - `const conn = new Connection(remotePeerInfo, remoteMa, isInitiator = true)`

Expand All @@ -86,7 +127,7 @@ Creates a new Connection instance.
`remoteMa` is the [multiaddr](https://github.com/multiformats/multiaddr) address used to communicate with the remote peer.
`isInitiator` is a `boolean` indicating whether the peer creating the Connection instance initiated the connection. Default value: `true`.

### Update connection metadata after connection upgrade
#### Update connection metadata after connection upgrade

- `JavaScript` - `conn.upgraded(multiplexer, encryption)`

Expand All @@ -95,15 +136,15 @@ Updates the connection metadata after being upgraded through the negotiation of
`multiplexer` is a stream muxer implementing the [interface-stream-muxer](https://github.com/libp2p/interface-stream-muxer).
`encryption` is a `string` with the encryption protocol. Example: `/secio/1.0.0`.

### Set local address
#### Set local address

- `JavaScript` - `conn.setLocalAddress(multiaddr)`

Set the local address used in the connection. It is obtained after running `identify`.

`multiaddr` is the local peer [multiaddr](https://github.com/multiformats/multiaddr) used.

### Create a new stream
#### Create a new stream

- `JavaScript` - `conn.newStream(protocol, options)`

Expand All @@ -115,77 +156,77 @@ Create a new stream within the connection.

It returns a `Promise` with the instance of the created `Stream`.

### Get the connection Streams
#### Get the connection Streams

- `JavaScript` - `conn.getStreams()`

Get all the streams associated with this connection.

It returns an `Array` with the instance of all the streams created in this connection.

### Close connection
#### Close connection

- `JavaScript` - `conn.close()`

This method closes the connection to the remote peer, as well as all the streams running in the connection.

It returns a `Promise`.

### Connection identifier
#### Connection identifier

- `JavaScript` - `conn.id`

This property contains the identifier of the connection.

### Remote peer info
#### Remote peer info

- `JavaScript` - `conn.peerInfo`

This property contains the remote peer info of this connection.

### Status of the connection
#### Status of the connection

- `JavaScript` - `conn.status`

This property contains the status of the connection. It can be either `OPEN`, `CLOSED` or `CLOSING`. Once the creating is created it is in an `OPEN` status. When a `conn.close()` happens, the status will change to `CLOSING` and finally, after all the connection streams are properly closed, the status will be `CLOSED`.
This property contains the status of the connection. It can be either `OPEN`, `CLOSED` or `CLOSING`. Once the connection is created it is in an `OPEN` status. When a `conn.close()` happens, the status will change to `CLOSING` and finally, after all the connection streams are properly closed, the status will be `CLOSED`.

### Endpoints multiaddr
#### Endpoints multiaddr

- `JavaScript` - `conn.endpoints`

This property contains an object with the `local` and `remote` multiaddrs as properties. The `local` multiaddr is `undefined` until the connection is upgraded and `identify` finishes.

### Timeline
#### Timeline

- `JavaScript` - `conn.timeline`

This property contains an object with the `open` and `close` timestamps of the connection. The `close` timestamp is `undefined` until the connection is closed.

### Role of the connection
#### Role of the connection

- `JavaScript` - `conn.role`

This property contains the role of the peer in the connection. It can be `INITIATOR` or `RESPONDER`.

### Multiplexer
#### Multiplexer

- `JavaScript` - `conn.multiplexer`

This property contains a reference to the `multiplexer` being used in the connection. It is `undefined` until the connection has been upgraded.

### Encryption
#### Encryption

- `JavaScript` - `conn.encryption`

This property contains the encryption method being used in the connection. It is `undefined` until the connection has been upgraded.

### Tags
#### Tags

- `JavaScript` - `conn.tags`

This property contains an array of tags associated with the connection. New tags can be pushed to this array during the connection's lifetime.

## Stream
### Stream

A valid stream (one that follows this abstraction), must implement the following API:

Expand All @@ -201,7 +242,7 @@ It can be obtained as follows:
const { Stream } = require('interface-connection')
```

### Creating a stream instance
#### Creating a stream instance

- `JavaScript` - `const stream = new Stream(iterableDuplex, connection, isInitator, options)`

Expand All @@ -214,51 +255,51 @@ Creates a new Stream instance.
`options` is an object containing the stream options.
`options.signal` is a boolean for using the abortable signal. Default value: `true`.

### Get a connection Source
#### Get a connection Source

- `JavaScript` - `stream.source`

This getter returns a reference to the connection "source", which is an iterable object that can be consumed.

### Get a connection data collector
#### Get a connection data collector

- `JavaScript` - `stream.sink`

This getter returns a reference to the connection "sink", which is an iterator that consumes (or drains) a source.

### Close connection
#### Close connection

- `JavaScript` - `stream.close()`

This method closes a stream with the other peer.

It returns a `Promise`.

### Stream identifier
#### Stream identifier

- `JavaScript` - `stream.id`

This property contains the identifier of the stream.

### Stream associated connection
#### Stream associated connection

- `JavaScript` - `stream.connection`

This property contains the connection associated with this stream.

### Role of the strean
#### Role of the strean

- `JavaScript` - `stream.role`

This property contains the role of the peer in the stream. It can be `INITIATOR` or `RESPONDER`.

### Timeline
#### Timeline

- `JavaScript` - `stream.timeline`

This property contains an object with the `open` and `close` timestamps of the stream. The `close` timestamp is `undefined` until the stream is closed.

## Role
### Role

It can be obtained as follows:

Expand All @@ -269,7 +310,7 @@ const { ROLE } = require('interface-connection')
// ROLE.RESPONDER
```

## Status
### Status

It can be obtained as follows:

Expand All @@ -280,12 +321,3 @@ const { STATUS } = require('interface-connection')
// STATUS.CLOSED
// STATUS.CLOSING
```

---

notes:
- should follow the remaining Duplex stream operations
- should have backpressure into account
- should enable half duplex streams (close from one side, but still open for the other)
- should support full duplex
- tests should be performed by passing two streams
26 changes: 21 additions & 5 deletions src/connection.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict'

const { Duplex } = require('stream')

const PeerInfo = require('peer-info')
const multiaddr = require('multiaddr')
const multistream = require('multistream-select')
Expand All @@ -16,15 +18,19 @@ const defaultMaxNumberOfAttemptsForHasMultiplexer = 5

/**
* An implementation of the js-libp2p connection.
* This is an abstract duplex connection between two nodes and
* each transport must pipe its socket through this.
*/
class Connection {
class Connection extends Duplex {
/**
* Creates an instance of Connection.
* @param {PeerInfo} peerInfo remote peer PeerInfo
* @param {multiaddr} remoteMa remote peer multiaddr
* @param {boolean} [isInitiator=true] peer initiated the connection
*/
constructor (peerInfo, remoteMa, isInitiator = true) {
super()

assert(PeerInfo.isPeerInfo(peerInfo), 'peerInfo must be an instance of PeerInfo')
assert(multiaddr.isMultiaddr(remoteMa), 'remoteMa must be an instance of multiaddr')
assert(typeof isInitiator === 'boolean', 'isInitiator must be a boolean')
Expand Down Expand Up @@ -190,16 +196,26 @@ class Connection {
return
}

if (this.status !== STATUS.CLOSING) {
this.status = STATUS.CLOSING
this.timeline.close = Date.now()
if (this._closing) {
return this._closing
}

this.status = STATUS.CLOSING

// Close all streams
await Promise.all(this._streams.map((stream) => stream.close()))
this._closing = await this._closeStreams()

this.timeline.close = Date.now()
this.status = STATUS.CLOSED
}

/**
* Close all the streams associated with this connection.
* @return {Promise}
*/
_closeStreams () {
return Promise.all(this._streams.map((stream) => stream.close()))
}
}

module.exports = withIs(Connection, { className: 'Connection', symbolName: '@libp2p/interface-connection/connection' })

0 comments on commit c7299d1

Please sign in to comment.