mirror of
https://github.com/fluencelabs/js-libp2p
synced 2025-03-31 14:51:05 +00:00
Updates all deps needed to support passing lists of byte arrays where they have been created from multiple input buffers. When reading multiplexed data, all messages arrive in length-prefixed buffers, which means the first few bytes tell the consumer how many bytes long next chunk will be. One length prefixed chunk can be delivered in several payloads from the underlying network transport. The first payload can also include the length prefix and some or all of the data, so we stitch these together in a `Uint8ArrayList` to avoid having to concatenate `Uint8Array`s together. Previously once we'd received enough bytes to satisfy the length prefix we'd concatenate the bytes together, but this is a potentially expensive operation where transports have small message sizes so instead just pass the `Uint8ArrayList` to the consumer and let them decide wether to concatenate or not as some consumers will be smart enough to operate on lists of `Uint8Array`s instead of always requiring a contiguous block of memory. BREAKING CHANGE: Streams are now `Duplex<Uint8ArrayList, Uint8ArrayList | Uint8Array>`
191 lines
6.3 KiB
TypeScript
191 lines
6.3 KiB
TypeScript
/* eslint-env mocha */
|
|
|
|
import { expect } from 'aegir/chai'
|
|
import { createLibp2pNode, Libp2pNode } from '../../src/libp2p.js'
|
|
import { TCP } from '@libp2p/tcp'
|
|
import { Mplex } from '@libp2p/mplex'
|
|
import { Plaintext } from '../../src/insecure/index.js'
|
|
import { createPeerId } from '../utils/creators/peer.js'
|
|
import { codes } from '../../src/errors.js'
|
|
import type { PeerId } from '@libp2p/interface-peer-id'
|
|
|
|
async function createNode (peerId: PeerId) {
|
|
return await createLibp2pNode({
|
|
peerId,
|
|
addresses: {
|
|
listen: ['/ip4/0.0.0.0/tcp/0']
|
|
},
|
|
transports: [
|
|
new TCP()
|
|
],
|
|
streamMuxers: [
|
|
new Mplex()
|
|
],
|
|
connectionEncryption: [
|
|
new Plaintext()
|
|
]
|
|
})
|
|
}
|
|
|
|
describe('Fetch', () => {
|
|
let sender: Libp2pNode
|
|
let receiver: Libp2pNode
|
|
const PREFIX_A = '/moduleA/'
|
|
const PREFIX_B = '/moduleB/'
|
|
const DATA_A = { foobar: 'hello world' }
|
|
const DATA_B = { foobar: 'goodnight moon' }
|
|
|
|
const generateLookupFunction = function (prefix: string, data: Record<string, string>) {
|
|
return async function (key: string): Promise<Uint8Array | null> {
|
|
key = key.slice(prefix.length) // strip prefix from key
|
|
const val = data[key]
|
|
if (val != null) {
|
|
return (new TextEncoder()).encode(val)
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
const peerIdA = await createPeerId()
|
|
const peerIdB = await createPeerId()
|
|
sender = await createNode(peerIdA)
|
|
receiver = await createNode(peerIdB)
|
|
|
|
await sender.start()
|
|
await receiver.start()
|
|
|
|
await Promise.all([
|
|
...sender.getMultiaddrs().map(async addr => await receiver.dial(addr)),
|
|
...receiver.getMultiaddrs().map(async addr => await sender.dial(addr))
|
|
])
|
|
})
|
|
|
|
afterEach(async () => {
|
|
receiver.fetchService.unregisterLookupFunction(PREFIX_A)
|
|
receiver.fetchService.unregisterLookupFunction(PREFIX_B)
|
|
|
|
await sender.stop()
|
|
await receiver.stop()
|
|
})
|
|
|
|
it('fetch key that exists in receivers datastore', async () => {
|
|
receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A))
|
|
|
|
const rawData = await sender.fetch(receiver.peerId, '/moduleA/foobar')
|
|
|
|
if (rawData == null) {
|
|
throw new Error('Value was not found')
|
|
}
|
|
|
|
const value = (new TextDecoder()).decode(rawData)
|
|
expect(value).to.equal('hello world')
|
|
})
|
|
|
|
it('Different lookups for different prefixes', async () => {
|
|
receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A))
|
|
receiver.fetchService.registerLookupFunction(PREFIX_B, generateLookupFunction(PREFIX_B, DATA_B))
|
|
|
|
const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar')
|
|
|
|
if (rawDataA == null) {
|
|
throw new Error('Value was not found')
|
|
}
|
|
|
|
const valueA = (new TextDecoder()).decode(rawDataA)
|
|
expect(valueA).to.equal('hello world')
|
|
|
|
// Different lookup functions can be registered on different prefixes, and have different
|
|
// values for the same key underneath the different prefix.
|
|
const rawDataB = await sender.fetch(receiver.peerId, '/moduleB/foobar')
|
|
|
|
if (rawDataB == null) {
|
|
throw new Error('Value was not found')
|
|
}
|
|
|
|
const valueB = (new TextDecoder()).decode(rawDataB)
|
|
expect(valueB).to.equal('goodnight moon')
|
|
})
|
|
|
|
it('fetch key that does not exist in receivers datastore', async () => {
|
|
receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A))
|
|
const result = await sender.fetch(receiver.peerId, '/moduleA/garbage')
|
|
|
|
expect(result).to.equal(null)
|
|
})
|
|
|
|
it('fetch key with unknown prefix throws error', async () => {
|
|
receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A))
|
|
|
|
await expect(sender.fetch(receiver.peerId, '/moduleUNKNOWN/foobar'))
|
|
.to.eventually.be.rejected.with.property('code', codes.ERR_INVALID_PARAMETERS)
|
|
})
|
|
|
|
it('registering multiple handlers for same prefix errors', async () => {
|
|
receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_A))
|
|
|
|
expect(() => receiver.fetchService.registerLookupFunction(PREFIX_A, generateLookupFunction(PREFIX_A, DATA_B)))
|
|
.to.throw().with.property('code', codes.ERR_KEY_ALREADY_EXISTS)
|
|
})
|
|
|
|
it('can unregister handler', async () => {
|
|
const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A)
|
|
receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction)
|
|
const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar')
|
|
|
|
if (rawDataA == null) {
|
|
throw new Error('Value was not found')
|
|
}
|
|
|
|
const valueA = (new TextDecoder()).decode(rawDataA)
|
|
expect(valueA).to.equal('hello world')
|
|
|
|
receiver.fetchService.unregisterLookupFunction(PREFIX_A, lookupFunction)
|
|
|
|
await expect(sender.fetch(receiver.peerId, '/moduleA/foobar'))
|
|
.to.eventually.be.rejectedWith(/No lookup function registered for key/)
|
|
})
|
|
|
|
it('can unregister all handlers', async () => {
|
|
const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A)
|
|
receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction)
|
|
const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar')
|
|
|
|
if (rawDataA == null) {
|
|
throw new Error('Value was not found')
|
|
}
|
|
|
|
const valueA = (new TextDecoder()).decode(rawDataA)
|
|
expect(valueA).to.equal('hello world')
|
|
|
|
receiver.fetchService.unregisterLookupFunction(PREFIX_A)
|
|
|
|
await expect(sender.fetch(receiver.peerId, '/moduleA/foobar'))
|
|
.to.eventually.be.rejectedWith(/No lookup function registered for key/)
|
|
})
|
|
|
|
it('does not unregister wrong handlers', async () => {
|
|
const lookupFunction = generateLookupFunction(PREFIX_A, DATA_A)
|
|
receiver.fetchService.registerLookupFunction(PREFIX_A, lookupFunction)
|
|
const rawDataA = await sender.fetch(receiver.peerId, '/moduleA/foobar')
|
|
|
|
if (rawDataA == null) {
|
|
throw new Error('Value was not found')
|
|
}
|
|
|
|
const valueA = (new TextDecoder()).decode(rawDataA)
|
|
expect(valueA).to.equal('hello world')
|
|
|
|
receiver.fetchService.unregisterLookupFunction(PREFIX_A, async () => { return null })
|
|
|
|
const rawDataB = await sender.fetch(receiver.peerId, '/moduleA/foobar')
|
|
|
|
if (rawDataB == null) {
|
|
throw new Error('Value was not found')
|
|
}
|
|
|
|
const valueB = (new TextDecoder()).decode(rawDataB)
|
|
expect(valueB).to.equal('hello world')
|
|
})
|
|
})
|