node-opcua-address-space 2.115.0 → 2.116.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-opcua-address-space",
3
- "version": "2.115.0",
3
+ "version": "2.116.0",
4
4
  "description": "pure nodejs OPCUA SDK - module address-space",
5
5
  "main": "./dist/src/index_current.js",
6
6
  "types": "./dist/source/index.d.ts",
@@ -26,7 +26,7 @@
26
26
  "node-opcua-assert": "2.105.0",
27
27
  "node-opcua-basic-types": "2.114.0",
28
28
  "node-opcua-binary-stream": "2.114.0",
29
- "node-opcua-client-dynamic-extension-object": "2.114.0",
29
+ "node-opcua-client-dynamic-extension-object": "2.116.0",
30
30
  "node-opcua-constants": "2.114.0",
31
31
  "node-opcua-crypto": "4.5.0",
32
32
  "node-opcua-data-access": "2.114.0",
@@ -41,7 +41,7 @@
41
41
  "node-opcua-nodeset-ua": "2.114.0",
42
42
  "node-opcua-numeric-range": "2.114.0",
43
43
  "node-opcua-object-registry": "2.114.0",
44
- "node-opcua-pseudo-session": "2.114.0",
44
+ "node-opcua-pseudo-session": "2.116.0",
45
45
  "node-opcua-service-browse": "2.114.0",
46
46
  "node-opcua-service-call": "2.114.0",
47
47
  "node-opcua-service-history": "2.114.0",
@@ -84,7 +84,7 @@
84
84
  "internet of things"
85
85
  ],
86
86
  "homepage": "http://node-opcua.github.io/",
87
- "gitHead": "2a65943304091de9876f69db24b289c157612880",
87
+ "gitHead": "713ad387571a323c8d886ad1c907f50ad96a5e76",
88
88
  "files": [
89
89
  "dist",
90
90
  "distHelpers",
@@ -8,6 +8,7 @@ import { CallMethodRequest } from "node-opcua-service-call";
8
8
  import { StatusCode, StatusCodes } from "node-opcua-status-code";
9
9
  import { CallMethodResultOptions } from "node-opcua-types";
10
10
  import { Variant } from "node-opcua-variant";
11
+ import { ResponseCallback } from "node-opcua-pseudo-session";
11
12
  import { ISessionContext, IAddressSpace, UAMethod, UAObject } from "node-opcua-address-space-base";
12
13
 
13
14
  import { getMethodDeclaration_ArgumentList, verifyArguments_ArgumentList } from "./argument_list";
@@ -28,7 +29,6 @@ import { resolveOpaqueOnAddressSpace } from "./resolve_opaque_on_address_space";
28
29
  // A ByteString is structurally the same as a one dimensional array of Byte.
29
30
  // A server shall accept a ByteString if an array of Byte is expected.
30
31
  // BadNoCommunication
31
- type ResponseCallback<T> = (err: Error | null, result?: T) => void;
32
32
 
33
33
  export function callMethodHelper(
34
34
  context: ISessionContext,
package/source/index.ts CHANGED
@@ -41,7 +41,7 @@ export { promoteToMultiStateDiscrete } from "../src/data_access/ua_multistate_di
41
41
  export { promoteToMultiStateValueDiscrete } from "../src/data_access/ua_multistate_value_discrete_impl";
42
42
  export { promoteToTwoStateDiscrete } from "../src/data_access/ua_two_state_discrete_impl";
43
43
  export { validateDataType } from "../src/data_access/ua_multistate_value_discrete_impl";
44
-
44
+ export { validateDataTypeCorrectness } from "../src/validate_data_type_correctness";
45
45
  export * from "./ua_root_folder";
46
46
  export * from "./session_context";
47
47
  export * from "./pseudo_session";
@@ -1,15 +1,173 @@
1
1
  import { checkDebugFlag, make_debugLog, make_errorLog } from "node-opcua-debug";
2
2
  import { CallbackT } from "node-opcua-status-code";
3
- import { IAddressSpace } from "node-opcua-address-space-base";
4
-
3
+ import { IAddressSpace, RequiredModel } from "node-opcua-address-space-base";
4
+ import { ReaderStateParserLike, Xml2Json } from "node-opcua-xml2json";
5
+ import { minDate } from "node-opcua-date-time";
5
6
  import { adjustNamespaceArray } from "../../src/nodeset_tools/adjust_namespace_array";
6
7
  import { NodeSetLoaderOptions } from "../interfaces/nodeset_loader_options";
8
+ import { NamespacePrivate } from "../../src/namespace_private";
7
9
  import { NodeSetLoader } from "./load_nodeset2";
8
10
 
9
11
  const doDebug = checkDebugFlag(__filename);
10
12
  const debugLog = make_debugLog(__filename);
11
13
  const errorLog = make_errorLog(__filename);
12
14
 
15
+ interface Model extends RequiredModel {
16
+ requiredModel: RequiredModel[];
17
+ }
18
+ interface NodesetInfo {
19
+ namespaceUris: string[];
20
+ models: Model[];
21
+ }
22
+
23
+ async function parseDependencies(xmlData: string): Promise<NodesetInfo> {
24
+ const namespaceUris: string[] = [];
25
+
26
+ const models: Model[] = [];
27
+ let currentModel: Model | undefined = undefined;
28
+ const state0: ReaderStateParserLike = {
29
+ parser: {
30
+ UANodeSet: {
31
+ parser: {
32
+ NamespaceUris: {
33
+ parser: {
34
+ Uri: {
35
+ finish() {
36
+ namespaceUris.push(this.text);
37
+ }
38
+ }
39
+ }
40
+ },
41
+ Models: {
42
+ parser: {
43
+ Model: {
44
+ init(elementName: string, attrs: any) {
45
+ const modelUri = attrs.ModelUri;
46
+ const version = attrs.Version;
47
+ const publicationDate = new Date(Date.parse(attrs.PublicationDate));
48
+ currentModel = {
49
+ modelUri,
50
+ version,
51
+ publicationDate,
52
+ requiredModel: []
53
+ };
54
+ doDebug && console.log(`currentModel = ${JSON.stringify(currentModel)}`);
55
+ models.push(currentModel);
56
+ },
57
+ parser: {
58
+ RequiredModel: {
59
+ init(elementName: string, attrs: any) {
60
+ const modelUri = attrs.ModelUri;
61
+ const version = attrs.Version;
62
+ const publicationDate = new Date(Date.parse(attrs.PublicationDate));
63
+
64
+ if (!currentModel) {
65
+ throw new Error("Internal Error");
66
+ }
67
+ currentModel.requiredModel.push({
68
+ modelUri,
69
+ version,
70
+ publicationDate
71
+ });
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ };
82
+ const parser = new Xml2Json(state0);
83
+ parser.parseStringSync(xmlData);
84
+ if (models.length === 0 && namespaceUris.length >= 1) {
85
+ models.push({
86
+ modelUri: namespaceUris[0],
87
+ version: "1",
88
+ publicationDate: minDate,
89
+ requiredModel: []
90
+ });
91
+ }
92
+ return { models, namespaceUris: namespaceUris };
93
+ }
94
+ interface NodesetDesc {
95
+ index: number;
96
+ xmlData: string;
97
+ namespaceModel: NodesetInfo;
98
+ }
99
+ /**
100
+ * Detect order of namespace loading
101
+ */
102
+ export async function preLoad(xmlFiles: string[], xmlLoader: (nodeset2xmlUri: string) => Promise<string>): Promise<NodesetDesc[]> {
103
+ // a nodeset2 file may define multiple namespaces
104
+ const namespaceDesc: NodesetDesc[] = [];
105
+ for (let index = 0; index < xmlFiles.length; index++) {
106
+ doDebug && console.log("---------------------------------------------", xmlFiles[index]);
107
+ const xmlData = await xmlLoader(xmlFiles[index]);
108
+
109
+ const indexStart = xmlData.match(/<UANodeSet/m)?.index;
110
+ const i1 = (xmlData.match(/<\/Models>/m)?.index || 0) + "</Models>".length;
111
+ const i2 = (xmlData.match(/<\/NamespaceUris>/m)?.index || 0) + "</NamespaceUris>".length;
112
+
113
+ const indexEnd = Math.max(i1, i2);
114
+ if (indexStart === undefined || indexEnd === undefined) {
115
+ throw new Error("Internal Error");
116
+ }
117
+ const xmlData2 = xmlData.substring(indexStart, indexEnd);
118
+ doDebug &&
119
+ console.log(
120
+ xmlData2
121
+ .split("\n")
122
+ .splice(0, 46)
123
+ .map((x, i) => `${i + 0} ${x}`)
124
+ .join("\n")
125
+ );
126
+ const namespaceModel = await parseDependencies(xmlData2);
127
+ namespaceDesc.push({ xmlData, namespaceModel, index });
128
+ }
129
+ return namespaceDesc;
130
+ }
131
+ export function findOrder(nodesetDescs: NodesetDesc[]): number[] {
132
+ // compute the order of loading of the namespaces
133
+ const order: number[] = [];
134
+ const visited: Set<string> = new Set<string>();
135
+
136
+ const findNodesetIndex = (namespaceUri: string) => {
137
+ const index = nodesetDescs.findIndex((x) => x.namespaceModel.models.findIndex((e) => e.modelUri === namespaceUri) !== -1);
138
+ return index;
139
+ };
140
+ const visit = (model: Model) => {
141
+ const key = model.modelUri;
142
+ if (visited.has(key)) {
143
+ return;
144
+ }
145
+ visited.add(key);
146
+ for (const requiredModel of model.requiredModel) {
147
+ const requiredModelIndex = findNodesetIndex(requiredModel.modelUri);
148
+ if (requiredModelIndex === -1) {
149
+ throw new Error("Cannot find namespace for " + requiredModel.modelUri);
150
+ }
151
+ const nd = nodesetDescs[requiredModelIndex];
152
+ for (const n of nd.namespaceModel.models) {
153
+ visit(n);
154
+ }
155
+ }
156
+ const nodesetIndex = findNodesetIndex(model.modelUri);
157
+ const alreadyIn = order.findIndex((x) => x === nodesetIndex) !== -1;
158
+ if (!alreadyIn) order.push(nodesetIndex);
159
+ };
160
+ const visit2 = (nodesetDesc: NodesetDesc) => {
161
+ for (const model of nodesetDesc.namespaceModel.models.values()) {
162
+ visit(model);
163
+ }
164
+ };
165
+ for (let index = 0; index < nodesetDescs.length; index++) {
166
+ const nodesetDesc = nodesetDescs[index];
167
+ visit2(nodesetDesc);
168
+ }
169
+ return order;
170
+ }
13
171
  /**
14
172
  * @param addressSpace the addressSpace to populate
15
173
  * @xmlFiles: a lis of xml files
@@ -26,15 +184,26 @@ export async function generateAddressSpaceRaw(
26
184
  if (!Array.isArray(xmlFiles)) {
27
185
  xmlFiles = [xmlFiles];
28
186
  }
29
- for (let index = 0; index < xmlFiles.length; index++) {
30
- const xmlData = await xmlLoader(xmlFiles[index]);
187
+
188
+ const nodesetDesc = await preLoad(xmlFiles, xmlLoader);
189
+ const order = findOrder(nodesetDesc);
190
+ for (let index = 0; index < order.length; index++) {
191
+ const nodesetIndex = order[index];
192
+ const nodeset = nodesetDesc[nodesetIndex];
193
+ debugLog(" loading ", nodesetIndex, nodeset.xmlData.length);
194
+ for (const model of nodeset.namespaceModel.models) {
195
+ const ns = addressSpace.registerNamespace(model.modelUri) as NamespacePrivate;
196
+ ns.setRequiredModels(model.requiredModel);
197
+ }
198
+
31
199
  try {
32
- await nodesetLoader.addNodeSetAsync(xmlData);
200
+ await nodesetLoader.addNodeSetAsync(nodeset.xmlData);
33
201
  } catch (err) {
34
202
  errorLog("generateAddressSpace: Loading xml file ", xmlFiles[index], " failed with error ", (err as Error).message);
35
203
  throw err;
36
204
  }
37
205
  }
206
+
38
207
  await nodesetLoader.terminateAsync();
39
208
  adjustNamespaceArray(addressSpace);
40
209
  // however process them in series
@@ -306,13 +306,7 @@ function makeNodeSetParserEngine(addressSpace: IAddressSpace, options: NodeSetLo
306
306
  // Model must not be already registered
307
307
  const existingNamespace = addressSpace1.getNamespace(model.modelUri);
308
308
  if (existingNamespace) {
309
- // special treatment for namespace 0
310
- // istanbul ignore else
311
- if (model.modelUri === "http://opcfoundation.org/UA/") {
312
- namespace = existingNamespace;
313
- } else {
314
- throw new Error(" namespace already registered " + model.modelUri);
315
- }
309
+ namespace = existingNamespace;
316
310
  } else {
317
311
  namespace = addressSpace1.registerNamespace(model.modelUri);
318
312
  namespace.setRequiredModels(model.requiredModels);
@@ -12,8 +12,8 @@ export * from "../source/helpers/call_helpers";
12
12
  export * from "../source/helpers/ensure_secure_access";
13
13
  export * from "../source/helpers/resolve_opaque_on_address_space";
14
14
  export * from "../source/interfaces/alarms_and_conditions/condition_info_i";
15
-
16
15
  export * from "../src/nodeset_tools/construct_namespace_dependency";
16
+ export * from "../src/validate_data_type_correctness";
17
17
 
18
18
 
19
19
  export * from "../source/set_namespace_meta_data";
@@ -94,6 +94,7 @@ import {
94
94
  } from "./ua_variable_impl_ext_obj";
95
95
  import { adjustDataValueStatusCode } from "./data_access/adjust_datavalue_status_code";
96
96
  import { _getBasicDataType } from "./get_basic_datatype";
97
+ import { validateDataTypeCorrectness} from "./validate_data_type_correctness";
97
98
 
98
99
  const debugLog = make_debugLog(__filename);
99
100
  const warningLog = make_warningLog(__filename);
@@ -149,94 +150,6 @@ function is_Variant_or_StatusCode(v: any): boolean {
149
150
  return is_Variant(v) || is_StatusCode(v);
150
151
  }
151
152
 
152
- function _dataType_toUADataType(addressSpace: IAddressSpace, dataType: DataType): UADataType {
153
- assert(addressSpace);
154
- assert(dataType !== DataType.Null);
155
-
156
- const dataTypeNode = addressSpace.findDataType(DataType[dataType]);
157
- /* istanbul ignore next */
158
- if (!dataTypeNode) {
159
- throw new Error(" Cannot find DataType " + DataType[dataType] + " in address Space");
160
- }
161
- return dataTypeNode as UADataType;
162
- }
163
- /*=
164
- *
165
- * @param addressSpace
166
- * @param dataTypeNodeId : the nodeId matching the dataType of the destination variable.
167
- * @param variantDataType: the dataType of the variant to write to the destination variable
168
- * @param nodeId
169
- * @return {boolean} true if the variant dataType is compatible with the Variable DataType
170
- */
171
- function validateDataType(
172
- addressSpace: IAddressSpace,
173
- dataTypeNodeId: NodeId,
174
- variantDataType: DataType,
175
- nodeId: NodeId,
176
- allowNulls: boolean
177
- ): boolean {
178
- if (variantDataType === DataType.ExtensionObject) {
179
- return true;
180
- }
181
- if (variantDataType === DataType.Null && allowNulls) {
182
- return true;
183
- }
184
- if (variantDataType === DataType.Null && !allowNulls) {
185
- return false;
186
- }
187
- let builtInType: DataType;
188
- let builtInUADataType: UADataType;
189
-
190
- const destUADataType = addressSpace.findDataType(dataTypeNodeId)!;
191
- assert(destUADataType instanceof UADataTypeImpl);
192
-
193
- if (destUADataType.isAbstract || destUADataType.nodeId.namespace !== 0) {
194
- builtInUADataType = destUADataType;
195
- } else {
196
- builtInType = addressSpace.findCorrespondingBasicDataType(destUADataType);
197
- builtInUADataType = addressSpace.findDataType(builtInType)!;
198
- }
199
- assert(builtInUADataType instanceof UADataTypeImpl);
200
-
201
- const enumerationUADataType = addressSpace.findDataType("Enumeration");
202
- if (!enumerationUADataType) {
203
- throw new Error("cannot find Enumeration DataType node in standard address space");
204
- }
205
- if (destUADataType.isSubtypeOf(enumerationUADataType)) {
206
- // istanbul ignore next
207
- if (doDebug) {
208
- debugLog("destUADataType.", destUADataType.browseName.toString(), destUADataType.nodeId.toString());
209
- debugLog(
210
- "enumerationUADataType.",
211
- enumerationUADataType.browseName.toString(),
212
- enumerationUADataType.nodeId.toString()
213
- );
214
- }
215
- return true;
216
- }
217
-
218
- // The value supplied for the attribute is not of the same type as the value.
219
- const variantUADataType = _dataType_toUADataType(addressSpace, variantDataType);
220
- assert(variantUADataType instanceof UADataTypeImpl);
221
-
222
- const dest_isSubTypeOf_variant = variantUADataType.isSubtypeOf(builtInUADataType);
223
-
224
- /* istanbul ignore next */
225
- if (doDebug) {
226
- if (dest_isSubTypeOf_variant) {
227
- /* istanbul ignore next*/
228
- debugLog(chalk.green(" ---------- Type match !!! "), " on ", nodeId.toString());
229
- } else {
230
- /* istanbul ignore next*/
231
- debugLog(chalk.red(" ---------- Type mismatch "), " on ", nodeId.toString());
232
- }
233
- debugLog(chalk.cyan(" Variable data Type is = "), destUADataType.browseName.toString());
234
- debugLog(chalk.cyan(" which matches basic Type = "), builtInUADataType.browseName.toString());
235
- debugLog(chalk.yellow(" Actual dataType = "), variantUADataType.browseName.toString());
236
- }
237
-
238
- return dest_isSubTypeOf_variant;
239
- }
240
153
 
241
154
  function default_func(this: UAVariable, dataValue1: DataValue, callback1: CallbackT<StatusCode>) {
242
155
  return _default_writable_timestamped_set_func.call(this, dataValue1, callback1);
@@ -1707,7 +1620,7 @@ export class UAVariableImpl extends BaseNodeImpl implements UAVariable {
1707
1620
  }
1708
1621
 
1709
1622
  public _validate_DataType(variantDataType: DataType): boolean {
1710
- return validateDataType(this.addressSpace, this.dataType, variantDataType, this.nodeId, /* allow Nulls */ false);
1623
+ return validateDataTypeCorrectness(this.addressSpace, this.dataType, variantDataType, /* allow Nulls */ false, this.nodeId);
1711
1624
  }
1712
1625
 
1713
1626
  public _internal_set_value(value: Variant): void {
@@ -0,0 +1,115 @@
1
+ import chalk from "chalk";
2
+ import { assert } from "node-opcua-assert";
3
+ import { IAddressSpace, UADataType } from "node-opcua-address-space-base";
4
+ import { DataType } from "node-opcua-basic-types";
5
+ import { make_debugLog, make_warningLog, checkDebugFlag, make_errorLog } from "node-opcua-debug";
6
+ import { NodeId } from "node-opcua-nodeid";
7
+
8
+ const debugLog = make_debugLog(__filename);
9
+ const doDebug = checkDebugFlag(__filename);
10
+
11
+ function _dataType_toUADataType(addressSpace: IAddressSpace, dataType: DataType): UADataType {
12
+ assert(addressSpace);
13
+ assert(dataType !== DataType.Null);
14
+
15
+ const dataTypeNode = addressSpace.findDataType(DataType[dataType]);
16
+ /* istanbul ignore next */
17
+ if (!dataTypeNode) {
18
+ throw new Error(" Cannot find DataType " + DataType[dataType] + " in address Space");
19
+ }
20
+ return dataTypeNode as UADataType;
21
+ }
22
+
23
+ const validDataTypeForEnumValue = [DataType.Int32];
24
+ // , DataType.UInt32, DataType.Int64, DataType.UInt64];
25
+
26
+ /*=
27
+ *
28
+ * @param addressSpace
29
+ * @param dataTypeNodeId : the nodeId matching the dataType of the destination variable.
30
+ * @param variantDataType: the dataType of the variant to write to the destination variable
31
+ * @param nodeId
32
+ * @return {boolean} true if the variant dataType is compatible with the Variable DataType
33
+ */
34
+ export function validateDataTypeCorrectness(
35
+ addressSpace: IAddressSpace,
36
+ dataTypeNodeId: NodeId,
37
+ variantDataType: DataType,
38
+ allowNulls: boolean,
39
+ context?: { toString(): string }
40
+ ): boolean {
41
+ if (variantDataType === DataType.Null && allowNulls) {
42
+ return true;
43
+ }
44
+ if (variantDataType === DataType.Null && !allowNulls) {
45
+ return false;
46
+ }
47
+ let builtInType: DataType;
48
+ let builtInUADataType: UADataType;
49
+
50
+ const destUADataType = addressSpace.findDataType(dataTypeNodeId)!;
51
+
52
+ // istanbul ignore next
53
+ if (!destUADataType) {
54
+ throw new Error("Cannot find UADataType " + dataTypeNodeId.toString() + " in address Space");
55
+ }
56
+
57
+ if (variantDataType === DataType.ExtensionObject) {
58
+ const structure = addressSpace.findDataType("Structure")!;
59
+ if (destUADataType.isSubtypeOf(structure)) {
60
+ return true;
61
+ }
62
+ return false;
63
+ }
64
+
65
+ if (destUADataType.isAbstract) {
66
+ builtInUADataType = destUADataType;
67
+ } else {
68
+ builtInType = addressSpace.findCorrespondingBasicDataType(destUADataType);
69
+ if (builtInType === DataType.ExtensionObject) {
70
+ // it should have been trapped earlier
71
+ return false;
72
+ }
73
+ builtInUADataType = addressSpace.findDataType(builtInType)!;
74
+ }
75
+
76
+ const enumerationUADataType = addressSpace.findDataType("Enumeration");
77
+ // istanbul ignore next
78
+ if (!enumerationUADataType) {
79
+ throw new Error("cannot find Enumeration DataType node in standard address space");
80
+ }
81
+ if (destUADataType.isSubtypeOf(enumerationUADataType)) {
82
+ // istanbul ignore next
83
+ if (doDebug) {
84
+ debugLog("destUADataType.", destUADataType.browseName.toString(), destUADataType.nodeId.toString());
85
+ debugLog(
86
+ "enumerationUADataType.",
87
+ enumerationUADataType.browseName.toString(),
88
+ enumerationUADataType.nodeId.toString()
89
+ );
90
+ }
91
+
92
+ return validDataTypeForEnumValue.indexOf(variantDataType) >= 0;
93
+ }
94
+
95
+ // The value supplied for the attribute is not of the same type as the value.
96
+ const variantUADataType = _dataType_toUADataType(addressSpace, variantDataType);
97
+
98
+ const dest_isSubTypeOf_variant = variantUADataType.isSubtypeOf(builtInUADataType);
99
+
100
+ // istanbul ignore next
101
+ if (doDebug) {
102
+ if (dest_isSubTypeOf_variant) {
103
+ /* istanbul ignore next*/
104
+ debugLog(chalk.green(" ---------- Type match !!! "), " on ", context?.toString());
105
+ } else {
106
+ /* istanbul ignore next*/
107
+ debugLog(chalk.red(" ---------- Type mismatch "), " on ", context?.toString());
108
+ }
109
+ debugLog(chalk.cyan(" Variable data Type is = "), destUADataType.browseName.toString());
110
+ debugLog(chalk.cyan(" which matches basic Type = "), builtInUADataType.browseName.toString());
111
+ debugLog(chalk.yellow(" Actual dataType = "), variantUADataType.browseName.toString());
112
+ }
113
+
114
+ return dest_isSubTypeOf_variant;
115
+ }