import { assert } from "node-opcua-assert" ; import { AttributeIds , BrowseDirection , makeResultMask , NodeClassMask } from "node-opcua-data-model" ; import { DataValue } from "node-opcua-data-value" ; import { make_debugLog , make_errorLog } from "node-opcua-debug" ; import { DataTypeFactory , EnumerationDefinitionSchema , FieldCategory , FieldInterfaceOptions , getBuildInType , StructuredTypeSchema , TypeDefinition } from "node-opcua-factory" ; import { NodeId , makeExpandedNodeId , resolveNodeId } from "node-opcua-nodeid" ; import { browseAll , BrowseDescriptionLike , IBasicSession } from "node-opcua-pseudo-session" ; import { StatusCodes } from "node-opcua-status-code" ; import { EnumDefinition , DataTypeDefinition , StructureDefinition , StructureType } from "node-opcua-types" ; import { ExtensionObject } from "node-opcua-extension-object" ; import { _findEncodings } from "./private/find_encodings" ; const debugLog = make_debugLog ( __filename ) ; const errorLog = make_errorLog ( __filename ) ; async function findSuperType ( session , dataTypeNodeId ) { const nodeToBrowse3 = { browseDirection : BrowseDirection . Inverse , includeSubtypes : false , nodeClassMask : NodeClassMask . DataType , nodeId : dataTypeNodeId , referenceTypeId : resolveNodeId ( "HasSubtype" ) , resultMask : makeResultMask ( "NodeId | ReferenceType | BrowseName | NodeClass" ) } ; const result3 = await browseAll ( session , nodeToBrowse3 ) ; if ( result3 . statusCode !== StatusCodes . Good ) { throw new Error ( "Cannot find superType for " + dataTypeNodeId . toString ( ) ) ; } result3 . references = result3 . references || [ ] ; if ( result3 . references . length !== 1 ) { console . log ( result3 . toString ( ) ) ; throw new Error ( "Invalid dataType with more than one superType " + dataTypeNodeId . toString ( ) ) ; } return result3 . references [ 0 ] . nodeId ; } async function findDataTypeCategory ( session , cache , dataTypeNodeId ) { const subTypeNodeId = await findSuperType ( session , dataTypeNodeId ) ; debugLog ( "subTypeNodeId  of " , dataTypeNodeId . toString ( ) , " is " , subTypeNodeId . toString ( ) ) ; const key = subTypeNodeId . toString ( ) ; if ( cache [ key ] ) { return cache [ key ] . category ; } let category ; if ( subTypeNodeId . namespace === 0 && subTypeNodeId . value <= 29 ) { switch ( subTypeNodeId . value ) { case 22 : category = FieldCategory . complex ; break ; case 29 : category = FieldCategory . enumeration ; break ; default : category = FieldCategory . basic ; break ; } return category ; } return await findDataTypeCategory ( session , cache , subTypeNodeId ) ; } async function findDataTypeBasicType ( session , cache , dataTypeNodeId ) { const subTypeNodeId = await findSuperType ( session , dataTypeNodeId ) ; debugLog ( "subTypeNodeId  of " , dataTypeNodeId . toString ( ) , " is " , subTypeNodeId . toString ( ) ) ; const key = subTypeNodeId . toString ( ) ; if ( cache [ key ] ) { return cache [ key ] . schema ; } if ( subTypeNodeId . namespace === 0 && subTypeNodeId . value < 29 ) { switch ( subTypeNodeId . value ) { case 22 : case 29 : throw new Error ( "Not expecting Structure or Enumeration" ) ; default : break ; } const nameDataValue = await session . read ( { attributeId : AttributeIds . BrowseName , nodeId : subTypeNodeId } ) ; const name = nameDataValue . value . value . name ! ; return getBuildInType ( name ) ; } return await findDataTypeBasicType ( session , cache , subTypeNodeId ) ; } export interface CacheForFieldResolution { fieldTypeName : string ; schema : TypeDefinition ; category : FieldCategory ; } async function readBrowseName ( session , nodeId ) { const dataValue = await session . read ( { nodeId , attributeId : AttributeIds . BrowseName } ) ; if ( dataValue . statusCode !== StatusCodes . Good ) { const message = "cannot extract BrowseName of nodeId = " + nodeId . toString ( ) + " statusCode = " + dataValue . statusCode . toString ( ) ; debugLog ( message ) ; throw new Error ( message ) ; } return dataValue . value ! . value . name ; } async function resolve2 ( session , dataTypeNodeId , dataTypeFactory , fieldTypeName , cache ) { const category = await findDataTypeCategory ( session , cache , dataTypeNodeId ) ; debugLog ( " type " + fieldTypeName + " has not been seen yet, let resolve it => (category = " , category , " )" ) ; let schema = undefined ; switch ( category ) { case FieldCategory . basic : schema = await findDataTypeBasicType ( session , cache , dataTypeNodeId ) ; if ( ! schema ) { errorLog ( "Cannot find basic type " + fieldTypeName ) ; } break ; default : case FieldCategory . enumeration : case FieldCategory . complex : { const dataTypeDefinitionDataValue = await session . read ( { attributeId : AttributeIds . DataTypeDefinition , nodeId : dataTypeNodeId } ) ; if ( dataTypeDefinitionDataValue . statusCode !== StatusCodes . Good ) { throw new Error ( " Cannot find dataType Definition ! with nodeId =" + dataTypeNodeId . toString ( ) ) ; } const definition = dataTypeDefinitionDataValue . value . value ; if ( category === FieldCategory . enumeration ) { if ( definition instanceof EnumDefinition ) { const e = new EnumerationDefinitionSchema ( { enumValues : definition . fields , name : fieldTypeName } ) ; dataTypeFactory . registerEnumeration ( e ) ; schema = e ; } } else { schema = await convertDataTypeDefinitionToStructureTypeSchema ( session , dataTypeNodeId , fieldTypeName , definition , dataTypeFactory , cache ) ; } } break ; } return { schema , category } ; } async function resolveFieldType ( session , dataTypeNodeId , dataTypeFactory , cache ) { if ( dataTypeNodeId . namespace === 0 && dataTypeNodeId . value === 22 ) { const category = FieldCategory . complex ; const fieldTypeName = "Structure" ; const schema = ExtensionObject . schema ; return { category , fieldTypeName , schema } ; } const key = dataTypeNodeId . toString ( ) ; const v = cache [ key ] ; if ( v ) { return v ; } if ( dataTypeNodeId . value === 0 ) { const v3 = { category : FieldCategory . basic , fieldTypeName : "Variant" , schema : dataTypeFactory . getSimpleType ( "Variant" ) } ; cache [ key ] = v3 ; return v3 ; } const fieldTypeName = await readBrowseName ( session , dataTypeNodeId ) ; let schema ; let category = FieldCategory . enumeration ; if ( dataTypeFactory . hasStructuredType ( fieldTypeName ! ) ) { schema = dataTypeFactory . getStructuredTypeSchema ( fieldTypeName ) ; category = FieldCategory . complex ; } else if ( dataTypeFactory . hasSimpleType ( fieldTypeName ! ) ) { category = FieldCategory . basic ; schema = dataTypeFactory . getSimpleType ( fieldTypeName ! ) ; } else if ( dataTypeFactory . hasEnumeration ( fieldTypeName ! ) ) { category = FieldCategory . enumeration ; schema = dataTypeFactory . getEnumeration ( fieldTypeName ! ) ! ; } else { debugLog ( " type " + fieldTypeName + " has not been seen yet, let resolve it" ) ; const res = await resolve2 ( session , dataTypeNodeId , dataTypeFactory , fieldTypeName , cache ) ; schema = res . schema ; category = res . category ; } if ( ! schema ) { throw new Error ( "expecting a schema here fieldTypeName=" + fieldTypeName + " " + dataTypeNodeId . toString ( ) + " category = " + category ) ; } const v2 = { category , fieldTypeName , schema } ; cache [ key ] = v2 ; return v2 ; } async function _setupEncodings ( session , dataTypeNodeId , schema ) { const isAbstractDV = await session . read ( { nodeId : dataTypeNodeId , attributeId : AttributeIds . IsAbstract } ) ; schema . dataTypeNodeId = dataTypeNodeId ; schema . id = dataTypeNodeId ; if ( isAbstractDV . statusCode === StatusCodes . Good && isAbstractDV . value . value === false ) { const encodings = await _findEncodings ( session , dataTypeNodeId ) ; schema . encodingDefaultBinary = makeExpandedNodeId ( encodings . binaryEncodingNodeId ) ; schema . encodingDefaultXml = makeExpandedNodeId ( encodings . xmlEncodingNodeId ) ; schema . encodingDefaultJson = makeExpandedNodeId ( encodings . jsonEncodingNodeId ) ; } return schema ; } export async function convertDataTypeDefinitionToStructureTypeSchema ( session , dataTypeNodeId , name , definition , dataTypeFactory , cache ) { if ( definition instanceof StructureDefinition ) { const fields = [ ] ; const isUnion = definition . structureType === StructureType . Union ; switch ( definition . structureType ) { case StructureType . Union : fields . push ( { fieldType : "UInt32" , name : "SwitchField" } ) ; break ; case StructureType . Structure : case StructureType . StructureWithOptionalFields : break ; } let switchValue = 1 ; let switchBit = 0 ; const bitFields = isUnion ? undefined : [ ] ; for ( const fieldD of definition . fields ! ) { const rt = ( await resolveFieldType ( session , fieldD . dataType , dataTypeFactory , cache ) ) ! ; if ( ! rt ) { console . log ( "convertDataTypeDefinitionToStructureTypeSchema cannot handle field" , fieldD . name , "in" , name , "because " + fieldD . dataType . toString ( ) + " cannot be resolved" ) ; continue ; } const { schema , category , fieldTypeName } = rt ; const field = { fieldType : fieldTypeName ! , name : fieldD . name ! , schema } ; if ( fieldD . isOptional ) { field . switchBit = switchBit ++ ; bitFields ?. push ( { name : fieldD . name ! + "Specified" , length : 1 } ) ; } if ( isUnion ) { field . switchValue = switchValue ; switchValue += 1 ; } assert ( fieldD . valueRank === - 1 || fieldD . valueRank === 1 || fieldD . valueRank === 0 ) ; if ( fieldD . valueRank === 1 ) { field . isArray = true ; } else { field . isArray = false ; } field . category = category ; field . schema = schema ; fields . push ( field ) ; } const a = await resolveFieldType ( session , definition . baseDataType , dataTypeFactory , cache ) ; const baseType = a ? a . fieldTypeName : "ExtensionObject" ; const os = new StructuredTypeSchema ( { baseType , bitFields , fields , id : 0 , name } ) ; const structuredTypeSchema = await _setupEncodings ( session , dataTypeNodeId , os ) ; return structuredTypeSchema ; } throw new Error ( "Not Implemented" ) ; }