@libp2p/peer-store
Version:
Stores information about peers libp2p knows on the network
197 lines • 7.35 kB
JavaScript
/* eslint-disable complexity */
import { publicKeyToProtobuf } from '@libp2p/crypto/keys';
import { InvalidParametersError } from '@libp2p/interface';
import { equals as uint8ArrayEquals } from 'uint8arrays/equals';
import { dedupeFilterAndSortAddresses } from './dedupe-addresses.js';
export async function toPeerPB(peerId, data, strategy, options) {
if (data == null) {
throw new InvalidParametersError('Invalid PeerData');
}
if (data.publicKey != null && peerId.publicKey != null && !data.publicKey.equals(peerId.publicKey)) {
throw new InvalidParametersError('publicKey bytes do not match peer id publicKey bytes');
}
const existingPeer = options.existingPeer?.peer;
if (existingPeer != null && !peerId.equals(existingPeer.id)) {
throw new InvalidParametersError('peer id did not match existing peer id');
}
let addresses = existingPeer?.addresses ?? [];
let protocols = new Set(existingPeer?.protocols ?? []);
let metadata = existingPeer?.metadata ?? new Map();
let tags = existingPeer?.tags ?? new Map();
let peerRecordEnvelope = existingPeer?.peerRecordEnvelope;
// when patching, we replace the original fields with passed values
if (strategy === 'patch') {
if (data.multiaddrs != null || data.addresses != null) {
addresses = [];
if (data.multiaddrs != null) {
addresses.push(...data.multiaddrs.map(multiaddr => ({
isCertified: false,
multiaddr
})));
}
if (data.addresses != null) {
addresses.push(...data.addresses);
}
}
if (data.protocols != null) {
protocols = new Set(data.protocols);
}
if (data.metadata != null) {
const metadataEntries = data.metadata instanceof Map ? [...data.metadata.entries()] : Object.entries(data.metadata);
metadata = createSortedMap(metadataEntries, {
validate: validateMetadata
});
}
if (data.tags != null) {
const tagsEntries = data.tags instanceof Map ? [...data.tags.entries()] : Object.entries(data.tags);
tags = createSortedMap(tagsEntries, {
validate: validateTag,
map: mapTag
});
}
if (data.peerRecordEnvelope != null) {
peerRecordEnvelope = data.peerRecordEnvelope;
}
}
// when merging, we join the original fields with passed values
if (strategy === 'merge') {
if (data.multiaddrs != null) {
addresses.push(...data.multiaddrs.map(multiaddr => ({
isCertified: false,
multiaddr
})));
}
if (data.addresses != null) {
addresses.push(...data.addresses);
}
if (data.protocols != null) {
protocols = new Set([...protocols, ...data.protocols]);
}
if (data.metadata != null) {
const metadataEntries = data.metadata instanceof Map ? [...data.metadata.entries()] : Object.entries(data.metadata);
for (const [key, value] of metadataEntries) {
if (value == null) {
metadata.delete(key);
}
else {
metadata.set(key, value);
}
}
metadata = createSortedMap([...metadata.entries()], {
validate: validateMetadata
});
}
if (data.tags != null) {
const tagsEntries = data.tags instanceof Map ? [...data.tags.entries()] : Object.entries(data.tags);
const mergedTags = new Map(tags);
for (const [key, value] of tagsEntries) {
if (value == null) {
mergedTags.delete(key);
}
else {
mergedTags.set(key, value);
}
}
tags = createSortedMap([...mergedTags.entries()], {
validate: validateTag,
map: mapTag
});
}
if (data.peerRecordEnvelope != null) {
peerRecordEnvelope = data.peerRecordEnvelope;
}
}
let publicKey;
if (existingPeer?.id.publicKey != null) {
publicKey = publicKeyToProtobuf(existingPeer.id.publicKey);
}
else if (data.publicKey != null) {
publicKey = publicKeyToProtobuf(data.publicKey);
}
else if (peerId.publicKey != null) {
publicKey = publicKeyToProtobuf(peerId.publicKey);
}
const output = {
addresses: await dedupeFilterAndSortAddresses(peerId, options.addressFilter ?? (async () => true), addresses, options.existingPeer?.peerPB.addresses),
protocols: [...protocols.values()].sort((a, b) => {
return a.localeCompare(b);
}),
metadata,
tags,
publicKey,
peerRecordEnvelope
};
// add observed addresses to multiaddrs
output.addresses.forEach(addr => {
addr.observed = options.existingPeer?.peerPB.addresses?.find(addr => uint8ArrayEquals(addr.multiaddr, addr.multiaddr))?.observed ?? Date.now();
});
// Ed25519 and secp256k1 have their public key embedded in them so no need to duplicate it
if (peerId.type !== 'RSA') {
delete output.publicKey;
}
return output;
}
/**
* In JS maps are ordered by insertion order so create a new map with the
* keys inserted in alphabetical order.
*/
function createSortedMap(entries, options) {
const output = new Map();
for (const [key, value] of entries) {
if (value == null) {
continue;
}
options.validate(key, value);
}
for (const [key, value] of entries.sort(([a], [b]) => {
return a.localeCompare(b);
})) {
if (value != null) {
output.set(key, options.map?.(key, value) ?? value);
}
}
return output;
}
function validateMetadata(key, value) {
if (typeof key !== 'string') {
throw new InvalidParametersError('Metadata key must be a string');
}
if (!(value instanceof Uint8Array)) {
throw new InvalidParametersError('Metadata value must be a Uint8Array');
}
}
function validateTag(key, tag) {
if (typeof key !== 'string') {
throw new InvalidParametersError('Tag name must be a string');
}
if (tag.value != null) {
if (parseInt(`${tag.value}`, 10) !== tag.value) {
throw new InvalidParametersError('Tag value must be an integer');
}
if (tag.value < 0 || tag.value > 100) {
throw new InvalidParametersError('Tag value must be between 0-100');
}
}
if (tag.ttl != null) {
if (parseInt(`${tag.ttl}`, 10) !== tag.ttl) {
throw new InvalidParametersError('Tag ttl must be an integer');
}
if (tag.ttl < 0) {
throw new InvalidParametersError('Tag ttl must be between greater than 0');
}
}
}
function mapTag(key, tag) {
let expiry;
if (tag.expiry != null) {
expiry = tag.expiry;
}
if (tag.ttl != null) {
expiry = BigInt(Date.now() + Number(tag.ttl));
}
return {
value: tag.value ?? 0,
expiry
};
}
//# sourceMappingURL=to-peer-pb.js.map