@libp2p/mplex 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +4 -0
- package/README.md +186 -0
- package/dist/src/decode.d.ts +7 -0
- package/dist/src/decode.d.ts.map +1 -0
- package/dist/src/decode.js +93 -0
- package/dist/src/decode.js.map +1 -0
- package/dist/src/encode.d.ts +7 -0
- package/dist/src/encode.d.ts.map +1 -0
- package/dist/src/encode.js +65 -0
- package/dist/src/encode.js.map +1 -0
- package/dist/src/index.d.ts +66 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +218 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/message-types.d.ts +51 -0
- package/dist/src/message-types.d.ts.map +1 -0
- package/dist/src/message-types.js +31 -0
- package/dist/src/message-types.js.map +1 -0
- package/dist/src/restrict-size.d.ts +9 -0
- package/dist/src/restrict-size.d.ts.map +1 -0
- package/dist/src/restrict-size.js +32 -0
- package/dist/src/restrict-size.js.map +1 -0
- package/dist/src/stream.d.ts +12 -0
- package/dist/src/stream.d.ts.map +1 -0
- package/dist/src/stream.js +146 -0
- package/dist/src/stream.js.map +1 -0
- package/package.json +169 -0
- package/src/decode.ts +132 -0
- package/src/encode.ts +79 -0
- package/src/index.ts +281 -0
- package/src/message-types.ts +79 -0
- package/src/restrict-size.ts +36 -0
- package/src/stream.ts +182 -0
package/LICENSE
ADDED
package/README.md
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
# js-libp2p-mplex
|
2
|
+
|
3
|
+
[](http://protocol.ai)
|
4
|
+
[](http://libp2p.io/)
|
5
|
+
[](http://webchat.freenode.net/?channels=%23libp2p)
|
6
|
+
[](https://discuss.libp2p.io)
|
7
|
+
[](https://codecov.io/gh/libp2p/js-libp2p-mplex)
|
8
|
+
[](https://github.com/libp2p/js-libp2p-mplex/actions/workflows/js-test-and-release.yml)
|
9
|
+
[](https://david-dm.org/libp2p/js-libp2p-mplex)
|
10
|
+
[](https://github.com/feross/standard)
|
11
|
+

|
12
|
+

|
13
|
+
|
14
|
+
> JavaScript implementation of [mplex](https://github.com/libp2p/specs/tree/master/mplex).
|
15
|
+
|
16
|
+
[](https://github.com/libp2p/interface-stream-muxer)
|
17
|
+
|
18
|
+
## Install
|
19
|
+
|
20
|
+
```sh
|
21
|
+
npm install @libp2p/mplex
|
22
|
+
```
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
```js
|
27
|
+
import { Mplex } from '@libp2p/mplex'
|
28
|
+
import { pipe } from 'it-pipe'
|
29
|
+
|
30
|
+
const muxer = new Mplex({
|
31
|
+
onStream: stream => { // Receive a duplex stream from the remote
|
32
|
+
// ...receive data from the remote and optionally send data back
|
33
|
+
},
|
34
|
+
onStreamEnd: stream => {
|
35
|
+
// ...handle any tracking you may need of stream closures
|
36
|
+
}
|
37
|
+
})
|
38
|
+
|
39
|
+
pipe(conn, muxer, conn) // conn is duplex connection to another peer
|
40
|
+
|
41
|
+
const stream = muxer.newStream() // Create a new duplex stream to the remote
|
42
|
+
|
43
|
+
// Use the duplex stream to send some data to the remote...
|
44
|
+
pipe([1, 2, 3], stream)
|
45
|
+
```
|
46
|
+
|
47
|
+
## API
|
48
|
+
|
49
|
+
### `const muxer = new Mplex([options])`
|
50
|
+
|
51
|
+
Create a new _duplex_ stream that can be piped together with a connection in order to allow multiplexed communications.
|
52
|
+
|
53
|
+
e.g.
|
54
|
+
|
55
|
+
```js
|
56
|
+
import { Mplex } from '@libp2p/mplex'
|
57
|
+
import { pipe } from 'it-pipe'
|
58
|
+
|
59
|
+
// Create a duplex muxer
|
60
|
+
const muxer = new Mplex()
|
61
|
+
|
62
|
+
// Use the muxer in a pipeline
|
63
|
+
pipe(conn, muxer, conn) // conn is duplex connection to another peer
|
64
|
+
```
|
65
|
+
|
66
|
+
`options` is an optional `Object` that may have the following properties:
|
67
|
+
|
68
|
+
* `onStream` - A function called when receiving a new stream from the remote. e.g.
|
69
|
+
```js
|
70
|
+
// Receive a new stream on the muxed connection
|
71
|
+
const onStream = stream => {
|
72
|
+
// Read from this stream and write back to it (echo server)
|
73
|
+
pipe(
|
74
|
+
stream,
|
75
|
+
source => (async function * () {
|
76
|
+
for await (const data of source) yield data
|
77
|
+
})(),
|
78
|
+
stream
|
79
|
+
)
|
80
|
+
}
|
81
|
+
const muxer = new Mplex({ onStream })
|
82
|
+
// ...
|
83
|
+
```
|
84
|
+
**Note:** The `onStream` function can be passed in place of the `options` object. i.e.
|
85
|
+
```js
|
86
|
+
new Mplex(stream => { /* ... */ })
|
87
|
+
```
|
88
|
+
* `onStreamEnd` - A function called when a stream ends
|
89
|
+
```js
|
90
|
+
// Receive a notification when a stream ends
|
91
|
+
const onStreamEnd = stream => {
|
92
|
+
// Manage any tracking changes, etc
|
93
|
+
}
|
94
|
+
const muxer = new Mplex({ onStreamEnd })
|
95
|
+
// ...
|
96
|
+
```
|
97
|
+
* `signal` - An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) which can be used to abort the muxer, _including_ all of it's multiplexed connections. e.g.
|
98
|
+
```js
|
99
|
+
const controller = new AbortController()
|
100
|
+
const muxer = new Mplex({ signal: controller.signal })
|
101
|
+
|
102
|
+
pipe(conn, muxer, conn)
|
103
|
+
|
104
|
+
controller.abort()
|
105
|
+
```
|
106
|
+
* `maxMsgSize` - The maximum size in bytes the data field of multiplexed messages may contain (default 1MB)
|
107
|
+
|
108
|
+
### `muxer.onStream`
|
109
|
+
|
110
|
+
Use this property as an alternative to passing `onStream` as an option to the `Mplex` constructor.
|
111
|
+
|
112
|
+
### `muxer.onStreamEnd`
|
113
|
+
|
114
|
+
Use this property as an alternative to passing `onStreamEnd` as an option to the `Mplex` constructor.
|
115
|
+
|
116
|
+
### `muxer.streams`
|
117
|
+
|
118
|
+
Returns an `Array` of streams that are currently open. Closed streams will not be returned.
|
119
|
+
|
120
|
+
### `const stream = muxer.newStream([options])`
|
121
|
+
|
122
|
+
Initiate a new stream with the remote. Returns a [duplex stream](https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#duplex-it).
|
123
|
+
|
124
|
+
e.g.
|
125
|
+
|
126
|
+
```js
|
127
|
+
// Create a new stream on the muxed connection
|
128
|
+
const stream = muxer.newStream()
|
129
|
+
|
130
|
+
// Use this new stream like any other duplex stream:
|
131
|
+
pipe([1, 2, 3], stream, consume)
|
132
|
+
```
|
133
|
+
|
134
|
+
In addition to `sink` and `source` properties, this stream also has the following API, that will **normally _not_ be used by stream consumers**.
|
135
|
+
|
136
|
+
#### `stream.close()`
|
137
|
+
|
138
|
+
Closes the stream for **reading**. If iterating over the source of this stream in a `for await of` loop, it will return (exit the loop) after any buffered data has been consumed.
|
139
|
+
|
140
|
+
This function is called automatically by the muxer when it receives a `CLOSE` message from the remote.
|
141
|
+
|
142
|
+
The source will return normally, the sink will continue to consume.
|
143
|
+
|
144
|
+
#### `stream.abort([err])`
|
145
|
+
|
146
|
+
Closes the stream for **reading** _and_ **writing**. This should be called when a _local error_ has occurred.
|
147
|
+
|
148
|
+
Note, if called without an error any buffered data in the source can still be consumed and the stream will end normally.
|
149
|
+
|
150
|
+
This will cause a `RESET` message to be sent to the remote, _unless_ the sink has already ended.
|
151
|
+
|
152
|
+
The sink will return and the source will throw if an error is passed or return normally if not.
|
153
|
+
|
154
|
+
#### `stream.reset()`
|
155
|
+
|
156
|
+
Closes the stream _immediately_ for **reading** _and_ **writing**. This should be called when a _remote error_ has occurred.
|
157
|
+
|
158
|
+
This function is called automatically by the muxer when it receives a `RESET` message from the remote.
|
159
|
+
|
160
|
+
The sink will return and the source will throw.
|
161
|
+
|
162
|
+
#### `stream.timeline`
|
163
|
+
|
164
|
+
Returns an `object` with `close` and `open` times of the stream.
|
165
|
+
|
166
|
+
#### `stream.id`
|
167
|
+
|
168
|
+
Returns a `string` with an identifier unique to **this** muxer. Identifiers are not unique across muxers.
|
169
|
+
|
170
|
+
## Contribute
|
171
|
+
|
172
|
+
The libp2p implementation in JavaScript is a work in progress. As such, there are a few things you can do right now to help out:
|
173
|
+
|
174
|
+
- Go through the modules and **check out existing issues**. This is especially useful for modules in active development. Some knowledge of IPFS/libp2p may be required, as well as the infrastructure behind it - for instance, you may need to read up on p2p and more complex operations like muxing to be able to help technically.
|
175
|
+
- **Perform code reviews**. More eyes will help a) speed the project along b) ensure quality and c) reduce possible future bugs.
|
176
|
+
|
177
|
+
## License
|
178
|
+
|
179
|
+
Licensed under either of
|
180
|
+
|
181
|
+
* Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / http://www.apache.org/licenses/LICENSE-2.0)
|
182
|
+
* MIT ([LICENSE-MIT](LICENSE-MIT) / http://opensource.org/licenses/MIT)
|
183
|
+
|
184
|
+
### Contribution
|
185
|
+
|
186
|
+
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
|
@@ -0,0 +1,7 @@
|
|
1
|
+
import type { Source } from 'it-stream-types';
|
2
|
+
import type { Message } from './message-types.js';
|
3
|
+
/**
|
4
|
+
* Decode a chunk and yield an _array_ of decoded messages
|
5
|
+
*/
|
6
|
+
export declare function decode(source: Source<Uint8Array>): AsyncGenerator<Message[], void, unknown>;
|
7
|
+
//# sourceMappingURL=decode.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../../src/decode.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAmHjD;;GAEG;AACH,wBAAwB,MAAM,CAAE,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,4CAUzD"}
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import { MessageTypeNames, MessageTypes } from './message-types.js';
|
2
|
+
import { Uint8ArrayList } from 'uint8arraylist';
|
3
|
+
class Decoder {
|
4
|
+
constructor() {
|
5
|
+
this._buffer = new Uint8ArrayList();
|
6
|
+
this._headerInfo = null;
|
7
|
+
}
|
8
|
+
write(chunk) {
|
9
|
+
if (chunk == null || chunk.length === 0) {
|
10
|
+
return [];
|
11
|
+
}
|
12
|
+
this._buffer.append(chunk);
|
13
|
+
const msgs = [];
|
14
|
+
while (this._buffer.length !== 0) {
|
15
|
+
if (this._headerInfo == null) {
|
16
|
+
try {
|
17
|
+
this._headerInfo = this._decodeHeader(this._buffer);
|
18
|
+
}
|
19
|
+
catch (_) {
|
20
|
+
break; // We haven't received enough data yet
|
21
|
+
}
|
22
|
+
}
|
23
|
+
const { id, type, length, offset } = this._headerInfo;
|
24
|
+
const bufferedDataLength = this._buffer.length - offset;
|
25
|
+
if (bufferedDataLength < length) {
|
26
|
+
break; // not enough data yet
|
27
|
+
}
|
28
|
+
const msg = {
|
29
|
+
id,
|
30
|
+
type
|
31
|
+
};
|
32
|
+
if (type === MessageTypes.NEW_STREAM || type === MessageTypes.MESSAGE_INITIATOR || type === MessageTypes.MESSAGE_RECEIVER) {
|
33
|
+
msg.data = this._buffer.slice(offset, offset + length);
|
34
|
+
}
|
35
|
+
msgs.push(msg);
|
36
|
+
this._buffer.consume(offset + length);
|
37
|
+
this._headerInfo = null;
|
38
|
+
}
|
39
|
+
return msgs;
|
40
|
+
}
|
41
|
+
/**
|
42
|
+
* Attempts to decode the message header from the buffer
|
43
|
+
*/
|
44
|
+
_decodeHeader(data) {
|
45
|
+
const { value: h, offset } = readVarInt(data);
|
46
|
+
const { value: length, offset: end } = readVarInt(data, offset);
|
47
|
+
const type = h & 7;
|
48
|
+
// @ts-expect-error h is a number not a CODE
|
49
|
+
if (MessageTypeNames[type] == null) {
|
50
|
+
throw new Error(`Invalid type received: ${type}`);
|
51
|
+
}
|
52
|
+
// @ts-expect-error h is a number not a CODE
|
53
|
+
return { id: h >> 3, type, offset: offset + end, length };
|
54
|
+
}
|
55
|
+
}
|
56
|
+
const MSB = 0x80;
|
57
|
+
const REST = 0x7F;
|
58
|
+
function readVarInt(buf, offset = 0) {
|
59
|
+
let res = 0;
|
60
|
+
let shift = 0;
|
61
|
+
let counter = offset;
|
62
|
+
let b;
|
63
|
+
const l = buf.length;
|
64
|
+
do {
|
65
|
+
if (counter >= l || shift > 49) {
|
66
|
+
offset = 0;
|
67
|
+
throw new RangeError('Could not decode varint');
|
68
|
+
}
|
69
|
+
b = buf.get(counter++);
|
70
|
+
res += shift < 28
|
71
|
+
? (b & REST) << shift
|
72
|
+
: (b & REST) * Math.pow(2, shift);
|
73
|
+
shift += 7;
|
74
|
+
} while (b >= MSB);
|
75
|
+
offset = counter - offset;
|
76
|
+
return {
|
77
|
+
value: res,
|
78
|
+
offset
|
79
|
+
};
|
80
|
+
}
|
81
|
+
/**
|
82
|
+
* Decode a chunk and yield an _array_ of decoded messages
|
83
|
+
*/
|
84
|
+
export async function* decode(source) {
|
85
|
+
const decoder = new Decoder();
|
86
|
+
for await (const chunk of source) {
|
87
|
+
const msgs = decoder.write(chunk);
|
88
|
+
if (msgs.length > 0) {
|
89
|
+
yield msgs;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
//# sourceMappingURL=decode.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"decode.js","sourceRoot":"","sources":["../../src/decode.ts"],"names":[],"mappings":"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"}
|
@@ -0,0 +1,7 @@
|
|
1
|
+
import { Message } from './message-types.js';
|
2
|
+
import type { Source } from 'it-stream-types';
|
3
|
+
/**
|
4
|
+
* Encode and yield one or more messages
|
5
|
+
*/
|
6
|
+
export declare function encode(source: Source<Message | Message[]>): AsyncGenerator<Uint8Array, void, undefined>;
|
7
|
+
//# sourceMappingURL=encode.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"encode.d.ts","sourceRoot":"","sources":["../../src/encode.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAgB,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AA+D7C;;GAEG;AACH,wBAAwB,MAAM,CAAE,MAAM,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,EAAE,CAAC,+CAUlE"}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import varint from 'varint';
|
2
|
+
import { MessageTypes } from './message-types.js';
|
3
|
+
const POOL_SIZE = 10 * 1024;
|
4
|
+
function allocUnsafe(size) {
|
5
|
+
if (globalThis.Buffer != null) {
|
6
|
+
return Buffer.allocUnsafe(size);
|
7
|
+
}
|
8
|
+
return new Uint8Array(size);
|
9
|
+
}
|
10
|
+
class Encoder {
|
11
|
+
constructor() {
|
12
|
+
this._pool = allocUnsafe(POOL_SIZE);
|
13
|
+
this._poolOffset = 0;
|
14
|
+
}
|
15
|
+
/**
|
16
|
+
* Encodes the given message and returns it and its header
|
17
|
+
*/
|
18
|
+
write(msg) {
|
19
|
+
const pool = this._pool;
|
20
|
+
let offset = this._poolOffset;
|
21
|
+
varint.encode(msg.id << 3 | msg.type, pool, offset);
|
22
|
+
offset += varint.encode.bytes;
|
23
|
+
if ((msg.type === MessageTypes.NEW_STREAM || msg.type === MessageTypes.MESSAGE_INITIATOR || msg.type === MessageTypes.MESSAGE_RECEIVER) && msg.data != null) {
|
24
|
+
varint.encode(msg.data.length, pool, offset);
|
25
|
+
}
|
26
|
+
else {
|
27
|
+
varint.encode(0, pool, offset);
|
28
|
+
}
|
29
|
+
offset += varint.encode.bytes;
|
30
|
+
const header = pool.slice(this._poolOffset, offset);
|
31
|
+
if (POOL_SIZE - offset < 100) {
|
32
|
+
this._pool = allocUnsafe(POOL_SIZE);
|
33
|
+
this._poolOffset = 0;
|
34
|
+
}
|
35
|
+
else {
|
36
|
+
this._poolOffset = offset;
|
37
|
+
}
|
38
|
+
if ((msg.type === MessageTypes.NEW_STREAM || msg.type === MessageTypes.MESSAGE_INITIATOR || msg.type === MessageTypes.MESSAGE_RECEIVER) && msg.data != null) {
|
39
|
+
return [
|
40
|
+
header,
|
41
|
+
msg.data instanceof Uint8Array ? msg.data : msg.data.slice()
|
42
|
+
];
|
43
|
+
}
|
44
|
+
return [
|
45
|
+
header
|
46
|
+
];
|
47
|
+
}
|
48
|
+
}
|
49
|
+
const encoder = new Encoder();
|
50
|
+
/**
|
51
|
+
* Encode and yield one or more messages
|
52
|
+
*/
|
53
|
+
export async function* encode(source) {
|
54
|
+
for await (const msg of source) {
|
55
|
+
if (Array.isArray(msg)) {
|
56
|
+
for (const m of msg) {
|
57
|
+
yield* encoder.write(m);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
else {
|
61
|
+
yield* encoder.write(msg);
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
//# sourceMappingURL=encode.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"encode.js","sourceRoot":"","sources":["../../src/encode.ts"],"names":[],"mappings":"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"}
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import { Pushable } from 'it-pushable';
|
2
|
+
import { Message } from './message-types.js';
|
3
|
+
import type { AbortOptions } from '@libp2p/interfaces';
|
4
|
+
import type { Sink } from 'it-stream-types';
|
5
|
+
import type { Muxer } from '@libp2p/interfaces/stream-muxer';
|
6
|
+
import type { Stream } from '@libp2p/interfaces/connection';
|
7
|
+
import type { ComponentMetricsTracker } from '@libp2p/interfaces/metrics';
|
8
|
+
export interface MplexStream extends Stream {
|
9
|
+
source: Pushable<Uint8Array>;
|
10
|
+
}
|
11
|
+
export interface MplexOptions extends AbortOptions {
|
12
|
+
onStream?: (...args: any[]) => void;
|
13
|
+
onStreamEnd?: (...args: any[]) => void;
|
14
|
+
maxMsgSize?: number;
|
15
|
+
metrics?: ComponentMetricsTracker;
|
16
|
+
}
|
17
|
+
export declare class Mplex implements Muxer {
|
18
|
+
static multicodec: string;
|
19
|
+
sink: Sink<Uint8Array>;
|
20
|
+
source: AsyncIterable<Uint8Array>;
|
21
|
+
private _streamId;
|
22
|
+
private readonly _streams;
|
23
|
+
private readonly _options;
|
24
|
+
private readonly _source;
|
25
|
+
constructor(options?: MplexOptions);
|
26
|
+
/**
|
27
|
+
* Returns a Map of streams and their ids
|
28
|
+
*/
|
29
|
+
get streams(): Stream[];
|
30
|
+
/**
|
31
|
+
* Initiate a new stream with the given name. If no name is
|
32
|
+
* provided, the id of the stream will be used.
|
33
|
+
*/
|
34
|
+
newStream(name?: string): Stream;
|
35
|
+
/**
|
36
|
+
* Called whenever an inbound stream is created
|
37
|
+
*/
|
38
|
+
_newReceiverStream(options: {
|
39
|
+
id: number;
|
40
|
+
name: string;
|
41
|
+
}): MplexStream;
|
42
|
+
_newStream(options: {
|
43
|
+
id: number;
|
44
|
+
name: string;
|
45
|
+
type: 'initiator' | 'receiver';
|
46
|
+
registry: Map<number, MplexStream>;
|
47
|
+
}): MplexStream;
|
48
|
+
/**
|
49
|
+
* Creates a sink with an abortable source. Incoming messages will
|
50
|
+
* also have their size restricted. All messages will be varint decoded.
|
51
|
+
*/
|
52
|
+
_createSink(): Sink<Uint8Array, Promise<void>>;
|
53
|
+
/**
|
54
|
+
* Creates a source that restricts outgoing message sizes
|
55
|
+
* and varint encodes them
|
56
|
+
*/
|
57
|
+
_createSource(): AsyncGenerator<Uint8Array, void, undefined> & {
|
58
|
+
push: (value: Message) => import("it-pushable").PushableV<Message>;
|
59
|
+
end: (err?: Error | undefined) => import("it-pushable").PushableV<Message>;
|
60
|
+
return: () => {
|
61
|
+
done: boolean;
|
62
|
+
};
|
63
|
+
};
|
64
|
+
_handleIncoming(message: Message): void;
|
65
|
+
}
|
66
|
+
//# sourceMappingURL=index.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAa,MAAM,aAAa,CAAA;AAKjD,OAAO,EAAkC,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAK5E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iCAAiC,CAAA;AAC5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAC3D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAA;AAsBzE,MAAM,WAAW,WAAY,SAAQ,MAAM;IACzC,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAA;CAC7B;AAED,MAAM,WAAW,YAAa,SAAQ,YAAY;IAChD,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACnC,WAAW,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAA;IACtC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,uBAAuB,CAAA;CAClC;AAED,qBAAa,KAAM,YAAW,KAAK;IACjC,MAAM,CAAC,UAAU,SAAiB;IAE3B,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAA;IACtB,MAAM,EAAE,aAAa,CAAC,UAAU,CAAC,CAAA;IAExC,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+E;IACxG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8D;gBAEzE,OAAO,CAAC,EAAE,YAAY;IA6BnC;;OAEG;IACH,IAAI,OAAO,aAUV;IAED;;;OAGG;IACH,SAAS,CAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM;IAOjC;;OAEG;IACH,kBAAkB,CAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;IAMzD,UAAU,CAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;KAAE;IAmCrH;;;OAGG;IACH,WAAW;IA+BX;;;OAGG;IACH,aAAa;;;;;;;IAyCb,eAAe,CAAE,OAAO,EAAE,OAAO;CA0ClC"}
|
@@ -0,0 +1,218 @@
|
|
1
|
+
import { pipe } from 'it-pipe';
|
2
|
+
import { pushableV } from 'it-pushable';
|
3
|
+
import { abortableSource } from 'abortable-iterator';
|
4
|
+
import { encode } from './encode.js';
|
5
|
+
import { decode } from './decode.js';
|
6
|
+
import { restrictSize } from './restrict-size.js';
|
7
|
+
import { MessageTypes, MessageTypeNames } from './message-types.js';
|
8
|
+
import { createStream } from './stream.js';
|
9
|
+
import { toString as uint8ArrayToString } from 'uint8arrays';
|
10
|
+
import { trackedMap } from '@libp2p/tracked-map';
|
11
|
+
import { logger } from '@libp2p/logger';
|
12
|
+
import each from 'it-foreach';
|
13
|
+
const log = logger('libp2p:mplex');
|
14
|
+
function printMessage(msg) {
|
15
|
+
const output = {
|
16
|
+
...msg,
|
17
|
+
type: `${MessageTypeNames[msg.type]} (${msg.type})`
|
18
|
+
};
|
19
|
+
if (msg.type === MessageTypes.NEW_STREAM) {
|
20
|
+
output.data = uint8ArrayToString(msg.data instanceof Uint8Array ? msg.data : msg.data.slice());
|
21
|
+
}
|
22
|
+
if (msg.type === MessageTypes.MESSAGE_INITIATOR || msg.type === MessageTypes.MESSAGE_RECEIVER) {
|
23
|
+
output.data = uint8ArrayToString(msg.data instanceof Uint8Array ? msg.data : msg.data.slice(), 'base16');
|
24
|
+
}
|
25
|
+
return output;
|
26
|
+
}
|
27
|
+
export class Mplex {
|
28
|
+
constructor(options) {
|
29
|
+
options = options ?? {};
|
30
|
+
this._streamId = 0;
|
31
|
+
this._streams = {
|
32
|
+
/**
|
33
|
+
* Stream to ids map
|
34
|
+
*/
|
35
|
+
initiators: trackedMap({ metrics: options.metrics, component: 'mplex', metric: 'initiatorStreams' }),
|
36
|
+
/**
|
37
|
+
* Stream to ids map
|
38
|
+
*/
|
39
|
+
receivers: trackedMap({ metrics: options.metrics, component: 'mplex', metric: 'receiverStreams' })
|
40
|
+
};
|
41
|
+
this._options = options;
|
42
|
+
/**
|
43
|
+
* An iterable sink
|
44
|
+
*/
|
45
|
+
this.sink = this._createSink();
|
46
|
+
/**
|
47
|
+
* An iterable source
|
48
|
+
*/
|
49
|
+
const source = this._createSource();
|
50
|
+
this._source = source;
|
51
|
+
this.source = source;
|
52
|
+
}
|
53
|
+
/**
|
54
|
+
* Returns a Map of streams and their ids
|
55
|
+
*/
|
56
|
+
get streams() {
|
57
|
+
// Inbound and Outbound streams may have the same ids, so we need to make those unique
|
58
|
+
const streams = [];
|
59
|
+
this._streams.initiators.forEach(stream => {
|
60
|
+
streams.push(stream);
|
61
|
+
});
|
62
|
+
this._streams.receivers.forEach(stream => {
|
63
|
+
streams.push(stream);
|
64
|
+
});
|
65
|
+
return streams;
|
66
|
+
}
|
67
|
+
/**
|
68
|
+
* Initiate a new stream with the given name. If no name is
|
69
|
+
* provided, the id of the stream will be used.
|
70
|
+
*/
|
71
|
+
newStream(name) {
|
72
|
+
const id = this._streamId++;
|
73
|
+
name = name == null ? id.toString() : name.toString();
|
74
|
+
const registry = this._streams.initiators;
|
75
|
+
return this._newStream({ id, name, type: 'initiator', registry });
|
76
|
+
}
|
77
|
+
/**
|
78
|
+
* Called whenever an inbound stream is created
|
79
|
+
*/
|
80
|
+
_newReceiverStream(options) {
|
81
|
+
const { id, name } = options;
|
82
|
+
const registry = this._streams.receivers;
|
83
|
+
return this._newStream({ id, name, type: 'receiver', registry });
|
84
|
+
}
|
85
|
+
_newStream(options) {
|
86
|
+
const { id, name, type, registry } = options;
|
87
|
+
log('new %s stream %s %s', type, id, name);
|
88
|
+
if (registry.has(id)) {
|
89
|
+
throw new Error(`${type} stream ${id} already exists!`);
|
90
|
+
}
|
91
|
+
const send = (msg) => {
|
92
|
+
if (log.enabled) {
|
93
|
+
log('%s stream %s send', type, id, printMessage(msg));
|
94
|
+
}
|
95
|
+
if (msg.type === MessageTypes.NEW_STREAM || msg.type === MessageTypes.MESSAGE_INITIATOR || msg.type === MessageTypes.MESSAGE_RECEIVER) {
|
96
|
+
msg.data = msg.data instanceof Uint8Array ? msg.data : msg.data.slice();
|
97
|
+
}
|
98
|
+
this._source.push(msg);
|
99
|
+
};
|
100
|
+
const onEnd = () => {
|
101
|
+
log('%s stream %s %s ended', type, id, name);
|
102
|
+
registry.delete(id);
|
103
|
+
if (this._options.onStreamEnd != null) {
|
104
|
+
this._options.onStreamEnd(stream);
|
105
|
+
}
|
106
|
+
};
|
107
|
+
const stream = createStream({ id, name, send, type, onEnd, maxMsgSize: this._options.maxMsgSize });
|
108
|
+
registry.set(id, stream);
|
109
|
+
return stream;
|
110
|
+
}
|
111
|
+
/**
|
112
|
+
* Creates a sink with an abortable source. Incoming messages will
|
113
|
+
* also have their size restricted. All messages will be varint decoded.
|
114
|
+
*/
|
115
|
+
_createSink() {
|
116
|
+
const sink = async (source) => {
|
117
|
+
if (this._options.signal != null) {
|
118
|
+
source = abortableSource(source, this._options.signal);
|
119
|
+
}
|
120
|
+
try {
|
121
|
+
await pipe(source, source => each(source, (buf) => {
|
122
|
+
// console.info('incoming', uint8ArrayToString(buf, 'base64'))
|
123
|
+
}), decode, restrictSize(this._options.maxMsgSize), async (source) => {
|
124
|
+
for await (const msg of source) {
|
125
|
+
this._handleIncoming(msg);
|
126
|
+
}
|
127
|
+
});
|
128
|
+
this._source.end();
|
129
|
+
}
|
130
|
+
catch (err) {
|
131
|
+
log('error in sink', err);
|
132
|
+
this._source.end(err); // End the source with an error
|
133
|
+
}
|
134
|
+
};
|
135
|
+
return sink;
|
136
|
+
}
|
137
|
+
/**
|
138
|
+
* Creates a source that restricts outgoing message sizes
|
139
|
+
* and varint encodes them
|
140
|
+
*/
|
141
|
+
_createSource() {
|
142
|
+
const onEnd = (err) => {
|
143
|
+
const { initiators, receivers } = this._streams;
|
144
|
+
// Abort all the things!
|
145
|
+
for (const s of initiators.values()) {
|
146
|
+
s.abort(err);
|
147
|
+
}
|
148
|
+
for (const s of receivers.values()) {
|
149
|
+
s.abort(err);
|
150
|
+
}
|
151
|
+
};
|
152
|
+
const source = pushableV({ onEnd });
|
153
|
+
/*
|
154
|
+
const p = pipe(
|
155
|
+
source,
|
156
|
+
source => each(source, (msgs) => {
|
157
|
+
if (log.enabled) {
|
158
|
+
msgs.forEach(msg => {
|
159
|
+
log('outgoing message', printMessage(msg))
|
160
|
+
})
|
161
|
+
}
|
162
|
+
}),
|
163
|
+
source => encode(source),
|
164
|
+
source => each(source, (buf) => {
|
165
|
+
console.info('outgoing', uint8ArrayToString(buf, 'base64'))
|
166
|
+
})
|
167
|
+
)
|
168
|
+
|
169
|
+
return Object.assign(p, {
|
170
|
+
push: source.push,
|
171
|
+
end: source.end,
|
172
|
+
return: source.return
|
173
|
+
})
|
174
|
+
*/
|
175
|
+
return Object.assign(encode(source), {
|
176
|
+
push: source.push,
|
177
|
+
end: source.end,
|
178
|
+
return: source.return
|
179
|
+
});
|
180
|
+
}
|
181
|
+
_handleIncoming(message) {
|
182
|
+
const { id, type } = message;
|
183
|
+
if (log.enabled) {
|
184
|
+
log('incoming message', printMessage(message));
|
185
|
+
}
|
186
|
+
// Create a new stream?
|
187
|
+
if (message.type === MessageTypes.NEW_STREAM) {
|
188
|
+
const stream = this._newReceiverStream({ id, name: uint8ArrayToString(message.data instanceof Uint8Array ? message.data : message.data.slice()) });
|
189
|
+
if (this._options.onStream != null) {
|
190
|
+
this._options.onStream(stream);
|
191
|
+
}
|
192
|
+
return;
|
193
|
+
}
|
194
|
+
const list = (type & 1) === 1 ? this._streams.initiators : this._streams.receivers;
|
195
|
+
const stream = list.get(id);
|
196
|
+
if (stream == null) {
|
197
|
+
return log('missing stream %s', id);
|
198
|
+
}
|
199
|
+
switch (type) {
|
200
|
+
case MessageTypes.MESSAGE_INITIATOR:
|
201
|
+
case MessageTypes.MESSAGE_RECEIVER:
|
202
|
+
stream.source.push(message.data.slice());
|
203
|
+
break;
|
204
|
+
case MessageTypes.CLOSE_INITIATOR:
|
205
|
+
case MessageTypes.CLOSE_RECEIVER:
|
206
|
+
stream.close();
|
207
|
+
break;
|
208
|
+
case MessageTypes.RESET_INITIATOR:
|
209
|
+
case MessageTypes.RESET_RECEIVER:
|
210
|
+
stream.reset();
|
211
|
+
break;
|
212
|
+
default:
|
213
|
+
log('unknown message type %s', type);
|
214
|
+
}
|
215
|
+
}
|
216
|
+
}
|
217
|
+
Mplex.multicodec = '/mplex/6.7.0';
|
218
|
+
//# sourceMappingURL=index.js.map
|