fission-suite/webnative

View on GitHub
src/fs/protocol/basic.ts

Summary

Maintainability
A
1 hr
Test Coverage
import * as DagCBOR from "@ipld/dag-cbor"
import * as DagPB from "@ipld/dag-pb"
import * as Uint8arrays from "uint8arrays"
import { CID } from "multiformats/cid"

import * as Blob from "../../common/blob.js"
import * as Crypto from "../../components/crypto/implementation.js"
import * as DAG from "../../dag/index.js"
import * as Depot from "../../components/depot/implementation.js"

import * as FsTypeCheck from "../types/check.js"
import * as Link from "../link.js"
import * as TypeCheck from "../../common/type-checks.js"
import { SimpleLinks, Links, SimpleLink, HardLink, SoftLink, BaseLink } from "../types.js"
import { SymmAlg } from "../../components/crypto/implementation.js"
import { decodeCID } from "../../common/index.js"


export const DEFAULT_AES_ALG = SymmAlg.AES_CTR


export const getFile = async (depot: Depot.Implementation, cid: CID): Promise<Uint8Array> => {
  return depot.getUnixFile(cid)
}

export const getEncryptedFile = async (depot: Depot.Implementation, crypto: Crypto.Implementation, cid: CID, key: Uint8Array): Promise<Uint8Array> => {
  const buf = await getFile(depot, cid)

  // NOTE: Somehow the try/catch is needed by the integration test with the CAR file.
  //       Maybe a mistake in the CAR file or how the file system code worked before?
  let withAlgorithm

  try {
    withAlgorithm = DagCBOR.decode(buf)
  } catch {
    // Not CBOR?
    return buf
  }

  if (!TypeCheck.hasProp(withAlgorithm, "alg") || !TypeCheck.hasProp(withAlgorithm, "cip") || !isSymmAlg(withAlgorithm.alg) || !ArrayBuffer.isView(withAlgorithm.cip)) {
    throw new Error(`Unexpected private block. Expected "alg" and "cip" field.`)
  }

  const alg = withAlgorithm.alg
  const cip = new Uint8Array(withAlgorithm.cip.buffer)
  const toDecode = await crypto.aes.decrypt(cip, key, alg)

  const decoded = DagCBOR.decode(toDecode)
  if (decoded instanceof Uint8Array) return decoded
  if (typeof decoded === "string") return Uint8arrays.fromString(decoded, "utf8")

  // Legacy
  return Uint8arrays.fromString(JSON.stringify(decoded), "utf8")
}

export const putFile = async (depot: Depot.Implementation, content: Uint8Array): Promise<Depot.PutResult> => {
  return depot.putChunked(content)
}

export const putEncryptedFile = async (depot: Depot.Implementation, crypto: Crypto.Implementation, content: Uint8Array | Object, key: Uint8Array): Promise<Depot.PutResult> => {
  const normalized = TypeCheck.isBlob(content) ? await Blob.toUint8Array(content) : content
  const encoded = DagCBOR.encode(normalized)

  const alg = SymmAlg.AES_GCM
  const cip = await crypto.aes.encrypt(encoded, key, alg)
  const toAdd = DagCBOR.encode({ alg, cip })

  return putFile(depot, toAdd)
}

export const getSimpleLinks = async (depot: Depot.Implementation, cid: CID): Promise<SimpleLinks> => {
  const dagNode = await DAG.getPB(depot, cid)

  return Link.arrToMap(
    dagNode.Links.map(Link.fromDAGLink)
  )
}

export const getFileSystemLinks = async (depot: Depot.Implementation, cid: CID): Promise<Links> => {
  const topNode = await DAG.getPB(depot, cid)

  const links = await Promise.all(topNode.Links.map(async l => {
    const innerNode = await DAG.getPB(depot, l.Hash)
    const innerLinks = Link.arrToMap(innerNode.Links.map(Link.fromDAGLink))
    const isSoftLink = !!innerLinks[ "softLink" ]

    if (isSoftLink) {
      const a = await depot.getUnixFile(decodeCID(innerLinks[ "softLink" ].cid))
      const b = new TextDecoder().decode(a)
      return JSON.parse(b)
    }

    const f = await DagCBOR.decode(
      await getFile(
        depot,
        decodeCID(innerLinks[ "metadata" ].cid)
      )
    )

    return {
      ...Link.fromDAGLink(l),
      isFile: TypeCheck.hasProp(f, "isFile") ? f.isFile : false
    }
  }))

  return Link.arrToMap(links)
}

export const putLinks = async (
  depot: Depot.Implementation,
  links: Links | SimpleLinks
): Promise<Depot.PutResult> => {
  const dagLinks = Object
    .values(links)
    .reduce(async (acc: Promise<DagPB.PBLink[]>, l: HardLink | SoftLink | BaseLink | SimpleLink) => {
      const arr = await acc

      if (FsTypeCheck.isSoftLink(l)) {
        const softLink = await depot.putChunked(
          Uint8arrays.fromString(
            JSON.stringify(l),
            "utf8"
          )
        )

        const dagNodeCID = await DAG.putPB(depot, [
          DagPB.createLink("softLink", softLink.size, softLink.cid)
        ])

        const dagNodeSize = await depot.size(dagNodeCID)

        return [ ...arr, DagPB.createLink(l.name, dagNodeSize, dagNodeCID) ]
      } else if (TypeCheck.hasProp(l, "Hash") && l.Hash) {
        return [ ...arr, l as DagPB.PBLink ]
      } else if (FsTypeCheck.isSimpleLink(l)) {
        return [ ...arr, Link.toDAGLink(l) ]
      } else {
        return arr
      }
    }, Promise.resolve([]))



  const cid = await DAG.putPB(
    depot,
    await dagLinks
  )

  return {
    cid,
    isFile: false,
    size: await depot.size(cid)
  }
}



// ㊙️


function isSymmAlg(alg: unknown): alg is SymmAlg {
  return TypeCheck.isString(alg) && (Object.values(SymmAlg) as string[]).includes(alg as string)
}