@libp2p/interface-compliance-tests 3.0.7-ea8a0637 → 3.0.7-eabf6f36

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.
Files changed (183) hide show
  1. package/README.md +9 -0
  2. package/dist/src/connection/index.d.ts +5 -0
  3. package/dist/src/connection/index.d.ts.map +1 -0
  4. package/dist/src/connection/index.js +150 -0
  5. package/dist/src/connection/index.js.map +1 -0
  6. package/dist/src/connection-encryption/index.d.ts +5 -0
  7. package/dist/src/connection-encryption/index.d.ts.map +1 -0
  8. package/dist/src/connection-encryption/index.js +71 -0
  9. package/dist/src/connection-encryption/index.js.map +1 -0
  10. package/dist/src/connection-encryption/utils/index.d.ts +3 -0
  11. package/dist/src/connection-encryption/utils/index.d.ts.map +1 -0
  12. package/dist/src/connection-encryption/utils/index.js +19 -0
  13. package/dist/src/connection-encryption/utils/index.js.map +1 -0
  14. package/dist/src/index.d.ts.map +1 -1
  15. package/dist/src/is-valid-tick.d.ts.map +1 -1
  16. package/dist/src/is-valid-tick.js.map +1 -1
  17. package/dist/src/mocks/connection-encrypter.d.ts +3 -0
  18. package/dist/src/mocks/connection-encrypter.d.ts.map +1 -0
  19. package/dist/src/mocks/connection-encrypter.js +98 -0
  20. package/dist/src/mocks/connection-encrypter.js.map +1 -0
  21. package/dist/src/mocks/connection-gater.d.ts +3 -0
  22. package/dist/src/mocks/connection-gater.d.ts.map +1 -0
  23. package/dist/src/mocks/connection-gater.js +17 -0
  24. package/dist/src/mocks/connection-gater.js.map +1 -0
  25. package/dist/src/mocks/connection-manager.d.ts +29 -0
  26. package/dist/src/mocks/connection-manager.d.ts.map +1 -0
  27. package/dist/src/mocks/connection-manager.js +145 -0
  28. package/dist/src/mocks/connection-manager.js.map +1 -0
  29. package/dist/src/mocks/connection.d.ts +32 -0
  30. package/dist/src/mocks/connection.d.ts.map +1 -0
  31. package/dist/src/mocks/connection.js +167 -0
  32. package/dist/src/mocks/connection.js.map +1 -0
  33. package/dist/src/mocks/duplex.d.ts +3 -0
  34. package/dist/src/mocks/duplex.d.ts.map +1 -0
  35. package/dist/src/mocks/duplex.js +9 -0
  36. package/dist/src/mocks/duplex.js.map +1 -0
  37. package/dist/src/mocks/index.d.ts +13 -0
  38. package/dist/src/mocks/index.d.ts.map +1 -0
  39. package/dist/src/mocks/index.js +11 -0
  40. package/dist/src/mocks/index.js.map +1 -0
  41. package/dist/src/mocks/metrics.d.ts +3 -0
  42. package/dist/src/mocks/metrics.d.ts.map +1 -0
  43. package/dist/src/mocks/metrics.js +115 -0
  44. package/dist/src/mocks/metrics.js.map +1 -0
  45. package/dist/src/mocks/multiaddr-connection.d.ts +17 -0
  46. package/dist/src/mocks/multiaddr-connection.d.ts.map +1 -0
  47. package/dist/src/mocks/multiaddr-connection.js +60 -0
  48. package/dist/src/mocks/multiaddr-connection.js.map +1 -0
  49. package/dist/src/mocks/muxer.d.ts +36 -0
  50. package/dist/src/mocks/muxer.d.ts.map +1 -0
  51. package/dist/src/mocks/muxer.js +213 -0
  52. package/dist/src/mocks/muxer.js.map +1 -0
  53. package/dist/src/mocks/peer-discovery.d.ts +22 -0
  54. package/dist/src/mocks/peer-discovery.d.ts.map +1 -0
  55. package/dist/src/mocks/peer-discovery.js +47 -0
  56. package/dist/src/mocks/peer-discovery.js.map +1 -0
  57. package/dist/src/mocks/registrar.d.ts +18 -0
  58. package/dist/src/mocks/registrar.d.ts.map +1 -0
  59. package/dist/src/mocks/registrar.js +66 -0
  60. package/dist/src/mocks/registrar.js.map +1 -0
  61. package/dist/src/mocks/upgrader.d.ts +10 -0
  62. package/dist/src/mocks/upgrader.d.ts.map +1 -0
  63. package/dist/src/mocks/upgrader.js +31 -0
  64. package/dist/src/mocks/upgrader.js.map +1 -0
  65. package/dist/src/peer-discovery/index.d.ts +5 -0
  66. package/dist/src/peer-discovery/index.d.ts.map +1 -0
  67. package/dist/src/peer-discovery/index.js +66 -0
  68. package/dist/src/peer-discovery/index.js.map +1 -0
  69. package/dist/src/pubsub/api.d.ts +6 -0
  70. package/dist/src/pubsub/api.d.ts.map +1 -0
  71. package/dist/src/pubsub/api.js +87 -0
  72. package/dist/src/pubsub/api.js.map +1 -0
  73. package/dist/src/pubsub/connection-handlers.d.ts +6 -0
  74. package/dist/src/pubsub/connection-handlers.d.ts.map +1 -0
  75. package/dist/src/pubsub/connection-handlers.js +329 -0
  76. package/dist/src/pubsub/connection-handlers.js.map +1 -0
  77. package/dist/src/pubsub/emit-self.d.ts +6 -0
  78. package/dist/src/pubsub/emit-self.d.ts.map +1 -0
  79. package/dist/src/pubsub/emit-self.js +80 -0
  80. package/dist/src/pubsub/emit-self.js.map +1 -0
  81. package/dist/src/pubsub/index.d.ts +18 -0
  82. package/dist/src/pubsub/index.d.ts.map +1 -0
  83. package/dist/src/pubsub/index.js +17 -0
  84. package/dist/src/pubsub/index.js.map +1 -0
  85. package/dist/src/pubsub/messages.d.ts +6 -0
  86. package/dist/src/pubsub/messages.d.ts.map +1 -0
  87. package/dist/src/pubsub/messages.js +48 -0
  88. package/dist/src/pubsub/messages.js.map +1 -0
  89. package/dist/src/pubsub/multiple-nodes.d.ts +6 -0
  90. package/dist/src/pubsub/multiple-nodes.d.ts.map +1 -0
  91. package/dist/src/pubsub/multiple-nodes.js +350 -0
  92. package/dist/src/pubsub/multiple-nodes.js.map +1 -0
  93. package/dist/src/pubsub/two-nodes.d.ts +6 -0
  94. package/dist/src/pubsub/two-nodes.d.ts.map +1 -0
  95. package/dist/src/pubsub/two-nodes.js +217 -0
  96. package/dist/src/pubsub/two-nodes.js.map +1 -0
  97. package/dist/src/pubsub/utils.d.ts +6 -0
  98. package/dist/src/pubsub/utils.d.ts.map +1 -0
  99. package/dist/src/pubsub/utils.js +22 -0
  100. package/dist/src/pubsub/utils.js.map +1 -0
  101. package/dist/src/stream-muxer/base-test.d.ts +5 -0
  102. package/dist/src/stream-muxer/base-test.d.ts.map +1 -0
  103. package/dist/src/stream-muxer/base-test.js +153 -0
  104. package/dist/src/stream-muxer/base-test.js.map +1 -0
  105. package/dist/src/stream-muxer/close-test.d.ts +5 -0
  106. package/dist/src/stream-muxer/close-test.d.ts.map +1 -0
  107. package/dist/src/stream-muxer/close-test.js +357 -0
  108. package/dist/src/stream-muxer/close-test.js.map +1 -0
  109. package/dist/src/stream-muxer/fixtures/pb/message.d.ts +13 -0
  110. package/dist/src/stream-muxer/fixtures/pb/message.d.ts.map +1 -0
  111. package/dist/src/stream-muxer/fixtures/pb/message.js +67 -0
  112. package/dist/src/stream-muxer/fixtures/pb/message.js.map +1 -0
  113. package/dist/src/stream-muxer/index.d.ts +5 -0
  114. package/dist/src/stream-muxer/index.d.ts.map +1 -0
  115. package/dist/src/stream-muxer/index.js +13 -0
  116. package/dist/src/stream-muxer/index.js.map +1 -0
  117. package/dist/src/stream-muxer/mega-stress-test.d.ts +5 -0
  118. package/dist/src/stream-muxer/mega-stress-test.d.ts.map +1 -0
  119. package/dist/src/stream-muxer/mega-stress-test.js +11 -0
  120. package/dist/src/stream-muxer/mega-stress-test.js.map +1 -0
  121. package/dist/src/stream-muxer/spawner.d.ts +4 -0
  122. package/dist/src/stream-muxer/spawner.d.ts.map +1 -0
  123. package/dist/src/stream-muxer/spawner.js +37 -0
  124. package/dist/src/stream-muxer/spawner.js.map +1 -0
  125. package/dist/src/stream-muxer/stress-test.d.ts +5 -0
  126. package/dist/src/stream-muxer/stress-test.d.ts.map +1 -0
  127. package/dist/src/stream-muxer/stress-test.js +23 -0
  128. package/dist/src/stream-muxer/stress-test.js.map +1 -0
  129. package/dist/src/transport/dial-test.d.ts +5 -0
  130. package/dist/src/transport/dial-test.d.ts.map +1 -0
  131. package/dist/src/transport/dial-test.js +98 -0
  132. package/dist/src/transport/dial-test.js.map +1 -0
  133. package/dist/src/transport/filter-test.d.ts +5 -0
  134. package/dist/src/transport/filter-test.d.ts.map +1 -0
  135. package/dist/src/transport/filter-test.js +18 -0
  136. package/dist/src/transport/filter-test.js.map +1 -0
  137. package/dist/src/transport/index.d.ts +15 -0
  138. package/dist/src/transport/index.d.ts.map +1 -0
  139. package/dist/src/transport/index.js +11 -0
  140. package/dist/src/transport/index.js.map +1 -0
  141. package/dist/src/transport/listen-test.d.ts +5 -0
  142. package/dist/src/transport/listen-test.d.ts.map +1 -0
  143. package/dist/src/transport/listen-test.js +152 -0
  144. package/dist/src/transport/listen-test.js.map +1 -0
  145. package/package.json +72 -5
  146. package/src/connection/index.ts +182 -0
  147. package/src/connection-encryption/index.ts +97 -0
  148. package/src/connection-encryption/utils/index.ts +24 -0
  149. package/src/index.ts +0 -1
  150. package/src/is-valid-tick.ts +0 -1
  151. package/src/mocks/connection-encrypter.ts +113 -0
  152. package/src/mocks/connection-gater.ts +18 -0
  153. package/src/mocks/connection-manager.ts +211 -0
  154. package/src/mocks/connection.ts +226 -0
  155. package/src/mocks/duplex.ts +10 -0
  156. package/src/mocks/index.ts +12 -0
  157. package/src/mocks/metrics.ts +162 -0
  158. package/src/mocks/multiaddr-connection.ts +76 -0
  159. package/src/mocks/muxer.ts +303 -0
  160. package/src/mocks/peer-discovery.ts +60 -0
  161. package/src/mocks/registrar.ts +88 -0
  162. package/src/mocks/upgrader.ts +49 -0
  163. package/src/peer-discovery/index.ts +90 -0
  164. package/src/pubsub/api.ts +114 -0
  165. package/src/pubsub/connection-handlers.ts +413 -0
  166. package/src/pubsub/emit-self.ts +99 -0
  167. package/src/pubsub/index.ts +34 -0
  168. package/src/pubsub/messages.ts +59 -0
  169. package/src/pubsub/multiple-nodes.ts +440 -0
  170. package/src/pubsub/two-nodes.ts +273 -0
  171. package/src/pubsub/utils.ts +29 -0
  172. package/src/stream-muxer/base-test.ts +196 -0
  173. package/src/stream-muxer/close-test.ts +442 -0
  174. package/src/stream-muxer/fixtures/pb/message.proto +7 -0
  175. package/src/stream-muxer/fixtures/pb/message.ts +87 -0
  176. package/src/stream-muxer/index.ts +15 -0
  177. package/src/stream-muxer/mega-stress-test.ts +14 -0
  178. package/src/stream-muxer/spawner.ts +55 -0
  179. package/src/stream-muxer/stress-test.ts +27 -0
  180. package/src/transport/dial-test.ts +124 -0
  181. package/src/transport/filter-test.ts +25 -0
  182. package/src/transport/index.ts +25 -0
  183. package/src/transport/listen-test.ts +191 -0
@@ -0,0 +1,97 @@
1
+ import { UnexpectedPeerError } from '@libp2p/interface/errors'
2
+ import * as PeerIdFactory from '@libp2p/peer-id-factory'
3
+ import { expect } from 'aegir/chai'
4
+ import all from 'it-all'
5
+ import { pipe } from 'it-pipe'
6
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
7
+ import peers from '../peers.js'
8
+ import { createMaConnPair } from './utils/index.js'
9
+ import type { TestSetup } from '../index.js'
10
+ import type { ConnectionEncrypter } from '@libp2p/interface/connection-encrypter'
11
+ import type { PeerId } from '@libp2p/interface/peer-id'
12
+
13
+ export default (common: TestSetup<ConnectionEncrypter>): void => {
14
+ describe('interface-connection-encrypter compliance tests', () => {
15
+ let crypto: ConnectionEncrypter
16
+ let localPeer: PeerId
17
+ let remotePeer: PeerId
18
+ let mitmPeer: PeerId
19
+
20
+ before(async () => {
21
+ [
22
+ crypto,
23
+ localPeer,
24
+ remotePeer,
25
+ mitmPeer
26
+ ] = await Promise.all([
27
+ common.setup(),
28
+ PeerIdFactory.createFromJSON(peers[0]),
29
+ PeerIdFactory.createFromJSON(peers[1]),
30
+ PeerIdFactory.createFromJSON(peers[2])
31
+ ])
32
+ })
33
+
34
+ after(async () => {
35
+ await common.teardown()
36
+ })
37
+
38
+ it('has a protocol string', () => {
39
+ expect(crypto.protocol).to.exist()
40
+ expect(crypto.protocol).to.be.a('string')
41
+ })
42
+
43
+ it('it wraps the provided duplex connection', async () => {
44
+ const [localConn, remoteConn] = createMaConnPair()
45
+
46
+ const [
47
+ inboundResult,
48
+ outboundResult
49
+ ] = await Promise.all([
50
+ crypto.secureInbound(remotePeer, localConn),
51
+ crypto.secureOutbound(localPeer, remoteConn, remotePeer)
52
+ ])
53
+
54
+ // Echo server
55
+ void pipe(inboundResult.conn, inboundResult.conn)
56
+
57
+ // Send some data and collect the result
58
+ const input = uint8ArrayFromString('data to encrypt')
59
+ const result = await pipe(
60
+ [input],
61
+ outboundResult.conn,
62
+ async (source) => all(source)
63
+ )
64
+
65
+ expect(result).to.eql([input])
66
+ })
67
+
68
+ it('should return the remote peer id', async () => {
69
+ const [localConn, remoteConn] = createMaConnPair()
70
+
71
+ const [
72
+ inboundResult,
73
+ outboundResult
74
+ ] = await Promise.all([
75
+ crypto.secureInbound(remotePeer, localConn),
76
+ crypto.secureOutbound(localPeer, remoteConn, remotePeer)
77
+ ])
78
+
79
+ // Inbound should return the initiator (local) peer
80
+ expect(inboundResult.remotePeer.toBytes()).to.equalBytes(localPeer.toBytes())
81
+ // Outbound should return the receiver (remote) peer
82
+ expect(outboundResult.remotePeer.toBytes()).to.equalBytes(remotePeer.toBytes())
83
+ })
84
+
85
+ it('inbound connections should verify peer integrity if known', async () => {
86
+ const [localConn, remoteConn] = createMaConnPair()
87
+
88
+ await Promise.all([
89
+ crypto.secureInbound(remotePeer, localConn, mitmPeer),
90
+ crypto.secureOutbound(localPeer, remoteConn, remotePeer)
91
+ ]).then(() => expect.fail(), (err) => {
92
+ expect(err).to.exist()
93
+ expect(err).to.have.property('code', UnexpectedPeerError.code)
94
+ })
95
+ })
96
+ })
97
+ }
@@ -0,0 +1,24 @@
1
+ import { multiaddr } from '@multiformats/multiaddr'
2
+ import { duplexPair } from 'it-pair/duplex'
3
+ import type { MultiaddrConnection } from '@libp2p/interface/connection'
4
+ import type { Duplex, Source } from 'it-stream-types'
5
+
6
+ export function createMaConnPair (): [MultiaddrConnection, MultiaddrConnection] {
7
+ const [local, remote] = duplexPair<Uint8Array>()
8
+
9
+ function duplexToMaConn (duplex: Duplex<AsyncGenerator<Uint8Array>, Source<Uint8Array>, Promise<void>>): MultiaddrConnection {
10
+ const output: MultiaddrConnection = {
11
+ ...duplex,
12
+ close: async () => {},
13
+ abort: () => {},
14
+ remoteAddr: multiaddr('/ip4/127.0.0.1/tcp/4001'),
15
+ timeline: {
16
+ open: Date.now()
17
+ }
18
+ }
19
+
20
+ return output
21
+ }
22
+
23
+ return [duplexToMaConn(local), duplexToMaConn(remote)]
24
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  export interface TestSetup<T, SetupArgs = Record<string, unknown>> {
3
2
  setup: (args?: SetupArgs) => Promise<T>
4
3
  teardown: () => Promise<void>
@@ -1,4 +1,3 @@
1
-
2
1
  /**
3
2
  * A tick is considered valid if it happened between now
4
3
  * and `ms` milliseconds ago
@@ -0,0 +1,113 @@
1
+ import { UnexpectedPeerError } from '@libp2p/interface/errors'
2
+ import { peerIdFromBytes } from '@libp2p/peer-id'
3
+ import { multiaddr } from '@multiformats/multiaddr'
4
+ import { handshake } from 'it-handshake'
5
+ import map from 'it-map'
6
+ import { duplexPair } from 'it-pair/duplex'
7
+ import { pipe } from 'it-pipe'
8
+ import type { ConnectionEncrypter } from '@libp2p/interface/connection-encrypter'
9
+ import type { Transform, Source } from 'it-stream-types'
10
+
11
+ // A basic transform that does nothing to the data
12
+ const transform = <T>(): Transform<Source<T>, AsyncGenerator<T>> => {
13
+ return (source: Source<T>) => (async function * () {
14
+ for await (const chunk of source) {
15
+ yield chunk
16
+ }
17
+ })()
18
+ }
19
+
20
+ export function mockConnectionEncrypter (): ConnectionEncrypter {
21
+ const encrypter: ConnectionEncrypter = {
22
+ protocol: 'insecure',
23
+ secureInbound: async (localPeer, duplex, expectedPeer) => {
24
+ // 1. Perform a basic handshake.
25
+ const shake = handshake<Uint8Array>(duplex)
26
+ shake.write(localPeer.toBytes())
27
+ const remoteId = await shake.read()
28
+
29
+ if (remoteId == null) {
30
+ throw new Error('Could not read remote ID')
31
+ }
32
+
33
+ const remotePeer = peerIdFromBytes(remoteId.slice())
34
+ shake.rest()
35
+
36
+ if (expectedPeer?.equals(remotePeer) === false) {
37
+ throw new UnexpectedPeerError()
38
+ }
39
+
40
+ // 2. Create your encryption box/unbox wrapper
41
+ const wrapper = duplexPair<Uint8Array>()
42
+ const encrypt = transform<Uint8Array>() // Use transform iterables to modify data
43
+ const decrypt = transform<Uint8Array>()
44
+
45
+ void pipe(
46
+ wrapper[0], // We write to wrapper
47
+ encrypt, // The data is encrypted
48
+ shake.stream, // It goes to the remote peer
49
+ source => map(source, (list) => list.subarray()), // turn lists into arrays
50
+ decrypt, // Decrypt the incoming data
51
+ wrapper[0] // Pipe to the wrapper
52
+ )
53
+
54
+ return {
55
+ conn: {
56
+ ...wrapper[1],
57
+ close: async () => { },
58
+ localAddr: multiaddr('/ip4/127.0.0.1/tcp/4001'),
59
+ remoteAddr: multiaddr('/ip4/127.0.0.1/tcp/4002'),
60
+ timeline: {
61
+ open: Date.now()
62
+ },
63
+ conn: true
64
+ },
65
+ remotePeer,
66
+ remoteExtensions: {}
67
+ }
68
+ },
69
+ secureOutbound: async (localPeer, duplex, remotePeer) => {
70
+ // 1. Perform a basic handshake.
71
+ const shake = handshake<Uint8Array>(duplex)
72
+ shake.write(localPeer.toBytes())
73
+ const remoteId = await shake.read()
74
+
75
+ if (remoteId == null) {
76
+ throw new Error('Could not read remote ID')
77
+ }
78
+
79
+ shake.rest()
80
+
81
+ // 2. Create your encryption box/unbox wrapper
82
+ const wrapper = duplexPair<Uint8Array>()
83
+ const encrypt = transform<Uint8Array>()
84
+ const decrypt = transform<Uint8Array>()
85
+
86
+ void pipe(
87
+ wrapper[0], // We write to wrapper
88
+ encrypt, // The data is encrypted
89
+ shake.stream, // It goes to the remote peer
90
+ source => map(source, (list) => list.subarray()), // turn lists into arrays
91
+ decrypt, // Decrypt the incoming data
92
+ wrapper[0] // Pipe to the wrapper
93
+ )
94
+
95
+ return {
96
+ conn: {
97
+ ...wrapper[1],
98
+ close: async () => { },
99
+ localAddr: multiaddr('/ip4/127.0.0.1/tcp/4001'),
100
+ remoteAddr: multiaddr('/ip4/127.0.0.1/tcp/4002'),
101
+ timeline: {
102
+ open: Date.now()
103
+ },
104
+ conn: true
105
+ },
106
+ remotePeer: peerIdFromBytes(remoteId.slice()),
107
+ remoteExtensions: {}
108
+ }
109
+ }
110
+ }
111
+
112
+ return encrypter
113
+ }
@@ -0,0 +1,18 @@
1
+ import type { ConnectionGater } from '@libp2p/interface/connection-gater'
2
+
3
+ export function mockConnectionGater (): ConnectionGater {
4
+ return {
5
+ denyDialPeer: async () => Promise.resolve(false),
6
+ denyDialMultiaddr: async () => Promise.resolve(false),
7
+ denyInboundConnection: async () => Promise.resolve(false),
8
+ denyOutboundConnection: async () => Promise.resolve(false),
9
+ denyInboundEncryptedConnection: async () => Promise.resolve(false),
10
+ denyOutboundEncryptedConnection: async () => Promise.resolve(false),
11
+ denyInboundUpgradedConnection: async () => Promise.resolve(false),
12
+ denyOutboundUpgradedConnection: async () => Promise.resolve(false),
13
+ denyInboundRelayReservation: async () => Promise.resolve(false),
14
+ denyOutboundRelayedConnection: async () => Promise.resolve(false),
15
+ denyInboundRelayedConnection: async () => Promise.resolve(false),
16
+ filterMultiaddrForPeer: async () => Promise.resolve(true)
17
+ }
18
+ }
@@ -0,0 +1,211 @@
1
+ import { CodeError } from '@libp2p/interface/errors'
2
+ import { isPeerId, type PeerId } from '@libp2p/interface/peer-id'
3
+ import { PeerMap } from '@libp2p/peer-collections'
4
+ import { peerIdFromString } from '@libp2p/peer-id'
5
+ import { isMultiaddr, type Multiaddr } from '@multiformats/multiaddr'
6
+ import { connectionPair } from './connection.js'
7
+ import type { Libp2pEvents, PendingDial } from '@libp2p/interface'
8
+ import type { Connection } from '@libp2p/interface/connection'
9
+ import type { EventEmitter } from '@libp2p/interface/events'
10
+ import type { PubSub } from '@libp2p/interface/pubsub'
11
+ import type { Startable } from '@libp2p/interface/startable'
12
+ import type { ConnectionManager } from '@libp2p/interface-internal/connection-manager'
13
+ import type { Registrar } from '@libp2p/interface-internal/registrar'
14
+
15
+ export interface MockNetworkComponents {
16
+ peerId: PeerId
17
+ registrar: Registrar
18
+ connectionManager: ConnectionManager
19
+ events: EventEmitter<Libp2pEvents>
20
+ pubsub?: PubSub
21
+ }
22
+
23
+ class MockNetwork {
24
+ private components: MockNetworkComponents[] = []
25
+
26
+ addNode (components: MockNetworkComponents): void {
27
+ this.components.push(components)
28
+ }
29
+
30
+ getNode (peerId: PeerId | Multiaddr []): MockNetworkComponents {
31
+ if (Array.isArray(peerId) && peerId.length > 0) {
32
+ peerId = peerIdFromString(peerId[0].getPeerId() ?? '')
33
+ } else if (isPeerId(peerId)) {
34
+ for (const components of this.components) {
35
+ if (peerId.equals(components.peerId)) {
36
+ return components
37
+ }
38
+ }
39
+ }
40
+
41
+ throw new CodeError('Peer not found', 'ERR_PEER_NOT_FOUND')
42
+ }
43
+
44
+ reset (): void {
45
+ this.components = []
46
+ }
47
+ }
48
+
49
+ export const mockNetwork = new MockNetwork()
50
+
51
+ export interface MockConnectionManagerComponents {
52
+ peerId: PeerId
53
+ registrar: Registrar
54
+ events: EventEmitter<Libp2pEvents>
55
+ }
56
+
57
+ class MockConnectionManager implements ConnectionManager, Startable {
58
+ private connections: Connection[] = []
59
+ private readonly components: MockConnectionManagerComponents
60
+ private started = false
61
+
62
+ constructor (components: MockConnectionManagerComponents) {
63
+ this.components = components
64
+ }
65
+
66
+ isStarted (): boolean {
67
+ return this.started
68
+ }
69
+
70
+ async start (): Promise<void> {
71
+ this.started = true
72
+ }
73
+
74
+ async stop (): Promise<void> {
75
+ for (const connection of this.connections) {
76
+ await this.closeConnections(connection.remotePeer)
77
+ }
78
+
79
+ this.started = false
80
+ }
81
+
82
+ getConnections (peerId?: PeerId): Connection[] {
83
+ if (peerId != null) {
84
+ return this.connections
85
+ .filter(c => c.remotePeer.toString() === peerId.toString())
86
+ }
87
+
88
+ return this.connections
89
+ }
90
+
91
+ getConnectionsMap (): PeerMap<Connection[]> {
92
+ const map = new PeerMap<Connection[]>()
93
+
94
+ for (const conn of this.connections) {
95
+ const conns: Connection[] = map.get(conn.remotePeer) ?? []
96
+ conns.push(conn)
97
+
98
+ map.set(conn.remotePeer, conns)
99
+ }
100
+
101
+ return map
102
+ }
103
+
104
+ async openConnection (peerId: PeerId | Multiaddr | Multiaddr[]): Promise<Connection> {
105
+ if (this.components == null) {
106
+ throw new CodeError('Not initialized', 'ERR_NOT_INITIALIZED')
107
+ }
108
+
109
+ if (isMultiaddr(peerId)) {
110
+ throw new CodeError('Dialing multiaddrs not supported', 'ERR_NOT_SUPPORTED')
111
+ }
112
+
113
+ let existingConnections: Connection[] = []
114
+
115
+ if (Array.isArray(peerId) && peerId.length > 0) {
116
+ existingConnections = this.getConnections(peerIdFromString(peerId[0].getPeerId() ?? ''))
117
+ } else if (isPeerId(peerId)) {
118
+ existingConnections = this.getConnections(peerId)
119
+ }
120
+
121
+ if (existingConnections.length > 0) {
122
+ return existingConnections[0]
123
+ }
124
+
125
+ const componentsB = mockNetwork.getNode(peerId)
126
+
127
+ const [aToB, bToA] = connectionPair(this.components, componentsB)
128
+
129
+ // track connections
130
+ this.connections.push(aToB)
131
+ ;(componentsB.connectionManager as MockConnectionManager).connections.push(bToA)
132
+
133
+ this.components.events.safeDispatchEvent('connection:open', {
134
+ detail: aToB
135
+ })
136
+
137
+ for (const protocol of this.components.registrar.getProtocols()) {
138
+ for (const topology of this.components.registrar.getTopologies(protocol)) {
139
+ topology.onConnect?.(componentsB.peerId, aToB)
140
+ }
141
+ }
142
+
143
+ this.components.events.safeDispatchEvent('peer:connect', { detail: componentsB.peerId })
144
+
145
+ componentsB.events.safeDispatchEvent('connection:open', {
146
+ detail: bToA
147
+ })
148
+
149
+ for (const protocol of componentsB.registrar.getProtocols()) {
150
+ for (const topology of componentsB.registrar.getTopologies(protocol)) {
151
+ topology.onConnect?.(this.components.peerId, bToA)
152
+ }
153
+ }
154
+
155
+ componentsB.events.safeDispatchEvent('peer:connect', { detail: this.components.peerId })
156
+
157
+ return aToB
158
+ }
159
+
160
+ async closeConnections (peerId: PeerId): Promise<void> {
161
+ if (this.components == null) {
162
+ throw new CodeError('Not initialized', 'ERR_NOT_INITIALIZED')
163
+ }
164
+
165
+ const connections = this.getConnections(peerId)
166
+
167
+ if (connections.length === 0) {
168
+ return
169
+ }
170
+
171
+ const componentsB = mockNetwork.getNode(peerId)
172
+
173
+ for (const protocol of this.components.registrar.getProtocols()) {
174
+ this.components.registrar.getTopologies(protocol).forEach(topology => {
175
+ topology.onDisconnect?.(componentsB.peerId)
176
+ })
177
+ }
178
+
179
+ for (const conn of connections) {
180
+ await conn.close()
181
+ }
182
+
183
+ this.connections = this.connections.filter(c => !c.remotePeer.equals(peerId))
184
+
185
+ if (this.connections.filter(c => !c.remotePeer.equals(peerId)).length === 0) {
186
+ componentsB.events.safeDispatchEvent('peer:disconnect', { detail: peerId })
187
+ }
188
+
189
+ await componentsB.connectionManager?.closeConnections(this.components.peerId)
190
+
191
+ if (componentsB.connectionManager?.getConnectionsMap().get(this.components.peerId) == null) {
192
+ componentsB.events.safeDispatchEvent('peer:disconnect', { detail: this.components.peerId })
193
+ }
194
+ }
195
+
196
+ async acceptIncomingConnection (): Promise<boolean> {
197
+ return true
198
+ }
199
+
200
+ afterUpgradeInbound (): void {
201
+
202
+ }
203
+
204
+ getDialQueue (): PendingDial[] {
205
+ return []
206
+ }
207
+ }
208
+
209
+ export function mockConnectionManager (components: MockConnectionManagerComponents): ConnectionManager {
210
+ return new MockConnectionManager(components)
211
+ }
@@ -0,0 +1,226 @@
1
+ import { CodeError } from '@libp2p/interface/errors'
2
+ import { logger } from '@libp2p/logger'
3
+ import * as mss from '@libp2p/multistream-select'
4
+ import { peerIdFromString } from '@libp2p/peer-id'
5
+ import { duplexPair } from 'it-pair/duplex'
6
+ import { pipe } from 'it-pipe'
7
+ import { mockMultiaddrConnection } from './multiaddr-connection.js'
8
+ import { mockMuxer } from './muxer.js'
9
+ import { mockRegistrar } from './registrar.js'
10
+ import type { AbortOptions } from '@libp2p/interface'
11
+ import type { MultiaddrConnection, Connection, Stream, Direction, ConnectionTimeline, ConnectionStatus } from '@libp2p/interface/connection'
12
+ import type { PeerId } from '@libp2p/interface/peer-id'
13
+ import type { StreamMuxer, StreamMuxerFactory } from '@libp2p/interface/stream-muxer'
14
+ import type { Registrar } from '@libp2p/interface-internal/registrar'
15
+ import type { Multiaddr } from '@multiformats/multiaddr'
16
+ import type { Duplex, Source } from 'it-stream-types'
17
+ import type { Uint8ArrayList } from 'uint8arraylist'
18
+
19
+ const log = logger('libp2p:mock-connection')
20
+
21
+ export interface MockConnectionOptions {
22
+ direction?: Direction
23
+ registrar?: Registrar
24
+ muxerFactory?: StreamMuxerFactory
25
+ }
26
+
27
+ interface MockConnectionInit {
28
+ remoteAddr: Multiaddr
29
+ remotePeer: PeerId
30
+ direction: Direction
31
+ maConn: MultiaddrConnection
32
+ muxer: StreamMuxer
33
+ }
34
+
35
+ class MockConnection implements Connection {
36
+ public id: string
37
+ public remoteAddr: Multiaddr
38
+ public remotePeer: PeerId
39
+ public direction: Direction
40
+ public timeline: ConnectionTimeline
41
+ public multiplexer?: string
42
+ public encryption?: string
43
+ public status: ConnectionStatus
44
+ public streams: Stream[]
45
+ public tags: string[]
46
+ public transient: boolean
47
+
48
+ private readonly muxer: StreamMuxer
49
+ private readonly maConn: MultiaddrConnection
50
+
51
+ constructor (init: MockConnectionInit) {
52
+ const { remoteAddr, remotePeer, direction, maConn, muxer } = init
53
+
54
+ this.id = `mock-connection-${Math.random()}`
55
+ this.remoteAddr = remoteAddr
56
+ this.remotePeer = remotePeer
57
+ this.direction = direction
58
+ this.status = 'open'
59
+ this.direction = direction
60
+ this.timeline = maConn.timeline
61
+ this.multiplexer = 'test-multiplexer'
62
+ this.encryption = 'yes-yes-very-secure'
63
+ this.streams = []
64
+ this.tags = []
65
+ this.muxer = muxer
66
+ this.maConn = maConn
67
+ this.transient = false
68
+ }
69
+
70
+ async newStream (protocols: string | string[], options?: AbortOptions): Promise<Stream> {
71
+ if (!Array.isArray(protocols)) {
72
+ protocols = [protocols]
73
+ }
74
+
75
+ if (protocols.length === 0) {
76
+ throw new Error('protocols must have a length')
77
+ }
78
+
79
+ if (this.status !== 'open') {
80
+ throw new CodeError('connection must be open to create streams', 'ERR_CONNECTION_CLOSED')
81
+ }
82
+
83
+ const id = `${Math.random()}`
84
+ const stream = await this.muxer.newStream(id)
85
+ const result = await mss.select(stream, protocols, options)
86
+
87
+ stream.protocol = result.protocol
88
+ stream.direction = 'outbound'
89
+ stream.sink = result.stream.sink
90
+ stream.source = result.stream.source
91
+
92
+ this.streams.push(stream)
93
+
94
+ return stream
95
+ }
96
+
97
+ addStream (stream: Stream): void {
98
+ this.streams.push(stream)
99
+ }
100
+
101
+ removeStream (id: string): void {
102
+ this.streams = this.streams.filter(stream => stream.id !== id)
103
+ }
104
+
105
+ async close (options?: AbortOptions): Promise<void> {
106
+ this.status = 'closing'
107
+ await Promise.all(
108
+ this.streams.map(async s => s.close(options))
109
+ )
110
+ await this.maConn.close()
111
+ this.status = 'closed'
112
+ this.timeline.close = Date.now()
113
+ }
114
+
115
+ abort (err: Error): void {
116
+ this.status = 'closing'
117
+ this.streams.forEach(s => {
118
+ s.abort(err)
119
+ })
120
+ this.maConn.abort(err)
121
+ this.status = 'closed'
122
+ this.timeline.close = Date.now()
123
+ }
124
+ }
125
+
126
+ export function mockConnection (maConn: MultiaddrConnection, opts: MockConnectionOptions = {}): Connection {
127
+ const remoteAddr = maConn.remoteAddr
128
+ const remotePeerIdStr = remoteAddr.getPeerId() ?? '12D3KooWCrhmFM1BCPGBkNzbPfDk4cjYmtAYSpZwUBC69Qg2kZyq'
129
+
130
+ if (remotePeerIdStr == null) {
131
+ throw new Error('Remote multiaddr must contain a peer id')
132
+ }
133
+
134
+ const remotePeer = peerIdFromString(remotePeerIdStr)
135
+ const direction = opts.direction ?? 'inbound'
136
+ const registrar = opts.registrar ?? mockRegistrar()
137
+ const muxerFactory = opts.muxerFactory ?? mockMuxer()
138
+
139
+ const muxer = muxerFactory.createStreamMuxer({
140
+ direction,
141
+ onIncomingStream: (muxedStream) => {
142
+ try {
143
+ mss.handle(muxedStream, registrar.getProtocols())
144
+ .then(({ stream, protocol }) => {
145
+ log('%s: incoming stream opened on %s', direction, protocol)
146
+ muxedStream.protocol = protocol
147
+ muxedStream.sink = stream.sink
148
+ muxedStream.source = stream.source
149
+
150
+ connection.addStream(muxedStream)
151
+ const { handler } = registrar.getHandler(protocol)
152
+
153
+ handler({ connection, stream: muxedStream })
154
+ }).catch(err => {
155
+ log.error(err)
156
+ })
157
+ } catch (err: any) {
158
+ log.error(err)
159
+ }
160
+ },
161
+ onStreamEnd: (muxedStream) => {
162
+ connection.removeStream(muxedStream.id)
163
+ }
164
+ })
165
+
166
+ void pipe(
167
+ maConn, muxer, maConn
168
+ )
169
+
170
+ const connection = new MockConnection({
171
+ remoteAddr,
172
+ remotePeer,
173
+ direction,
174
+ maConn,
175
+ muxer
176
+ })
177
+
178
+ return connection
179
+ }
180
+
181
+ export function mockStream (stream: Duplex<AsyncGenerator<Uint8ArrayList>, Source<Uint8ArrayList | Uint8Array>, Promise<void>>): Stream {
182
+ return {
183
+ ...stream,
184
+ close: async () => {},
185
+ closeRead: async () => {},
186
+ closeWrite: async () => {},
187
+ abort: () => {},
188
+ direction: 'outbound',
189
+ protocol: '/foo/1.0.0',
190
+ timeline: {
191
+ open: Date.now()
192
+ },
193
+ metadata: {},
194
+ id: `stream-${Date.now()}`,
195
+ status: 'open',
196
+ readStatus: 'ready',
197
+ writeStatus: 'ready'
198
+ }
199
+ }
200
+
201
+ export interface Peer {
202
+ peerId: PeerId
203
+ registrar: Registrar
204
+ }
205
+
206
+ export function multiaddrConnectionPair (a: { peerId: PeerId, registrar: Registrar }, b: { peerId: PeerId, registrar: Registrar }): [ MultiaddrConnection, MultiaddrConnection ] {
207
+ const [peerBtoPeerA, peerAtoPeerB] = duplexPair<Uint8Array>()
208
+
209
+ return [
210
+ mockMultiaddrConnection(peerAtoPeerB, b.peerId),
211
+ mockMultiaddrConnection(peerBtoPeerA, a.peerId)
212
+ ]
213
+ }
214
+
215
+ export function connectionPair (a: { peerId: PeerId, registrar: Registrar }, b: { peerId: PeerId, registrar: Registrar }): [ Connection, Connection ] {
216
+ const [peerBtoPeerA, peerAtoPeerB] = multiaddrConnectionPair(a, b)
217
+
218
+ return [
219
+ mockConnection(peerBtoPeerA, {
220
+ registrar: a.registrar
221
+ }),
222
+ mockConnection(peerAtoPeerB, {
223
+ registrar: b.registrar
224
+ })
225
+ ]
226
+ }