OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-protocol / schema-codecs.js

126 lines · javascript ref:
import * as clientInternalSchemas from './client-internal-schemas.js'
import * as durabilitySchemas from './durability-schemas.js'
import * as evidenceSchemas from './evidence-schemas.js'
import * as resultBindingSchemas from './result-binding.js'
import * as wireSchemas from './schemas.js'
import { decodeDispatchFrame, encodeDispatchFrame } from './dispatch.js'
import { decodeOuterEnvelope, encodeOuterEnvelope } from './outer-envelope.js'
import {
  ABI_STATUS,
  IMPLEMENTED_SCHEMAS,
  SCHEMA_CATEGORY,
  schemaCategory
} from './registry.js'

const sources = Object.freeze({
  ...wireSchemas,
  ...resultBindingSchemas,
  ...durabilitySchemas,
  ...evidenceSchemas,
  ...clientInternalSchemas,
  blindDispatchFrameV1: Object.freeze({ encode: encodeDispatchFrame, decode: decodeDispatchFrame }),
  blindOuterEnvelopeV1: Object.freeze({ encode: encodeOuterEnvelope, decode: decodeOuterEnvelope })
})

function exportName (schemaName) {
  return schemaName[0].toLowerCase() + schemaName.slice(1)
}

const codecs = {}
const missingCodecNames = []
const invalidCodecNames = []

for (const schemaName of ABI_STATUS.protocolOwnedRequiredSchemaNames) {
  const codec = sources[exportName(schemaName)]
  if (codec == null) {
    missingCodecNames.push(schemaName)
    continue
  }
  const contextualFactory = schemaName === 'CellBlobV1' && typeof codec === 'function'
  const customFraming = (schemaName === 'BlindDispatchFrameV1' || schemaName === 'BlindOuterEnvelopeV1') &&
    typeof codec.encode === 'function' && typeof codec.decode === 'function'
  if (!contextualFactory && !customFraming && (typeof codec !== 'object' || typeof codec.preencode !== 'function' ||
      typeof codec.encode !== 'function' || typeof codec.decode !== 'function')) {
    invalidCodecNames.push(schemaName)
    continue
  }
  codecs[schemaName] = codec
}

export const EXECUTABLE_SCHEMA_CODECS = Object.freeze(codecs)

const wireRequiredCodecNames = ABI_STATUS.wireRequiredSchemaNames
const wireMissingCodecNames = wireRequiredCodecNames.filter(name => !Object.hasOwn(codecs, name))
const wireInvalidCodecNames = invalidCodecNames.filter(name => wireRequiredCodecNames.includes(name))
const wireCodecNames = Object.keys(codecs).filter(name => schemaCategory(name) === SCHEMA_CATEGORY.WIRE)

export const WIRE_EXECUTABLE_SCHEMA_CODEC_STATUS = Object.freeze({
  requiredCodecCount: wireRequiredCodecNames.length,
  executableCodecCount: wireCodecNames.length,
  missingCodecNames: Object.freeze(wireMissingCodecNames),
  invalidCodecNames: Object.freeze(wireInvalidCodecNames),
  nonWireLeakNames: Object.freeze(wireCodecNames.filter(name =>
    !wireRequiredCodecNames.includes(name))),
  complete: wireMissingCodecNames.length === 0 && wireInvalidCodecNames.length === 0 &&
    wireCodecNames.length === wireRequiredCodecNames.length
})

export const EXECUTABLE_SCHEMA_CODEC_STATUS = Object.freeze({
  requiredCodecCount: ABI_STATUS.protocolOwnedRequiredSchemaNames.length,
  executableCodecCount: Object.keys(codecs).length,
  missingCodecNames: Object.freeze(missingCodecNames),
  invalidCodecNames: Object.freeze(invalidCodecNames),
  privateIpcCodecNames: Object.freeze(ABI_STATUS.externallyOwnedSchemaNames),
  privateIpcLeakNames: Object.freeze(Object.keys(codecs).filter(name => schemaCategory(name) === SCHEMA_CATEGORY.PRIVATE_IPC)),
  complete: missingCodecNames.length === 0 && invalidCodecNames.length === 0 &&
    Object.keys(codecs).length === ABI_STATUS.protocolOwnedRequiredSchemaNames.length
})

export function validateExecutableSchemaFieldMetadata (metadataRows, executableCodecs) {
  const metadataByName = new Map(metadataRows.map(row => [row.name, row]))
  const checkedSchemaNames = []
  const skippedSchemaNames = []
  const mismatches = []

  for (const [schemaName, codec] of Object.entries(executableCodecs)) {
    const metadata = metadataByName.get(schemaName)
    const metadataFields = metadata?.fields?.map(([name]) => name)
    const executableFields = codec?.schemaFields
    const placeholder = metadataFields?.length === 1 && metadataFields[0] === 'canonicalDefinition'
    if (!Array.isArray(metadataFields) || !Array.isArray(executableFields) || placeholder) {
      skippedSchemaNames.push(schemaName)
      continue
    }
    checkedSchemaNames.push(schemaName)
    if ((codec.schemaName != null && codec.schemaName !== schemaName) ||
        metadataFields.length !== executableFields.length ||
        metadataFields.some((field, index) => field !== executableFields[index])) {
      mismatches.push(Object.freeze({
        schemaName,
        executableSchemaName: codec.schemaName,
        metadataFields: Object.freeze([...metadataFields]),
        executableFields: Object.freeze([...executableFields])
      }))
    }
  }

  return Object.freeze({
    checkedSchemaNames: Object.freeze(checkedSchemaNames),
    skippedSchemaNames: Object.freeze(skippedSchemaNames),
    mismatches: Object.freeze(mismatches),
    complete: mismatches.length === 0
  })
}

export const EXECUTABLE_SCHEMA_FIELD_STATUS = validateExecutableSchemaFieldMetadata(
  IMPLEMENTED_SCHEMAS,
  EXECUTABLE_SCHEMA_CODECS
)

if (!WIRE_EXECUTABLE_SCHEMA_CODEC_STATUS.complete ||
    WIRE_EXECUTABLE_SCHEMA_CODEC_STATUS.nonWireLeakNames.length !== 0 ||
    EXECUTABLE_SCHEMA_CODEC_STATUS.privateIpcLeakNames.length !== 0 ||
    !EXECUTABLE_SCHEMA_FIELD_STATUS.complete) {
  throw new Error(`executable schema codec catalog is incomplete, drifts from metadata, or crosses the PRIVATE_IPC ownership boundary: missing=${missingCodecNames.join(',')} invalid=${invalidCodecNames.join(',')} private=${EXECUTABLE_SCHEMA_CODEC_STATUS.privateIpcLeakNames.join(',')} fieldDrift=${EXECUTABLE_SCHEMA_FIELD_STATUS.mismatches.map(row => row.schemaName).join(',')}`)
}