module import * as chalk from "chalk" ; import { assert } from "node-opcua-assert" ; import { AttributeIds , makeNodeClassMask , makeResultMask , QualifiedName } from "node-opcua-data-model" ; import { checkDebugFlag , make_debugLog , make_errorLog , make_warningLog } from "node-opcua-debug" ; import { ConstructorFuncWithSchema , DataTypeFactory , getStandardDataTypeFactory } from "node-opcua-factory" ; import { ExpandedNodeId , NodeId , resolveNodeId , sameNodeId } from "node-opcua-nodeid" ; import { browseAll , BrowseDescriptionLike , IBasicSession } from "node-opcua-pseudo-session" ; import { createDynamicObjectConstructor , DataTypeAndEncodingId , MapDataTypeAndEncodingIdProvider , parseBinaryXSDAsync } from "node-opcua-schemas" ; import { BrowseDescriptionOptions , BrowseDirection , BrowseResult , ReferenceDescription } from "node-opcua-service-browse" ; import { makeBrowsePath } from "node-opcua-service-translate-browse-path" ; import { StatusCodes } from "node-opcua-status-code" ; import { ReadValueIdOptions , StructureDefinition } from "node-opcua-types" ; import { ExtraDataTypeManager } from "../extra_data_type_manager" ; import { CacheForFieldResolution , convertDataTypeDefinitionToStructureTypeSchema } from "../convert_data_type_definition_to_structuretype_schema" ; const doDebug = checkDebugFlag ( __filename ) ; const debugLog = make_debugLog ( __filename ) ; const errorLog = make_errorLog ( __filename ) ; const warningLog = make_warningLog ( __filename ) ; async function _readDeprecatedFlag ( session , dataTypeDictionary ) { const browsePath = makeBrowsePath ( dataTypeDictionary , ".Deprecated" ) ; const a = await session . translateBrowsePath ( browsePath ) ; if ( ! a . targets || a . targets . length === 0 ) { debugLog ( "Cannot find Deprecated property for dataTypeDictionary " + dataTypeDictionary . toString ( ) ) ; return false ; } const deprecatedFlagNodeId = a . targets [ 0 ] . targetId ; const dataValue = await session . read ( { nodeId : deprecatedFlagNodeId , attributeId : AttributeIds . Value } ) ; return dataValue . value . value ; } async function _readNamespaceUriProperty ( session , dataTypeDictionary ) { const a = await session . translateBrowsePath ( makeBrowsePath ( dataTypeDictionary , ".NamespaceUri" ) ) ; if ( ! a . targets || a . targets . length === 0 ) { return "??dataTypeDictionary doesn't expose NamespaceUri property??" ; } const namespaceUriProp = a . targets [ 0 ] . targetId ; const dataValue = await session . read ( { nodeId : namespaceUriProp , attributeId : AttributeIds . Value } ) ; return dataValue . value . value || "<not set>" ; } interface IDataTypeDescription { browseName : QualifiedName ; nodeId : NodeId ; encodings ? : DataTypeAndEncodingId ; symbolicName ? : string ; } async function _getDataTypeDescriptions ( session , dataTypeDictionaryNodeId ) { const nodeToBrowse2 = { browseDirection : BrowseDirection . Forward , includeSubtypes : false , nodeClassMask : makeNodeClassMask ( "Variable" ) , nodeId : dataTypeDictionaryNodeId , referenceTypeId : resolveNodeId ( "HasComponent" ) , resultMask : makeResultMask ( "NodeId | BrowseName" ) } ; const result2 = await browseAll ( session , nodeToBrowse2 ) ; result2 . references = result2 . references || [ ] ; return result2 . references . map ( ( r ) => ( { nodeId : r . nodeId , browseName : r . browseName } ) ) ; } async function _enrichWithDescriptionOf ( session , dataTypeDescriptions ) { const nodesToBrowse3 = [ ] ; for ( const ref of dataTypeDescriptions ) { ref . browseName . toString ( ) ; nodesToBrowse3 . push ( { browseDirection : BrowseDirection . Inverse , includeSubtypes : false , nodeClassMask : makeNodeClassMask ( "Object" ) , nodeId : ref . nodeId . toString ( ) , referenceTypeId : resolveNodeId ( "HasDescription" ) , resultMask : makeResultMask ( "NodeId" ) } ) ; } if ( nodesToBrowse3 . length === 0 ) { return [ ] ; } const results3 = await browseAll ( session , nodesToBrowse3 ) ; const binaryEncodings = [ ] ; const nodesToBrowseDataType = [ ] ; let i = 0 ; for ( const result3 of results3 ) { const dataTypeDescription = dataTypeDescriptions [ i ++ ] ; result3 . references = result3 . references || [ ] ; if ( result3 . references . length !== 1 ) { warningLog ( "_enrichWithDescriptionOf : expecting 1 reference for " , dataTypeDescription . browseName . toString ( ) ) ; continue ; } for ( const ref of result3 . references ) { const binaryEncodingNodeId = ref . nodeId ; dataTypeDescription . encodings = dataTypeDescription . encodings || { binaryEncodingNodeId : NodeId . nullNodeId , dataTypeNodeId : NodeId . nullNodeId , jsonEncodingNodeId : NodeId . nullNodeId , xmlEncodingNodeId : NodeId . nullNodeId } ; dataTypeDescription . encodings . binaryEncodingNodeId = binaryEncodingNodeId ; binaryEncodings . push ( binaryEncodingNodeId ) ; nodesToBrowseDataType . push ( { browseDirection : BrowseDirection . Inverse , includeSubtypes : false , nodeClassMask : makeNodeClassMask ( "DataType" ) , nodeId : ref . nodeId . toString ( ) , referenceTypeId : resolveNodeId ( "HasEncoding" ) , resultMask : makeResultMask ( "NodeId | BrowseName" ) } ) ; } } const dataTypeNodeIds = [ ] ; if ( nodesToBrowseDataType . length > 0 ) { const results4 = await browseAll ( session , nodesToBrowseDataType ) ; i = 0 ; for ( const result4 of results4 ) { result4 . references = result4 . references || [ ] ; if ( result4 . references . length !== 1 ) { console . log ( "What's going on ?" , result4 . toString ( ) ) ; } for ( const ref of result4 . references ) { const dataTypeNodeId = ref . nodeId ; dataTypeNodeIds . push ( dataTypeNodeId ) ; const dataTypeDescription = dataTypeDescriptions [ i ++ ] ; dataTypeDescription . encodings ! . dataTypeNodeId = dataTypeNodeId ; } } } return dataTypeNodeIds ; } interface IDataTypeDefInfo { className : string ; dataTypeNodeId : NodeId ; dataTypeDefinition : StructureDefinition ; } type DataTypeDefinitions = IDataTypeDefInfo [ ] ; function sortStructure ( dataTypeDefinitions ) { const dataTypeDefinitionsSorted = [ ] ; const _visited = { } ; const _map = { } ; for ( const d of dataTypeDefinitions ) { _map [ d . dataTypeNodeId . toString ( ) ] = d ; } function _visit ( d ) { const hash = d . dataTypeNodeId . toString ( ) ; if ( _visited [ hash ] ) { return ; } const bbb = _map [ d . dataTypeDefinition . baseDataType . toString ( ) ] ; if ( bbb ) { _visit ( bbb ) ; } for ( const f of d . dataTypeDefinition . fields || [ ] ) { const ddd = _map [ f . dataType . toString ( ) ] ; if ( ! ddd ) { continue ; } _visit ( ddd ) ; } _visited [ hash ] = d ; dataTypeDefinitionsSorted . push ( d ) ; } for ( const d of dataTypeDefinitions ) { _visit ( d ) ; } return dataTypeDefinitionsSorted ; } async function _extractDataTypeDictionaryFromDefinition ( session , dataTypeDictionaryNodeId , dataTypeFactory ) { assert ( dataTypeFactory , "expecting a dataTypeFactory" ) ; const dataTypeDescriptions = await _getDataTypeDescriptions ( session , dataTypeDictionaryNodeId ) ; const dataTypeNodeIds = await _enrichWithDescriptionOf ( session , dataTypeDescriptions ) ; const nodesToRead = dataTypeNodeIds . map ( ( nodeId ) => ( { attributeId : AttributeIds . DataTypeDefinition , nodeId } ) ) ; const cache = { } ; const dataValuesWithDataTypeDefinition = nodesToRead . length > 0 ? await session . read ( nodesToRead ) : [ ] ; const dataTypeDefinitions = [ ] ; let index = 0 ; for ( const dataValue of dataValuesWithDataTypeDefinition ) { const dataTypeNodeId = dataTypeNodeIds [ index ] ; const dataTypeDescription = dataTypeDescriptions [ index ] ; if ( dataValue . statusCode === StatusCodes . Good ) { const dataTypeDefinition = dataValue . value . value ; if ( dataTypeDefinition && dataTypeDefinition instanceof StructureDefinition ) { const className = dataTypeDescription . browseName . name ! ; dataTypeDefinitions . push ( { className , dataTypeNodeId , dataTypeDefinition } ) ; } } else { debugLog ( "dataTypeNodeId " , dataTypeNodeId . toString ( ) , " has no DataTypeDescription attribute" , dataValue . statusCode . toString ( ) ) ; } index ++ ; } const dataTypeDefinitionsSorted = sortStructure ( dataTypeDefinitions ) ; if ( doDebug ) { debugLog ( "order " , dataTypeDefinitionsSorted . map ( ( a ) => a . className + " " + a . dataTypeNodeId ) . join ( " ->  " ) ) ; } for ( const { className , dataTypeNodeId , dataTypeDefinition } of dataTypeDefinitionsSorted ) { if ( doDebug ) { debugLog ( chalk . yellow ( "--------------------------------------- " ) , className , dataTypeNodeId . toString ( ) ) ; } if ( dataTypeFactory . hasStructuredType ( className ) ) { continue ; } try { const schema = await convertDataTypeDefinitionToStructureTypeSchema ( session , dataTypeNodeId , className , dataTypeDefinition , dataTypeFactory , cache ) ; if ( doDebug ) { debugLog ( chalk . red ( "Registering " ) , chalk . cyan ( className . padEnd ( 30 , " " ) ) , schema . dataTypeNodeId . toString ( ) ) ; } const Constructor = createDynamicObjectConstructor ( schema , dataTypeFactory ) as ConstructorFuncWithSchema ; assert ( Constructor . schema === schema ) ; } catch ( err ) { console . log ( "Constructor verification err: " , ( < Error > err ) . message ) ; console . log ( "For this reason class " + className + " has not been registered" ) ; console . log ( err ) ; } } } async function _extractNodeIds ( session , dataTypeDictionaryNodeId ) { const map = { } ; const dataTypeDescriptions = await _getDataTypeDescriptions ( session , dataTypeDictionaryNodeId ) ; await _enrichWithDescriptionOf ( session , dataTypeDescriptions ) ; for ( const dataTypeDescription of dataTypeDescriptions ) { map [ dataTypeDescription . browseName . name ! . toString ( ) ] = dataTypeDescription . encodings ! ; } return { getDataTypeAndEncodingId ( key ) { return map [ key ] || null ; } } ; } interface TypeDictionaryInfo { reference : ReferenceDescription ; dataTypeDictionaryNodeId : NodeId ; isDictionaryDeprecated : boolean ; rawSchema : string ; dependencies : { [ key ] : string ; } ; targetNamespace : string ; } async function _extractDataTypeDictionary ( session , d , dataTypeManager ) { const dataTypeDictionaryNodeId = d . reference . nodeId ; const isDictionaryDeprecated = d . isDictionaryDeprecated ; const rawSchema = d . rawSchema ; const name = await session . read ( { nodeId : dataTypeDictionaryNodeId , attributeId : AttributeIds . BrowseName } ) ; const namespace = await _readNamespaceUriProperty ( session , dataTypeDictionaryNodeId ) ; if ( isDictionaryDeprecated || rawSchema . length === 0 ) { debugLog ( "DataTypeDictionary is deprecated or BSD schema stored in dataValue is null !" , chalk . cyan ( name . value . value . toString ( ) ) , "namespace =" , namespace ) ; debugLog ( "let's use the new way (1.04) and let's crawl all dataTypes exposed by this name space" ) ; const dataTypeFactory2 = dataTypeManager . getDataTypeFactory ( dataTypeDictionaryNodeId . namespace ) ; if ( ! dataTypeFactory2 ) { throw new Error ( "cannot find dataTypeFactory for namespace " + dataTypeDictionaryNodeId . namespace ) ; } await _extractDataTypeDictionaryFromDefinition ( session , dataTypeDictionaryNodeId , dataTypeFactory2 ) ; return ; } else { debugLog ( " ----- Using old method for extracting schema => with BSD files" ) ; if ( doDebug ) { debugLog ( "---------------------------------------------" ) ; debugLog ( rawSchema . toString ( ) ) ; debugLog ( "---------------------------------------------" ) ; } const idProvider = await _extractNodeIds ( session , dataTypeDictionaryNodeId ) ; const dataTypeFactory1 = dataTypeManager . getDataTypeFactory ( dataTypeDictionaryNodeId . namespace ) ; await parseBinaryXSDAsync ( rawSchema , idProvider , dataTypeFactory1 ) ; } } async function _exploreDataTypeDefinition ( session , dataTypeDictionaryTypeNode , dataTypeFactory , namespaces ) { const nodeToBrowse = { browseDirection : BrowseDirection . Forward , includeSubtypes : false , nodeClassMask : makeNodeClassMask ( "Variable" ) , nodeId : dataTypeDictionaryTypeNode , referenceTypeId : resolveNodeId ( "HasComponent" ) , resultMask : makeResultMask ( "ReferenceType | IsForward | BrowseName | NodeClass | TypeDefinition" ) } ; const result = await browseAll ( session , nodeToBrowse ) ; const references = result . references || [ ] ; if ( references . length === 0 ) { return ; } const nodesToBrowse2 = references . map ( ( ref ) => { return { browseDirection : BrowseDirection . Inverse , includeSubtypes : false , nodeClassMask : makeNodeClassMask ( "Object | Variable" ) , nodeId : ref . nodeId , referenceTypeId : resolveNodeId ( "HasDescription" ) , resultMask : makeResultMask ( "NodeId | ReferenceType | BrowseName | NodeClass | TypeDefinition" ) } ; } ) ; const results2 = await browseAll ( session , nodesToBrowse2 ) ; const binaryEncodingNodeIds = results2 . map ( ( br ) => { const defaultBin = br . references ! . filter ( ( r ) => r . browseName . toString ( ) === "Default Binary" ) ; if ( defaultBin . length < 1 ) { return ExpandedNodeId ; } return ExpandedNodeId . fromNodeId ( defaultBin [ 0 ] . nodeId , namespaces [ defaultBin [ 0 ] . nodeId . namespace ] ) ; } ) ; if ( doDebug ) { console . log ( chalk . bgWhite . red ( "testing new constructors" ) ) ; for ( let i = 0 ; i < references . length ; i ++ ) { const ref = references [ i ] ; const binaryEncoding = binaryEncodingNodeIds [ i ] ; const name = ref . browseName ! . name ! . toString ( ) ; if ( doDebug ) { debugLog ( "      type " , name . padEnd ( 30 , " " ) , binaryEncoding . toString ( ) ) ; } try { const constructor = dataTypeFactory . getStructureTypeConstructor ( name ) ; const testObject = new constructor ( ) ; debugLog ( testObject . toString ( ) ) ; } catch ( err ) { debugLog ( "         Error cannot construct Extension Object " + name ) ; debugLog ( "         " + ( < Error > err ) . message ) ; } } } } const regexTargetNamespaceAttribute = /TargetNamespace="([^"]+)"|TargetNamespace='([^"]+)'/ ; function extractTargetNamespaceAttribute ( xmlElement ) { const c2 = xmlElement . match ( regexTargetNamespaceAttribute ) ; if ( c2 ) { return c2 [ 1 ] || c2 [ 2 ] ; } return "" ; } const regexNamespaceRef = /xmlns:(.*)=(("([^"]+)")|('([^']+)'))/ ; function extraNamespaceRef ( attribute ) { const c = attribute . match ( regexNamespaceRef ) ; if ( c ) { const xmlns = c [ 1 ] as string ; const namespace = c [ 3 ] || c [ 4 ] ; return { xmlns , namespace } ; } return null ; } param session param dataTypeManager async export async function populateDataTypeManager103 ( session , dataTypeManager ) { debugLog ( "in ... populateDataTypeManager" ) ; const dataValueNamespaceArray = await session . read ( { attributeId : AttributeIds . Value , nodeId : resolveNodeId ( "Server_NamespaceArray" ) } ) ; const namespaceArray = dataValueNamespaceArray . value . value ; if ( ! namespaceArray ) { debugLog ( "session: cannot read Server_NamespaceArray" ) ; return ; } if ( doDebug ) { debugLog ( "namespaceArray " , namespaceArray . map ( ( a , index ) => " " + index . toString ( ) . padEnd ( 3 ) + ":" + a ) . join ( " " ) ) ; } if ( dataValueNamespaceArray . statusCode === StatusCodes . Good && namespaceArray && namespaceArray . length > 0 ) { dataTypeManager . setNamespaceArray ( namespaceArray ) ; for ( let namespaceIndex = 1 ; namespaceIndex < namespaceArray . length ; namespaceIndex ++ ) { if ( ! dataTypeManager . hasDataTypeFactory ( namespaceIndex ) ) { const dataTypeFactory1 = new DataTypeFactory ( [ getStandardDataTypeFactory ( ) ] ) ; dataTypeManager . registerDataTypeFactory ( namespaceIndex , dataTypeFactory1 ) ; } } } if ( ! dataValueNamespaceArray . value . value && dataTypeManager . namespaceArray . length === 0 ) { dataTypeManager . setNamespaceArray ( [ ] ) ; } const dataTypeDictionaryType = resolveNodeId ( "DataTypeDictionaryType" ) ; const opcBinaryNodeId = resolveNodeId ( "OPCBinarySchema_TypeSystem" ) ; const nodeToBrowse = { browseDirection : BrowseDirection . Forward , includeSubtypes : false , nodeClassMask : makeNodeClassMask ( "Variable" ) , nodeId : opcBinaryNodeId , referenceTypeId : resolveNodeId ( "HasComponent" ) , resultMask : makeResultMask ( "ReferenceType | IsForward | BrowseName | NodeClass | TypeDefinition" ) } ; const result = await browseAll ( session , nodeToBrowse ) ; if ( doDebug ) { debugLog ( result . statusCode . toString ( ) ) ; debugLog ( result . references ?. map ( ( r ) => r . browseName ?. toString ( ) ) . join ( " " ) ) ; } const references = result . references ! . filter ( ( e ) => e . nodeId . namespace !== 0 && sameNodeId ( e . typeDefinition , dataTypeDictionaryType ) ) ; debugLog ( `found ${ references . length } dictionary` ) ; async function putInCorrectOrder ( ) { const infos = [ ] ; const innerMap = { } ; for ( const reference of references ) { const dataTypeDictionaryNodeId = reference . nodeId ; const isDictionaryDeprecated = await _readDeprecatedFlag ( session , dataTypeDictionaryNodeId ) ; const rawSchemaDataValue = await session . read ( { attributeId : AttributeIds . Value , nodeId : dataTypeDictionaryNodeId } ) ; const rawSchema = rawSchemaDataValue . value . value ? rawSchemaDataValue . value . value . toString ( ) : "" ; const info = { dataTypeDictionaryNodeId , dependencies : { } , isDictionaryDeprecated , rawSchema , reference , targetNamespace : "" } ; infos . push ( info ) ; if ( ! isDictionaryDeprecated && rawSchema . length > 0 ) { if ( doDebug ) { console . log ( "schema" , rawSchema ) ; } const matches = rawSchema . match ( /<opc:TypeDictionary([^>]+)>/ ) ; if ( matches ) { const typeDictionaryElementAttributes = matches [ 1 ] ; info . targetNamespace = extractTargetNamespaceAttribute ( typeDictionaryElementAttributes ) ; const nsKeyNamespace = { } ; for ( const attribute of typeDictionaryElementAttributes . split ( " " ) ) { const r = extraNamespaceRef ( attribute ) ; if ( r ) { const { xmlns , namespace } = r ; nsKeyNamespace [ xmlns ] = namespace ; debugLog ( "xxxx ns= " , xmlns , "=>" , namespace ) ; } } info . dependencies = nsKeyNamespace ; debugLog ( "xxx targetNamespace = " , info . targetNamespace ) ; innerMap [ info . targetNamespace ] = info ; } } else { info . targetNamespace = namespaceArray [ dataTypeDictionaryNodeId . namespace ] ; debugLog ( "xxx targetNamespace = " , info . targetNamespace ) ; innerMap [ info . targetNamespace ] = info ; } } const orderedList = [ ] ; const visited = { } ; function explore ( d ) { if ( visited [ d . targetNamespace ] ) { return ; } visited [ d . targetNamespace ] = 1 ; for ( const [ xmlns , namespace ] of Object . entries ( d . dependencies ) ) { if ( ! innerMap [ namespace ] || namespace === d . targetNamespace ) { continue ; } explore ( innerMap [ namespace ] ) ; } orderedList . push ( d ) ; } for ( const d of infos ) { explore ( d ) ; } debugLog ( " Ordered List = " , orderedList . map ( ( a ) => a . targetNamespace ) . join ( "  " ) ) ; return orderedList ; } const dataTypeDictionaryInfo = await putInCorrectOrder ( ) ; const map = { } ; for ( const d of dataTypeDictionaryInfo ) { map [ d . targetNamespace ] = d ; debugLog ( " fixing based dataTypeFactory dependencies for  " , d . targetNamespace , "index = " , d . dataTypeDictionaryNodeId . namespace ) ; const baseDataFactories = [ getStandardDataTypeFactory ( ) ] ; for ( const namespace of Object . values ( d . dependencies ) ) { if ( namespace === d . targetNamespace ) { continue ; } const baseDataFactory = map [ namespace ] ; if ( ! baseDataFactory ) { continue ; } const namespaceIndex = baseDataFactory . dataTypeDictionaryNodeId . namespace ; if ( dataTypeManager . hasDataTypeFactory ( namespaceIndex ) ) { const dep = dataTypeManager . getDataTypeFactory ( namespaceIndex ) ; baseDataFactories . push ( dep ) ; debugLog ( "   considering , " , baseDataFactory . targetNamespace , "index = " , baseDataFactory . dataTypeDictionaryNodeId . namespace ) ; } } const dataTypeFactory = dataTypeManager . getDataTypeFactory ( d . dataTypeDictionaryNodeId . namespace ) ; if ( dataTypeFactory ) { dataTypeFactory . repairBaseDataFactories ( baseDataFactories ) ; } } async function processReferenceOnDataTypeDictionaryType ( d ) { debugLog ( chalk . cyan ( "processReferenceOnDataTypeDictionaryType on  " ) , d . targetNamespace ) ; const ref = d . reference ; const dataTypeDictionaryNodeId = d . reference . nodeId ; await _extractDataTypeDictionary ( session , d , dataTypeManager ) ; if ( doDebug ) { debugLog ( chalk . bgWhite ( "                                         => " ) , ref . browseName . toString ( ) , ref . nodeId . toString ( ) ) ; } const dataTypeFactory = dataTypeManager . getDataTypeFactoryForNamespace ( dataTypeDictionaryNodeId . namespace ) ; await _exploreDataTypeDefinition ( session , dataTypeDictionaryNodeId , dataTypeFactory , dataTypeManager . namespaceArray ) ; } for ( const d of dataTypeDictionaryInfo ) { try { await processReferenceOnDataTypeDictionaryType ( d ) . catch ( ( e ) => { console . log ( e ) ; debugLog ( "processReferenceOnDataTypeDictionaryType has failed " ) ; debugLog ( "Error" , e . message ) ; debugLog ( e ) ; return e ; } ) ; } catch ( err ) { debugLog ( chalk . red ( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx " ) , err ) ; } } debugLog ( "out ... populateDataTypeManager" ) ; }