@libp2p/interface-compliance-tests 6.1.8 → 6.1.9-05d559f54
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/dist/src/matchers.d.ts +6 -0
- package/dist/src/matchers.d.ts.map +1 -1
- package/dist/src/matchers.js +6 -0
- package/dist/src/matchers.js.map +1 -1
- package/dist/src/mocks/index.d.ts +0 -2
- package/dist/src/mocks/index.d.ts.map +1 -1
- package/dist/src/mocks/index.js +0 -2
- package/dist/src/mocks/index.js.map +1 -1
- package/dist/src/mocks/muxer.d.ts +8 -3
- package/dist/src/mocks/muxer.d.ts.map +1 -1
- package/dist/src/mocks/muxer.js +15 -6
- package/dist/src/mocks/muxer.js.map +1 -1
- package/dist/src/mocks/upgrader.d.ts.map +1 -1
- package/dist/src/mocks/upgrader.js +0 -1
- package/dist/src/mocks/upgrader.js.map +1 -1
- package/dist/src/transport/index.d.ts +23 -10
- package/dist/src/transport/index.d.ts.map +1 -1
- package/dist/src/transport/index.js +418 -6
- package/dist/src/transport/index.js.map +1 -1
- package/dist/src/transport/utils.d.ts +17 -0
- package/dist/src/transport/utils.d.ts.map +1 -0
- package/dist/src/transport/utils.js +63 -0
- package/dist/src/transport/utils.js.map +1 -0
- package/package.json +16 -15
- package/src/matchers.ts +6 -0
- package/src/mocks/index.ts +0 -2
- package/src/mocks/muxer.ts +24 -10
- package/src/mocks/upgrader.ts +1 -3
- package/src/transport/index.ts +570 -16
- package/src/transport/utils.ts +76 -0
- package/dist/src/connection/index.d.ts +0 -5
- package/dist/src/connection/index.d.ts.map +0 -1
- package/dist/src/connection/index.js +0 -135
- package/dist/src/connection/index.js.map +0 -1
- package/dist/src/mocks/connection-gater.d.ts +0 -3
- package/dist/src/mocks/connection-gater.d.ts.map +0 -1
- package/dist/src/mocks/connection-gater.js +0 -17
- package/dist/src/mocks/connection-gater.js.map +0 -1
- package/dist/src/mocks/metrics.d.ts +0 -3
- package/dist/src/mocks/metrics.d.ts.map +0 -1
- package/dist/src/mocks/metrics.js +0 -286
- package/dist/src/mocks/metrics.js.map +0 -1
- package/dist/src/mocks/peer-discovery.d.ts +0 -21
- package/dist/src/mocks/peer-discovery.d.ts.map +0 -1
- package/dist/src/mocks/peer-discovery.js +0 -47
- package/dist/src/mocks/peer-discovery.js.map +0 -1
- package/dist/src/transport/dial-test.d.ts +0 -5
- package/dist/src/transport/dial-test.d.ts.map +0 -1
- package/dist/src/transport/dial-test.js +0 -99
- package/dist/src/transport/dial-test.js.map +0 -1
- package/dist/src/transport/filter-test.d.ts +0 -5
- package/dist/src/transport/filter-test.d.ts.map +0 -1
- package/dist/src/transport/filter-test.js +0 -24
- package/dist/src/transport/filter-test.js.map +0 -1
- package/dist/src/transport/listen-test.d.ts +0 -5
- package/dist/src/transport/listen-test.d.ts.map +0 -1
- package/dist/src/transport/listen-test.js +0 -154
- package/dist/src/transport/listen-test.js.map +0 -1
- package/dist/typedoc-urls.json +0 -49
- package/src/connection/index.ts +0 -166
- package/src/mocks/connection-gater.ts +0 -18
- package/src/mocks/metrics.ts +0 -385
- package/src/mocks/peer-discovery.ts +0 -59
- package/src/transport/dial-test.ts +0 -125
- package/src/transport/filter-test.ts +0 -32
- package/src/transport/listen-test.ts +0 -192
package/src/mocks/muxer.ts
CHANGED
|
@@ -27,9 +27,15 @@ interface ResetMessage {
|
|
|
27
27
|
direction: Direction
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
interface
|
|
30
|
+
interface CloseWriteMessage {
|
|
31
31
|
id: string
|
|
32
|
-
type: '
|
|
32
|
+
type: 'closeWrite'
|
|
33
|
+
direction: Direction
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface CloseReadMessage {
|
|
37
|
+
id: string
|
|
38
|
+
type: 'closeRead'
|
|
33
39
|
direction: Direction
|
|
34
40
|
}
|
|
35
41
|
|
|
@@ -39,7 +45,7 @@ interface CreateMessage {
|
|
|
39
45
|
direction: 'outbound'
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
type StreamMessage = DataMessage | ResetMessage |
|
|
48
|
+
type StreamMessage = DataMessage | ResetMessage | CloseWriteMessage | CloseReadMessage | CreateMessage
|
|
43
49
|
|
|
44
50
|
export interface MockMuxedStreamInit extends AbstractStreamInit {
|
|
45
51
|
push: Pushable<StreamMessage>
|
|
@@ -84,16 +90,21 @@ class MuxedStream extends AbstractStream {
|
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
sendCloseWrite (): void {
|
|
87
|
-
const closeMsg:
|
|
93
|
+
const closeMsg: CloseWriteMessage = {
|
|
88
94
|
id: this.id,
|
|
89
|
-
type: '
|
|
95
|
+
type: 'closeWrite',
|
|
90
96
|
direction: this.direction
|
|
91
97
|
}
|
|
92
98
|
this.push.push(closeMsg)
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
sendCloseRead (): void {
|
|
96
|
-
|
|
102
|
+
const closeMsg: CloseReadMessage = {
|
|
103
|
+
id: this.id,
|
|
104
|
+
type: 'closeRead',
|
|
105
|
+
direction: this.direction
|
|
106
|
+
}
|
|
107
|
+
this.push.push(closeMsg)
|
|
97
108
|
}
|
|
98
109
|
}
|
|
99
110
|
|
|
@@ -153,10 +164,10 @@ class MockMuxer implements StreamMuxer {
|
|
|
153
164
|
}
|
|
154
165
|
)
|
|
155
166
|
|
|
156
|
-
this.log('
|
|
167
|
+
this.log('muxer ended')
|
|
157
168
|
this.input.end()
|
|
158
169
|
} catch (err: any) {
|
|
159
|
-
this.log('
|
|
170
|
+
this.log.error('muxer errored - %e', err)
|
|
160
171
|
this.input.end(err)
|
|
161
172
|
}
|
|
162
173
|
}
|
|
@@ -192,9 +203,12 @@ class MockMuxer implements StreamMuxer {
|
|
|
192
203
|
} else if (message.type === 'reset') {
|
|
193
204
|
this.log('-> reset stream %s %s', muxedStream.direction, muxedStream.id)
|
|
194
205
|
muxedStream.reset()
|
|
195
|
-
} else if (message.type === '
|
|
196
|
-
this.log('-> closing stream %s %s', muxedStream.direction, muxedStream.id)
|
|
206
|
+
} else if (message.type === 'closeWrite') {
|
|
207
|
+
this.log('-> closing writeable end of stream %s %s', muxedStream.direction, muxedStream.id)
|
|
197
208
|
muxedStream.remoteCloseWrite()
|
|
209
|
+
} else if (message.type === 'closeRead') {
|
|
210
|
+
this.log('-> closing readable end of stream %s %s', muxedStream.direction, muxedStream.id)
|
|
211
|
+
muxedStream.remoteCloseRead()
|
|
198
212
|
}
|
|
199
213
|
}
|
|
200
214
|
|
package/src/mocks/upgrader.ts
CHANGED
|
@@ -28,7 +28,7 @@ class MockUpgrader implements Upgrader {
|
|
|
28
28
|
return connection
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
async upgradeInbound (multiaddrConnection: MultiaddrConnection, opts: UpgraderOptions = {}): Promise<
|
|
31
|
+
async upgradeInbound (multiaddrConnection: MultiaddrConnection, opts: UpgraderOptions = {}): Promise<void> {
|
|
32
32
|
const connection = mockConnection(multiaddrConnection, {
|
|
33
33
|
direction: 'inbound',
|
|
34
34
|
registrar: this.registrar,
|
|
@@ -36,8 +36,6 @@ class MockUpgrader implements Upgrader {
|
|
|
36
36
|
})
|
|
37
37
|
|
|
38
38
|
this.events?.safeDispatchEvent('connection:open', { detail: connection })
|
|
39
|
-
|
|
40
|
-
return connection
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
|
package/src/transport/index.ts
CHANGED
|
@@ -1,27 +1,581 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import { stop } from '@libp2p/interface'
|
|
2
|
+
import { expect } from 'aegir/chai'
|
|
3
|
+
import delay from 'delay'
|
|
4
|
+
import drain from 'it-drain'
|
|
5
|
+
import { pushable } from 'it-pushable'
|
|
6
|
+
import pDefer from 'p-defer'
|
|
7
|
+
import { pEvent } from 'p-event'
|
|
8
|
+
import pRetry from 'p-retry'
|
|
9
|
+
import pWaitFor from 'p-wait-for'
|
|
10
|
+
import { raceSignal } from 'race-signal'
|
|
11
|
+
import { isValidTick } from '../is-valid-tick.js'
|
|
12
|
+
import { createPeer, getTransportManager, getUpgrader, slowNetwork } from './utils.js'
|
|
4
13
|
import type { TestSetup } from '../index.js'
|
|
5
|
-
import type {
|
|
14
|
+
import type { Echo } from '@libp2p/echo'
|
|
15
|
+
import type { Connection, Libp2p, Stream, StreamHandler } from '@libp2p/interface'
|
|
6
16
|
import type { Multiaddr } from '@multiformats/multiaddr'
|
|
17
|
+
import type { MultiaddrMatcher } from '@multiformats/multiaddr-matcher'
|
|
18
|
+
import type { Libp2pInit } from 'libp2p'
|
|
19
|
+
import type { DeferredPromise } from 'p-defer'
|
|
7
20
|
|
|
8
|
-
export interface
|
|
9
|
-
|
|
10
|
-
|
|
21
|
+
export interface TransportTestFixtures {
|
|
22
|
+
/**
|
|
23
|
+
* Addresses that will be used to dial listeners - both addresses must resolve
|
|
24
|
+
* to the same node
|
|
25
|
+
*/
|
|
26
|
+
dialAddrs?: [Multiaddr, Multiaddr]
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Filter out any addresses that cannot be dialed by the transport
|
|
30
|
+
*/
|
|
31
|
+
dialMultiaddrMatcher: MultiaddrMatcher
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Filter out any addresses that cannot be listened on by the transport
|
|
35
|
+
*/
|
|
36
|
+
listenMultiaddrMatcher: MultiaddrMatcher
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Config that creates a libp2p node that can dial a listener
|
|
40
|
+
*/
|
|
41
|
+
dialer: Libp2pInit
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Config that creates a libp2p node that can accept dials
|
|
45
|
+
*/
|
|
46
|
+
listener?: Libp2pInit
|
|
11
47
|
}
|
|
12
48
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
49
|
+
async function getSetup (common: TestSetup<TransportTestFixtures>): Promise<{ dialer: Libp2p<{ echo: Echo }>, listener?: Libp2p<{ echo: Echo }>, dialAddrs: Multiaddr[], dialMultiaddrMatcher: MultiaddrMatcher, listenMultiaddrMatcher: MultiaddrMatcher }> {
|
|
50
|
+
const setup = await common.setup()
|
|
51
|
+
const dialer = await createPeer(setup.dialer)
|
|
52
|
+
let listener
|
|
53
|
+
|
|
54
|
+
if (setup.listener != null) {
|
|
55
|
+
listener = await createPeer(setup.listener)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const dialAddrs = listener?.getMultiaddrs() ?? setup.dialAddrs
|
|
59
|
+
|
|
60
|
+
if (dialAddrs == null) {
|
|
61
|
+
throw new Error('Listener config or dial addresses must be specified')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
dialer,
|
|
66
|
+
listener,
|
|
67
|
+
dialAddrs: dialAddrs.filter(ma => setup.dialMultiaddrMatcher.exactMatch(ma)),
|
|
68
|
+
dialMultiaddrMatcher: setup.dialMultiaddrMatcher,
|
|
69
|
+
listenMultiaddrMatcher: setup.listenMultiaddrMatcher
|
|
70
|
+
}
|
|
19
71
|
}
|
|
20
72
|
|
|
21
73
|
export default (common: TestSetup<TransportTestFixtures>): void => {
|
|
22
74
|
describe('interface-transport', () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
75
|
+
let dialer: Libp2p<{ echo: Echo }>
|
|
76
|
+
let listener: Libp2p<{ echo: Echo }> | undefined
|
|
77
|
+
let dialAddrs: Multiaddr[]
|
|
78
|
+
let dialMultiaddrMatcher: MultiaddrMatcher
|
|
79
|
+
|
|
80
|
+
afterEach(async () => {
|
|
81
|
+
await stop(dialer, listener)
|
|
82
|
+
await common.teardown()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('simple', async () => {
|
|
86
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
87
|
+
|
|
88
|
+
const input = Uint8Array.from([0, 1, 2, 3, 4])
|
|
89
|
+
const output = await dialer.services.echo.echo(dialAddrs[0], input, {
|
|
90
|
+
signal: AbortSignal.timeout(5000)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
expect(output).to.equalBytes(input)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should listen on multiple addresses', async () => {
|
|
97
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
98
|
+
|
|
99
|
+
const input = Uint8Array.from([0, 1, 2, 3, 4])
|
|
100
|
+
|
|
101
|
+
await expect(dialer.services.echo.echo(dialAddrs[0], input, {
|
|
102
|
+
signal: AbortSignal.timeout(5000)
|
|
103
|
+
})).to.eventually.deep.equal(input)
|
|
104
|
+
|
|
105
|
+
await expect(dialer.services.echo.echo(dialAddrs[1], input, {
|
|
106
|
+
signal: AbortSignal.timeout(5000)
|
|
107
|
+
})).to.eventually.deep.equal(input)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('can close connections', async () => {
|
|
111
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
112
|
+
|
|
113
|
+
const conn = await dialer.dial(dialAddrs[0], {
|
|
114
|
+
signal: AbortSignal.timeout(5000)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
await conn.close()
|
|
118
|
+
expect(isValidTick(conn.timeline.close)).to.equal(true)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('abort before dialing throws AbortError', async () => {
|
|
122
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
123
|
+
|
|
124
|
+
const controller = new AbortController()
|
|
125
|
+
controller.abort()
|
|
126
|
+
|
|
127
|
+
await expect(dialer.dial(dialAddrs[0], {
|
|
128
|
+
signal: controller.signal
|
|
129
|
+
})).to.eventually.be.rejected()
|
|
130
|
+
.with.property('name', 'AbortError')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('abort while dialing throws AbortError', async () => {
|
|
134
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
135
|
+
slowNetwork(dialer, 100)
|
|
136
|
+
|
|
137
|
+
const controller = new AbortController()
|
|
138
|
+
setTimeout(() => { controller.abort() }, 50)
|
|
139
|
+
|
|
140
|
+
await expect(dialer.dial(dialAddrs[0], {
|
|
141
|
+
signal: controller.signal
|
|
142
|
+
})).to.eventually.be.rejected()
|
|
143
|
+
.with.property('name', 'AbortError')
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('should close all streams when the connection closes', async () => {
|
|
147
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
148
|
+
|
|
149
|
+
let incomingConnectionPromise: DeferredPromise<Connection> | undefined
|
|
150
|
+
|
|
151
|
+
if (listener != null) {
|
|
152
|
+
incomingConnectionPromise = pDefer<Connection>()
|
|
153
|
+
|
|
154
|
+
listener.addEventListener('connection:open', (event) => {
|
|
155
|
+
const conn = event.detail
|
|
156
|
+
|
|
157
|
+
if (conn.remotePeer.equals(dialer.peerId)) {
|
|
158
|
+
incomingConnectionPromise?.resolve(conn)
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const connection = await dialer.dial(dialAddrs[0])
|
|
164
|
+
let remoteConn: Connection | undefined
|
|
165
|
+
|
|
166
|
+
if (incomingConnectionPromise != null) {
|
|
167
|
+
remoteConn = await incomingConnectionPromise.promise
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < 5; i++) {
|
|
171
|
+
await connection.newStream('/echo/1.0.0', {
|
|
172
|
+
maxOutboundStreams: 5
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const streams = connection.streams
|
|
177
|
+
|
|
178
|
+
// Close the connection and verify all streams have been closed
|
|
179
|
+
await connection.close()
|
|
180
|
+
|
|
181
|
+
await pWaitFor(() => connection.streams.length === 0, {
|
|
182
|
+
timeout: 5000
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
if (remoteConn != null) {
|
|
186
|
+
await pWaitFor(() => remoteConn.streams.length === 0, {
|
|
187
|
+
timeout: 5000
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
expect(streams.find(stream => stream.status === 'open')).to.be.undefined()
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should not handle connection if upgradeInbound rejects', async function () {
|
|
195
|
+
({ dialer, listener, dialAddrs, dialMultiaddrMatcher } = await getSetup(common))
|
|
196
|
+
|
|
197
|
+
if (listener == null) {
|
|
198
|
+
return this.skip()
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const upgrader = getUpgrader(listener)
|
|
202
|
+
upgrader.upgradeInbound = async () => {
|
|
203
|
+
await delay(100)
|
|
204
|
+
throw new Error('Oh noes!')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
await expect(dialer.dial(dialAddrs[0])).to.eventually.be.rejected
|
|
208
|
+
.with.property('name', 'EncryptionFailedError')
|
|
209
|
+
|
|
210
|
+
expect(dialer.getConnections().filter(conn => {
|
|
211
|
+
return dialMultiaddrMatcher.exactMatch(conn.remoteAddr)
|
|
212
|
+
})).to.have.lengthOf(0)
|
|
213
|
+
|
|
214
|
+
if (listener != null) {
|
|
215
|
+
const remoteConnections = listener.getConnections(dialer.peerId)
|
|
216
|
+
.filter(conn => dialMultiaddrMatcher.exactMatch(conn.remoteAddr))
|
|
217
|
+
expect(remoteConnections).to.have.lengthOf(0)
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('should omit peerid in listening addresses', async function () {
|
|
222
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
223
|
+
|
|
224
|
+
if (listener == null) {
|
|
225
|
+
return this.skip()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const tm = getTransportManager(listener)
|
|
229
|
+
const transportListeners = tm.getListeners()
|
|
230
|
+
|
|
231
|
+
for (const transportListener of transportListeners) {
|
|
232
|
+
for (const ma of transportListener.getAddrs()) {
|
|
233
|
+
expect(ma.toString()).to.not.include(`/p2p/${listener.peerId}`)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should handle one big write', async function () {
|
|
239
|
+
const timeout = 120_000
|
|
240
|
+
this.timeout(timeout);
|
|
241
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
242
|
+
|
|
243
|
+
const input = new Uint8Array(1024 * 1024 * 10).fill(5)
|
|
244
|
+
const output = await dialer.services.echo.echo(dialAddrs[0], input, {
|
|
245
|
+
signal: AbortSignal.timeout(timeout)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
expect(output).to.equalBytes(input)
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('should handle many small writes', async function () {
|
|
252
|
+
const timeout = 120_000
|
|
253
|
+
this.timeout(timeout);
|
|
254
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
255
|
+
|
|
256
|
+
for (let i = 0; i < 2000; i++) {
|
|
257
|
+
const input = new Uint8Array(1024).fill(5)
|
|
258
|
+
const output = await dialer.services.echo.echo(dialAddrs[0], input, {
|
|
259
|
+
signal: AbortSignal.timeout(timeout)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
expect(output).to.equalBytes(input)
|
|
263
|
+
}
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
it('can close a stream for reading but send a large amount of data', async function () {
|
|
267
|
+
const timeout = 120_000
|
|
268
|
+
this.timeout(timeout);
|
|
269
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
270
|
+
|
|
271
|
+
if (listener == null) {
|
|
272
|
+
return this.skip()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const protocol = '/send-data/1.0.0'
|
|
276
|
+
const chunkSize = 1024
|
|
277
|
+
const bytes = chunkSize * 1024 * 10
|
|
278
|
+
const deferred = pDefer()
|
|
279
|
+
|
|
280
|
+
await listener.handle(protocol, ({ stream }) => {
|
|
281
|
+
Promise.resolve().then(async () => {
|
|
282
|
+
let read = 0
|
|
283
|
+
|
|
284
|
+
for await (const buf of stream.source) {
|
|
285
|
+
read += buf.byteLength
|
|
286
|
+
|
|
287
|
+
if (read === bytes) {
|
|
288
|
+
deferred.resolve()
|
|
289
|
+
break
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
.catch(err => {
|
|
294
|
+
deferred.reject(err)
|
|
295
|
+
stream.abort(err)
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
const stream = await dialer.dialProtocol(dialAddrs[0], protocol)
|
|
300
|
+
|
|
301
|
+
await stream.closeRead()
|
|
302
|
+
|
|
303
|
+
await stream.sink((async function * () {
|
|
304
|
+
for (let i = 0; i < bytes; i += chunkSize) {
|
|
305
|
+
yield new Uint8Array(chunkSize)
|
|
306
|
+
}
|
|
307
|
+
})())
|
|
308
|
+
|
|
309
|
+
await stream.close()
|
|
310
|
+
|
|
311
|
+
await deferred.promise
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
it('can close a stream for writing but receive a large amount of data', async function () {
|
|
315
|
+
const timeout = 120_000
|
|
316
|
+
this.timeout(timeout);
|
|
317
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
318
|
+
|
|
319
|
+
if (listener == null) {
|
|
320
|
+
return this.skip()
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const protocol = '/receive-data/1.0.0'
|
|
324
|
+
const chunkSize = 1024
|
|
325
|
+
const bytes = chunkSize * 1024 * 10
|
|
326
|
+
const deferred = pDefer()
|
|
327
|
+
|
|
328
|
+
await listener.handle(protocol, ({ stream }) => {
|
|
329
|
+
Promise.resolve().then(async () => {
|
|
330
|
+
await stream.sink((async function * () {
|
|
331
|
+
for (let i = 0; i < bytes; i += chunkSize) {
|
|
332
|
+
yield new Uint8Array(chunkSize)
|
|
333
|
+
}
|
|
334
|
+
})())
|
|
335
|
+
|
|
336
|
+
await stream.close()
|
|
337
|
+
})
|
|
338
|
+
.catch(err => {
|
|
339
|
+
deferred.reject(err)
|
|
340
|
+
stream.abort(err)
|
|
341
|
+
})
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
const stream = await dialer.dialProtocol(dialAddrs[0], protocol)
|
|
345
|
+
|
|
346
|
+
await stream.closeWrite()
|
|
347
|
+
|
|
348
|
+
let read = 0
|
|
349
|
+
|
|
350
|
+
for await (const buf of stream.source) {
|
|
351
|
+
read += buf.byteLength
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
expect(read).to.equal(bytes)
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
it('can close local stream for writing and reading while a remote stream is writing', async function () {
|
|
358
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
359
|
+
|
|
360
|
+
if (listener == null) {
|
|
361
|
+
return this.skip()
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* NodeA NodeB
|
|
366
|
+
* | <--- STOP_SENDING |
|
|
367
|
+
* | FIN ---> |
|
|
368
|
+
* | <--- FIN |
|
|
369
|
+
* | FIN_ACK ---> |
|
|
370
|
+
* | <--- FIN_ACK |
|
|
371
|
+
*/
|
|
372
|
+
|
|
373
|
+
const getRemoteStream = pDefer<Stream>()
|
|
374
|
+
const protocol = '/close-local-while-remote-writes/1.0.0'
|
|
375
|
+
|
|
376
|
+
const streamHandler: StreamHandler = ({ stream }) => {
|
|
377
|
+
void Promise.resolve().then(async () => {
|
|
378
|
+
getRemoteStream.resolve(stream)
|
|
379
|
+
})
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
await listener.handle(protocol, (info) => {
|
|
383
|
+
streamHandler(info)
|
|
384
|
+
}, {
|
|
385
|
+
runOnLimitedConnection: true
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
const connection = await dialer.dial(dialAddrs[0])
|
|
389
|
+
|
|
390
|
+
// open a stream on the echo protocol
|
|
391
|
+
const stream = await connection.newStream(protocol, {
|
|
392
|
+
runOnLimitedConnection: true
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// close the write end immediately
|
|
396
|
+
const p = stream.closeWrite()
|
|
397
|
+
|
|
398
|
+
const remoteStream = await getRemoteStream.promise
|
|
399
|
+
// close the readable end of the remote stream
|
|
400
|
+
await remoteStream.closeRead()
|
|
401
|
+
|
|
402
|
+
// keep the remote write end open, this should delay the FIN_ACK reply to the local stream
|
|
403
|
+
const remoteInputStream = pushable<Uint8Array>()
|
|
404
|
+
void remoteStream.sink(remoteInputStream)
|
|
405
|
+
|
|
406
|
+
// wait for remote to receive local close-write
|
|
407
|
+
await pRetry(() => {
|
|
408
|
+
if (remoteStream.readStatus !== 'closed') {
|
|
409
|
+
throw new Error('Remote stream read status ' + remoteStream.readStatus)
|
|
410
|
+
}
|
|
411
|
+
}, {
|
|
412
|
+
minTimeout: 100
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
// remote closes write
|
|
416
|
+
remoteInputStream.end()
|
|
417
|
+
|
|
418
|
+
// wait to receive FIN_ACK
|
|
419
|
+
await p
|
|
420
|
+
|
|
421
|
+
// wait for remote to notice closure
|
|
422
|
+
await pRetry(() => {
|
|
423
|
+
if (remoteStream.status !== 'closed') {
|
|
424
|
+
throw new Error('Remote stream not closed')
|
|
425
|
+
}
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
assertStreamClosed(stream)
|
|
429
|
+
assertStreamClosed(remoteStream)
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
it('can close local stream for writing and reading while a remote stream is writing using source/sink', async function () {
|
|
433
|
+
({ dialer, listener, dialAddrs } = await getSetup(common))
|
|
434
|
+
|
|
435
|
+
if (listener == null) {
|
|
436
|
+
return this.skip()
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* NodeA NodeB
|
|
441
|
+
* | FIN ---> |
|
|
442
|
+
* | <--- FIN |
|
|
443
|
+
* | FIN_ACK ---> |
|
|
444
|
+
* | <--- FIN_ACK |
|
|
445
|
+
*/
|
|
446
|
+
|
|
447
|
+
const getRemoteStream = pDefer<Stream>()
|
|
448
|
+
const protocol = '/close-local-while-remote-reads/1.0.0'
|
|
449
|
+
|
|
450
|
+
const streamHandler: StreamHandler = ({ stream }) => {
|
|
451
|
+
void Promise.resolve().then(async () => {
|
|
452
|
+
getRemoteStream.resolve(stream)
|
|
453
|
+
})
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
await listener.handle(protocol, (info) => {
|
|
457
|
+
streamHandler(info)
|
|
458
|
+
}, {
|
|
459
|
+
runOnLimitedConnection: true
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
const connection = await dialer.dial(dialAddrs[0])
|
|
463
|
+
|
|
464
|
+
// open a stream on the echo protocol
|
|
465
|
+
const stream = await connection.newStream(protocol, {
|
|
466
|
+
runOnLimitedConnection: true
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
// keep the remote write end open, this should delay the FIN_ACK reply to the local stream
|
|
470
|
+
const p = stream.sink([])
|
|
471
|
+
|
|
472
|
+
const remoteStream = await getRemoteStream.promise
|
|
473
|
+
// close the readable end of the remote stream
|
|
474
|
+
await remoteStream.closeRead()
|
|
475
|
+
// readable end should finish
|
|
476
|
+
await drain(remoteStream.source)
|
|
477
|
+
|
|
478
|
+
// wait for remote to receive local close-write
|
|
479
|
+
await pRetry(() => {
|
|
480
|
+
if (remoteStream.readStatus !== 'closed') {
|
|
481
|
+
throw new Error('Remote stream read status ' + remoteStream.readStatus)
|
|
482
|
+
}
|
|
483
|
+
}, {
|
|
484
|
+
minTimeout: 100
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
// remote closes write
|
|
488
|
+
await remoteStream.sink([])
|
|
489
|
+
|
|
490
|
+
// wait to receive FIN_ACK
|
|
491
|
+
await p
|
|
492
|
+
|
|
493
|
+
// close read end of stream
|
|
494
|
+
await stream.closeRead()
|
|
495
|
+
// readable end should finish
|
|
496
|
+
await drain(stream.source)
|
|
497
|
+
|
|
498
|
+
// wait for remote to notice closure
|
|
499
|
+
await pRetry(() => {
|
|
500
|
+
if (remoteStream.status !== 'closed') {
|
|
501
|
+
throw new Error('Remote stream not closed')
|
|
502
|
+
}
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
assertStreamClosed(stream)
|
|
506
|
+
assertStreamClosed(remoteStream)
|
|
507
|
+
})
|
|
508
|
+
})
|
|
509
|
+
|
|
510
|
+
describe('events', () => {
|
|
511
|
+
let dialer: Libp2p<{ echo: Echo }>
|
|
512
|
+
let listener: Libp2p<{ echo: Echo }> | undefined
|
|
513
|
+
let listenMultiaddrMatcher: MultiaddrMatcher
|
|
514
|
+
|
|
515
|
+
afterEach(async () => {
|
|
516
|
+
await stop(dialer, listener)
|
|
517
|
+
await common.teardown()
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
it('emits listening', async function () {
|
|
521
|
+
({ dialer, listener, listenMultiaddrMatcher } = await getSetup(common))
|
|
522
|
+
|
|
523
|
+
if (listener == null) {
|
|
524
|
+
return this.skip()
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
await listener.stop()
|
|
528
|
+
|
|
529
|
+
const transportListeningPromise = pDefer()
|
|
530
|
+
|
|
531
|
+
listener.addEventListener('transport:listening', (event) => {
|
|
532
|
+
const transportListener = event.detail
|
|
533
|
+
|
|
534
|
+
if (transportListener.getAddrs().some(ma => listenMultiaddrMatcher.exactMatch(ma))) {
|
|
535
|
+
transportListeningPromise.resolve()
|
|
536
|
+
}
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
await listener.start()
|
|
540
|
+
|
|
541
|
+
await raceSignal(transportListeningPromise.promise, AbortSignal.timeout(1000), {
|
|
542
|
+
errorMessage: 'Did not emit listening event'
|
|
543
|
+
})
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
it('emits close', async function () {
|
|
547
|
+
({ dialer, listener } = await getSetup(common))
|
|
548
|
+
|
|
549
|
+
if (listener == null) {
|
|
550
|
+
return this.skip()
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const transportManager = getTransportManager(listener)
|
|
554
|
+
const transportListener = transportManager.getListeners()
|
|
555
|
+
.filter(listener => listener.getAddrs().some(ma => listenMultiaddrMatcher.exactMatch(ma)))
|
|
556
|
+
.pop()
|
|
557
|
+
|
|
558
|
+
if (transportListener == null) {
|
|
559
|
+
throw new Error('Could not find address listener')
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const p = pEvent(transportListener, 'close')
|
|
563
|
+
|
|
564
|
+
await listener.stop()
|
|
565
|
+
|
|
566
|
+
await raceSignal(p, AbortSignal.timeout(1000), {
|
|
567
|
+
errorMessage: 'Did not emit close event'
|
|
568
|
+
})
|
|
569
|
+
})
|
|
26
570
|
})
|
|
27
571
|
}
|
|
572
|
+
|
|
573
|
+
function assertStreamClosed (stream: Stream): void {
|
|
574
|
+
expect(stream.status).to.equal('closed')
|
|
575
|
+
expect(stream.readStatus).to.equal('closed')
|
|
576
|
+
expect(stream.writeStatus).to.equal('closed')
|
|
577
|
+
|
|
578
|
+
expect(stream.timeline.close).to.be.a('number')
|
|
579
|
+
expect(stream.timeline.closeRead).to.be.a('number')
|
|
580
|
+
expect(stream.timeline.closeWrite).to.be.a('number')
|
|
581
|
+
}
|