@libp2p/multistream-select 4.0.6-9c67c5b3d → 4.0.6-bb6ceb192

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/src/select.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  import { CodeError } from '@libp2p/interface/errors'
2
- import { handshake } from 'it-handshake'
3
- import merge from 'it-merge'
4
- import { pushable } from 'it-pushable'
5
- import { reader } from 'it-reader'
2
+ import { lpStream } from 'it-length-prefixed-stream'
3
+ import * as varint from 'uint8-varint'
6
4
  import { Uint8ArrayList } from 'uint8arraylist'
7
5
  import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
6
+ import { MAX_PROTOCOL_LENGTH } from './constants.js'
8
7
  import * as multistream from './multistream.js'
9
8
  import { PROTOCOL_ID } from './index.js'
10
- import type { ByteArrayInit, ByteListInit, MultistreamSelectInit, ProtocolStream } from './index.js'
11
- import type { Duplex, Source } from 'it-stream-types'
9
+ import type { MultistreamSelectInit, ProtocolStream } from './index.js'
10
+ import type { Duplex } from 'it-stream-types'
12
11
 
13
12
  /**
14
13
  * Negotiate a protocol to use from a list of protocols.
@@ -53,12 +52,12 @@ import type { Duplex, Source } from 'it-stream-types'
53
52
  * // }
54
53
  * ```
55
54
  */
56
- export async function select (stream: Duplex<AsyncGenerator<Uint8Array>, Source<Uint8Array>>, protocols: string | string[], options: ByteArrayInit): Promise<ProtocolStream<Uint8Array>>
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>> {
55
+ export async function select <Stream extends Duplex<any, any, any>> (stream: Stream, protocols: string | string[], options: MultistreamSelectInit): Promise<ProtocolStream<Stream>> {
59
56
  protocols = Array.isArray(protocols) ? [...protocols] : [protocols]
60
- const { reader, writer, rest, stream: shakeStream } = handshake(stream)
61
-
57
+ const lp = lpStream(stream, {
58
+ ...options,
59
+ maxDataLength: MAX_PROTOCOL_LENGTH
60
+ })
62
61
  const protocol = protocols.shift()
63
62
 
64
63
  if (protocol == null) {
@@ -66,39 +65,39 @@ export async function select (stream: any, protocols: string | string[], options
66
65
  }
67
66
 
68
67
  options?.log.trace('select: write ["%s", "%s"]', PROTOCOL_ID, protocol)
69
- const p1 = uint8ArrayFromString(PROTOCOL_ID)
70
- const p2 = uint8ArrayFromString(protocol)
71
- multistream.writeAll(writer, [p1, p2], options)
68
+ const p1 = uint8ArrayFromString(`${PROTOCOL_ID}\n`)
69
+ const p2 = uint8ArrayFromString(`${protocol}\n`)
70
+ await multistream.writeAll(lp, [p1, p2], options)
72
71
 
73
- let response = await multistream.readString(reader, options)
72
+ options?.log.trace('select: reading multistream-select header')
73
+ let response = await multistream.readString(lp, options)
74
74
  options?.log.trace('select: read "%s"', response)
75
75
 
76
76
  // Read the protocol response if we got the protocolId in return
77
77
  if (response === PROTOCOL_ID) {
78
- response = await multistream.readString(reader, options)
78
+ options?.log.trace('select: reading protocol response')
79
+ response = await multistream.readString(lp, options)
79
80
  options?.log.trace('select: read "%s"', response)
80
81
  }
81
82
 
82
83
  // We're done
83
84
  if (response === protocol) {
84
- rest()
85
- return { stream: shakeStream, protocol }
85
+ return { stream: lp.unwrap(), protocol }
86
86
  }
87
87
 
88
88
  // We haven't gotten a valid ack, try the other protocols
89
89
  for (const protocol of protocols) {
90
90
  options?.log.trace('select: write "%s"', protocol)
91
- multistream.write(writer, uint8ArrayFromString(protocol), options)
92
- const response = await multistream.readString(reader, options)
91
+ await multistream.write(lp, uint8ArrayFromString(`${protocol}\n`), options)
92
+ options?.log.trace('select: reading protocol response')
93
+ const response = await multistream.readString(lp, options)
93
94
  options?.log.trace('select: read "%s" for "%s"', response, protocol)
94
95
 
95
96
  if (response === protocol) {
96
- rest() // End our writer so others can start writing to stream
97
- return { stream: shakeStream, protocol }
97
+ return { stream: lp.unwrap(), protocol }
98
98
  }
99
99
  }
100
100
 
101
- rest()
102
101
  throw new CodeError('protocol selection failed', 'ERR_UNSUPPORTED_PROTOCOL')
103
102
  }
104
103
 
@@ -110,49 +109,80 @@ export async function select (stream: any, protocols: string | string[], options
110
109
  *
111
110
  * Use when it is known that the receiver supports the desired protocol.
112
111
  */
113
- export function lazySelect (stream: Duplex<Source<Uint8Array>, Source<Uint8Array>>, protocol: string): ProtocolStream<Uint8Array>
114
- export function lazySelect (stream: Duplex<Source<Uint8ArrayList | Uint8Array>, Source<Uint8ArrayList | Uint8Array>>, protocol: string): ProtocolStream<Uint8ArrayList, Uint8ArrayList | Uint8Array>
115
- export function lazySelect (stream: Duplex<any>, protocol: string): ProtocolStream<any> {
116
- // This is a signal to write the multistream headers if the consumer tries to
117
- // read from the source
118
- const negotiateTrigger = pushable()
119
- let negotiated = false
120
- return {
121
- stream: {
122
- sink: async source => {
123
- await stream.sink((async function * () {
124
- let first = true
125
- for await (const chunk of merge(source, negotiateTrigger)) {
126
- if (first) {
127
- first = false
128
- negotiated = true
129
- negotiateTrigger.end()
130
- const p1 = uint8ArrayFromString(PROTOCOL_ID)
131
- const p2 = uint8ArrayFromString(protocol)
132
- const list = new Uint8ArrayList(multistream.encode(p1), multistream.encode(p2))
133
- if (chunk.length > 0) list.append(chunk)
134
- yield * list
135
- } else {
136
- yield chunk
137
- }
138
- }
139
- })())
140
- },
141
- source: (async function * () {
142
- if (!negotiated) negotiateTrigger.push(new Uint8Array())
143
- const byteReader = reader(stream.source)
144
- let response = await multistream.readString(byteReader)
145
- if (response === PROTOCOL_ID) {
146
- response = await multistream.readString(byteReader)
147
- }
148
- if (response !== protocol) {
149
- throw new CodeError('protocol selection failed', 'ERR_UNSUPPORTED_PROTOCOL')
150
- }
151
- for await (const chunk of byteReader) {
152
- yield * chunk
112
+ export function lazySelect <Stream extends Duplex<any, any, any>> (stream: Stream, protocol: string, options: MultistreamSelectInit): ProtocolStream<Stream> {
113
+ const originalSink = stream.sink.bind(stream)
114
+ const originalSource = stream.source
115
+ let selected = false
116
+
117
+ const lp = lpStream({
118
+ sink: originalSink,
119
+ source: originalSource
120
+ }, {
121
+ ...options,
122
+ maxDataLength: MAX_PROTOCOL_LENGTH
123
+ })
124
+
125
+ stream.sink = async source => {
126
+ const { sink } = lp.unwrap()
127
+
128
+ await sink(async function * () {
129
+ for await (const buf of source) {
130
+ // if writing before selecting, send selection with first data chunk
131
+ if (!selected) {
132
+ selected = true
133
+ options?.log.trace('lazy: write ["%s", "%s", data] in sink', PROTOCOL_ID, protocol)
134
+
135
+ const protocolString = `${protocol}\n`
136
+
137
+ // send protocols in first chunk of data written to transport
138
+ yield new Uint8ArrayList(
139
+ Uint8Array.from([19]), // length of PROTOCOL_ID plus newline
140
+ uint8ArrayFromString(`${PROTOCOL_ID}\n`),
141
+ varint.encode(protocolString.length),
142
+ uint8ArrayFromString(protocolString),
143
+ buf
144
+ ).subarray()
145
+
146
+ options?.log.trace('lazy: wrote ["%s", "%s", data] in sink', PROTOCOL_ID, protocol)
147
+ } else {
148
+ yield buf
153
149
  }
154
- })()
155
- },
150
+ }
151
+ }())
152
+ }
153
+
154
+ stream.source = (async function * () {
155
+ // if reading before selecting, send selection before first data chunk
156
+ if (!selected) {
157
+ selected = true
158
+ options?.log.trace('lazy: write ["%s", "%s", data] in source', PROTOCOL_ID, protocol)
159
+ await lp.writeV([
160
+ uint8ArrayFromString(`${PROTOCOL_ID}\n`),
161
+ uint8ArrayFromString(`${protocol}\n`)
162
+ ])
163
+ options?.log.trace('lazy: wrote ["%s", "%s", data] in source', PROTOCOL_ID, protocol)
164
+ }
165
+
166
+ options?.log.trace('lazy: reading multistream select header')
167
+ let response = await multistream.readString(lp, options)
168
+ options?.log.trace('lazy: read multistream select header "%s"', response)
169
+
170
+ if (response === PROTOCOL_ID) {
171
+ response = await multistream.readString(lp, options)
172
+ }
173
+
174
+ options?.log.trace('lazy: read protocol "%s", expecting "%s"', response, protocol)
175
+
176
+ if (response !== protocol) {
177
+ throw new CodeError('protocol selection failed', 'ERR_UNSUPPORTED_PROTOCOL')
178
+ }
179
+
180
+ options?.log.trace('lazy: reading rest of "%s" stream', protocol)
181
+ yield * lp.unwrap().source
182
+ })()
183
+
184
+ return {
185
+ stream,
156
186
  protocol
157
187
  }
158
188
  }