@libp2p/interface-compliance-tests 3.0.6 → 3.0.7-06f4901a

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 (176) hide show
  1. package/README.md +12 -3
  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 +151 -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 +18 -0
  13. package/dist/src/connection-encryption/utils/index.js.map +1 -0
  14. package/dist/src/index.d.ts +1 -1
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/mocks/connection-encrypter.d.ts +3 -0
  17. package/dist/src/mocks/connection-encrypter.d.ts.map +1 -0
  18. package/dist/src/mocks/connection-encrypter.js +98 -0
  19. package/dist/src/mocks/connection-encrypter.js.map +1 -0
  20. package/dist/src/mocks/connection-gater.d.ts +3 -0
  21. package/dist/src/mocks/connection-gater.d.ts.map +1 -0
  22. package/dist/src/mocks/connection-gater.js +17 -0
  23. package/dist/src/mocks/connection-gater.js.map +1 -0
  24. package/dist/src/mocks/connection-manager.d.ts +29 -0
  25. package/dist/src/mocks/connection-manager.d.ts.map +1 -0
  26. package/dist/src/mocks/connection-manager.js +145 -0
  27. package/dist/src/mocks/connection-manager.js.map +1 -0
  28. package/dist/src/mocks/connection.d.ts +32 -0
  29. package/dist/src/mocks/connection.d.ts.map +1 -0
  30. package/dist/src/mocks/connection.js +162 -0
  31. package/dist/src/mocks/connection.js.map +1 -0
  32. package/dist/src/mocks/duplex.d.ts +3 -0
  33. package/dist/src/mocks/duplex.d.ts.map +1 -0
  34. package/dist/src/mocks/duplex.js +9 -0
  35. package/dist/src/mocks/duplex.js.map +1 -0
  36. package/dist/src/mocks/index.d.ts +13 -0
  37. package/dist/src/mocks/index.d.ts.map +1 -0
  38. package/dist/src/mocks/index.js +11 -0
  39. package/dist/src/mocks/index.js.map +1 -0
  40. package/dist/src/mocks/metrics.d.ts +3 -0
  41. package/dist/src/mocks/metrics.d.ts.map +1 -0
  42. package/dist/src/mocks/metrics.js +115 -0
  43. package/dist/src/mocks/metrics.js.map +1 -0
  44. package/dist/src/mocks/multiaddr-connection.d.ts +17 -0
  45. package/dist/src/mocks/multiaddr-connection.d.ts.map +1 -0
  46. package/dist/src/mocks/multiaddr-connection.js +51 -0
  47. package/dist/src/mocks/multiaddr-connection.js.map +1 -0
  48. package/dist/src/mocks/muxer.d.ts +8 -0
  49. package/dist/src/mocks/muxer.d.ts.map +1 -0
  50. package/dist/src/mocks/muxer.js +341 -0
  51. package/dist/src/mocks/muxer.js.map +1 -0
  52. package/dist/src/mocks/peer-discovery.d.ts +22 -0
  53. package/dist/src/mocks/peer-discovery.d.ts.map +1 -0
  54. package/dist/src/mocks/peer-discovery.js +47 -0
  55. package/dist/src/mocks/peer-discovery.js.map +1 -0
  56. package/dist/src/mocks/registrar.d.ts +18 -0
  57. package/dist/src/mocks/registrar.d.ts.map +1 -0
  58. package/dist/src/mocks/registrar.js +66 -0
  59. package/dist/src/mocks/registrar.js.map +1 -0
  60. package/dist/src/mocks/upgrader.d.ts +10 -0
  61. package/dist/src/mocks/upgrader.d.ts.map +1 -0
  62. package/dist/src/mocks/upgrader.js +31 -0
  63. package/dist/src/mocks/upgrader.js.map +1 -0
  64. package/dist/src/peer-discovery/index.d.ts +5 -0
  65. package/dist/src/peer-discovery/index.d.ts.map +1 -0
  66. package/dist/src/peer-discovery/index.js +66 -0
  67. package/dist/src/peer-discovery/index.js.map +1 -0
  68. package/dist/src/pubsub/api.d.ts +6 -0
  69. package/dist/src/pubsub/api.d.ts.map +1 -0
  70. package/dist/src/pubsub/api.js +87 -0
  71. package/dist/src/pubsub/api.js.map +1 -0
  72. package/dist/src/pubsub/connection-handlers.d.ts +6 -0
  73. package/dist/src/pubsub/connection-handlers.d.ts.map +1 -0
  74. package/dist/src/pubsub/connection-handlers.js +329 -0
  75. package/dist/src/pubsub/connection-handlers.js.map +1 -0
  76. package/dist/src/pubsub/emit-self.d.ts +6 -0
  77. package/dist/src/pubsub/emit-self.d.ts.map +1 -0
  78. package/dist/src/pubsub/emit-self.js +80 -0
  79. package/dist/src/pubsub/emit-self.js.map +1 -0
  80. package/dist/src/pubsub/index.d.ts +18 -0
  81. package/dist/src/pubsub/index.d.ts.map +1 -0
  82. package/dist/src/pubsub/index.js +17 -0
  83. package/dist/src/pubsub/index.js.map +1 -0
  84. package/dist/src/pubsub/messages.d.ts +6 -0
  85. package/dist/src/pubsub/messages.d.ts.map +1 -0
  86. package/dist/src/pubsub/messages.js +48 -0
  87. package/dist/src/pubsub/messages.js.map +1 -0
  88. package/dist/src/pubsub/multiple-nodes.d.ts +6 -0
  89. package/dist/src/pubsub/multiple-nodes.d.ts.map +1 -0
  90. package/dist/src/pubsub/multiple-nodes.js +350 -0
  91. package/dist/src/pubsub/multiple-nodes.js.map +1 -0
  92. package/dist/src/pubsub/two-nodes.d.ts +6 -0
  93. package/dist/src/pubsub/two-nodes.d.ts.map +1 -0
  94. package/dist/src/pubsub/two-nodes.js +217 -0
  95. package/dist/src/pubsub/two-nodes.js.map +1 -0
  96. package/dist/src/pubsub/utils.d.ts +6 -0
  97. package/dist/src/pubsub/utils.d.ts.map +1 -0
  98. package/dist/src/pubsub/utils.js +22 -0
  99. package/dist/src/pubsub/utils.js.map +1 -0
  100. package/dist/src/stream-muxer/base-test.d.ts +5 -0
  101. package/dist/src/stream-muxer/base-test.d.ts.map +1 -0
  102. package/dist/src/stream-muxer/base-test.js +153 -0
  103. package/dist/src/stream-muxer/base-test.js.map +1 -0
  104. package/dist/src/stream-muxer/close-test.d.ts +5 -0
  105. package/dist/src/stream-muxer/close-test.d.ts.map +1 -0
  106. package/dist/src/stream-muxer/close-test.js +269 -0
  107. package/dist/src/stream-muxer/close-test.js.map +1 -0
  108. package/dist/src/stream-muxer/index.d.ts +5 -0
  109. package/dist/src/stream-muxer/index.d.ts.map +1 -0
  110. package/dist/src/stream-muxer/index.js +13 -0
  111. package/dist/src/stream-muxer/index.js.map +1 -0
  112. package/dist/src/stream-muxer/mega-stress-test.d.ts +5 -0
  113. package/dist/src/stream-muxer/mega-stress-test.d.ts.map +1 -0
  114. package/dist/src/stream-muxer/mega-stress-test.js +11 -0
  115. package/dist/src/stream-muxer/mega-stress-test.js.map +1 -0
  116. package/dist/src/stream-muxer/spawner.d.ts +4 -0
  117. package/dist/src/stream-muxer/spawner.d.ts.map +1 -0
  118. package/dist/src/stream-muxer/spawner.js +36 -0
  119. package/dist/src/stream-muxer/spawner.js.map +1 -0
  120. package/dist/src/stream-muxer/stress-test.d.ts +5 -0
  121. package/dist/src/stream-muxer/stress-test.d.ts.map +1 -0
  122. package/dist/src/stream-muxer/stress-test.js +23 -0
  123. package/dist/src/stream-muxer/stress-test.js.map +1 -0
  124. package/dist/src/transport/dial-test.d.ts +5 -0
  125. package/dist/src/transport/dial-test.d.ts.map +1 -0
  126. package/dist/src/transport/dial-test.js +98 -0
  127. package/dist/src/transport/dial-test.js.map +1 -0
  128. package/dist/src/transport/filter-test.d.ts +5 -0
  129. package/dist/src/transport/filter-test.d.ts.map +1 -0
  130. package/dist/src/transport/filter-test.js +18 -0
  131. package/dist/src/transport/filter-test.js.map +1 -0
  132. package/dist/src/transport/index.d.ts +15 -0
  133. package/dist/src/transport/index.d.ts.map +1 -0
  134. package/dist/src/transport/index.js +11 -0
  135. package/dist/src/transport/index.js.map +1 -0
  136. package/dist/src/transport/listen-test.d.ts +5 -0
  137. package/dist/src/transport/listen-test.d.ts.map +1 -0
  138. package/dist/src/transport/listen-test.js +152 -0
  139. package/dist/src/transport/listen-test.js.map +1 -0
  140. package/package.json +71 -95
  141. package/src/connection/index.ts +184 -0
  142. package/src/connection-encryption/index.ts +97 -0
  143. package/src/connection-encryption/utils/index.ts +23 -0
  144. package/src/index.ts +1 -1
  145. package/src/mocks/connection-encrypter.ts +113 -0
  146. package/src/mocks/connection-gater.ts +18 -0
  147. package/src/mocks/connection-manager.ts +211 -0
  148. package/src/mocks/connection.ts +218 -0
  149. package/src/mocks/duplex.ts +10 -0
  150. package/src/mocks/index.ts +12 -0
  151. package/src/mocks/metrics.ts +162 -0
  152. package/src/mocks/multiaddr-connection.ts +67 -0
  153. package/src/mocks/muxer.ts +447 -0
  154. package/src/mocks/peer-discovery.ts +60 -0
  155. package/src/mocks/registrar.ts +88 -0
  156. package/src/mocks/upgrader.ts +49 -0
  157. package/src/peer-discovery/index.ts +90 -0
  158. package/src/pubsub/api.ts +114 -0
  159. package/src/pubsub/connection-handlers.ts +413 -0
  160. package/src/pubsub/emit-self.ts +99 -0
  161. package/src/pubsub/index.ts +34 -0
  162. package/src/pubsub/messages.ts +59 -0
  163. package/src/pubsub/multiple-nodes.ts +440 -0
  164. package/src/pubsub/two-nodes.ts +273 -0
  165. package/src/pubsub/utils.ts +29 -0
  166. package/src/stream-muxer/base-test.ts +196 -0
  167. package/src/stream-muxer/close-test.ts +346 -0
  168. package/src/stream-muxer/index.ts +15 -0
  169. package/src/stream-muxer/mega-stress-test.ts +14 -0
  170. package/src/stream-muxer/spawner.ts +54 -0
  171. package/src/stream-muxer/stress-test.ts +27 -0
  172. package/src/transport/dial-test.ts +124 -0
  173. package/src/transport/filter-test.ts +25 -0
  174. package/src/transport/index.ts +25 -0
  175. package/src/transport/listen-test.ts +191 -0
  176. package/dist/typedoc-urls.json +0 -3
@@ -0,0 +1,162 @@
1
+ import type { MultiaddrConnection, Stream, Connection } from '@libp2p/interface/connection'
2
+ import type { Metric, MetricGroup, StopTimer, Metrics, CalculatedMetricOptions, MetricOptions } from '@libp2p/interface/metrics'
3
+
4
+ class DefaultMetric implements Metric {
5
+ public value: number = 0
6
+
7
+ update (value: number): void {
8
+ this.value = value
9
+ }
10
+
11
+ increment (value: number = 1): void {
12
+ this.value += value
13
+ }
14
+
15
+ decrement (value: number = 1): void {
16
+ this.value -= value
17
+ }
18
+
19
+ reset (): void {
20
+ this.value = 0
21
+ }
22
+
23
+ timer (): StopTimer {
24
+ const start = Date.now()
25
+
26
+ return () => {
27
+ this.value = Date.now() - start
28
+ }
29
+ }
30
+ }
31
+
32
+ class DefaultGroupMetric implements MetricGroup {
33
+ public values: Record<string, number> = {}
34
+
35
+ update (values: Record<string, number>): void {
36
+ Object.entries(values).forEach(([key, value]) => {
37
+ this.values[key] = value
38
+ })
39
+ }
40
+
41
+ increment (values: Record<string, number | unknown>): void {
42
+ Object.entries(values).forEach(([key, value]) => {
43
+ this.values[key] = this.values[key] ?? 0
44
+ const inc = typeof value === 'number' ? value : 1
45
+
46
+ this.values[key] += Number(inc)
47
+ })
48
+ }
49
+
50
+ decrement (values: Record<string, number | unknown>): void {
51
+ Object.entries(values).forEach(([key, value]) => {
52
+ this.values[key] = this.values[key] ?? 0
53
+ const dec = typeof value === 'number' ? value : 1
54
+
55
+ this.values[key] -= Number(dec)
56
+ })
57
+ }
58
+
59
+ reset (): void {
60
+ this.values = {}
61
+ }
62
+
63
+ timer (key: string): StopTimer {
64
+ const start = Date.now()
65
+
66
+ return () => {
67
+ this.values[key] = Date.now() - start
68
+ }
69
+ }
70
+ }
71
+
72
+ class MockMetrics implements Metrics {
73
+ public metrics = new Map<string, any>()
74
+
75
+ trackMultiaddrConnection (maConn: MultiaddrConnection): void {
76
+
77
+ }
78
+
79
+ trackProtocolStream (stream: Stream, connection: Connection): void {
80
+
81
+ }
82
+
83
+ registerMetric (name: string, opts: CalculatedMetricOptions): void
84
+ registerMetric (name: string, opts?: MetricOptions): Metric
85
+ registerMetric (name: string, opts: any): any {
86
+ if (name == null ?? name.trim() === '') {
87
+ throw new Error('Metric name is required')
88
+ }
89
+
90
+ if (opts?.calculate != null) {
91
+ // calculated metric
92
+ this.metrics.set(name, opts.calculate)
93
+ return
94
+ }
95
+
96
+ const metric = new DefaultMetric()
97
+ this.metrics.set(name, metric)
98
+
99
+ return metric
100
+ }
101
+
102
+ registerCounter (name: string, opts: CalculatedMetricOptions): void
103
+ registerCounter (name: string, opts?: MetricOptions): Metric
104
+ registerCounter (name: string, opts: any): any {
105
+ if (name == null ?? name.trim() === '') {
106
+ throw new Error('Metric name is required')
107
+ }
108
+
109
+ if (opts?.calculate != null) {
110
+ // calculated metric
111
+ this.metrics.set(name, opts.calculate)
112
+ return
113
+ }
114
+
115
+ const metric = new DefaultMetric()
116
+ this.metrics.set(name, metric)
117
+
118
+ return metric
119
+ }
120
+
121
+ registerMetricGroup (name: string, opts: CalculatedMetricOptions<Record<string, number>>): void
122
+ registerMetricGroup (name: string, opts?: MetricOptions): MetricGroup
123
+ registerMetricGroup (name: string, opts: any): any {
124
+ if (name == null ?? name.trim() === '') {
125
+ throw new Error('Metric name is required')
126
+ }
127
+
128
+ if (opts?.calculate != null) {
129
+ // calculated metric
130
+ this.metrics.set(name, opts.calculate)
131
+ return
132
+ }
133
+
134
+ const metric = new DefaultGroupMetric()
135
+ this.metrics.set(name, metric)
136
+
137
+ return metric
138
+ }
139
+
140
+ registerCounterGroup (name: string, opts: CalculatedMetricOptions<Record<string, number>>): void
141
+ registerCounterGroup (name: string, opts?: MetricOptions): MetricGroup
142
+ registerCounterGroup (name: string, opts: any): any {
143
+ if (name == null ?? name.trim() === '') {
144
+ throw new Error('Metric name is required')
145
+ }
146
+
147
+ if (opts?.calculate != null) {
148
+ // calculated metric
149
+ this.metrics.set(name, opts.calculate)
150
+ return
151
+ }
152
+
153
+ const metric = new DefaultGroupMetric()
154
+ this.metrics.set(name, metric)
155
+
156
+ return metric
157
+ }
158
+ }
159
+
160
+ export function mockMetrics (): () => Metrics {
161
+ return () => new MockMetrics()
162
+ }
@@ -0,0 +1,67 @@
1
+ import { multiaddr } from '@multiformats/multiaddr'
2
+ import { abortableSource } from 'abortable-iterator'
3
+ import { duplexPair } from 'it-pair/duplex'
4
+ import type { MultiaddrConnection } from '@libp2p/interface/connection'
5
+ import type { PeerId } from '@libp2p/interface/peer-id'
6
+ import type { Multiaddr } from '@multiformats/multiaddr'
7
+ import type { Duplex } from 'it-stream-types'
8
+
9
+ export function mockMultiaddrConnection (source: Duplex<AsyncGenerator<Uint8Array>> & Partial<MultiaddrConnection>, peerId: PeerId): MultiaddrConnection {
10
+ const maConn: MultiaddrConnection = {
11
+ async close () {
12
+
13
+ },
14
+ timeline: {
15
+ open: Date.now()
16
+ },
17
+ remoteAddr: multiaddr(`/ip4/127.0.0.1/tcp/4001/p2p/${peerId.toString()}`),
18
+ ...source
19
+ }
20
+
21
+ return maConn
22
+ }
23
+
24
+ export interface MockMultiaddrConnPairOptions {
25
+ addrs: Multiaddr[]
26
+ remotePeer: PeerId
27
+ }
28
+
29
+ /**
30
+ * Returns both sides of a mocked MultiaddrConnection
31
+ */
32
+ export function mockMultiaddrConnPair (opts: MockMultiaddrConnPairOptions): { inbound: MultiaddrConnection, outbound: MultiaddrConnection } {
33
+ const { addrs, remotePeer } = opts
34
+ const controller = new AbortController()
35
+ const [localAddr, remoteAddr] = addrs
36
+ const [inboundStream, outboundStream] = duplexPair<Uint8Array>()
37
+
38
+ const outbound: MultiaddrConnection = {
39
+ ...outboundStream,
40
+ remoteAddr: remoteAddr.toString().includes(`/p2p/${remotePeer.toString()}`) ? remoteAddr : remoteAddr.encapsulate(`/p2p/${remotePeer.toString()}`),
41
+ timeline: {
42
+ open: Date.now()
43
+ },
44
+ close: async () => {
45
+ outbound.timeline.close = Date.now()
46
+ controller.abort()
47
+ }
48
+ }
49
+
50
+ const inbound: MultiaddrConnection = {
51
+ ...inboundStream,
52
+ remoteAddr: localAddr,
53
+ timeline: {
54
+ open: Date.now()
55
+ },
56
+ close: async () => {
57
+ inbound.timeline.close = Date.now()
58
+ controller.abort()
59
+ }
60
+ }
61
+
62
+ // Make the sources abortable so we can close them easily
63
+ inbound.source = abortableSource(inbound.source, controller.signal)
64
+ outbound.source = abortableSource(outbound.source, controller.signal)
65
+
66
+ return { inbound, outbound }
67
+ }
@@ -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
+ }