node-opcua-address-space 2.181.1 → 2.182.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.
Files changed (121) hide show
  1. package/api/helpers/ensure_secure_access.ts +53 -2
  2. package/api/index.ts +30 -1
  3. package/api/loader/generateAddressSpaceRaw.ts +44 -139
  4. package/api/loader/load_nodeset2.ts +5 -0
  5. package/api/loader/narrow_instance_datatypes.ts +264 -0
  6. package/api/loader/nodeset_builtin_formats.ts +143 -0
  7. package/api/loader/nodeset_format.ts +153 -0
  8. package/api/loader/nodeset_image.ts +37 -4
  9. package/api/loader/nodeset_image_codec.ts +153 -34
  10. package/api/loader/nodeset_record.ts +26 -2
  11. package/api/loader/nodeset_record_applier.ts +54 -3
  12. package/api/loader/nodeset_records_to_xml.ts +620 -0
  13. package/api/loader/nodeset_source.ts +185 -26
  14. package/api/loader/nodeset_to_image.ts +16 -5
  15. package/api/loader/nodeset_xml_header.ts +119 -0
  16. package/api/loader/nodeset_xml_primitives.ts +209 -0
  17. package/api/loader/nodeset_xml_producer.ts +61 -2
  18. package/api/loader/parsers/variant_parser.ts +9 -0
  19. package/bin/nodeset_equivalence.js +750 -0
  20. package/bin/nodeset_tool_common.js +72 -0
  21. package/bin/opcua-nodeset-image.js +93 -103
  22. package/bin/opcua-nodeset-live.js +139 -0
  23. package/dist/api/helpers/ensure_secure_access.d.ts +20 -0
  24. package/dist/api/helpers/ensure_secure_access.js +39 -0
  25. package/dist/api/helpers/ensure_secure_access.js.map +1 -1
  26. package/dist/api/index.d.ts +7 -2
  27. package/dist/api/index.js +21 -1
  28. package/dist/api/index.js.map +1 -1
  29. package/dist/api/loader/generateAddressSpaceRaw.d.ts +1 -7
  30. package/dist/api/loader/generateAddressSpaceRaw.js +32 -125
  31. package/dist/api/loader/generateAddressSpaceRaw.js.map +1 -1
  32. package/dist/api/loader/load_nodeset2.js +4 -0
  33. package/dist/api/loader/load_nodeset2.js.map +1 -1
  34. package/dist/api/loader/narrow_instance_datatypes.d.ts +30 -0
  35. package/dist/api/loader/narrow_instance_datatypes.js +217 -0
  36. package/dist/api/loader/narrow_instance_datatypes.js.map +1 -0
  37. package/dist/api/loader/nodeset_builtin_formats.d.ts +27 -0
  38. package/dist/api/loader/nodeset_builtin_formats.js +133 -0
  39. package/dist/api/loader/nodeset_builtin_formats.js.map +1 -0
  40. package/dist/api/loader/nodeset_format.d.ts +123 -0
  41. package/dist/api/loader/nodeset_format.js +63 -0
  42. package/dist/api/loader/nodeset_format.js.map +1 -0
  43. package/dist/api/loader/nodeset_image.d.ts +17 -1
  44. package/dist/api/loader/nodeset_image.js +32 -4
  45. package/dist/api/loader/nodeset_image.js.map +1 -1
  46. package/dist/api/loader/nodeset_image_codec.d.ts +4 -0
  47. package/dist/api/loader/nodeset_image_codec.js +142 -53
  48. package/dist/api/loader/nodeset_image_codec.js.map +1 -1
  49. package/dist/api/loader/nodeset_record.d.ts +26 -2
  50. package/dist/api/loader/nodeset_record.js +11 -2
  51. package/dist/api/loader/nodeset_record.js.map +1 -1
  52. package/dist/api/loader/nodeset_record_applier.js +52 -3
  53. package/dist/api/loader/nodeset_record_applier.js.map +1 -1
  54. package/dist/api/loader/nodeset_records_to_xml.d.ts +16 -0
  55. package/dist/api/loader/nodeset_records_to_xml.js +536 -0
  56. package/dist/api/loader/nodeset_records_to_xml.js.map +1 -0
  57. package/dist/api/loader/nodeset_source.d.ts +68 -7
  58. package/dist/api/loader/nodeset_source.js +157 -20
  59. package/dist/api/loader/nodeset_source.js.map +1 -1
  60. package/dist/api/loader/nodeset_to_image.d.ts +4 -4
  61. package/dist/api/loader/nodeset_to_image.js +14 -4
  62. package/dist/api/loader/nodeset_to_image.js.map +1 -1
  63. package/dist/api/loader/nodeset_xml_header.d.ts +22 -0
  64. package/dist/api/loader/nodeset_xml_header.js +113 -0
  65. package/dist/api/loader/nodeset_xml_header.js.map +1 -0
  66. package/dist/api/loader/nodeset_xml_primitives.d.ts +37 -0
  67. package/dist/api/loader/nodeset_xml_primitives.js +196 -0
  68. package/dist/api/loader/nodeset_xml_primitives.js.map +1 -0
  69. package/dist/api/loader/nodeset_xml_producer.js +56 -1
  70. package/dist/api/loader/nodeset_xml_producer.js.map +1 -1
  71. package/dist/api/loader/parsers/variant_parser.js +7 -0
  72. package/dist/api/loader/parsers/variant_parser.js.map +1 -1
  73. package/dist/impl/_instantiate_helpers.js +1 -0
  74. package/dist/impl/_instantiate_helpers.js.map +1 -1
  75. package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.js +4 -0
  76. package/dist/impl/alarms_and_conditions/ua_alarm_condition_impl.js.map +1 -1
  77. package/dist/impl/alarms_and_conditions/ua_certificate_expiration_alarm_impl.js +14 -0
  78. package/dist/impl/alarms_and_conditions/ua_certificate_expiration_alarm_impl.js.map +1 -1
  79. package/dist/impl/base_node_impl.d.ts +9 -0
  80. package/dist/impl/base_node_impl.js +11 -0
  81. package/dist/impl/base_node_impl.js.map +1 -1
  82. package/dist/impl/base_node_private.js +67 -97
  83. package/dist/impl/base_node_private.js.map +1 -1
  84. package/dist/impl/nodeset_tools/construct_namespace_dependency.js +5 -1
  85. package/dist/impl/nodeset_tools/construct_namespace_dependency.js.map +1 -1
  86. package/dist/impl/nodeset_tools/nodeset_to_records.js +61 -15
  87. package/dist/impl/nodeset_tools/nodeset_to_records.js.map +1 -1
  88. package/dist/impl/nodeset_tools/nodeset_to_xml.d.ts +1 -8
  89. package/dist/impl/nodeset_tools/nodeset_to_xml.js +174 -190
  90. package/dist/impl/nodeset_tools/nodeset_to_xml.js.map +1 -1
  91. package/dist/impl/ua_data_type_impl.d.ts +21 -0
  92. package/dist/impl/ua_data_type_impl.js +27 -2
  93. package/dist/impl/ua_data_type_impl.js.map +1 -1
  94. package/dist/impl/ua_object_type_impl.d.ts +9 -0
  95. package/dist/impl/ua_object_type_impl.js +12 -1
  96. package/dist/impl/ua_object_type_impl.js.map +1 -1
  97. package/dist/impl/ua_variable_impl.js +27 -10
  98. package/dist/impl/ua_variable_impl.js.map +1 -1
  99. package/dist/impl/ua_variable_impl_ext_obj.js +19 -2
  100. package/dist/impl/ua_variable_impl_ext_obj.js.map +1 -1
  101. package/dist/impl/ua_variable_type_impl.d.ts +8 -1
  102. package/dist/impl/ua_variable_type_impl.js +39 -2
  103. package/dist/impl/ua_variable_type_impl.js.map +1 -1
  104. package/dist/tsconfig_base.tsbuildinfo +1 -1
  105. package/distNodeJS/generate_address_space.js +4 -0
  106. package/distNodeJS/generate_address_space.js.map +1 -1
  107. package/impl/_instantiate_helpers.ts +3 -0
  108. package/impl/alarms_and_conditions/ua_alarm_condition_impl.ts +10 -0
  109. package/impl/alarms_and_conditions/ua_certificate_expiration_alarm_impl.ts +17 -0
  110. package/impl/base_node_impl.ts +15 -0
  111. package/impl/base_node_private.ts +97 -146
  112. package/impl/nodeset_tools/construct_namespace_dependency.ts +5 -1
  113. package/impl/nodeset_tools/nodeset_to_records.ts +66 -16
  114. package/impl/nodeset_tools/nodeset_to_xml.ts +127 -160
  115. package/impl/ua_data_type_impl.ts +40 -2
  116. package/impl/ua_object_type_impl.ts +16 -3
  117. package/impl/ua_variable_impl.ts +27 -10
  118. package/impl/ua_variable_impl_ext_obj.ts +20 -2
  119. package/impl/ua_variable_type_impl.ts +48 -2
  120. package/package.json +29 -27
  121. package/source_nodejs/generate_address_space.ts +7 -1
@@ -0,0 +1,72 @@
1
+ /**
2
+ * What the nodeset command line tools share: resolving the files a document requires, and the
3
+ * shape of a NodesetSource over a file on disk.
4
+ */
5
+ const fs = require("node:fs");
6
+ const path = require("node:path");
7
+ const { preLoad } = require("../dist/api/index.js");
8
+ const { readNodeSet2XmlFile } = require("../distNodeJS/index.js");
9
+
10
+ const kb = (n) => `${(n / 1024).toFixed(0)} KB`;
11
+
12
+ /** a NodeSet2 file as the loader takes it: named, and read whole when asked for */
13
+ const asFile = (file) => ({ name: file, source: () => [new Uint8Array(fs.readFileSync(file))] });
14
+
15
+ const imageFileOf = (file, outDir) => {
16
+ const base = path.basename(file).replace(/\.xml$/i, "");
17
+ return path.join(outDir ?? path.dirname(file), `${base}.ndjson.gz`);
18
+ };
19
+
20
+ /** the files `file` requires, in load order, from the node-opcua-nodesets catalog and `requireFiles` */
21
+ async function dependencyChain(file, requireFiles = []) {
22
+ let catalog = [];
23
+ let nodesets = {};
24
+ try {
25
+ ({ nodesetCatalog: catalog, nodesets } = require("node-opcua-nodesets"));
26
+ } catch {
27
+ /* the catalog is optional */
28
+ }
29
+ const known = new Map(); // model uri -> file
30
+ for (const meta of catalog) {
31
+ if (nodesets[meta.name] && fs.existsSync(nodesets[meta.name])) known.set(meta.uri, nodesets[meta.name]);
32
+ }
33
+ for (const required of requireFiles) {
34
+ const [desc] = await preLoad([required], readNodeSet2XmlFile);
35
+ for (const model of desc.namespaceModel.models) known.set(model.modelUri, required);
36
+ }
37
+ const order = [];
38
+ const visit = async (f) => {
39
+ const [desc] = await preLoad([f], readNodeSet2XmlFile);
40
+ for (const model of desc.namespaceModel.models) {
41
+ for (const required of model.requiredModel) {
42
+ const dependency = known.get(required.modelUri);
43
+ if (!dependency) {
44
+ throw new Error(
45
+ `${path.basename(f)} requires ${required.modelUri}: not in the catalog, pass it with --require`
46
+ );
47
+ }
48
+ if (!order.includes(dependency) && dependency !== f) await visit(dependency);
49
+ }
50
+ }
51
+ if (!order.includes(f)) order.push(f);
52
+ };
53
+ await visit(file);
54
+ return order;
55
+ }
56
+
57
+ /**
58
+ * the record stream of a document, whichever form it is in. A gzip stream starts `1f 8b`, a
59
+ * NodeSet2 XML document `<`, and an uncompressed NodeSet-NDJSON document `{`, so the three are
60
+ * told apart by their first byte with no filename and no metadata to trust.
61
+ */
62
+ async function recordsOfFile(file) {
63
+ const { imageNodesetRecords } = require("../dist/api/index.js");
64
+ const { nodesetFileToImage } = require("../distNodeJS/index.js");
65
+ const bytes = new Uint8Array(fs.readFileSync(file));
66
+ const image = bytes[0] === 0x1f && bytes[1] === 0x8b ? bytes : await nodesetFileToImage(file);
67
+ const out = [];
68
+ for await (const record of imageNodesetRecords(image)) out.push(record);
69
+ return out;
70
+ }
71
+
72
+ module.exports = { asFile, dependencyChain, imageFileOf, kb, recordsOfFile };
@@ -1,53 +1,55 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * opcua-nodeset-image: precompiled images of NodeSet2 files, without writing code.
3
+ * opcua-nodeset-image: everything you can do to a nodeset document without an information model.
4
+ *
5
+ * Nothing here builds an address space. A NodeSet2 document and its NodeSet-NDJSON form are two
6
+ * spellings of the same thing, so converting between them, reading one, or comparing two is a
7
+ * transcription: no DataType definitions are needed, no dependency chain has to be resolved, and
8
+ * nothing has to be loaded. That is what keeps this tool cheap, and what makes its answers about
9
+ * the document rather than about node-opcua. For the few operations that do need a model, and
10
+ * pay for one, see `opcua-nodeset-live`.
4
11
  *
5
12
  * opcua-nodeset-image build <file.xml>... [--out <dir>]
6
13
  * writes <name>.ndjson.gz next to each source, or into --out; prints source size,
7
- * image size, node count and elapsed time; exit code 1 when a file fails, the others
8
- * are still processed
9
- * opcua-nodeset-image verify <file.xml>... [--require <file.xml>]...
10
- * loads each file from XML and from its image into two address spaces and compares
11
- * them; dependencies come from the node-opcua-nodesets catalog, or from --require
12
- * opcua-nodeset-image info <image>
13
- * prints the header and the trailer of an image
14
- * opcua-nodeset-image export <file.xml>... [--out <image>]
15
- * loads the files (dependencies resolved as for verify) and exports the last one's
16
- * namespace, from the live address space, to an image: the round-trip fixture
14
+ * image size, node count and elapsed time. Exit 1 if a file fails, the others are
15
+ * still processed
16
+ * opcua-nodeset-image xml <file>... [--out <dir>]
17
+ * the other direction: writes <name>.nodeset2.xml from a document in either form
18
+ * opcua-nodeset-image records <file>
19
+ * the record stream as NDJSON on stdout, one record per line, uncompressed
20
+ * opcua-nodeset-image info <file>
21
+ * the header and the trailer of an image
22
+ * opcua-nodeset-image diff <a> <b>
23
+ * the two documents compared record by record; either may be XML or NDJSON
17
24
  */
18
25
  const fs = require("node:fs");
19
26
  const path = require("node:path");
20
27
  const { performance } = require("node:perf_hooks");
21
- const { AddressSpace, generateAddressSpaceRaw, namespaceToImage, preLoad, readNodesetImageInfo } = require("../dist/api/index.js");
22
- const { addressSpacePackageVersion, digestAddressSpace, nodesetFileToImage, readNodeSet2XmlFile } = require("../distNodeJS/index.js");
28
+ const { readNodesetImageInfo, recordsToNodeset2XML } = require("../dist/api/index.js");
29
+ const { nodesetFileToImage } = require("../distNodeJS/index.js");
30
+ const { imageFileOf, kb, recordsOfFile } = require("./nodeset_tool_common.js");
23
31
 
24
32
  const usage = () => {
25
- console.error("usage: opcua-nodeset-image build <file.xml>... [--out <dir>]");
26
- console.error(" opcua-nodeset-image verify <file.xml>... [--require <file.xml>]...");
27
- console.error(" opcua-nodeset-image info <image>");
28
- console.error(" opcua-nodeset-image export <file.xml>... [--out <image>] [--require <file.xml>]...");
33
+ console.error("usage: opcua-nodeset-image build <file.xml>... [--out <dir>]");
34
+ console.error(" opcua-nodeset-image xml <file>... [--out <dir>]");
35
+ console.error(" opcua-nodeset-image records <file>");
36
+ console.error(" opcua-nodeset-image info <file>");
37
+ console.error(" opcua-nodeset-image diff <a> <b>");
29
38
  process.exit(2);
30
39
  };
31
40
 
32
41
  function parseArgs(argv) {
33
42
  const files = [];
34
- const options = { out: undefined, require: [] };
43
+ const options = { out: undefined };
35
44
  for (let i = 0; i < argv.length; i++) {
36
45
  const arg = argv[i];
37
46
  if (arg === "--out") options.out = argv[++i];
38
- else if (arg === "--require") options.require.push(argv[++i]);
39
47
  else if (arg.startsWith("--")) usage();
40
48
  else files.push(arg);
41
49
  }
42
50
  return { files, options };
43
51
  }
44
52
 
45
- const imageFileOf = (file, outDir) => {
46
- const base = path.basename(file).replace(/\.xml$/i, "");
47
- return path.join(outDir ?? path.dirname(file), `${base}.ndjson.gz`);
48
- };
49
- const kb = (n) => `${(n / 1024).toFixed(0)} KB`;
50
-
51
53
  async function build(files, options) {
52
54
  let failed = 0;
53
55
  for (const file of files) {
@@ -70,60 +72,23 @@ async function build(files, options) {
70
72
  return failed > 0 ? 1 : 0;
71
73
  }
72
74
 
73
- /** the files a document requires, in load order, from the catalog and the --require files */
74
- async function dependencyChain(file, requireFiles) {
75
- let catalog = [];
76
- let nodesets = {};
77
- try {
78
- ({ nodesetCatalog: catalog, nodesets } = require("node-opcua-nodesets"));
79
- } catch {
80
- /* the catalog is optional */
81
- }
82
- const known = new Map(); // model uri -> file
83
- for (const meta of catalog) {
84
- if (nodesets[meta.name] && fs.existsSync(nodesets[meta.name])) known.set(meta.uri, nodesets[meta.name]);
85
- }
86
- for (const required of requireFiles) {
87
- const [desc] = await preLoad([required], readNodeSet2XmlFile);
88
- for (const model of desc.namespaceModel.models) known.set(model.modelUri, required);
89
- }
90
- const order = [];
91
- const visit = async (f) => {
92
- const [desc] = await preLoad([f], readNodeSet2XmlFile);
93
- for (const model of desc.namespaceModel.models) {
94
- for (const required of model.requiredModel) {
95
- const dependency = known.get(required.modelUri);
96
- if (!dependency) throw new Error(`${path.basename(f)} requires ${required.modelUri}: not in the catalog, pass it with --require`);
97
- if (!order.includes(dependency) && dependency !== f) await visit(dependency);
98
- }
99
- }
100
- if (!order.includes(f)) order.push(f);
101
- };
102
- await visit(file);
103
- return order;
104
- }
105
-
106
- async function verify(files, options) {
75
+ async function toXml(files, options) {
107
76
  let failed = 0;
108
77
  for (const file of files) {
78
+ const t0 = performance.now();
109
79
  try {
110
- const chain = await dependencyChain(file, options.require);
111
- const image = await nodesetFileToImage(file);
112
- const load = async (sources) => {
113
- const addressSpace = AddressSpace.create();
114
- try {
115
- await generateAddressSpaceRaw(addressSpace, sources, {});
116
- return digestAddressSpace(addressSpace);
117
- } finally {
118
- addressSpace.dispose();
119
- }
120
- };
121
- const asFile = (f) => ({ name: f, source: () => [new Uint8Array(fs.readFileSync(f))] });
122
- const fromXml = await load(chain.map(asFile));
123
- const fromImage = await load(chain.map((f) => (f === file ? { name: `${f} (image)`, source: image } : asFile(f))));
124
- const same = fromXml.hash === fromImage.hash && fromXml.nodes === fromImage.nodes && fromXml.references === fromImage.references;
125
- console.log(`${path.basename(file)}: ${fromXml.nodes} nodes, ${fromXml.references} references, image ${same ? "identical" : "DIFFERS"}`);
126
- if (!same) failed += 1;
80
+ const records = await recordsOfFile(file);
81
+ const xml = recordsToNodeset2XML(records);
82
+ const base = path
83
+ .basename(file)
84
+ .replace(/\.ndjson\.gz$/i, "")
85
+ .replace(/\.xml$/i, "");
86
+ const target = path.join(options.out ?? path.dirname(file), `${base}.nodeset2.xml`);
87
+ fs.mkdirSync(path.dirname(target), { recursive: true });
88
+ fs.writeFileSync(target, xml);
89
+ console.log(
90
+ `${path.basename(file)}: ${records.length - 1} nodes -> ${path.basename(target)} ${kb(xml.length)}, ${(performance.now() - t0).toFixed(0)} ms`
91
+ );
127
92
  } catch (err) {
128
93
  failed += 1;
129
94
  console.error(`${file}: ${err.message}`);
@@ -132,28 +97,13 @@ async function verify(files, options) {
132
97
  return failed > 0 ? 1 : 0;
133
98
  }
134
99
 
135
- async function exportNamespace(files, options) {
136
- const last = files[files.length - 1];
137
- const chain = await dependencyChain(last, [...options.require, ...files.slice(0, -1)]);
138
- const addressSpace = AddressSpace.create();
139
- try {
140
- const asFile = (f) => ({ name: f, source: () => [new Uint8Array(fs.readFileSync(f))] });
141
- await generateAddressSpaceRaw(addressSpace, chain.map(asFile), {});
142
- const [desc] = await preLoad([last], readNodeSet2XmlFile);
143
- const modelUri = desc.namespaceModel.models[0]?.modelUri;
144
- const namespace = addressSpace.getNamespaceArray().find((n) => n.namespaceUri === modelUri);
145
- if (!namespace) throw new Error(`${path.basename(last)}: namespace ${modelUri} not found after loading`);
146
- const t0 = performance.now();
147
- const image = await namespaceToImage(namespace, { addressSpaceVersion: addressSpacePackageVersion() });
148
- const target = options.out ?? imageFileOf(last, undefined).replace(/\.ndjson\.gz$/, ".export.ndjson.gz");
149
- fs.mkdirSync(path.dirname(target), { recursive: true });
150
- fs.writeFileSync(target, image);
151
- const info = await readNodesetImageInfo(image);
152
- console.log(`${path.basename(last)}: namespace ${modelUri} -> ${path.basename(target)} ${kb(image.length)}, ${info.trailer.nodes} nodes, ${(performance.now() - t0).toFixed(0)} ms`);
153
- return 0;
154
- } finally {
155
- addressSpace.dispose();
100
+ async function records(files) {
101
+ for (const file of files) {
102
+ for (const record of await recordsOfFile(file)) {
103
+ console.log(JSON.stringify(record));
104
+ }
156
105
  }
106
+ return 0;
157
107
  }
158
108
 
159
109
  async function info(files) {
@@ -165,6 +115,44 @@ async function info(files) {
165
115
  return 0;
166
116
  }
167
117
 
118
+ /** the key a record is matched on across two documents; the header is one of a kind */
119
+ const keyOf = (record) => (record.kind === "header" ? "header" : record.nodeId.toString());
120
+
121
+ async function diff(files) {
122
+ if (files.length !== 2) usage();
123
+ const [a, b] = await Promise.all(files.map(recordsOfFile));
124
+ const right = new Map(b.map((r) => [keyOf(r), r]));
125
+ const seen = new Set();
126
+ const differences = [];
127
+ for (const left of a) {
128
+ const key = keyOf(left);
129
+ seen.add(key);
130
+ const other = right.get(key);
131
+ if (!other) {
132
+ differences.push(`${key}: only in ${path.basename(files[0])}`);
133
+ continue;
134
+ }
135
+ for (const field of new Set([...Object.keys(left), ...Object.keys(other)])) {
136
+ const l = JSON.stringify(left[field]);
137
+ const r = JSON.stringify(other[field]);
138
+ if (l !== r) {
139
+ differences.push(`${key}.${field}\n A ${String(l).slice(0, 200)}\n B ${String(r).slice(0, 200)}`);
140
+ }
141
+ }
142
+ }
143
+ for (const record of b) {
144
+ if (!seen.has(keyOf(record))) differences.push(`${keyOf(record)}: only in ${path.basename(files[1])}`);
145
+ }
146
+ for (const line of differences.slice(0, 40)) console.log(line);
147
+ if (differences.length > 40) console.log(`... and ${differences.length - 40} more`);
148
+ console.log(
149
+ differences.length === 0
150
+ ? `${path.basename(files[0])} and ${path.basename(files[1])}: identical, ${a.length} records`
151
+ : `${differences.length} difference(s) over ${a.length} records`
152
+ );
153
+ return differences.length === 0 ? 0 : 1;
154
+ }
155
+
168
156
  async function main() {
169
157
  const [command, ...rest] = process.argv.slice(2);
170
158
  const { files, options } = parseArgs(rest);
@@ -172,21 +160,23 @@ async function main() {
172
160
  switch (command) {
173
161
  case "build":
174
162
  return build(files, options);
175
- case "verify":
176
- return verify(files, options);
163
+ case "xml":
164
+ return toXml(files, options);
165
+ case "records":
166
+ return records(files);
177
167
  case "info":
178
168
  return info(files);
179
- case "export":
180
- return exportNamespace(files, options);
169
+ case "diff":
170
+ return diff(files);
181
171
  default:
182
- usage();
172
+ return usage();
183
173
  }
184
174
  }
185
175
 
186
176
  main().then(
187
177
  (code) => process.exit(code),
188
178
  (err) => {
189
- console.error(err.message);
179
+ console.error(err.stack || err.message);
190
180
  process.exit(1);
191
181
  }
192
182
  );
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * opcua-nodeset-live: the nodeset operations that need an information model, and only those.
4
+ *
5
+ * Each of these builds a real address space, which means resolving a dependency chain, loading
6
+ * every namespace a document requires, and paying for the whole of node-opcua. That is the point
7
+ * of keeping them apart from `opcua-nodeset-image`: converting a document between its two
8
+ * spellings needs none of it, and a tool that quietly loaded a model to do a transcription would
9
+ * be answering a different question from the one it was asked.
10
+ *
11
+ * What genuinely needs a model:
12
+ *
13
+ * opcua-nodeset-live verify <file.xml>... [--require <file.xml>]...
14
+ * loads each file from XML and from its image into two address spaces and compares them.
15
+ * The comparison is of models, so there has to be one
16
+ * opcua-nodeset-live export <file.xml>... [--out <image>] [--require <file.xml>]...
17
+ * loads the files and writes the last one's namespace back out as an image, from the live
18
+ * address space. This is how a namespace that was never a document becomes one
19
+ * opcua-nodeset-live equivalence <file.xml>... [--require <file.xml>]... [--dump <dir>] [--quiet]
20
+ * the full round trip of the RFC, section 9.2: what a *given implementation* loses when it
21
+ * loads a document and writes it back. The narrower claim, about the format alone, is
22
+ * checked without an address space and lives in test_nodeset_records_to_xml.ts
23
+ */
24
+ const fs = require("node:fs");
25
+ const path = require("node:path");
26
+ const { performance } = require("node:perf_hooks");
27
+ const { AddressSpace, generateAddressSpaceRaw, namespaceToImage, preLoad, readNodesetImageInfo } = require("../dist/api/index.js");
28
+ const {
29
+ addressSpacePackageVersion,
30
+ digestAddressSpace,
31
+ nodesetFileToImage,
32
+ readNodeSet2XmlFile
33
+ } = require("../distNodeJS/index.js");
34
+ const { asFile, dependencyChain, imageFileOf, kb } = require("./nodeset_tool_common.js");
35
+ const { equivalence } = require("./nodeset_equivalence.js");
36
+
37
+ const usage = () => {
38
+ console.error("usage: opcua-nodeset-live verify <file.xml>... [--require <file.xml>]...");
39
+ console.error(" opcua-nodeset-live export <file.xml>... [--out <image>] [--require <file.xml>]...");
40
+ console.error(" opcua-nodeset-live equivalence <file.xml>... [--require <file.xml>]... [--dump <dir>] [--quiet]");
41
+ process.exit(2);
42
+ };
43
+
44
+ function parseArgs(argv) {
45
+ const files = [];
46
+ const options = { out: undefined, require: [] };
47
+ for (let i = 0; i < argv.length; i++) {
48
+ const arg = argv[i];
49
+ if (arg === "--out") options.out = argv[++i];
50
+ else if (arg === "--require") options.require.push(argv[++i]);
51
+ else if (arg.startsWith("--")) usage();
52
+ else files.push(arg);
53
+ }
54
+ return { files, options };
55
+ }
56
+
57
+ async function verify(files, options) {
58
+ let failed = 0;
59
+ for (const file of files) {
60
+ try {
61
+ const chain = await dependencyChain(file, options.require);
62
+ const image = await nodesetFileToImage(file);
63
+ const load = async (sources) => {
64
+ const addressSpace = AddressSpace.create();
65
+ try {
66
+ await generateAddressSpaceRaw(addressSpace, sources, {});
67
+ return digestAddressSpace(addressSpace);
68
+ } finally {
69
+ addressSpace.dispose();
70
+ }
71
+ };
72
+ const fromXml = await load(chain.map(asFile));
73
+ const fromImage = await load(chain.map((f) => (f === file ? { name: `${f} (image)`, source: image } : asFile(f))));
74
+ const same =
75
+ fromXml.hash === fromImage.hash &&
76
+ fromXml.nodes === fromImage.nodes &&
77
+ fromXml.references === fromImage.references;
78
+ console.log(
79
+ `${path.basename(file)}: ${fromXml.nodes} nodes, ${fromXml.references} references, image ${same ? "identical" : "DIFFERS"}`
80
+ );
81
+ if (!same) failed += 1;
82
+ } catch (err) {
83
+ failed += 1;
84
+ console.error(`${file}: ${err.message}`);
85
+ }
86
+ }
87
+ return failed > 0 ? 1 : 0;
88
+ }
89
+
90
+ async function exportNamespace(files, options) {
91
+ const last = files[files.length - 1];
92
+ const chain = await dependencyChain(last, [...options.require, ...files.slice(0, -1)]);
93
+ const addressSpace = AddressSpace.create();
94
+ try {
95
+ await generateAddressSpaceRaw(addressSpace, chain.map(asFile), {});
96
+ const [desc] = await preLoad([last], readNodeSet2XmlFile);
97
+ const modelUri = desc.namespaceModel.models[0]?.modelUri;
98
+ const namespace = addressSpace.getNamespaceArray().find((n) => n.namespaceUri === modelUri);
99
+ if (!namespace) throw new Error(`${path.basename(last)}: namespace ${modelUri} not found after loading`);
100
+ const t0 = performance.now();
101
+ const image = await namespaceToImage(namespace, { addressSpaceVersion: addressSpacePackageVersion() });
102
+ const target = options.out ?? imageFileOf(last, undefined).replace(/\.ndjson\.gz$/, ".export.ndjson.gz");
103
+ fs.mkdirSync(path.dirname(target), { recursive: true });
104
+ fs.writeFileSync(target, image);
105
+ const info = await readNodesetImageInfo(image);
106
+ console.log(
107
+ `${path.basename(last)}: namespace ${modelUri} -> ${path.basename(target)} ${kb(image.length)}, ${info.trailer.nodes} nodes, ${(performance.now() - t0).toFixed(0)} ms`
108
+ );
109
+ return 0;
110
+ } finally {
111
+ addressSpace.dispose();
112
+ }
113
+ }
114
+
115
+ async function main() {
116
+ const [command, ...rest] = process.argv.slice(2);
117
+ if (!command) usage();
118
+ if (command === "equivalence") {
119
+ return equivalence(rest);
120
+ }
121
+ const { files, options } = parseArgs(rest);
122
+ if (files.length === 0) usage();
123
+ switch (command) {
124
+ case "verify":
125
+ return verify(files, options);
126
+ case "export":
127
+ return exportNamespace(files, options);
128
+ default:
129
+ return usage();
130
+ }
131
+ }
132
+
133
+ main().then(
134
+ (code) => process.exit(code),
135
+ (err) => {
136
+ console.error(err.stack || err.message);
137
+ process.exit(1);
138
+ }
139
+ );
@@ -17,3 +17,23 @@ export interface EnsureObjectIsSecureOptions {
17
17
  * @param options `hideStructure` to also hide the nodes from unauthorised sessions
18
18
  */
19
19
  export declare function ensureObjectIsSecure(node: BaseNode, options?: EnsureObjectIsSecureOptions): void;
20
+ /**
21
+ * keep the structure of `node` and its aggregates browsable by every session, leaving every
22
+ * other permission as declared.
23
+ *
24
+ * A node whose RolePermissions grant Browse to neither Anonymous nor AuthenticatedUser is
25
+ * hidden from any session that holds none of the roles it names. Where those roles are ones
26
+ * the server never assigns, nobody sees the node, and its parent - browsable by everyone -
27
+ * shows an instance missing the mandatory children of its type. `Opc.Ua.NodeSet2.xml` does
28
+ * this to eleven nodes under Server/PublishSubscribe (the methods of SecurityGroups and
29
+ * KeyPushTargets, with their arguments), reserved to the SecurityKeyServerAdmin role
30
+ * (CTT Base Info Core Structure 002, FEAT-30).
31
+ *
32
+ * Such a node gains Browse for Anonymous and AuthenticatedUser, which every session's
33
+ * permissions are evaluated against; Read, Write and Call stay with the roles it named. A
34
+ * node without RolePermissions, or that already grants Browse to one of the two, is left as
35
+ * it is.
36
+ *
37
+ * @param node the node whose structure, and whose aggregates' structure, must stay visible
38
+ */
39
+ export declare function ensureStructureIsBrowsable(node: BaseNode): void;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureObjectIsSecure = ensureObjectIsSecure;
4
+ exports.ensureStructureIsBrowsable = ensureStructureIsBrowsable;
4
5
  const node_opcua_data_model_1 = require("node-opcua-data-model");
5
6
  const node_opcua_types_1 = require("node-opcua-types");
6
7
  const session_context_js_1 = require("../session_context.js");
@@ -64,4 +65,42 @@ function ensureObjectIsSecure(node, options = {}) {
64
65
  ensureObjectIsSecure(child, options);
65
66
  }
66
67
  }
68
+ /** the two Roles every Session's permissions are evaluated against, granted Browse and nothing else */
69
+ const browseForEverySession = [
70
+ { roleId: session_context_js_1.WellKnownRoles.Anonymous, permissions: (0, node_opcua_data_model_1.makePermissionFlag)("Browse") },
71
+ { roleId: session_context_js_1.WellKnownRoles.AuthenticatedUser, permissions: (0, node_opcua_data_model_1.makePermissionFlag)("Browse") }
72
+ ];
73
+ function grantsBrowseToEverySession(rolePermissions) {
74
+ return rolePermissions.some(({ roleId, permissions }) => (permissions & node_opcua_data_model_1.PermissionFlag.Browse) !== 0 &&
75
+ roleId.namespace === 0 &&
76
+ (roleId.value === session_context_js_1.WellKnownRoles.Anonymous || roleId.value === session_context_js_1.WellKnownRoles.AuthenticatedUser));
77
+ }
78
+ /**
79
+ * keep the structure of `node` and its aggregates browsable by every session, leaving every
80
+ * other permission as declared.
81
+ *
82
+ * A node whose RolePermissions grant Browse to neither Anonymous nor AuthenticatedUser is
83
+ * hidden from any session that holds none of the roles it names. Where those roles are ones
84
+ * the server never assigns, nobody sees the node, and its parent - browsable by everyone -
85
+ * shows an instance missing the mandatory children of its type. `Opc.Ua.NodeSet2.xml` does
86
+ * this to eleven nodes under Server/PublishSubscribe (the methods of SecurityGroups and
87
+ * KeyPushTargets, with their arguments), reserved to the SecurityKeyServerAdmin role
88
+ * (CTT Base Info Core Structure 002, FEAT-30).
89
+ *
90
+ * Such a node gains Browse for Anonymous and AuthenticatedUser, which every session's
91
+ * permissions are evaluated against; Read, Write and Call stay with the roles it named. A
92
+ * node without RolePermissions, or that already grants Browse to one of the two, is left as
93
+ * it is.
94
+ *
95
+ * @param node the node whose structure, and whose aggregates' structure, must stay visible
96
+ */
97
+ function ensureStructureIsBrowsable(node) {
98
+ const rolePermissions = node.rolePermissions;
99
+ if (rolePermissions && !grantsBrowseToEverySession(rolePermissions)) {
100
+ node.setRolePermissions([...rolePermissions, ...browseForEverySession]);
101
+ }
102
+ for (const child of node.findReferencesExAsObject("Aggregates", node_opcua_data_model_1.BrowseDirection.Forward)) {
103
+ ensureStructureIsBrowsable(child);
104
+ }
105
+ }
67
106
  //# sourceMappingURL=ensure_secure_access.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ensure_secure_access.js","sourceRoot":"","sources":["../../../api/helpers/ensure_secure_access.ts"],"names":[],"mappings":";;AA0DA,oDAmBC;AA5ED,iEAAmI;AACnI,uDAAuD;AACvD,8DAAuD;AAEvD,SAAS,gBAAgB,CAAC,OAAqB;IAC3C,IAAI,OAAO,CAAC,YAAY,KAAK,sCAAmB,CAAC,cAAc,EAAE,CAAC;QAC9D,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC;AAED;;;;;EAKE;AACF,MAAM,gBAAgB,GAAG;IACrB,EAAE,MAAM,EAAE,mCAAc,CAAC,aAAa,EAAE,WAAW,EAAE,sCAAc,EAAE;IACrE,EAAE,MAAM,EAAE,mCAAc,CAAC,cAAc,EAAE,WAAW,EAAE,sCAAc,EAAE;IACtE,EAAE,MAAM,EAAE,mCAAc,CAAC,UAAU,EAAE,WAAW,EAAE,sCAAc,EAAE;IAClE,EAAE,MAAM,EAAE,mCAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAA,0CAAkB,EAAC,QAAQ,CAAC,EAAE;IAC9E,EAAE,MAAM,EAAE,mCAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAA,0CAAkB,EAAC,QAAQ,CAAC,EAAE;IAC9E,EAAE,MAAM,EAAE,mCAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAA,0CAAkB,EAAC,QAAQ,CAAC,EAAE;CACjF,CAAC;AACF,8EAA8E;AAC9E,mFAAmF;AACnF,oFAAoF;AACpF,4CAA4C;AAC5C,MAAM,qBAAqB,GAAG;IAC1B,GAAG,gBAAgB;IACnB,EAAE,MAAM,EAAE,mCAAc,CAAC,SAAS,EAAE,WAAW,EAAE,IAAA,0CAAkB,EAAC,QAAQ,CAAC,EAAE;IAC/E,EAAE,MAAM,EAAE,mCAAc,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAA,0CAAkB,EAAC,QAAQ,CAAC,EAAE;CAC1F,CAAC;AACF,iEAAiE;AACjE,MAAM,iBAAiB,GAAG,gBAAgB,CAAC;AAE3C,MAAM,oBAAoB,GAAG,IAAA,kDAA0B,EAAC,sCAAsC,CAAC,CAAC;AAYhG;;;;;;;GAOG;AACH,SAAgB,oBAAoB,CAAC,IAAc,EAAE,UAAuC,EAAE;IAC1F,MAAM,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,qBAAqB,CAAC;IACtF,IAAI,CAAC,qBAAqB,CAAC,oBAAoB,CAAC,CAAC;IACjD,IAAI,IAAI,CAAC,SAAS,KAAK,iCAAS,CAAC,QAAQ,EAAE,CAAC;QACxC,MAAM,QAAQ,GAAG,IAAkB,CAAC;QACpC,QAAQ,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,IAAI,CAAC,SAAS,KAAK,iCAAS,CAAC,MAAM,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,IAAgB,CAAC;QAChC,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAC3C,CAAC;IACD,IAAI,IAAI,CAAC,SAAS,KAAK,iCAAS,CAAC,MAAM,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,IAAgB,CAAC;QAChC,MAAM,CAAC,kBAAkB,CAAC,WAAW,CAAC,CAAC;IAC3C,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,wBAAwB,CAAC,YAAY,EAAE,uCAAe,CAAC,OAAO,CAAC,CAAC;IACtF,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC3B,oBAAoB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACzC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"ensure_secure_access.js","sourceRoot":"","sources":["../../../api/helpers/ensure_secure_access.ts"],"names":[],"mappings":"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"}
@@ -92,14 +92,19 @@ export * from "./interfaces/state_machine/ua_transition_ex.js";
92
92
  export * from "./interfaces/ua_subscription_diagnostics_variable_ex.js";
93
93
  export { ensureDatatypeExtracted, ensureDatatypeExtractedWithCallback } from "./loader/ensure_datatype_extracted.js";
94
94
  export * from "./loader/generateAddressSpaceRaw.js";
95
- export { imageLinesToRecords, imageNodesetRecords, inflatedImageLines, isNodesetImage, NodesetImageError, type NodesetImageHeader, type NodesetImageTrailer, NodesetImageWriter, type NodesetImageWriterOptions, nodesetImageProblem, type ReadNodesetImageOptions, readNodesetImageInfo, setImageInflater } from "./loader/nodeset_image.js";
95
+ export { type NarrowedDataType, narrowInstanceDataTypes } from "./loader/narrow_instance_datatypes.js";
96
+ export { isNdjsonHeaderLine, NDJSON_IMAGE_FORMAT, NODESET2_XML_FORMAT, registerBuiltinNodesetFormats } from "./loader/nodeset_builtin_formats.js";
97
+ export { findNodesetFormat, type NodesetDocument, type NodesetFormat, type NodesetHead, type NodesetModel, type NodesetModelInfo, nodesetFormatByName, nodesetFormats, registerNodesetFormat } from "./loader/nodeset_format.js";
98
+ export { type ImageDeflater, imageLinesToRecords, imageNodesetRecords, inflatedImageLines, isNodesetImage, NodesetImageError, type NodesetImageHeader, type NodesetImageTrailer, NodesetImageWriter, type NodesetImageWriterOptions, nodesetImageProblem, type ReadNodesetImageOptions, readNodesetImageInfo, setImageDeflater, setImageInflater } from "./loader/nodeset_image.js";
96
99
  export { decodeValue, encodeValue, type JsonNodeId, type JsonQualifiedName, type JsonValue } from "./loader/nodeset_image_codec.js";
97
100
  export { MemoryNodesetImageStore, type NodesetImageStore, nodesetImageKey, sharedMemoryNodesetImageStore } from "./loader/nodeset_image_store.js";
98
101
  export { NODESET_RECORD_SCHEMA, type NodesetDataTypeDefinitionRecord, type NodesetDefinitionField, type NodesetHeaderRecord, type NodesetModelRecord, type NodesetNodeRecord, type NodesetRecord, type NodesetRecordConsumer, type NodesetRecordProducer, type NodesetReferenceRecord, type NodesetRolePermissionRecord, recordBytes, XmlExtensionObjectFragment } from "./loader/nodeset_record.js";
99
- export type { NamedNodesetSource, NodesetChunk, NodesetChunkStream, NodesetSource } from "./loader/nodeset_source.js";
102
+ export { type RecordsToNodeset2XMLOptions, recordsToNodeset2XML } from "./loader/nodeset_records_to_xml.js";
103
+ export type { NamedNodesetSource, NodesetChunk, NodesetChunkStream, NodesetContainer, NodesetSource, NodesetSourceKind } from "./loader/nodeset_source.js";
100
104
  export { sha256Hex } from "./loader/nodeset_source.js";
101
105
  export * from "./loader/nodeset_source_helpers.js";
102
106
  export { type NodesetToImageOptions, nodesetToImage } from "./loader/nodeset_to_image.js";
107
+ export { headerComplete, parseDependencies, sliceHeader } from "./loader/nodeset_xml_header.js";
103
108
  export { makeXmlNodesetRecordReader, type XmlNodesetRecordReader, xmlNodesetRecords } from "./loader/nodeset_xml_producer.js";
104
109
  export * from "./loader/register_node_promoter.js";
105
110
  export * from "./namespace.js";