@helia/bitswap 0.0.0 → 1.0.0-59de059

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 (62) hide show
  1. package/dist/index.min.js +6 -1
  2. package/dist/src/bitswap.d.ts +3 -2
  3. package/dist/src/bitswap.d.ts.map +1 -1
  4. package/dist/src/bitswap.js +7 -14
  5. package/dist/src/bitswap.js.map +1 -1
  6. package/dist/src/constants.d.ts +2 -0
  7. package/dist/src/constants.d.ts.map +1 -1
  8. package/dist/src/constants.js +2 -0
  9. package/dist/src/constants.js.map +1 -1
  10. package/dist/src/index.d.ts +22 -45
  11. package/dist/src/index.d.ts.map +1 -1
  12. package/dist/src/index.js.map +1 -1
  13. package/dist/src/network.d.ts +8 -7
  14. package/dist/src/network.d.ts.map +1 -1
  15. package/dist/src/network.js +71 -169
  16. package/dist/src/network.js.map +1 -1
  17. package/dist/src/pb/message.d.ts +6 -6
  18. package/dist/src/pb/message.d.ts.map +1 -1
  19. package/dist/src/pb/message.js +37 -20
  20. package/dist/src/pb/message.js.map +1 -1
  21. package/dist/src/peer-want-lists/index.d.ts +3 -2
  22. package/dist/src/peer-want-lists/index.d.ts.map +1 -1
  23. package/dist/src/peer-want-lists/index.js +7 -3
  24. package/dist/src/peer-want-lists/index.js.map +1 -1
  25. package/dist/src/peer-want-lists/ledger.d.ts +3 -1
  26. package/dist/src/peer-want-lists/ledger.d.ts.map +1 -1
  27. package/dist/src/peer-want-lists/ledger.js +8 -0
  28. package/dist/src/peer-want-lists/ledger.js.map +1 -1
  29. package/dist/src/session.d.ts +13 -8
  30. package/dist/src/session.d.ts.map +1 -1
  31. package/dist/src/session.js +25 -88
  32. package/dist/src/session.js.map +1 -1
  33. package/dist/src/stats.d.ts +2 -2
  34. package/dist/src/stats.d.ts.map +1 -1
  35. package/dist/src/stats.js +4 -4
  36. package/dist/src/stats.js.map +1 -1
  37. package/dist/src/utils/merge-messages.d.ts +3 -0
  38. package/dist/src/utils/merge-messages.d.ts.map +1 -0
  39. package/dist/src/utils/merge-messages.js +51 -0
  40. package/dist/src/utils/merge-messages.js.map +1 -0
  41. package/dist/src/utils/split-message.d.ts +14 -0
  42. package/dist/src/utils/split-message.d.ts.map +1 -0
  43. package/dist/src/utils/split-message.js +99 -0
  44. package/dist/src/utils/split-message.js.map +1 -0
  45. package/dist/src/want-list.d.ts +20 -23
  46. package/dist/src/want-list.d.ts.map +1 -1
  47. package/dist/src/want-list.js +136 -133
  48. package/dist/src/want-list.js.map +1 -1
  49. package/package.json +6 -6
  50. package/src/bitswap.ts +9 -16
  51. package/src/constants.ts +2 -0
  52. package/src/index.ts +24 -51
  53. package/src/network.ts +83 -200
  54. package/src/pb/message.ts +40 -20
  55. package/src/peer-want-lists/index.ts +9 -5
  56. package/src/peer-want-lists/ledger.ts +11 -1
  57. package/src/session.ts +31 -120
  58. package/src/stats.ts +6 -6
  59. package/src/utils/merge-messages.ts +70 -0
  60. package/src/utils/split-message.ts +131 -0
  61. package/src/want-list.ts +205 -212
  62. package/dist/typedoc-urls.json +0 -24
package/src/index.ts CHANGED
@@ -9,10 +9,9 @@
9
9
  import { Bitswap as BitswapClass } from './bitswap.js'
10
10
  import type { BitswapNetworkNotifyProgressEvents, BitswapNetworkWantProgressEvents } from './network.js'
11
11
  import type { WantType } from './pb/message.js'
12
- import type { CreateSessionOptions } from '@helia/interface'
12
+ import type { BlockBroker, CreateSessionOptions } from '@helia/interface'
13
13
  import type { Routing } from '@helia/interface/routing'
14
14
  import type { Libp2p, AbortOptions, Startable, ComponentLogger, Metrics, PeerId } from '@libp2p/interface'
15
- import type { PeerSet } from '@libp2p/peer-collections'
16
15
  import type { Blockstore } from 'interface-blockstore'
17
16
  import type { CID } from 'multiformats/cid'
18
17
  import type { MultihashHasher } from 'multiformats/hashes/interface'
@@ -29,52 +28,12 @@ export type BitswapWantBlockProgressEvents =
29
28
  ProgressEvent<'bitswap:want-block:block', CID> |
30
29
  BitswapNetworkWantProgressEvents
31
30
 
32
- /**
33
- * A bitswap session is a network overlay consisting of peers that all have the
34
- * first block in a file. Subsequent requests will only go to these peers.
35
- */
36
- export interface BitswapSession {
37
- /**
38
- * The peers in this session
39
- */
40
- peers: PeerSet
41
-
42
- /**
43
- * Fetch an additional CID from this DAG
44
- */
45
- want(cid: CID, options?: AbortOptions & ProgressOptions<BitswapWantProgressEvents>): Promise<Uint8Array>
46
- }
47
-
48
31
  export interface WantListEntry {
49
32
  cid: CID
50
33
  priority: number
51
34
  wantType: WantType
52
35
  }
53
36
 
54
- export interface CreateBitswapSessionOptions extends CreateSessionOptions<BitswapWantProgressEvents> {
55
- /**
56
- * If true, query connected peers before searching for providers via
57
- * Helia routers
58
- *
59
- * @default true
60
- */
61
- queryConnectedPeers?: boolean
62
-
63
- /**
64
- * If true, search for providers via Helia routers to query for the root CID
65
- *
66
- * @default true
67
- */
68
- queryRoutingPeers?: boolean
69
-
70
- /**
71
- * The priority to use when querying availability of the root CID
72
- *
73
- * @default 1
74
- */
75
- priority?: number
76
- }
77
-
78
37
  export interface Bitswap extends Startable {
79
38
  /**
80
39
  * Returns the current state of the wantlist
@@ -100,7 +59,7 @@ export interface Bitswap extends Startable {
100
59
  /**
101
60
  * Start a session to retrieve a file from the network
102
61
  */
103
- createSession(root: CID, options?: AbortOptions & ProgressOptions<BitswapWantProgressEvents>): Promise<BitswapSession>
62
+ createSession(options?: CreateSessionOptions<BitswapWantProgressEvents>): Required<Pick<BlockBroker<BitswapWantProgressEvents>, 'retrieve'>>
104
63
  }
105
64
 
106
65
  export interface MultihashHasherLoader {
@@ -158,14 +117,6 @@ export interface BitswapOptions {
158
117
  */
159
118
  protocol?: string
160
119
 
161
- /**
162
- * When a new peer connects, sending our WantList should complete within this
163
- * many ms
164
- *
165
- * @default 5000
166
- */
167
- messageSendTimeout?: number
168
-
169
120
  /**
170
121
  * When sending want list updates to peers, how many messages to send at once
171
122
  *
@@ -208,6 +159,28 @@ export interface BitswapOptions {
208
159
  * @default 1024
209
160
  */
210
161
  maxSizeReplaceHasWithBlock?: number
162
+
163
+ /**
164
+ * The maximum size in bytes of a message that we will send. If a message is
165
+ * larger than this (due to lots of blocks or wantlist entries) it will be
166
+ * broken up into several smaller messages that are under this size.
167
+ *
168
+ * @see https://github.com/ipfs/boxo/blob/eeea414587350401b6b804f0574ed8436833331d/bitswap/client/internal/messagequeue/messagequeue.go#L33
169
+ *
170
+ * @default 2097152
171
+ */
172
+ maxOutgoingMessageSize?: number
173
+
174
+ /**
175
+ * The maximum size in bytes of an incoming message that we will process.
176
+ *
177
+ * Messages larger than this will cause the incoming stream to be reset.
178
+ *
179
+ * Defaults to `maxOutgoingMessageSize`
180
+ *
181
+ * @default 2097152
182
+ */
183
+ maxIncomingMessageSize?: number
211
184
  }
212
185
 
213
186
  export const createBitswap = (components: BitswapComponents, options: BitswapOptions = {}): Bitswap => {
package/src/network.ts CHANGED
@@ -1,56 +1,25 @@
1
1
  import { CodeError, TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
2
2
  import { PeerQueue, type PeerQueueJobOptions } from '@libp2p/utils/peer-queue'
3
- import { Circuit } from '@multiformats/multiaddr-matcher'
4
- import { anySignal } from 'any-signal'
5
- import debug from 'debug'
6
3
  import drain from 'it-drain'
7
4
  import * as lp from 'it-length-prefixed'
8
- import { lpStream } from 'it-length-prefixed-stream'
9
5
  import map from 'it-map'
10
6
  import { pipe } from 'it-pipe'
11
7
  import take from 'it-take'
12
- import { base64 } from 'multiformats/bases/base64'
13
- import { CID } from 'multiformats/cid'
14
8
  import { CustomProgressEvent } from 'progress-events'
15
9
  import { raceEvent } from 'race-event'
16
- import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
17
- import { BITSWAP_120, DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_OUTBOUND_STREAMS, DEFAULT_MAX_PROVIDERS_PER_REQUEST, DEFAULT_MESSAGE_RECEIVE_TIMEOUT, DEFAULT_MESSAGE_SEND_TIMEOUT, DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS } from './constants.js'
10
+ import { BITSWAP_120, DEFAULT_MAX_INBOUND_STREAMS, DEFAULT_MAX_INCOMING_MESSAGE_SIZE, DEFAULT_MAX_OUTBOUND_STREAMS, DEFAULT_MAX_OUTGOING_MESSAGE_SIZE, DEFAULT_MAX_PROVIDERS_PER_REQUEST, DEFAULT_MESSAGE_RECEIVE_TIMEOUT, DEFAULT_MESSAGE_SEND_CONCURRENCY, DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS } from './constants.js'
18
11
  import { BitswapMessage } from './pb/message.js'
12
+ import { mergeMessages } from './utils/merge-messages.js'
13
+ import { splitMessage } from './utils/split-message.js'
19
14
  import type { WantOptions } from './bitswap.js'
20
15
  import type { MultihashHasherLoader } from './index.js'
21
- import type { Block, BlockPresence, WantlistEntry } from './pb/message.js'
16
+ import type { Block } from './pb/message.js'
22
17
  import type { Provider, Routing } from '@helia/interface/routing'
23
- import type { Libp2p, AbortOptions, Connection, PeerId, IncomingStreamData, Topology, MetricGroup, ComponentLogger, Metrics, IdentifyResult } from '@libp2p/interface'
18
+ import type { Libp2p, AbortOptions, Connection, PeerId, IncomingStreamData, Topology, ComponentLogger, IdentifyResult, Counter } from '@libp2p/interface'
24
19
  import type { Logger } from '@libp2p/logger'
20
+ import type { CID } from 'multiformats/cid'
25
21
  import type { ProgressEvent, ProgressOptions } from 'progress-events'
26
22
 
27
- // Add a formatter for a bitswap message
28
- debug.formatters.B = (b?: BitswapMessage): string => {
29
- if (b == null) {
30
- return 'undefined'
31
- }
32
-
33
- return JSON.stringify({
34
- blocks: b.blocks?.map(b => ({
35
- data: `${uint8ArrayToString(b.data, 'base64').substring(0, 10)}...`,
36
- prefix: uint8ArrayToString(b.prefix, 'base64')
37
- })),
38
- blockPresences: b.blockPresences?.map(p => ({
39
- ...p,
40
- cid: CID.decode(p.cid).toString()
41
- })),
42
- wantlist: b.wantlist == null
43
- ? undefined
44
- : {
45
- full: b.wantlist.full,
46
- entries: b.wantlist.entries.map(e => ({
47
- ...e,
48
- cid: CID.decode(e.cid).toString()
49
- }))
50
- }
51
- }, null, 2)
52
- }
53
-
54
23
  export type BitswapNetworkProgressEvents =
55
24
  ProgressEvent<'bitswap:network:dial', PeerId>
56
25
 
@@ -69,17 +38,17 @@ export interface NetworkInit {
69
38
  maxInboundStreams?: number
70
39
  maxOutboundStreams?: number
71
40
  messageReceiveTimeout?: number
72
- messageSendTimeout?: number
73
41
  messageSendConcurrency?: number
74
42
  protocols?: string[]
75
43
  runOnTransientConnections?: boolean
44
+ maxOutgoingMessageSize?: number
45
+ maxIncomingMessageSize?: number
76
46
  }
77
47
 
78
48
  export interface NetworkComponents {
79
49
  routing: Routing
80
50
  logger: ComponentLogger
81
51
  libp2p: Libp2p
82
- metrics?: Metrics
83
52
  }
84
53
 
85
54
  export interface BitswapMessageEventDetail {
@@ -107,10 +76,11 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
107
76
  private readonly maxOutboundStreams: number
108
77
  private readonly messageReceiveTimeout: number
109
78
  private registrarIds: string[]
110
- private readonly metrics?: { blocksSent: MetricGroup, dataSent: MetricGroup }
79
+ private readonly metrics: { blocksSent?: Counter, dataSent?: Counter }
111
80
  private readonly sendQueue: PeerQueue<void, SendMessageJobOptions>
112
- private readonly messageSendTimeout: number
113
81
  private readonly runOnTransientConnections: boolean
82
+ private readonly maxOutgoingMessageSize: number
83
+ private readonly maxIncomingMessageSize: number
114
84
 
115
85
  constructor (components: NetworkComponents, init: NetworkInit = {}) {
116
86
  super()
@@ -127,20 +97,18 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
127
97
  this.maxInboundStreams = init.maxInboundStreams ?? DEFAULT_MAX_INBOUND_STREAMS
128
98
  this.maxOutboundStreams = init.maxOutboundStreams ?? DEFAULT_MAX_OUTBOUND_STREAMS
129
99
  this.messageReceiveTimeout = init.messageReceiveTimeout ?? DEFAULT_MESSAGE_RECEIVE_TIMEOUT
130
- this.messageSendTimeout = init.messageSendTimeout ?? DEFAULT_MESSAGE_SEND_TIMEOUT
131
100
  this.runOnTransientConnections = init.runOnTransientConnections ?? DEFAULT_RUN_ON_TRANSIENT_CONNECTIONS
132
-
133
- if (components.metrics != null) {
134
- this.metrics = {
135
- blocksSent: components.metrics?.registerMetricGroup('ipfs_bitswap_sent_blocks'),
136
- dataSent: components.metrics?.registerMetricGroup('ipfs_bitswap_sent_data_bytes')
137
- }
101
+ this.maxIncomingMessageSize = init.maxIncomingMessageSize ?? DEFAULT_MAX_OUTGOING_MESSAGE_SIZE
102
+ this.maxOutgoingMessageSize = init.maxOutgoingMessageSize ?? init.maxIncomingMessageSize ?? DEFAULT_MAX_INCOMING_MESSAGE_SIZE
103
+ this.metrics = {
104
+ blocksSent: components.libp2p.metrics?.registerCounter('helia_bitswap_sent_blocks_total'),
105
+ dataSent: components.libp2p.metrics?.registerCounter('helia_bitswap_sent_data_bytes_total')
138
106
  }
139
107
 
140
108
  this.sendQueue = new PeerQueue({
141
- concurrency: init.messageSendConcurrency,
142
- metrics: components.metrics,
143
- metricName: 'ipfs_bitswap_message_send_queue'
109
+ concurrency: init.messageSendConcurrency ?? DEFAULT_MESSAGE_SEND_CONCURRENCY,
110
+ metrics: components.libp2p.metrics,
111
+ metricName: 'helia_bitswap_message_send_queue'
144
112
  })
145
113
  this.sendQueue.addEventListener('error', (evt) => {
146
114
  this.log.error('error sending wantlist to peer', evt.detail)
@@ -217,21 +185,29 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
217
185
  Promise.resolve().then(async () => {
218
186
  this.log('incoming new bitswap %s stream from %p', stream.protocol, connection.remotePeer)
219
187
  const abortListener = (): void => {
220
- stream.abort(new CodeError('Incoming Bitswap stream timed out', 'ERR_TIMEOUT'))
188
+ if (stream.status === 'open') {
189
+ stream.abort(new CodeError('Incoming Bitswap stream timed out', 'ERR_TIMEOUT'))
190
+ } else {
191
+ this.log('stream aborted with status', stream.status)
192
+ }
221
193
  }
222
194
 
223
195
  let signal = AbortSignal.timeout(this.messageReceiveTimeout)
224
196
  setMaxListeners(Infinity, signal)
225
197
  signal.addEventListener('abort', abortListener)
226
198
 
199
+ await stream.closeWrite()
200
+
227
201
  await pipe(
228
202
  stream,
229
- (source) => lp.decode(source),
203
+ (source) => lp.decode(source, {
204
+ maxDataLength: this.maxIncomingMessageSize
205
+ }),
230
206
  async (source) => {
231
207
  for await (const data of source) {
232
208
  try {
233
209
  const message = BitswapMessage.decode(data)
234
- this.log('incoming new bitswap %s message from %p %B', stream.protocol, connection.remotePeer, message)
210
+ this.log('incoming new bitswap %s message from %p on stream', stream.protocol, connection.remotePeer, stream.id)
235
211
 
236
212
  this.safeDispatchEvent('bitswap:message', {
237
213
  detail: {
@@ -246,7 +222,7 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
246
222
  setMaxListeners(Infinity, signal)
247
223
  signal.addEventListener('abort', abortListener)
248
224
  } catch (err: any) {
249
- this.log.error('error reading incoming bitswap message from %p', connection.remotePeer, err)
225
+ this.log.error('error reading incoming bitswap message from %p on stream', connection.remotePeer, stream.id, err)
250
226
  stream.abort(err)
251
227
  break
252
228
  }
@@ -267,29 +243,12 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
267
243
  options?.onProgress?.(new CustomProgressEvent<PeerId>('bitswap:network:find-providers', cid))
268
244
 
269
245
  for await (const provider of this.routing.findProviders(cid, options)) {
270
- // unless we explicitly run on transient connections, skip peers that only
271
- // have circuit relay addresses as bitswap won't run over them
272
- if (!this.runOnTransientConnections) {
273
- let hasDirectAddress = false
274
-
275
- for (let ma of provider.multiaddrs) {
276
- if (ma.getPeerId() == null) {
277
- ma = ma.encapsulate(`/p2p/${provider.id}`)
278
- }
279
-
280
- if (!Circuit.exactMatch(ma)) {
281
- hasDirectAddress = true
282
- break
283
- }
284
- }
285
-
286
- if (!hasDirectAddress) {
287
- continue
288
- }
289
- }
246
+ // make sure we can dial the provider
247
+ const dialable = await this.libp2p.isDialable(provider.multiaddrs, {
248
+ runOnTransientConnection: this.runOnTransientConnections
249
+ })
290
250
 
291
- // ignore non-bitswap providers
292
- if (provider.protocols?.includes('transport-bitswap') === false) {
251
+ if (!dialable) {
293
252
  continue
294
253
  }
295
254
 
@@ -302,9 +261,9 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
302
261
  */
303
262
  async findAndConnect (cid: CID, options?: WantOptions): Promise<void> {
304
263
  await drain(
305
- take(
306
- map(this.findProviders(cid, options), async provider => this.connectTo(provider.id, options)),
307
- options?.maxProviders ?? DEFAULT_MAX_PROVIDERS_PER_REQUEST
264
+ map(
265
+ take(this.findProviders(cid, options), options?.maxProviders ?? DEFAULT_MAX_PROVIDERS_PER_REQUEST),
266
+ async provider => this.connectTo(provider.id, options)
308
267
  )
309
268
  )
310
269
  .catch(err => {
@@ -331,55 +290,55 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
331
290
  pendingBytes: msg.pendingBytes ?? 0
332
291
  }
333
292
 
334
- const signal = anySignal([AbortSignal.timeout(this.messageSendTimeout), options?.signal])
335
- setMaxListeners(Infinity, signal)
293
+ const existingJob = this.sendQueue.queue.find(job => {
294
+ return peerId.equals(job.options.peerId) && job.status === 'queued'
295
+ })
336
296
 
337
- try {
338
- const existingJob = this.sendQueue.find(peerId)
297
+ if (existingJob != null) {
298
+ // merge messages instead of adding new job
299
+ existingJob.options.message = mergeMessages(existingJob.options.message, message)
339
300
 
340
- if (existingJob?.status === 'queued') {
341
- // merge messages instead of adding new job
342
- existingJob.options.message = mergeMessages(existingJob.options.message, message)
301
+ await existingJob.join({
302
+ signal: options?.signal
303
+ })
343
304
 
344
- await existingJob.join({
345
- signal
346
- })
305
+ return
306
+ }
347
307
 
348
- return
349
- }
308
+ await this.sendQueue.add(async (options) => {
309
+ const message = options?.message
350
310
 
351
- await this.sendQueue.add(async (options) => {
352
- const message = options?.message
311
+ if (message == null) {
312
+ throw new CodeError('No message to send', 'ERR_NO_MESSAGE')
313
+ }
353
314
 
354
- if (message == null) {
355
- throw new CodeError('No message to send', 'ERR_NO_MESSAGE')
356
- }
315
+ this.log('sendMessage to %p', peerId)
357
316
 
358
- this.log('sendMessage to %p %B', peerId, message)
317
+ options?.onProgress?.(new CustomProgressEvent<PeerId>('bitswap:network:send-wantlist', peerId))
359
318
 
360
- options?.onProgress?.(new CustomProgressEvent<PeerId>('bitswap:network:send-wantlist', peerId))
319
+ const stream = await this.libp2p.dialProtocol(peerId, BITSWAP_120, options)
320
+ await stream.closeRead()
361
321
 
362
- const stream = await this.libp2p.dialProtocol(peerId, BITSWAP_120, options)
322
+ try {
323
+ await pipe(
324
+ splitMessage(message, this.maxOutgoingMessageSize),
325
+ (source) => lp.encode(source),
326
+ stream
327
+ )
363
328
 
364
- try {
365
- const lp = lpStream(stream)
366
- await lp.write(BitswapMessage.encode(message), options)
367
- await lp.unwrap().close(options)
368
- } catch (err: any) {
369
- options?.onProgress?.(new CustomProgressEvent<{ peer: PeerId, error: Error }>('bitswap:network:send-wantlist:error', { peer: peerId, error: err }))
370
- this.log.error('error sending message to %p', peerId, err)
371
- stream.abort(err)
372
- }
329
+ await stream.close(options)
330
+ } catch (err: any) {
331
+ options?.onProgress?.(new CustomProgressEvent<{ peer: PeerId, error: Error }>('bitswap:network:send-wantlist:error', { peer: peerId, error: err }))
332
+ this.log.error('error sending message to %p', peerId, err)
333
+ stream.abort(err)
334
+ }
373
335
 
374
- this._updateSentStats(peerId, message.blocks)
375
- }, {
376
- peerId,
377
- signal,
378
- message
379
- })
380
- } finally {
381
- signal.clear()
382
- }
336
+ this._updateSentStats(message.blocks)
337
+ }, {
338
+ peerId,
339
+ signal: options?.signal,
340
+ message
341
+ })
383
342
  }
384
343
 
385
344
  /**
@@ -417,90 +376,14 @@ export class Network extends TypedEventEmitter<NetworkEvents> {
417
376
  return connection
418
377
  }
419
378
 
420
- _updateSentStats (peerId: PeerId, blocks: Block[] = []): void {
421
- if (this.metrics != null) {
422
- let bytes = 0
423
-
424
- for (const block of blocks.values()) {
425
- bytes += block.data.byteLength
426
- }
427
-
428
- this.metrics.dataSent.increment({
429
- global: bytes,
430
- [peerId.toString()]: bytes
431
- })
432
- this.metrics.blocksSent.increment({
433
- global: blocks.length,
434
- [peerId.toString()]: blocks.length
435
- })
436
- }
437
- }
438
- }
439
-
440
- function mergeMessages (messageA: BitswapMessage, messageB: BitswapMessage): BitswapMessage {
441
- const wantListEntries = new Map<string, WantlistEntry>(
442
- (messageA.wantlist?.entries ?? []).map(entry => ([
443
- base64.encode(entry.cid),
444
- entry
445
- ]))
446
- )
447
-
448
- for (const entry of messageB.wantlist?.entries ?? []) {
449
- const key = base64.encode(entry.cid)
450
- const existingEntry = wantListEntries.get(key)
451
-
452
- if (existingEntry != null) {
453
- // take highest priority
454
- if (existingEntry.priority > entry.priority) {
455
- entry.priority = existingEntry.priority
456
- }
379
+ _updateSentStats (blocks: Block[] = []): void {
380
+ let bytes = 0
457
381
 
458
- // take later values if passed, otherwise use earlier ones
459
- entry.cancel = entry.cancel ?? existingEntry.cancel
460
- entry.wantType = entry.wantType ?? existingEntry.wantType
461
- entry.sendDontHave = entry.sendDontHave ?? existingEntry.sendDontHave
382
+ for (const block of blocks.values()) {
383
+ bytes += block.data.byteLength
462
384
  }
463
385
 
464
- wantListEntries.set(key, entry)
386
+ this.metrics.dataSent?.increment(bytes)
387
+ this.metrics.blocksSent?.increment(blocks.length)
465
388
  }
466
-
467
- const blockPresences = new Map<string, BlockPresence>(
468
- messageA.blockPresences.map(presence => ([
469
- base64.encode(presence.cid),
470
- presence
471
- ]))
472
- )
473
-
474
- for (const blockPresence of messageB.blockPresences) {
475
- const key = base64.encode(blockPresence.cid)
476
-
477
- // override earlier block presence with later one as if duplicated it is
478
- // likely to be more accurate since it is more recent
479
- blockPresences.set(key, blockPresence)
480
- }
481
-
482
- const blocks = new Map<string, Block>(
483
- messageA.blocks.map(block => ([
484
- base64.encode(block.data),
485
- block
486
- ]))
487
- )
488
-
489
- for (const block of messageB.blocks) {
490
- const key = base64.encode(block.data)
491
-
492
- blocks.set(key, block)
493
- }
494
-
495
- const output: BitswapMessage = {
496
- wantlist: {
497
- full: messageA.wantlist?.full ?? messageB.wantlist?.full ?? false,
498
- entries: [...wantListEntries.values()]
499
- },
500
- blockPresences: [...blockPresences.values()],
501
- blocks: [...blocks.values()],
502
- pendingBytes: messageA.pendingBytes + messageB.pendingBytes
503
- }
504
-
505
- return output
506
389
  }
package/src/pb/message.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  /* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
5
5
  /* eslint-disable @typescript-eslint/no-empty-interface */
6
6
 
7
- import { type Codec, decodeMessage, encodeMessage, enumeration, message } from 'protons-runtime'
7
+ import { type Codec, CodeError, decodeMessage, type DecodeOptions, encodeMessage, enumeration, message } from 'protons-runtime'
8
8
  import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'
9
9
  import type { Uint8ArrayList } from 'uint8arraylist'
10
10
 
@@ -69,7 +69,7 @@ export namespace WantlistEntry {
69
69
  if (opts.lengthDelimited !== false) {
70
70
  w.ldelim()
71
71
  }
72
- }, (reader, length) => {
72
+ }, (reader, length, opts = {}) => {
73
73
  const obj: any = {
74
74
  cid: uint8ArrayAlloc(0),
75
75
  priority: 0
@@ -119,8 +119,8 @@ export namespace WantlistEntry {
119
119
  return encodeMessage(obj, WantlistEntry.codec())
120
120
  }
121
121
 
122
- export const decode = (buf: Uint8Array | Uint8ArrayList): WantlistEntry => {
123
- return decodeMessage(buf, WantlistEntry.codec())
122
+ export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<WantlistEntry>): WantlistEntry => {
123
+ return decodeMessage(buf, WantlistEntry.codec(), opts)
124
124
  }
125
125
  }
126
126
 
@@ -154,7 +154,7 @@ export namespace Wantlist {
154
154
  if (opts.lengthDelimited !== false) {
155
155
  w.ldelim()
156
156
  }
157
- }, (reader, length) => {
157
+ }, (reader, length, opts = {}) => {
158
158
  const obj: any = {
159
159
  entries: []
160
160
  }
@@ -166,7 +166,13 @@ export namespace Wantlist {
166
166
 
167
167
  switch (tag >>> 3) {
168
168
  case 1: {
169
- obj.entries.push(WantlistEntry.codec().decode(reader, reader.uint32()))
169
+ if (opts.limits?.entries != null && obj.entries.length === opts.limits.entries) {
170
+ throw new CodeError('decode error - map field "entries" had too many elements', 'ERR_MAX_LENGTH')
171
+ }
172
+
173
+ obj.entries.push(WantlistEntry.codec().decode(reader, reader.uint32(), {
174
+ limits: opts.limits?.entries$
175
+ }))
170
176
  break
171
177
  }
172
178
  case 2: {
@@ -191,8 +197,8 @@ export namespace Wantlist {
191
197
  return encodeMessage(obj, Wantlist.codec())
192
198
  }
193
199
 
194
- export const decode = (buf: Uint8Array | Uint8ArrayList): Wantlist => {
195
- return decodeMessage(buf, Wantlist.codec())
200
+ export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<Wantlist>): Wantlist => {
201
+ return decodeMessage(buf, Wantlist.codec(), opts)
196
202
  }
197
203
  }
198
204
 
@@ -224,7 +230,7 @@ export namespace Block {
224
230
  if (opts.lengthDelimited !== false) {
225
231
  w.ldelim()
226
232
  }
227
- }, (reader, length) => {
233
+ }, (reader, length, opts = {}) => {
228
234
  const obj: any = {
229
235
  prefix: uint8ArrayAlloc(0),
230
236
  data: uint8ArrayAlloc(0)
@@ -262,8 +268,8 @@ export namespace Block {
262
268
  return encodeMessage(obj, Block.codec())
263
269
  }
264
270
 
265
- export const decode = (buf: Uint8Array | Uint8ArrayList): Block => {
266
- return decodeMessage(buf, Block.codec())
271
+ export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<Block>): Block => {
272
+ return decodeMessage(buf, Block.codec(), opts)
267
273
  }
268
274
  }
269
275
 
@@ -310,7 +316,7 @@ export namespace BlockPresence {
310
316
  if (opts.lengthDelimited !== false) {
311
317
  w.ldelim()
312
318
  }
313
- }, (reader, length) => {
319
+ }, (reader, length, opts = {}) => {
314
320
  const obj: any = {
315
321
  cid: uint8ArrayAlloc(0),
316
322
  type: BlockPresenceType.HaveBlock
@@ -348,8 +354,8 @@ export namespace BlockPresence {
348
354
  return encodeMessage(obj, BlockPresence.codec())
349
355
  }
350
356
 
351
- export const decode = (buf: Uint8Array | Uint8ArrayList): BlockPresence => {
352
- return decodeMessage(buf, BlockPresence.codec())
357
+ export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<BlockPresence>): BlockPresence => {
358
+ return decodeMessage(buf, BlockPresence.codec(), opts)
353
359
  }
354
360
  }
355
361
 
@@ -397,7 +403,7 @@ export namespace BitswapMessage {
397
403
  if (opts.lengthDelimited !== false) {
398
404
  w.ldelim()
399
405
  }
400
- }, (reader, length) => {
406
+ }, (reader, length, opts = {}) => {
401
407
  const obj: any = {
402
408
  blocks: [],
403
409
  blockPresences: [],
@@ -411,15 +417,29 @@ export namespace BitswapMessage {
411
417
 
412
418
  switch (tag >>> 3) {
413
419
  case 1: {
414
- obj.wantlist = Wantlist.codec().decode(reader, reader.uint32())
420
+ obj.wantlist = Wantlist.codec().decode(reader, reader.uint32(), {
421
+ limits: opts.limits?.wantlist
422
+ })
415
423
  break
416
424
  }
417
425
  case 3: {
418
- obj.blocks.push(Block.codec().decode(reader, reader.uint32()))
426
+ if (opts.limits?.blocks != null && obj.blocks.length === opts.limits.blocks) {
427
+ throw new CodeError('decode error - map field "blocks" had too many elements', 'ERR_MAX_LENGTH')
428
+ }
429
+
430
+ obj.blocks.push(Block.codec().decode(reader, reader.uint32(), {
431
+ limits: opts.limits?.blocks$
432
+ }))
419
433
  break
420
434
  }
421
435
  case 4: {
422
- obj.blockPresences.push(BlockPresence.codec().decode(reader, reader.uint32()))
436
+ if (opts.limits?.blockPresences != null && obj.blockPresences.length === opts.limits.blockPresences) {
437
+ throw new CodeError('decode error - map field "blockPresences" had too many elements', 'ERR_MAX_LENGTH')
438
+ }
439
+
440
+ obj.blockPresences.push(BlockPresence.codec().decode(reader, reader.uint32(), {
441
+ limits: opts.limits?.blockPresences$
442
+ }))
423
443
  break
424
444
  }
425
445
  case 5: {
@@ -444,7 +464,7 @@ export namespace BitswapMessage {
444
464
  return encodeMessage(obj, BitswapMessage.codec())
445
465
  }
446
466
 
447
- export const decode = (buf: Uint8Array | Uint8ArrayList): BitswapMessage => {
448
- return decodeMessage(buf, BitswapMessage.codec())
467
+ export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions<BitswapMessage>): BitswapMessage => {
468
+ return decodeMessage(buf, BitswapMessage.codec(), opts)
449
469
  }
450
470
  }