@libp2p/autonat-v2 0.0.0-2d6079bc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/client.ts ADDED
@@ -0,0 +1,628 @@
1
+ import { ProtocolError, serviceCapabilities, serviceDependencies } from '@libp2p/interface'
2
+ import { peerSet } from '@libp2p/peer-collections'
3
+ import { createScalableCuckooFilter } from '@libp2p/utils/filters'
4
+ import { isGlobalUnicast } from '@libp2p/utils/multiaddr/is-global-unicast'
5
+ import { isPrivate } from '@libp2p/utils/multiaddr/is-private'
6
+ import { PeerQueue } from '@libp2p/utils/peer-queue'
7
+ import { repeatingTask } from '@libp2p/utils/repeating-task'
8
+ import { trackedMap } from '@libp2p/utils/tracked-map'
9
+ import { anySignal } from 'any-signal'
10
+ import { pbStream } from 'it-protobuf-stream'
11
+ import { setMaxListeners } from 'main-event'
12
+ import { DEFAULT_CONNECTION_THRESHOLD, DIAL_DATA_CHUNK_SIZE, MAX_DIAL_DATA_BYTES, MAX_INBOUND_STREAMS, MAX_MESSAGE_SIZE, MAX_OUTBOUND_STREAMS, TIMEOUT } from './constants.ts'
13
+ import { DialBack, DialBackResponse, DialResponse, DialStatus, Message } from './pb/index.ts'
14
+ import { randomNumber } from './utils.ts'
15
+ import type { AutoNATv2Components, AutoNATv2ServiceInit } from './index.ts'
16
+ import type { Logger, Connection, Startable, AbortOptions, IncomingStreamData } from '@libp2p/interface'
17
+ import type { AddressType } from '@libp2p/interface-internal'
18
+ import type { PeerSet } from '@libp2p/peer-collections'
19
+ import type { Filter } from '@libp2p/utils/filters'
20
+ import type { RepeatingTask } from '@libp2p/utils/repeating-task'
21
+ import type { Multiaddr } from '@multiformats/multiaddr'
22
+
23
+ // if more than 3 peers manage to dial us on what we believe to be our external
24
+ // IP then we are convinced that it is, in fact, our external IP
25
+ // https://github.com/libp2p/specs/blob/master/autonat/autonat-v1.md#autonat-protocol
26
+ const REQUIRED_SUCCESSFUL_DIALS = 4
27
+ const REQUIRED_FAILED_DIALS = 8
28
+
29
+ interface DialResults {
30
+ /**
31
+ * The address being tested
32
+ */
33
+ multiaddr: Multiaddr
34
+
35
+ /**
36
+ * The number of successful dials from peers
37
+ */
38
+ success: number
39
+
40
+ /**
41
+ * The number of dial failures from peers
42
+ */
43
+ failure: number
44
+
45
+ /**
46
+ * For the multiaddr corresponding the the string key of the `dialResults`
47
+ * map, these are the IP segments that a successful dial result has been
48
+ * received from
49
+ */
50
+ networkSegments: string[]
51
+
52
+ /**
53
+ * Ensure that the same peer id can't verify multiple times
54
+ */
55
+ verifyingPeers: PeerSet
56
+
57
+ /**
58
+ * Updated when this address is verified or failed
59
+ */
60
+ result?: boolean
61
+
62
+ /**
63
+ * The type of address
64
+ */
65
+ type: AddressType
66
+
67
+ /**
68
+ * The last time the address was verified
69
+ */
70
+ lastVerified?: number
71
+ }
72
+
73
+ export interface AutoNATv2ClientInit extends AutoNATv2ServiceInit {
74
+ dialRequestProtocol: string
75
+ dialBackProtocol: string
76
+ maxDialDataBytes?: bigint
77
+ dialDataChunkSize?: number
78
+ }
79
+
80
+ export class AutoNATv2Client implements Startable {
81
+ private readonly components: AutoNATv2Components
82
+ private readonly dialRequestProtocol: string
83
+ private readonly dialBackProtocol: string
84
+ private readonly timeout: number
85
+ private readonly maxInboundStreams: number
86
+ private readonly maxOutboundStreams: number
87
+ private readonly maxMessageSize: number
88
+ private readonly maxDialDataBytes: bigint
89
+ private readonly dialDataChunkSize: number
90
+ private started: boolean
91
+ private readonly log: Logger
92
+ private topologyId?: string
93
+ private readonly dialResults: Map<string, DialResults>
94
+ private readonly findPeers: RepeatingTask
95
+ private readonly addressFilter: Filter
96
+ private readonly connectionThreshold: number
97
+ private readonly queue: PeerQueue
98
+ private readonly nonces: Set<bigint>
99
+
100
+ constructor (components: AutoNATv2Components, init: AutoNATv2ClientInit) {
101
+ this.components = components
102
+ this.log = components.logger.forComponent('libp2p:auto-nat-v2:client')
103
+ this.started = false
104
+ this.dialRequestProtocol = init.dialRequestProtocol
105
+ this.dialBackProtocol = init.dialBackProtocol
106
+ this.timeout = init.timeout ?? TIMEOUT
107
+ this.maxInboundStreams = init.maxInboundStreams ?? MAX_INBOUND_STREAMS
108
+ this.maxOutboundStreams = init.maxOutboundStreams ?? MAX_OUTBOUND_STREAMS
109
+ this.connectionThreshold = init.connectionThreshold ?? DEFAULT_CONNECTION_THRESHOLD
110
+ this.maxMessageSize = init.maxMessageSize ?? MAX_MESSAGE_SIZE
111
+ this.dialResults = trackedMap({
112
+ name: 'libp2p_autonat_v2_dial_results',
113
+ metrics: components.metrics
114
+ })
115
+ this.findPeers = repeatingTask(this.findRandomPeers.bind(this), 60_000)
116
+ this.addressFilter = createScalableCuckooFilter(1024)
117
+ this.queue = new PeerQueue({
118
+ concurrency: 3,
119
+ maxSize: 50
120
+ })
121
+ this.maxDialDataBytes = init.maxDialDataBytes ?? MAX_DIAL_DATA_BYTES
122
+ this.dialDataChunkSize = init.dialDataChunkSize ?? DIAL_DATA_CHUNK_SIZE
123
+
124
+ this.nonces = new Set()
125
+ }
126
+
127
+ readonly [Symbol.toStringTag] = '@libp2p/autonat-v2'
128
+
129
+ readonly [serviceCapabilities]: string[] = [
130
+ '@libp2p/autonat'
131
+ ]
132
+
133
+ get [serviceDependencies] (): string[] {
134
+ return [
135
+ '@libp2p/identify'
136
+ ]
137
+ }
138
+
139
+ isStarted (): boolean {
140
+ return this.started
141
+ }
142
+
143
+ async start (): Promise<void> {
144
+ if (this.started) {
145
+ return
146
+ }
147
+
148
+ this.topologyId = await this.components.registrar.register(this.dialRequestProtocol, {
149
+ onConnect: (peerId, connection) => {
150
+ this.verifyExternalAddresses(connection)
151
+ .catch(err => {
152
+ this.log.error('could not verify addresses - %e', err)
153
+ })
154
+ }
155
+ })
156
+
157
+ await this.components.registrar.handle(this.dialBackProtocol, (data) => {
158
+ void this.handleDialBackStream(data)
159
+ .catch(err => {
160
+ this.log.error('error handling incoming autonat stream - %e', err)
161
+ })
162
+ }, {
163
+ maxInboundStreams: this.maxInboundStreams,
164
+ maxOutboundStreams: this.maxOutboundStreams
165
+ })
166
+
167
+ this.findPeers.start()
168
+ this.started = true
169
+ }
170
+
171
+ async stop (): Promise<void> {
172
+ await this.components.registrar.unhandle(this.dialRequestProtocol)
173
+ await this.components.registrar.unhandle(this.dialBackProtocol)
174
+
175
+ if (this.topologyId != null) {
176
+ await this.components.registrar.unhandle(this.topologyId)
177
+ }
178
+
179
+ this.dialResults.clear()
180
+ this.findPeers.stop()
181
+ this.started = false
182
+ }
183
+
184
+ private allAddressesAreVerified (): boolean {
185
+ return this.components.addressManager.getAddressesWithMetadata().every(addr => {
186
+ if (addr.expires > Date.now()) {
187
+ // ignore any unverified addresses within their TTL
188
+ return true
189
+ }
190
+
191
+ return addr.verified
192
+ })
193
+ }
194
+
195
+ async findRandomPeers (options?: AbortOptions): Promise<void> {
196
+ // skip if all addresses are verified
197
+ if (this.allAddressesAreVerified()) {
198
+ return
199
+ }
200
+
201
+ const signal = anySignal([
202
+ AbortSignal.timeout(10_000),
203
+ options?.signal
204
+ ])
205
+
206
+ // spend a few seconds finding random peers - dial them which will run
207
+ // identify to trigger the topology callbacks and run AutoNAT
208
+ try {
209
+ this.log('starting random walk to find peers to run AutoNAT')
210
+
211
+ for await (const peer of this.components.randomWalk.walk({ signal })) {
212
+ if (!(await this.components.connectionManager.isDialable(peer.multiaddrs))) {
213
+ this.log.trace('random peer %p was not dialable %s', peer.id, peer.multiaddrs.map(ma => ma.toString()).join(', '))
214
+
215
+ // skip peers we can't dial
216
+ continue
217
+ }
218
+
219
+ try {
220
+ this.log.trace('dial random peer %p', peer.id)
221
+ await this.components.connectionManager.openConnection(peer.multiaddrs, {
222
+ signal
223
+ })
224
+ } catch {}
225
+
226
+ if (this.allAddressesAreVerified()) {
227
+ this.log('stopping random walk, all addresses are verified')
228
+ return
229
+ }
230
+
231
+ if (!this.hasConnectionCapacity()) {
232
+ this.log('stopping random walk, too close to max connections')
233
+ return
234
+ }
235
+ }
236
+ } catch {}
237
+ }
238
+
239
+ /**
240
+ * Handle an incoming AutoNAT request
241
+ */
242
+ async handleDialBackStream (data: IncomingStreamData): Promise<void> {
243
+ const signal = AbortSignal.timeout(this.timeout)
244
+ setMaxListeners(Infinity, signal)
245
+
246
+ const messages = pbStream(data.stream, {
247
+ maxDataLength: this.maxMessageSize
248
+ })
249
+
250
+ try {
251
+ const message = await messages.read(DialBack, {
252
+ signal
253
+ })
254
+
255
+ // TODO: need to verify that the incoming address is the one we asked the
256
+ // peer to dial us on
257
+ if (!this.nonces.has(message.nonce)) {
258
+ throw new ProtocolError('No matching dial found for nonce value')
259
+ }
260
+
261
+ this.nonces.delete(message.nonce)
262
+
263
+ await messages.write({
264
+ status: DialBackResponse.DialBackStatus.OK
265
+ }, DialBackResponse)
266
+
267
+ await data.stream.close({
268
+ signal
269
+ })
270
+ } catch (err: any) {
271
+ this.log.error('error handling incoming dial back stream - %e', err)
272
+ data.stream.abort(err)
273
+ }
274
+ }
275
+
276
+ private getUnverifiedMultiaddrs (segment: string, supportsIPv6: boolean): DialResults[] {
277
+ const addrs = this.components.addressManager.getAddressesWithMetadata()
278
+ .sort((a, b) => {
279
+ // sort addresses, de-prioritize observed addresses
280
+ if (a.type === 'observed' && b.type !== 'observed') {
281
+ return 1
282
+ }
283
+
284
+ if (b.type === 'observed' && a.type !== 'observed') {
285
+ return -1
286
+ }
287
+
288
+ return 0
289
+ })
290
+ .filter(addr => {
291
+ const expired = addr.expires < Date.now()
292
+
293
+ if (!expired) {
294
+ // skip verified/non-verified addresses within their TTL
295
+ return false
296
+ }
297
+
298
+ const options = addr.multiaddr.toOptions()
299
+
300
+ if (options.family === 6) {
301
+ // do not send IPv6 addresses to peers without IPv6 addresses
302
+ if (!supportsIPv6) {
303
+ return false
304
+ }
305
+
306
+ if (!isGlobalUnicast(addr.multiaddr)) {
307
+ // skip non-globally routable addresses
308
+ return false
309
+ }
310
+ }
311
+
312
+ if (isPrivate(addr.multiaddr)) {
313
+ // skip private addresses
314
+ return false
315
+ }
316
+
317
+ return true
318
+ })
319
+
320
+ const output: DialResults[] = []
321
+
322
+ for (const addr of addrs) {
323
+ const addrString = addr.multiaddr.toString()
324
+ let results = this.dialResults.get(addrString)
325
+
326
+ if (results != null) {
327
+ if (results.networkSegments.includes(segment)) {
328
+ this.log.trace('%a already has a network segment result from %s', results.multiaddr, segment)
329
+ // skip this address if we already have a dial result from the
330
+ // network segment the peer is in
331
+ continue
332
+ }
333
+ }
334
+
335
+ // will include this multiaddr, ensure we have a results object
336
+ if (results == null) {
337
+ const needsRevalidating = addr.expires < Date.now()
338
+
339
+ // allow re-validating addresses that worked previously
340
+ if (needsRevalidating) {
341
+ this.addressFilter.remove?.(addrString)
342
+ }
343
+
344
+ if (this.addressFilter.has(addrString)) {
345
+ continue
346
+ }
347
+
348
+ // only try to validate the address once
349
+ this.addressFilter.add(addrString)
350
+
351
+ this.log.trace('creating dial result %s %s', needsRevalidating ? 'to revalidate' : 'for', addrString)
352
+ results = {
353
+ multiaddr: addr.multiaddr,
354
+ success: 0,
355
+ failure: 0,
356
+ networkSegments: [],
357
+ verifyingPeers: peerSet(),
358
+ type: addr.type,
359
+ lastVerified: addr.lastVerified
360
+ }
361
+
362
+ this.dialResults.set(addrString, results)
363
+ }
364
+
365
+ output.push(results)
366
+ }
367
+
368
+ return output
369
+ }
370
+
371
+ /**
372
+ * Removes any multiaddr result objects created for old multiaddrs that we are
373
+ * no longer waiting on
374
+ */
375
+ private removeOutdatedMultiaddrResults (): void {
376
+ const unverifiedMultiaddrs = new Set(this.components.addressManager.getAddressesWithMetadata()
377
+ .filter(({ expires }) => {
378
+ if (expires < Date.now()) {
379
+ return true
380
+ }
381
+
382
+ return false
383
+ })
384
+ .map(({ multiaddr }) => multiaddr.toString())
385
+ )
386
+
387
+ for (const multiaddr of this.dialResults.keys()) {
388
+ if (!unverifiedMultiaddrs.has(multiaddr)) {
389
+ this.log.trace('remove results for %a', multiaddr)
390
+ this.dialResults.delete(multiaddr)
391
+ }
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Our multicodec topology noticed a new peer that supports autonat
397
+ */
398
+ async verifyExternalAddresses (connection: Connection): Promise<void> {
399
+ // do nothing if we are not running
400
+ if (!this.isStarted()) {
401
+ return
402
+ }
403
+
404
+ // perform cleanup
405
+ this.removeOutdatedMultiaddrResults()
406
+
407
+ const peer = await this.components.peerStore.get(connection.remotePeer)
408
+
409
+ // if the remote peer has IPv6 addresses, we can probably send them an IPv6
410
+ // address to verify, otherwise only send them IPv4 addresses
411
+ const supportsIPv6 = peer.addresses.some(({ multiaddr }) => {
412
+ return multiaddr.toOptions().family === 6
413
+ })
414
+
415
+ // get multiaddrs this peer is eligible to verify
416
+ const segment = this.getNetworkSegment(connection.remoteAddr)
417
+ const results = this.getUnverifiedMultiaddrs(segment, supportsIPv6)
418
+
419
+ if (results.length === 0) {
420
+ return
421
+ }
422
+
423
+ if (!this.hasConnectionCapacity()) {
424
+ // we are near the max connection limit - any dial attempts from remote
425
+ // peers may be rejected which will get flagged as false dial errors and
426
+ // lead us to un-verify an otherwise reachable address
427
+
428
+ if (results[0]?.lastVerified != null) {
429
+ this.log('automatically re-verifying %a because we are too close to the connection limit', results[0].multiaddr)
430
+ this.confirmAddress(results[0])
431
+ } else {
432
+ this.log('skipping verifying %a because we are too close to the connection limit', results[0]?.multiaddr)
433
+ }
434
+
435
+ return
436
+ }
437
+
438
+ this.queue.add(async (options: AbortOptions) => {
439
+ const signal = anySignal([options.signal, AbortSignal.timeout(this.timeout)])
440
+ const nonce = BigInt(randomNumber(0, Number.MAX_SAFE_INTEGER))
441
+ this.nonces.add(nonce)
442
+
443
+ try {
444
+ await this.askPeerToVerify(connection, segment, nonce, options)
445
+ } finally {
446
+ signal.clear()
447
+ this.nonces.delete(nonce)
448
+ }
449
+ }, {
450
+ peerId: connection.remotePeer
451
+ })
452
+ .catch(err => {
453
+ this.log.error('error from %p verifying addresses - %e', connection.remotePeer, err)
454
+ })
455
+ }
456
+
457
+ private async askPeerToVerify (connection: Connection, segment: string, nonce: bigint, options: AbortOptions): Promise<void> {
458
+ const unverifiedAddresses = [...this.dialResults.values()]
459
+ .filter(entry => entry.result == null)
460
+ .map(entry => entry.multiaddr)
461
+
462
+ if (unverifiedAddresses.length === 0) {
463
+ // no unverified addresses
464
+ this.queue.clear()
465
+ return
466
+ }
467
+
468
+ this.log.trace('asking %p to verify multiaddrs %s', connection.remotePeer, unverifiedAddresses)
469
+
470
+ const stream = await connection.newStream(this.dialRequestProtocol, options)
471
+
472
+ try {
473
+ const messages = pbStream(stream).pb(Message)
474
+ await messages.write({
475
+ dialRequest: {
476
+ addrs: unverifiedAddresses.map(ma => ma.bytes),
477
+ nonce
478
+ }
479
+ }, options)
480
+
481
+ while (true) {
482
+ let response = await messages.read(options)
483
+
484
+ if (response.dialDataRequest != null) {
485
+ if (response.dialDataRequest.numBytes > this.maxDialDataBytes) {
486
+ this.log('too many dial data byte requested by %p - %s/%s', connection.remotePeer, response.dialDataRequest.numBytes, this.maxDialDataBytes)
487
+ continue
488
+ }
489
+
490
+ this.log('sending %d bytes to %p as anti-amplification attack protection', response.dialDataRequest.numBytes, connection.remotePeer)
491
+
492
+ const buf = new Uint8Array(this.dialDataChunkSize)
493
+ const bufSize = BigInt(this.dialDataChunkSize)
494
+
495
+ for (let i = 0n; i < response.dialDataRequest.numBytes; i += bufSize) {
496
+ await messages.write({
497
+ dialDataResponse: {
498
+ data: buf
499
+ }
500
+ }, options)
501
+ }
502
+
503
+ response = await messages.read(options)
504
+ }
505
+
506
+ if (response.dialResponse == null) {
507
+ this.log('invalid autonat response from %p - %j', connection.remotePeer, response)
508
+ return
509
+ }
510
+
511
+ const status = response.dialResponse.status
512
+
513
+ if (status !== DialResponse.ResponseStatus.OK) {
514
+ return
515
+ }
516
+
517
+ const dialed = unverifiedAddresses[response.dialResponse.addrIdx]
518
+
519
+ if (dialed == null) {
520
+ this.log.trace('peer dialed unknown address')
521
+ continue
522
+ }
523
+
524
+ const results = this.dialResults.get(dialed.toString())
525
+
526
+ if (results == null) {
527
+ this.log.trace('peer reported %a as %s but there is no result object', dialed, response.dialResponse.status)
528
+ continue
529
+ }
530
+
531
+ if (results.networkSegments.includes(segment)) {
532
+ this.log.trace('%a results already included network segment %s', dialed, segment)
533
+ continue
534
+ }
535
+
536
+ if (results.result != null) {
537
+ this.log.trace('already resolved result for %a, ignoring response from', dialed, connection.remotePeer)
538
+ continue
539
+ }
540
+
541
+ if (results.verifyingPeers.has(connection.remotePeer)) {
542
+ this.log.trace('peer %p has already verified %a, ignoring response', connection.remotePeer, dialed)
543
+ continue
544
+ }
545
+
546
+ results.verifyingPeers.add(connection.remotePeer)
547
+ results.networkSegments.push(segment)
548
+
549
+ if (response.dialResponse.dialStatus === DialStatus.OK) {
550
+ this.log.trace('%p dialed %a successfully', connection.remotePeer, results.multiaddr)
551
+
552
+ results.success++
553
+
554
+ // observed addresses require more confirmations
555
+ if (results.type !== 'observed') {
556
+ this.confirmAddress(results)
557
+ continue
558
+ }
559
+ } else if (response.dialResponse.dialStatus === DialStatus.E_DIAL_ERROR) {
560
+ this.log.trace('%p could not dial %a', connection.remotePeer, results.multiaddr)
561
+ // the address was not dialable (e.g. not public)
562
+ results.failure++
563
+ } else if (response.dialResponse.dialStatus === DialStatus.E_DIAL_BACK_ERROR) {
564
+ this.log.trace('%p saw error while dialing %a', connection.remotePeer, results.multiaddr)
565
+ // the address was dialable but an error occurred during the dial back
566
+ continue
567
+ }
568
+
569
+ this.log('%a success %d failure %d', results.multiaddr, results.success, results.failure)
570
+
571
+ if (results.success === REQUIRED_SUCCESSFUL_DIALS) {
572
+ this.confirmAddress(results)
573
+ }
574
+
575
+ if (results.failure === REQUIRED_FAILED_DIALS) {
576
+ this.unconfirmAddress(results)
577
+ }
578
+ }
579
+ } finally {
580
+ try {
581
+ await stream.close(options)
582
+ } catch (err: any) {
583
+ stream.abort(err)
584
+ }
585
+ }
586
+ }
587
+
588
+ private hasConnectionCapacity (): boolean {
589
+ const connections = this.components.connectionManager.getConnections()
590
+ const currentConnectionCount = connections.length
591
+ const maxConnections = this.components.connectionManager.getMaxConnections()
592
+
593
+ return ((currentConnectionCount / maxConnections) * 100) < this.connectionThreshold
594
+ }
595
+
596
+ private confirmAddress (results: DialResults): void {
597
+ // we are now convinced
598
+ this.log('%s address %a is externally dialable', results.type, results.multiaddr)
599
+ this.components.addressManager.confirmObservedAddr(results.multiaddr)
600
+ this.dialResults.delete(results.multiaddr.toString())
601
+
602
+ // abort & remove any outstanding verification jobs for this multiaddr
603
+ results.result = true
604
+ }
605
+
606
+ private unconfirmAddress (results: DialResults): void {
607
+ // we are now unconvinced
608
+ this.log('%s address %a is not externally dialable', results.type, results.multiaddr)
609
+ this.components.addressManager.removeObservedAddr(results.multiaddr)
610
+ this.dialResults.delete(results.multiaddr.toString())
611
+
612
+ // abort & remove any outstanding verification jobs for this multiaddr
613
+ results.result = false
614
+ }
615
+
616
+ private getNetworkSegment (ma: Multiaddr): string {
617
+ // make sure we use different network segments
618
+ const options = ma.toOptions()
619
+
620
+ if (options.family === 4) {
621
+ const octets = options.host.split('.')
622
+ return octets[0].padStart(3, '0')
623
+ }
624
+
625
+ const octets = options.host.split(':')
626
+ return octets[0].padStart(4, '0')
627
+ }
628
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The prefix to use in the protocol
3
+ */
4
+ export const PROTOCOL_PREFIX = 'libp2p'
5
+
6
+ /**
7
+ * The name to use in the protocol
8
+ */
9
+ export const PROTOCOL_NAME = 'autonat'
10
+
11
+ /**
12
+ * The version to use in the protocol
13
+ */
14
+ export const PROTOCOL_VERSION = '2'
15
+ export const TIMEOUT = 30_000
16
+ export const MAX_INBOUND_STREAMS = 2
17
+ export const MAX_OUTBOUND_STREAMS = 20
18
+ export const DEFAULT_CONNECTION_THRESHOLD = 80
19
+ export const MAX_MESSAGE_SIZE = 8192
20
+
21
+ export const DIAL_REQUEST = 'dial-request'
22
+ export const DIAL_BACK = 'dial-back'
23
+ export const MAX_DIAL_DATA_BYTES = 200n * 1024n
24
+ export const DIAL_DATA_CHUNK_SIZE = 4096