UNPKG

@oddjs/odd

Version:
169 lines (147 loc) 5.32 kB
import * as Uint8arrays from "uint8arrays" import type { CID } from "multiformats/cid" import * as Basic from "../basic.js" import * as Check from "./types/check.js" import * as Crypto from "../../../components/crypto/implementation.js" import * as Depot from "../../../components/depot/implementation.js" import * as Namefilter from "./namefilter.js" import { BareNameFilter, PrivateName } from "./namefilter.js" import { DecryptedNode, PrivateAddResult, Revision } from "./types.js" import { Maybe, decodeCID } from "../../../common/index.js" import MMPT from "./mmpt.js" export const addNode = async ( depot: Depot.Implementation, crypto: Crypto.Implementation, mmpt: MMPT, node: DecryptedNode, key: Uint8Array ): Promise<PrivateAddResult> => { const { cid, size } = await Basic.putEncryptedFile(depot, crypto, node, key) const filter = await Namefilter.addRevision(crypto, node.bareNameFilter, key, node.revision) const name = await Namefilter.toPrivateName(crypto, filter) await mmpt.add(name, cid) // if the node is a file, we also add the content to the MMPT if (Check.isPrivateFileInfo(node)) { const key = Uint8arrays.fromString(node.key, "base64pad") const contentBareFilter = await Namefilter.addToBare(crypto, node.bareNameFilter, Namefilter.legacyEncodingMistake(key, "base64pad")) const contentFilter = await Namefilter.addRevision(crypto, contentBareFilter, key, node.revision) const contentName = await Namefilter.toPrivateName(crypto, contentFilter) await mmpt.add(contentName, decodeCID(node.content)) } const [ skeleton, isFile ] = Check.isPrivateFileInfo(node) ? [ {}, true ] : [ node.skeleton, false ] return { cid, name, key, size, isFile, skeleton } } export const readNode = async ( depot: Depot.Implementation, crypto: Crypto.Implementation, cid: CID, key: Uint8Array ): Promise<DecryptedNode> => { const contentBytes = await Basic.getEncryptedFile(depot, crypto, cid, key) const content = JSON.parse(Uint8arrays.toString(contentBytes, "utf8")) if (!Check.isDecryptedNode(content)) { throw new Error(`Could not parse a valid filesystem object: ${content}`) } return content } export const getByName = async ( depot: Depot.Implementation, crypto: Crypto.Implementation, mmpt: MMPT, name: PrivateName, key: Uint8Array ): Promise<Maybe<DecryptedNode>> => { const cid = await mmpt.get(name) if (cid === null) return null return getByCID(depot, crypto, cid, key) } export const getByCID = async ( depot: Depot.Implementation, crypto: Crypto.Implementation, cid: CID, key: Uint8Array ): Promise<DecryptedNode> => { return await readNode(depot, crypto, cid, key) } export const getLatestByName = async ( depot: Depot.Implementation, crypto: Crypto.Implementation, mmpt: MMPT, name: PrivateName, key: Uint8Array ): Promise<Maybe<DecryptedNode>> => { const cid = await mmpt.get(name) if (cid === null) return null return getLatestByCID(depot, crypto, mmpt, cid, key) } export const getLatestByCID = async ( depot: Depot.Implementation, crypto: Crypto.Implementation, mmpt: MMPT, cid: CID, key: Uint8Array ): Promise<DecryptedNode> => { const node = await getByCID(depot, crypto, cid, key) const latest = await findLatestRevision(crypto, mmpt, node.bareNameFilter, key, node.revision) return latest?.cid ? await getByCID(depot, crypto, decodeCID(latest?.cid), key) : node } export const getLatestByBareNameFilter = async ( depot: Depot.Implementation, crypto: Crypto.Implementation, mmpt: MMPT, bareName: BareNameFilter, key: Uint8Array ): Promise<Maybe<DecryptedNode>> => { const revisionFilter = await Namefilter.addRevision(crypto, bareName, key, 1) const name = await Namefilter.toPrivateName(crypto, revisionFilter) return getLatestByName(depot, crypto, mmpt, name, key) } export const findLatestRevision = async ( crypto: Crypto.Implementation, mmpt: MMPT, bareName: BareNameFilter, key: Uint8Array, lastKnownRevision: number ): Promise<Maybe<Revision>> => { // Exponential search forward let lowerBound = lastKnownRevision, upperBound = null let i = 0 let lastRevision: Maybe<Revision> = null while (upperBound === null) { const toCheck = lastKnownRevision + Math.pow(2, i) const thisRevision = await getRevision(crypto, mmpt, bareName, key, toCheck) if (thisRevision !== null) { lastRevision = thisRevision lowerBound = toCheck } else { upperBound = toCheck } i++ } // Binary search back while (lowerBound < (upperBound - 1)) { const midpoint = Math.floor((upperBound + lowerBound) / 2) const thisRevision = await getRevision(crypto, mmpt, bareName, key, midpoint) if (thisRevision !== null) { lastRevision = thisRevision lowerBound = midpoint } else { upperBound = midpoint } } return lastRevision } export const getRevision = async ( crypto: Crypto.Implementation, mmpt: MMPT, bareName: BareNameFilter, key: Uint8Array, revision: number ): Promise<Maybe<Revision>> => { const filter = await Namefilter.addRevision(crypto, bareName, key, revision) const name = await Namefilter.toPrivateName(crypto, filter) const cid = await mmpt.get(name) return cid ? { cid, name, number: revision } : null }