@libp2p/multistream-select 4.0.6-05b52d69c → 4.0.6-0b4a2ee79
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/README.md +1 -1
- package/dist/index.min.js +14 -2
- package/dist/src/handle.d.ts +3 -5
- package/dist/src/handle.d.ts.map +1 -1
- package/dist/src/handle.js +72 -16
- package/dist/src/handle.js.map +1 -1
- package/dist/src/index.d.ts +5 -12
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/multistream.d.ts +13 -8
- package/dist/src/multistream.d.ts.map +1 -1
- package/dist/src/multistream.js +14 -50
- package/dist/src/multistream.js.map +1 -1
- package/dist/src/select.d.ts +10 -15
- package/dist/src/select.d.ts.map +1 -1
- package/dist/src/select.js +242 -57
- package/dist/src/select.js.map +1 -1
- package/package.json +8 -12
- package/src/handle.ts +35 -21
- package/src/index.ts +5 -13
- package/src/multistream.ts +18 -67
- package/src/select.ts +246 -63
package/src/select.ts
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { CodeError } from '@libp2p/interface/errors'
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { reader } from 'it-reader'
|
|
2
|
+
import { lpStream } from 'it-length-prefixed-stream'
|
|
3
|
+
import pDefer from 'p-defer'
|
|
4
|
+
import * as varint from 'uint8-varint'
|
|
6
5
|
import { Uint8ArrayList } from 'uint8arraylist'
|
|
7
6
|
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
7
|
+
import { MAX_PROTOCOL_LENGTH } from './constants.js'
|
|
8
8
|
import * as multistream from './multistream.js'
|
|
9
9
|
import { PROTOCOL_ID } from './index.js'
|
|
10
|
-
import type {
|
|
11
|
-
import type {
|
|
10
|
+
import type { MultistreamSelectInit, ProtocolStream } from './index.js'
|
|
11
|
+
import type { AbortOptions } from '@libp2p/interface'
|
|
12
|
+
import type { Duplex } from 'it-stream-types'
|
|
13
|
+
|
|
14
|
+
export interface SelectStream extends Duplex<any, any, any> {
|
|
15
|
+
readStatus?: string
|
|
16
|
+
closeWrite?(options?: AbortOptions): Promise<void>
|
|
17
|
+
closeRead?(options?: AbortOptions): Promise<void>
|
|
18
|
+
close?(options?: AbortOptions): Promise<void>
|
|
19
|
+
}
|
|
12
20
|
|
|
13
21
|
/**
|
|
14
22
|
* Negotiate a protocol to use from a list of protocols.
|
|
@@ -53,12 +61,17 @@ import type { Duplex, Source } from 'it-stream-types'
|
|
|
53
61
|
* // }
|
|
54
62
|
* ```
|
|
55
63
|
*/
|
|
56
|
-
export async function select (stream:
|
|
57
|
-
export async function select (stream: Duplex<AsyncGenerator<Uint8ArrayList | Uint8Array>, Source<Uint8ArrayList | Uint8Array>>, protocols: string | string[], options?: ByteListInit): Promise<ProtocolStream<Uint8ArrayList, Uint8ArrayList | Uint8Array>>
|
|
58
|
-
export async function select (stream: any, protocols: string | string[], options?: MultistreamSelectInit): Promise<ProtocolStream<any>> {
|
|
64
|
+
export async function select <Stream extends SelectStream> (stream: Stream, protocols: string | string[], options: MultistreamSelectInit): Promise<ProtocolStream<Stream>> {
|
|
59
65
|
protocols = Array.isArray(protocols) ? [...protocols] : [protocols]
|
|
60
|
-
const { reader, writer, rest, stream: shakeStream } = handshake(stream)
|
|
61
66
|
|
|
67
|
+
if (protocols.length === 1) {
|
|
68
|
+
return optimisticSelect(stream, protocols[0], options)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const lp = lpStream(stream, {
|
|
72
|
+
...options,
|
|
73
|
+
maxDataLength: MAX_PROTOCOL_LENGTH
|
|
74
|
+
})
|
|
62
75
|
const protocol = protocols.shift()
|
|
63
76
|
|
|
64
77
|
if (protocol == null) {
|
|
@@ -66,93 +79,263 @@ export async function select (stream: any, protocols: string | string[], options
|
|
|
66
79
|
}
|
|
67
80
|
|
|
68
81
|
options?.log.trace('select: write ["%s", "%s"]', PROTOCOL_ID, protocol)
|
|
69
|
-
const p1 = uint8ArrayFromString(PROTOCOL_ID)
|
|
70
|
-
const p2 = uint8ArrayFromString(protocol)
|
|
71
|
-
multistream.writeAll(
|
|
82
|
+
const p1 = uint8ArrayFromString(`${PROTOCOL_ID}\n`)
|
|
83
|
+
const p2 = uint8ArrayFromString(`${protocol}\n`)
|
|
84
|
+
await multistream.writeAll(lp, [p1, p2], options)
|
|
72
85
|
|
|
73
|
-
|
|
86
|
+
options?.log.trace('select: reading multistream-select header')
|
|
87
|
+
let response = await multistream.readString(lp, options)
|
|
74
88
|
options?.log.trace('select: read "%s"', response)
|
|
75
89
|
|
|
76
90
|
// Read the protocol response if we got the protocolId in return
|
|
77
91
|
if (response === PROTOCOL_ID) {
|
|
78
|
-
|
|
92
|
+
options?.log.trace('select: reading protocol response')
|
|
93
|
+
response = await multistream.readString(lp, options)
|
|
79
94
|
options?.log.trace('select: read "%s"', response)
|
|
80
95
|
}
|
|
81
96
|
|
|
82
97
|
// We're done
|
|
83
98
|
if (response === protocol) {
|
|
84
|
-
|
|
85
|
-
return { stream: shakeStream, protocol }
|
|
99
|
+
return { stream: lp.unwrap(), protocol }
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
// We haven't gotten a valid ack, try the other protocols
|
|
89
103
|
for (const protocol of protocols) {
|
|
90
104
|
options?.log.trace('select: write "%s"', protocol)
|
|
91
|
-
multistream.write(
|
|
92
|
-
|
|
105
|
+
await multistream.write(lp, uint8ArrayFromString(`${protocol}\n`), options)
|
|
106
|
+
options?.log.trace('select: reading protocol response')
|
|
107
|
+
const response = await multistream.readString(lp, options)
|
|
93
108
|
options?.log.trace('select: read "%s" for "%s"', response, protocol)
|
|
94
109
|
|
|
95
110
|
if (response === protocol) {
|
|
96
|
-
|
|
97
|
-
return { stream: shakeStream, protocol }
|
|
111
|
+
return { stream: lp.unwrap(), protocol }
|
|
98
112
|
}
|
|
99
113
|
}
|
|
100
114
|
|
|
101
|
-
rest()
|
|
102
115
|
throw new CodeError('protocol selection failed', 'ERR_UNSUPPORTED_PROTOCOL')
|
|
103
116
|
}
|
|
104
117
|
|
|
105
118
|
/**
|
|
106
|
-
*
|
|
119
|
+
* Optimistically negotiates a protocol.
|
|
107
120
|
*
|
|
108
121
|
* It *does not* block writes waiting for the other end to respond. Instead, it
|
|
109
122
|
* simply assumes the negotiation went successfully and starts writing data.
|
|
110
123
|
*
|
|
111
124
|
* Use when it is known that the receiver supports the desired protocol.
|
|
112
125
|
*/
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// read from the source
|
|
118
|
-
const negotiateTrigger = pushable()
|
|
126
|
+
function optimisticSelect <Stream extends SelectStream> (stream: Stream, protocol: string, options: MultistreamSelectInit): ProtocolStream<Stream> {
|
|
127
|
+
const originalSink = stream.sink.bind(stream)
|
|
128
|
+
const originalSource = stream.source
|
|
129
|
+
|
|
119
130
|
let negotiated = false
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
throw new CodeError('protocol selection failed', 'ERR_UNSUPPORTED_PROTOCOL')
|
|
131
|
+
let negotiating = false
|
|
132
|
+
const doneNegotiating = pDefer()
|
|
133
|
+
|
|
134
|
+
let sentProtocol = false
|
|
135
|
+
let sendingProtocol = false
|
|
136
|
+
const doneSendingProtocol = pDefer()
|
|
137
|
+
|
|
138
|
+
let readProtocol = false
|
|
139
|
+
let readingProtocol = false
|
|
140
|
+
const doneReadingProtocol = pDefer()
|
|
141
|
+
|
|
142
|
+
const lp = lpStream({
|
|
143
|
+
sink: originalSink,
|
|
144
|
+
source: originalSource
|
|
145
|
+
}, {
|
|
146
|
+
...options,
|
|
147
|
+
maxDataLength: MAX_PROTOCOL_LENGTH
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
stream.sink = async source => {
|
|
151
|
+
const { sink } = lp.unwrap()
|
|
152
|
+
|
|
153
|
+
await sink(async function * () {
|
|
154
|
+
let sentData = false
|
|
155
|
+
|
|
156
|
+
for await (const buf of source) {
|
|
157
|
+
// started reading before the source yielded, wait for protocol send
|
|
158
|
+
if (sendingProtocol) {
|
|
159
|
+
await doneSendingProtocol.promise
|
|
150
160
|
}
|
|
151
|
-
|
|
152
|
-
|
|
161
|
+
|
|
162
|
+
// writing before reading, send the protocol and the first chunk of data
|
|
163
|
+
if (!sentProtocol) {
|
|
164
|
+
sendingProtocol = true
|
|
165
|
+
|
|
166
|
+
options?.log.trace('optimistic: write ["%s", "%s", data(%d)] in sink', PROTOCOL_ID, protocol, buf.byteLength)
|
|
167
|
+
|
|
168
|
+
const protocolString = `${protocol}\n`
|
|
169
|
+
|
|
170
|
+
// send protocols in first chunk of data written to transport
|
|
171
|
+
yield new Uint8ArrayList(
|
|
172
|
+
Uint8Array.from([19]), // length of PROTOCOL_ID plus newline
|
|
173
|
+
uint8ArrayFromString(`${PROTOCOL_ID}\n`),
|
|
174
|
+
varint.encode(protocolString.length),
|
|
175
|
+
uint8ArrayFromString(protocolString),
|
|
176
|
+
buf
|
|
177
|
+
).subarray()
|
|
178
|
+
|
|
179
|
+
options?.log.trace('optimistic: wrote ["%s", "%s", data(%d)] in sink', PROTOCOL_ID, protocol, buf.byteLength)
|
|
180
|
+
|
|
181
|
+
sentProtocol = true
|
|
182
|
+
sendingProtocol = false
|
|
183
|
+
doneSendingProtocol.resolve()
|
|
184
|
+
} else {
|
|
185
|
+
yield buf
|
|
153
186
|
}
|
|
154
|
-
|
|
155
|
-
|
|
187
|
+
|
|
188
|
+
sentData = true
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// special case - the source passed to the sink has ended but we didn't
|
|
192
|
+
// negotiated the protocol yet so do it now
|
|
193
|
+
if (!sentData) {
|
|
194
|
+
await negotiate()
|
|
195
|
+
}
|
|
196
|
+
}())
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function negotiate (): Promise<void> {
|
|
200
|
+
if (negotiating) {
|
|
201
|
+
options?.log.trace('optimistic: already negotiating %s stream', protocol)
|
|
202
|
+
await doneNegotiating.promise
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
negotiating = true
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
// we haven't sent the protocol yet, send it now
|
|
210
|
+
if (!sentProtocol) {
|
|
211
|
+
options?.log.trace('optimistic: doing send protocol for %s stream', protocol)
|
|
212
|
+
await doSendProtocol()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// if we haven't read the protocol response yet, do it now
|
|
216
|
+
if (!readProtocol) {
|
|
217
|
+
options?.log.trace('optimistic: doing read protocol for %s stream', protocol)
|
|
218
|
+
await doReadProtocol()
|
|
219
|
+
}
|
|
220
|
+
} finally {
|
|
221
|
+
negotiating = false
|
|
222
|
+
negotiated = true
|
|
223
|
+
doneNegotiating.resolve()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function doSendProtocol (): Promise<void> {
|
|
228
|
+
if (sendingProtocol) {
|
|
229
|
+
await doneSendingProtocol.promise
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
sendingProtocol = true
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
options?.log.trace('optimistic: write ["%s", "%s", data] in source', PROTOCOL_ID, protocol)
|
|
237
|
+
await lp.writeV([
|
|
238
|
+
uint8ArrayFromString(`${PROTOCOL_ID}\n`),
|
|
239
|
+
uint8ArrayFromString(`${protocol}\n`)
|
|
240
|
+
])
|
|
241
|
+
options?.log.trace('optimistic: wrote ["%s", "%s", data] in source', PROTOCOL_ID, protocol)
|
|
242
|
+
} finally {
|
|
243
|
+
sentProtocol = true
|
|
244
|
+
sendingProtocol = false
|
|
245
|
+
doneSendingProtocol.resolve()
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function doReadProtocol (): Promise<void> {
|
|
250
|
+
if (readingProtocol) {
|
|
251
|
+
await doneReadingProtocol.promise
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
readingProtocol = true
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
options?.log.trace('optimistic: reading multistream select header')
|
|
259
|
+
let response = await multistream.readString(lp, options)
|
|
260
|
+
options?.log.trace('optimistic: read multistream select header "%s"', response)
|
|
261
|
+
|
|
262
|
+
if (response === PROTOCOL_ID) {
|
|
263
|
+
response = await multistream.readString(lp, options)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
options?.log.trace('optimistic: read protocol "%s", expecting "%s"', response, protocol)
|
|
267
|
+
|
|
268
|
+
if (response !== protocol) {
|
|
269
|
+
throw new CodeError('protocol selection failed', 'ERR_UNSUPPORTED_PROTOCOL')
|
|
270
|
+
}
|
|
271
|
+
} finally {
|
|
272
|
+
readProtocol = true
|
|
273
|
+
readingProtocol = false
|
|
274
|
+
doneReadingProtocol.resolve()
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
stream.source = (async function * () {
|
|
279
|
+
// make sure we've done protocol negotiation before we read stream data
|
|
280
|
+
await negotiate()
|
|
281
|
+
|
|
282
|
+
options?.log.trace('optimistic: reading data from "%s" stream', protocol)
|
|
283
|
+
yield * lp.unwrap().source
|
|
284
|
+
})()
|
|
285
|
+
|
|
286
|
+
if (stream.closeRead != null) {
|
|
287
|
+
const originalCloseRead = stream.closeRead.bind(stream)
|
|
288
|
+
|
|
289
|
+
stream.closeRead = async (opts) => {
|
|
290
|
+
// we need to read & write to negotiate the protocol so ensure we've done
|
|
291
|
+
// this before closing the readable end of the stream
|
|
292
|
+
if (!negotiated) {
|
|
293
|
+
await negotiate().catch(err => {
|
|
294
|
+
options?.log.error('could not negotiate protocol before close read', err)
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// protocol has been negotiated, ok to close the readable end
|
|
299
|
+
await originalCloseRead(opts)
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (stream.closeWrite != null) {
|
|
304
|
+
const originalCloseWrite = stream.closeWrite.bind(stream)
|
|
305
|
+
|
|
306
|
+
stream.closeWrite = async (opts) => {
|
|
307
|
+
// we need to read & write to negotiate the protocol so ensure we've done
|
|
308
|
+
// this before closing the writable end of the stream
|
|
309
|
+
if (!negotiated) {
|
|
310
|
+
await negotiate().catch(err => {
|
|
311
|
+
options?.log.error('could not negotiate protocol before close write', err)
|
|
312
|
+
})
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// protocol has been negotiated, ok to close the writable end
|
|
316
|
+
await originalCloseWrite(opts)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (stream.close != null) {
|
|
321
|
+
const originalClose = stream.close.bind(stream)
|
|
322
|
+
|
|
323
|
+
stream.close = async (opts) => {
|
|
324
|
+
// the stream is being closed, don't try to negotiate a protocol if we
|
|
325
|
+
// haven't already
|
|
326
|
+
if (!negotiated) {
|
|
327
|
+
negotiated = true
|
|
328
|
+
negotiating = false
|
|
329
|
+
doneNegotiating.resolve()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// protocol has been negotiated, ok to close the writable end
|
|
333
|
+
await originalClose(opts)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
stream,
|
|
156
339
|
protocol
|
|
157
340
|
}
|
|
158
341
|
}
|