@libp2p/interface-compliance-tests 3.0.5 → 3.0.7-05abd49f
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 +14 -5
- package/dist/src/connection/index.d.ts +5 -0
- package/dist/src/connection/index.d.ts.map +1 -0
- package/dist/src/connection/index.js +151 -0
- package/dist/src/connection/index.js.map +1 -0
- package/dist/src/connection-encryption/index.d.ts +5 -0
- package/dist/src/connection-encryption/index.d.ts.map +1 -0
- package/dist/src/connection-encryption/index.js +71 -0
- package/dist/src/connection-encryption/index.js.map +1 -0
- package/dist/src/connection-encryption/utils/index.d.ts +3 -0
- package/dist/src/connection-encryption/utils/index.d.ts.map +1 -0
- package/dist/src/connection-encryption/utils/index.js +18 -0
- package/dist/src/connection-encryption/utils/index.js.map +1 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/is-valid-tick.d.ts.map +1 -1
- package/dist/src/mocks/connection-encrypter.d.ts +3 -0
- package/dist/src/mocks/connection-encrypter.d.ts.map +1 -0
- package/dist/src/mocks/connection-encrypter.js +98 -0
- package/dist/src/mocks/connection-encrypter.js.map +1 -0
- package/dist/src/mocks/connection-gater.d.ts +3 -0
- package/dist/src/mocks/connection-gater.d.ts.map +1 -0
- package/dist/src/mocks/connection-gater.js +17 -0
- package/dist/src/mocks/connection-gater.js.map +1 -0
- package/dist/src/mocks/connection-manager.d.ts +27 -0
- package/dist/src/mocks/connection-manager.d.ts.map +1 -0
- package/dist/src/mocks/connection-manager.js +145 -0
- package/dist/src/mocks/connection-manager.js.map +1 -0
- package/dist/src/mocks/connection.d.ts +32 -0
- package/dist/src/mocks/connection.d.ts.map +1 -0
- package/dist/src/mocks/connection.js +162 -0
- package/dist/src/mocks/connection.js.map +1 -0
- package/dist/src/mocks/duplex.d.ts +3 -0
- package/dist/src/mocks/duplex.d.ts.map +1 -0
- package/dist/src/mocks/duplex.js +9 -0
- package/dist/src/mocks/duplex.js.map +1 -0
- package/dist/src/mocks/index.d.ts +13 -0
- package/dist/src/mocks/index.d.ts.map +1 -0
- package/dist/src/mocks/index.js +11 -0
- package/dist/src/mocks/index.js.map +1 -0
- package/dist/src/mocks/metrics.d.ts +3 -0
- package/dist/src/mocks/metrics.d.ts.map +1 -0
- package/dist/src/mocks/metrics.js +115 -0
- package/dist/src/mocks/metrics.js.map +1 -0
- package/dist/src/mocks/multiaddr-connection.d.ts +17 -0
- package/dist/src/mocks/multiaddr-connection.d.ts.map +1 -0
- package/dist/src/mocks/multiaddr-connection.js +51 -0
- package/dist/src/mocks/multiaddr-connection.js.map +1 -0
- package/dist/src/mocks/muxer.d.ts +8 -0
- package/dist/src/mocks/muxer.d.ts.map +1 -0
- package/dist/src/mocks/muxer.js +341 -0
- package/dist/src/mocks/muxer.js.map +1 -0
- package/dist/src/mocks/peer-discovery.d.ts +22 -0
- package/dist/src/mocks/peer-discovery.d.ts.map +1 -0
- package/dist/src/mocks/peer-discovery.js +47 -0
- package/dist/src/mocks/peer-discovery.js.map +1 -0
- package/dist/src/mocks/registrar.d.ts +18 -0
- package/dist/src/mocks/registrar.d.ts.map +1 -0
- package/dist/src/mocks/registrar.js +66 -0
- package/dist/src/mocks/registrar.js.map +1 -0
- package/dist/src/mocks/upgrader.d.ts +10 -0
- package/dist/src/mocks/upgrader.d.ts.map +1 -0
- package/dist/src/mocks/upgrader.js +31 -0
- package/dist/src/mocks/upgrader.js.map +1 -0
- package/dist/src/peer-discovery/index.d.ts +5 -0
- package/dist/src/peer-discovery/index.d.ts.map +1 -0
- package/dist/src/peer-discovery/index.js +66 -0
- package/dist/src/peer-discovery/index.js.map +1 -0
- package/dist/src/stream-muxer/base-test.d.ts +5 -0
- package/dist/src/stream-muxer/base-test.d.ts.map +1 -0
- package/dist/src/stream-muxer/base-test.js +153 -0
- package/dist/src/stream-muxer/base-test.js.map +1 -0
- package/dist/src/stream-muxer/close-test.d.ts +5 -0
- package/dist/src/stream-muxer/close-test.d.ts.map +1 -0
- package/dist/src/stream-muxer/close-test.js +269 -0
- package/dist/src/stream-muxer/close-test.js.map +1 -0
- package/dist/src/stream-muxer/index.d.ts +5 -0
- package/dist/src/stream-muxer/index.d.ts.map +1 -0
- package/dist/src/stream-muxer/index.js +13 -0
- package/dist/src/stream-muxer/index.js.map +1 -0
- package/dist/src/stream-muxer/mega-stress-test.d.ts +5 -0
- package/dist/src/stream-muxer/mega-stress-test.d.ts.map +1 -0
- package/dist/src/stream-muxer/mega-stress-test.js +11 -0
- package/dist/src/stream-muxer/mega-stress-test.js.map +1 -0
- package/dist/src/stream-muxer/spawner.d.ts +4 -0
- package/dist/src/stream-muxer/spawner.d.ts.map +1 -0
- package/dist/src/stream-muxer/spawner.js +36 -0
- package/dist/src/stream-muxer/spawner.js.map +1 -0
- package/dist/src/stream-muxer/stress-test.d.ts +5 -0
- package/dist/src/stream-muxer/stress-test.d.ts.map +1 -0
- package/dist/src/stream-muxer/stress-test.js +23 -0
- package/dist/src/stream-muxer/stress-test.js.map +1 -0
- package/dist/src/transport/dial-test.d.ts +5 -0
- package/dist/src/transport/dial-test.d.ts.map +1 -0
- package/dist/src/transport/dial-test.js +98 -0
- package/dist/src/transport/dial-test.js.map +1 -0
- package/dist/src/transport/filter-test.d.ts +5 -0
- package/dist/src/transport/filter-test.d.ts.map +1 -0
- package/dist/src/transport/filter-test.js +18 -0
- package/dist/src/transport/filter-test.js.map +1 -0
- package/dist/src/transport/index.d.ts +15 -0
- package/dist/src/transport/index.d.ts.map +1 -0
- package/dist/src/transport/index.js +11 -0
- package/dist/src/transport/index.js.map +1 -0
- package/dist/src/transport/listen-test.d.ts +5 -0
- package/dist/src/transport/listen-test.d.ts.map +1 -0
- package/dist/src/transport/listen-test.js +152 -0
- package/dist/src/transport/listen-test.js.map +1 -0
- package/package.json +66 -95
- package/src/connection/index.ts +184 -0
- package/src/connection-encryption/index.ts +97 -0
- package/src/connection-encryption/utils/index.ts +23 -0
- package/src/index.ts +1 -1
- package/src/is-valid-tick.ts +1 -1
- package/src/mocks/connection-encrypter.ts +113 -0
- package/src/mocks/connection-gater.ts +18 -0
- package/src/mocks/connection-manager.ts +209 -0
- package/src/mocks/connection.ts +218 -0
- package/src/mocks/duplex.ts +10 -0
- package/src/mocks/index.ts +12 -0
- package/src/mocks/metrics.ts +162 -0
- package/src/mocks/multiaddr-connection.ts +67 -0
- package/src/mocks/muxer.ts +447 -0
- package/src/mocks/peer-discovery.ts +60 -0
- package/src/mocks/registrar.ts +88 -0
- package/src/mocks/upgrader.ts +49 -0
- package/src/peer-discovery/index.ts +90 -0
- package/src/stream-muxer/base-test.ts +196 -0
- package/src/stream-muxer/close-test.ts +346 -0
- package/src/stream-muxer/index.ts +15 -0
- package/src/stream-muxer/mega-stress-test.ts +14 -0
- package/src/stream-muxer/spawner.ts +54 -0
- package/src/stream-muxer/stress-test.ts +27 -0
- package/src/transport/dial-test.ts +124 -0
- package/src/transport/filter-test.ts +25 -0
- package/src/transport/index.ts +25 -0
- package/src/transport/listen-test.ts +191 -0
- package/dist/typedoc-urls.json +0 -3
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { CodeError } from '@libp2p/interface/errors'
|
|
2
|
+
import { type Logger, logger } from '@libp2p/logger'
|
|
3
|
+
import { abortableSource } from 'abortable-iterator'
|
|
4
|
+
import { anySignal } from 'any-signal'
|
|
5
|
+
import map from 'it-map'
|
|
6
|
+
import * as ndjson from 'it-ndjson'
|
|
7
|
+
import { pipe } from 'it-pipe'
|
|
8
|
+
import { type Pushable, pushable } from 'it-pushable'
|
|
9
|
+
import { Uint8ArrayList } from 'uint8arraylist'
|
|
10
|
+
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
|
|
11
|
+
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
12
|
+
import type { Stream } from '@libp2p/interface/connection'
|
|
13
|
+
import type { StreamMuxer, StreamMuxerFactory, StreamMuxerInit } from '@libp2p/interface/stream-muxer'
|
|
14
|
+
import type { Source } from 'it-stream-types'
|
|
15
|
+
|
|
16
|
+
let muxers = 0
|
|
17
|
+
let streams = 0
|
|
18
|
+
const MAX_MESSAGE_SIZE = 1024 * 1024
|
|
19
|
+
|
|
20
|
+
interface DataMessage {
|
|
21
|
+
id: string
|
|
22
|
+
type: 'data'
|
|
23
|
+
direction: 'initiator' | 'recipient'
|
|
24
|
+
chunk: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ResetMessage {
|
|
28
|
+
id: string
|
|
29
|
+
type: 'reset'
|
|
30
|
+
direction: 'initiator' | 'recipient'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface CloseMessage {
|
|
34
|
+
id: string
|
|
35
|
+
type: 'close'
|
|
36
|
+
direction: 'initiator' | 'recipient'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface CreateMessage {
|
|
40
|
+
id: string
|
|
41
|
+
type: 'create'
|
|
42
|
+
direction: 'initiator'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type StreamMessage = DataMessage | ResetMessage | CloseMessage | CreateMessage
|
|
46
|
+
|
|
47
|
+
class MuxedStream {
|
|
48
|
+
public id: string
|
|
49
|
+
public input: Pushable<Uint8ArrayList>
|
|
50
|
+
public stream: Stream
|
|
51
|
+
public type: 'initiator' | 'recipient'
|
|
52
|
+
|
|
53
|
+
private sinkEnded: boolean
|
|
54
|
+
private sourceEnded: boolean
|
|
55
|
+
private readonly abortController: AbortController
|
|
56
|
+
private readonly resetController: AbortController
|
|
57
|
+
private readonly closeController: AbortController
|
|
58
|
+
private readonly log: Logger
|
|
59
|
+
|
|
60
|
+
constructor (init: { id: string, type: 'initiator' | 'recipient', push: Pushable<StreamMessage>, onEnd: (err?: Error) => void }) {
|
|
61
|
+
const { id, type, push, onEnd } = init
|
|
62
|
+
|
|
63
|
+
this.log = logger(`libp2p:mock-muxer:stream:${id}:${type}`)
|
|
64
|
+
|
|
65
|
+
this.id = id
|
|
66
|
+
this.type = type
|
|
67
|
+
this.abortController = new AbortController()
|
|
68
|
+
this.resetController = new AbortController()
|
|
69
|
+
this.closeController = new AbortController()
|
|
70
|
+
|
|
71
|
+
this.sourceEnded = false
|
|
72
|
+
this.sinkEnded = false
|
|
73
|
+
|
|
74
|
+
let endErr: Error | undefined
|
|
75
|
+
|
|
76
|
+
const onSourceEnd = (err?: Error): void => {
|
|
77
|
+
if (this.sourceEnded) {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.log('onSourceEnd sink ended? %s', this.sinkEnded)
|
|
82
|
+
|
|
83
|
+
this.sourceEnded = true
|
|
84
|
+
|
|
85
|
+
if (err != null && endErr == null) {
|
|
86
|
+
endErr = err
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (this.sinkEnded) {
|
|
90
|
+
this.stream.stat.timeline.close = Date.now()
|
|
91
|
+
|
|
92
|
+
if (onEnd != null) {
|
|
93
|
+
onEnd(endErr)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const onSinkEnd = (err?: Error): void => {
|
|
99
|
+
if (this.sinkEnded) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.log('onSinkEnd source ended? %s', this.sourceEnded)
|
|
104
|
+
|
|
105
|
+
this.sinkEnded = true
|
|
106
|
+
|
|
107
|
+
if (err != null && endErr == null) {
|
|
108
|
+
endErr = err
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (this.sourceEnded) {
|
|
112
|
+
this.stream.stat.timeline.close = Date.now()
|
|
113
|
+
|
|
114
|
+
if (onEnd != null) {
|
|
115
|
+
onEnd(endErr)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.input = pushable({
|
|
121
|
+
onEnd: onSourceEnd
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
this.stream = {
|
|
125
|
+
id,
|
|
126
|
+
sink: async (source) => {
|
|
127
|
+
if (this.sinkEnded) {
|
|
128
|
+
throw new CodeError('stream closed for writing', 'ERR_SINK_ENDED')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const signal = anySignal([
|
|
132
|
+
this.abortController.signal,
|
|
133
|
+
this.resetController.signal,
|
|
134
|
+
this.closeController.signal
|
|
135
|
+
])
|
|
136
|
+
|
|
137
|
+
source = abortableSource(source, signal)
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
if (this.type === 'initiator') {
|
|
141
|
+
// If initiator, open a new stream
|
|
142
|
+
const createMsg: CreateMessage = {
|
|
143
|
+
id: this.id,
|
|
144
|
+
type: 'create',
|
|
145
|
+
direction: this.type
|
|
146
|
+
}
|
|
147
|
+
push.push(createMsg)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const list = new Uint8ArrayList()
|
|
151
|
+
|
|
152
|
+
for await (const chunk of source) {
|
|
153
|
+
list.append(chunk)
|
|
154
|
+
|
|
155
|
+
while (list.length > 0) {
|
|
156
|
+
const available = Math.min(list.length, MAX_MESSAGE_SIZE)
|
|
157
|
+
const dataMsg: DataMessage = {
|
|
158
|
+
id,
|
|
159
|
+
type: 'data',
|
|
160
|
+
chunk: uint8ArrayToString(list.subarray(0, available), 'base64pad'),
|
|
161
|
+
direction: this.type
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
push.push(dataMsg)
|
|
165
|
+
list.consume(available)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch (err: any) {
|
|
169
|
+
if (err.type === 'aborted' && err.message === 'The operation was aborted') {
|
|
170
|
+
if (this.closeController.signal.aborted) {
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (this.resetController.signal.aborted) {
|
|
175
|
+
err.message = 'stream reset'
|
|
176
|
+
err.code = 'ERR_STREAM_RESET'
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (this.abortController.signal.aborted) {
|
|
180
|
+
err.message = 'stream aborted'
|
|
181
|
+
err.code = 'ERR_STREAM_ABORT'
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Send no more data if this stream was remotely reset
|
|
186
|
+
if (err.code !== 'ERR_STREAM_RESET') {
|
|
187
|
+
const resetMsg: ResetMessage = {
|
|
188
|
+
id,
|
|
189
|
+
type: 'reset',
|
|
190
|
+
direction: this.type
|
|
191
|
+
}
|
|
192
|
+
push.push(resetMsg)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.log('sink erred', err)
|
|
196
|
+
|
|
197
|
+
this.input.end(err)
|
|
198
|
+
onSinkEnd(err)
|
|
199
|
+
return
|
|
200
|
+
} finally {
|
|
201
|
+
signal.clear()
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.log('sink ended')
|
|
205
|
+
|
|
206
|
+
onSinkEnd()
|
|
207
|
+
|
|
208
|
+
const closeMsg: CloseMessage = {
|
|
209
|
+
id,
|
|
210
|
+
type: 'close',
|
|
211
|
+
direction: this.type
|
|
212
|
+
}
|
|
213
|
+
push.push(closeMsg)
|
|
214
|
+
},
|
|
215
|
+
source: this.input,
|
|
216
|
+
|
|
217
|
+
// Close for reading
|
|
218
|
+
close: () => {
|
|
219
|
+
this.stream.closeRead()
|
|
220
|
+
this.stream.closeWrite()
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
closeRead: () => {
|
|
224
|
+
this.input.end()
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
closeWrite: () => {
|
|
228
|
+
this.closeController.abort()
|
|
229
|
+
|
|
230
|
+
const closeMsg: CloseMessage = {
|
|
231
|
+
id,
|
|
232
|
+
type: 'close',
|
|
233
|
+
direction: this.type
|
|
234
|
+
}
|
|
235
|
+
push.push(closeMsg)
|
|
236
|
+
onSinkEnd()
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
// Close for reading and writing (local error)
|
|
240
|
+
abort: (err: Error) => {
|
|
241
|
+
// End the source with the passed error
|
|
242
|
+
this.input.end(err)
|
|
243
|
+
this.abortController.abort()
|
|
244
|
+
onSinkEnd(err)
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
// Close immediately for reading and writing (remote error)
|
|
248
|
+
reset: () => {
|
|
249
|
+
const err = new CodeError('stream reset', 'ERR_STREAM_RESET')
|
|
250
|
+
this.resetController.abort()
|
|
251
|
+
this.input.end(err)
|
|
252
|
+
onSinkEnd(err)
|
|
253
|
+
},
|
|
254
|
+
stat: {
|
|
255
|
+
direction: type === 'initiator' ? 'outbound' : 'inbound',
|
|
256
|
+
timeline: {
|
|
257
|
+
open: Date.now()
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
metadata: {}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
class MockMuxer implements StreamMuxer {
|
|
266
|
+
public source: AsyncGenerator<Uint8Array>
|
|
267
|
+
public input: Pushable<Uint8Array>
|
|
268
|
+
public streamInput: Pushable<StreamMessage>
|
|
269
|
+
public name: string
|
|
270
|
+
public protocol: string = '/mock-muxer/1.0.0'
|
|
271
|
+
|
|
272
|
+
private readonly closeController: AbortController
|
|
273
|
+
private readonly registryInitiatorStreams: Map<string, MuxedStream>
|
|
274
|
+
private readonly registryRecipientStreams: Map<string, MuxedStream>
|
|
275
|
+
private readonly options: StreamMuxerInit
|
|
276
|
+
|
|
277
|
+
private readonly log: Logger
|
|
278
|
+
|
|
279
|
+
constructor (init?: StreamMuxerInit) {
|
|
280
|
+
this.name = `muxer:${muxers++}`
|
|
281
|
+
this.log = logger(`libp2p:mock-muxer:${this.name}`)
|
|
282
|
+
this.registryInitiatorStreams = new Map()
|
|
283
|
+
this.registryRecipientStreams = new Map()
|
|
284
|
+
this.log('create muxer')
|
|
285
|
+
this.options = init ?? { direction: 'inbound' }
|
|
286
|
+
this.closeController = new AbortController()
|
|
287
|
+
// receives data from the muxer at the other end of the stream
|
|
288
|
+
this.source = this.input = pushable({
|
|
289
|
+
onEnd: (err) => {
|
|
290
|
+
this.close(err)
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
// receives messages from all of the muxed streams
|
|
295
|
+
this.streamInput = pushable<StreamMessage>({
|
|
296
|
+
objectMode: true
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// receive incoming messages
|
|
301
|
+
async sink (source: Source<Uint8ArrayList | Uint8Array>): Promise<void> {
|
|
302
|
+
try {
|
|
303
|
+
await pipe(
|
|
304
|
+
abortableSource(source, this.closeController.signal),
|
|
305
|
+
(source) => map(source, buf => uint8ArrayToString(buf.subarray())),
|
|
306
|
+
ndjson.parse<StreamMessage>,
|
|
307
|
+
async (source) => {
|
|
308
|
+
for await (const message of source) {
|
|
309
|
+
this.log.trace('-> %s %s %s', message.type, message.direction, message.id)
|
|
310
|
+
this.handleMessage(message)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
this.log('muxed stream ended')
|
|
316
|
+
this.input.end()
|
|
317
|
+
} catch (err: any) {
|
|
318
|
+
this.log('muxed stream errored', err)
|
|
319
|
+
this.input.end(err)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
handleMessage (message: StreamMessage): void {
|
|
324
|
+
let muxedStream: MuxedStream | undefined
|
|
325
|
+
|
|
326
|
+
const registry = message.direction === 'initiator' ? this.registryRecipientStreams : this.registryInitiatorStreams
|
|
327
|
+
|
|
328
|
+
if (message.type === 'create') {
|
|
329
|
+
if (registry.has(message.id)) {
|
|
330
|
+
throw new Error(`Already had stream for ${message.id}`)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
muxedStream = this.createStream(message.id, 'recipient')
|
|
334
|
+
registry.set(muxedStream.stream.id, muxedStream)
|
|
335
|
+
|
|
336
|
+
if (this.options.onIncomingStream != null) {
|
|
337
|
+
this.options.onIncomingStream(muxedStream.stream)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
muxedStream = registry.get(message.id)
|
|
342
|
+
|
|
343
|
+
if (muxedStream == null) {
|
|
344
|
+
this.log.error(`No stream found for ${message.id}`)
|
|
345
|
+
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (message.type === 'data') {
|
|
350
|
+
muxedStream.input.push(new Uint8ArrayList(uint8ArrayFromString(message.chunk, 'base64pad')))
|
|
351
|
+
} else if (message.type === 'reset') {
|
|
352
|
+
this.log('-> reset stream %s %s', muxedStream.type, muxedStream.stream.id)
|
|
353
|
+
muxedStream.stream.reset()
|
|
354
|
+
} else if (message.type === 'close') {
|
|
355
|
+
this.log('-> closing stream %s %s', muxedStream.type, muxedStream.stream.id)
|
|
356
|
+
muxedStream.stream.closeRead()
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
get streams (): Stream[] {
|
|
361
|
+
return Array.from(this.registryRecipientStreams.values())
|
|
362
|
+
.concat(Array.from(this.registryInitiatorStreams.values()))
|
|
363
|
+
.map(({ stream }) => stream)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
newStream (name?: string): Stream {
|
|
367
|
+
if (this.closeController.signal.aborted) {
|
|
368
|
+
throw new Error('Muxer already closed')
|
|
369
|
+
}
|
|
370
|
+
this.log('newStream %s', name)
|
|
371
|
+
const storedStream = this.createStream(name, 'initiator')
|
|
372
|
+
this.registryInitiatorStreams.set(storedStream.stream.id, storedStream)
|
|
373
|
+
|
|
374
|
+
return storedStream.stream
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
createStream (name?: string, type: 'initiator' | 'recipient' = 'initiator'): MuxedStream {
|
|
378
|
+
const id = name ?? `${this.name}:stream:${streams++}`
|
|
379
|
+
|
|
380
|
+
this.log('createStream %s %s', type, id)
|
|
381
|
+
|
|
382
|
+
const muxedStream: MuxedStream = new MuxedStream({
|
|
383
|
+
id,
|
|
384
|
+
type,
|
|
385
|
+
push: this.streamInput,
|
|
386
|
+
onEnd: () => {
|
|
387
|
+
this.log('stream ended %s %s', type, id)
|
|
388
|
+
|
|
389
|
+
if (type === 'initiator') {
|
|
390
|
+
this.registryInitiatorStreams.delete(id)
|
|
391
|
+
} else {
|
|
392
|
+
this.registryRecipientStreams.delete(id)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (this.options.onStreamEnd != null) {
|
|
396
|
+
this.options.onStreamEnd(muxedStream.stream)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
return muxedStream
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
close (err?: Error): void {
|
|
405
|
+
if (this.closeController.signal.aborted) return
|
|
406
|
+
this.log('closing muxed streams')
|
|
407
|
+
|
|
408
|
+
if (err == null) {
|
|
409
|
+
this.streams.forEach(s => {
|
|
410
|
+
s.close()
|
|
411
|
+
})
|
|
412
|
+
} else {
|
|
413
|
+
this.streams.forEach(s => {
|
|
414
|
+
s.abort(err)
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
this.closeController.abort()
|
|
418
|
+
this.input.end(err)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
class MockMuxerFactory implements StreamMuxerFactory {
|
|
423
|
+
public protocol: string = '/mock-muxer/1.0.0'
|
|
424
|
+
|
|
425
|
+
createStreamMuxer (init?: StreamMuxerInit): StreamMuxer {
|
|
426
|
+
const mockMuxer = new MockMuxer(init)
|
|
427
|
+
|
|
428
|
+
void Promise.resolve().then(async () => {
|
|
429
|
+
void pipe(
|
|
430
|
+
mockMuxer.streamInput,
|
|
431
|
+
ndjson.stringify,
|
|
432
|
+
(source) => map(source, str => new Uint8ArrayList(uint8ArrayFromString(str))),
|
|
433
|
+
async (source) => {
|
|
434
|
+
for await (const buf of source) {
|
|
435
|
+
mockMuxer.input.push(buf.subarray())
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
)
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
return mockMuxer
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export function mockMuxer (): MockMuxerFactory {
|
|
446
|
+
return new MockMuxerFactory()
|
|
447
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { EventEmitter } from '@libp2p/interface/events'
|
|
2
|
+
import { peerDiscovery } from '@libp2p/interface/peer-discovery'
|
|
3
|
+
import * as PeerIdFactory from '@libp2p/peer-id-factory'
|
|
4
|
+
import { multiaddr } from '@multiformats/multiaddr'
|
|
5
|
+
import type { PeerDiscovery, PeerDiscoveryEvents } from '@libp2p/interface/peer-discovery'
|
|
6
|
+
import type { PeerInfo } from '@libp2p/interface/peer-info'
|
|
7
|
+
|
|
8
|
+
interface MockDiscoveryInit {
|
|
9
|
+
discoveryDelay?: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Emits 'peer' events on discovery.
|
|
14
|
+
*/
|
|
15
|
+
export class MockDiscovery extends EventEmitter<PeerDiscoveryEvents> implements PeerDiscovery {
|
|
16
|
+
public readonly options: MockDiscoveryInit
|
|
17
|
+
private _isRunning: boolean
|
|
18
|
+
private _timer: any
|
|
19
|
+
|
|
20
|
+
constructor (init = {}) {
|
|
21
|
+
super()
|
|
22
|
+
|
|
23
|
+
this.options = init
|
|
24
|
+
this._isRunning = false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
readonly [peerDiscovery] = this
|
|
28
|
+
|
|
29
|
+
start (): void {
|
|
30
|
+
this._isRunning = true
|
|
31
|
+
this._discoverPeer()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
stop (): void {
|
|
35
|
+
clearTimeout(this._timer)
|
|
36
|
+
this._isRunning = false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
isStarted (): boolean {
|
|
40
|
+
return this._isRunning
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_discoverPeer (): void {
|
|
44
|
+
if (!this._isRunning) return
|
|
45
|
+
|
|
46
|
+
PeerIdFactory.createEd25519PeerId()
|
|
47
|
+
.then(peerId => {
|
|
48
|
+
this._timer = setTimeout(() => {
|
|
49
|
+
this.safeDispatchEvent<PeerInfo>('peer', {
|
|
50
|
+
detail: {
|
|
51
|
+
id: peerId,
|
|
52
|
+
multiaddrs: [multiaddr('/ip4/127.0.0.1/tcp/8000')],
|
|
53
|
+
protocols: []
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}, this.options.discoveryDelay ?? 1000)
|
|
57
|
+
})
|
|
58
|
+
.catch(() => {})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import merge from 'merge-options'
|
|
2
|
+
import type { Connection } from '@libp2p/interface/connection'
|
|
3
|
+
import type { PeerId } from '@libp2p/interface/peer-id'
|
|
4
|
+
import type { Topology } from '@libp2p/interface/topology'
|
|
5
|
+
import type { IncomingStreamData, Registrar, StreamHandler, StreamHandlerOptions, StreamHandlerRecord } from '@libp2p/interface-internal/registrar'
|
|
6
|
+
|
|
7
|
+
export class MockRegistrar implements Registrar {
|
|
8
|
+
private readonly topologies = new Map<string, Array<{ id: string, topology: Topology }>>()
|
|
9
|
+
private readonly handlers = new Map<string, StreamHandlerRecord>()
|
|
10
|
+
|
|
11
|
+
getProtocols (): string[] {
|
|
12
|
+
return Array.from(this.handlers.keys()).sort()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async handle (protocol: string, handler: StreamHandler, opts?: StreamHandlerOptions): Promise<void> {
|
|
16
|
+
const options = merge.bind({ ignoreUndefined: true })({
|
|
17
|
+
maxInboundStreams: 1,
|
|
18
|
+
maxOutboundStreams: 1
|
|
19
|
+
}, opts)
|
|
20
|
+
|
|
21
|
+
if (this.handlers.has(protocol)) {
|
|
22
|
+
throw new Error(`Handler already registered for protocol ${protocol}`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
this.handlers.set(protocol, {
|
|
26
|
+
handler,
|
|
27
|
+
options
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async unhandle (protocol: string): Promise<void> {
|
|
32
|
+
this.handlers.delete(protocol)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getHandler (protocol: string): StreamHandlerRecord {
|
|
36
|
+
const handler = this.handlers.get(protocol)
|
|
37
|
+
|
|
38
|
+
if (handler == null) {
|
|
39
|
+
throw new Error(`No handler registered for protocol ${protocol}`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return handler
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async register (protocol: string, topology: Topology): Promise<string> {
|
|
46
|
+
const id = `topology-id-${Math.random()}`
|
|
47
|
+
let topologies = this.topologies.get(protocol)
|
|
48
|
+
|
|
49
|
+
if (topologies == null) {
|
|
50
|
+
topologies = []
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
topologies.push({
|
|
54
|
+
id,
|
|
55
|
+
topology
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
this.topologies.set(protocol, topologies)
|
|
59
|
+
|
|
60
|
+
return id
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
unregister (id: string | string[]): void {
|
|
64
|
+
if (!Array.isArray(id)) {
|
|
65
|
+
id = [id]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
id.forEach(id => this.topologies.delete(id))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getTopologies (protocol: string): Topology[] {
|
|
72
|
+
return (this.topologies.get(protocol) ?? []).map(t => t.topology)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function mockRegistrar (): Registrar {
|
|
77
|
+
return new MockRegistrar()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function mockIncomingStreamEvent (protocol: string, conn: Connection, remotePeer: PeerId): Promise<IncomingStreamData> {
|
|
81
|
+
return {
|
|
82
|
+
...await conn.newStream([protocol]),
|
|
83
|
+
// @ts-expect-error incomplete implementation
|
|
84
|
+
connection: {
|
|
85
|
+
remotePeer
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { mockConnection } from './connection.js'
|
|
2
|
+
import type { Libp2pEvents } from '@libp2p/interface'
|
|
3
|
+
import type { Connection, MultiaddrConnection } from '@libp2p/interface/connection'
|
|
4
|
+
import type { EventEmitter } from '@libp2p/interface/events'
|
|
5
|
+
import type { Registrar } from '@libp2p/interface-internal/registrar'
|
|
6
|
+
import type { Upgrader, UpgraderOptions } from '@libp2p/interface-internal/upgrader'
|
|
7
|
+
|
|
8
|
+
export interface MockUpgraderInit {
|
|
9
|
+
registrar?: Registrar
|
|
10
|
+
events?: EventEmitter<Libp2pEvents>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class MockUpgrader implements Upgrader {
|
|
14
|
+
private readonly registrar?: Registrar
|
|
15
|
+
private readonly events?: EventEmitter<Libp2pEvents>
|
|
16
|
+
|
|
17
|
+
constructor (init: MockUpgraderInit) {
|
|
18
|
+
this.registrar = init.registrar
|
|
19
|
+
this.events = init.events
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async upgradeOutbound (multiaddrConnection: MultiaddrConnection, opts: UpgraderOptions = {}): Promise<Connection> {
|
|
23
|
+
const connection = mockConnection(multiaddrConnection, {
|
|
24
|
+
direction: 'outbound',
|
|
25
|
+
registrar: this.registrar,
|
|
26
|
+
...opts
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
this.events?.safeDispatchEvent('connection:open', { detail: connection })
|
|
30
|
+
|
|
31
|
+
return connection
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async upgradeInbound (multiaddrConnection: MultiaddrConnection, opts: UpgraderOptions = {}): Promise<Connection> {
|
|
35
|
+
const connection = mockConnection(multiaddrConnection, {
|
|
36
|
+
direction: 'inbound',
|
|
37
|
+
registrar: this.registrar,
|
|
38
|
+
...opts
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
this.events?.safeDispatchEvent('connection:open', { detail: connection })
|
|
42
|
+
|
|
43
|
+
return connection
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function mockUpgrader (init: MockUpgraderInit = {}): Upgrader {
|
|
48
|
+
return new MockUpgrader(init)
|
|
49
|
+
}
|