node-opcua-file-transfer 2.51.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/readme.md ADDED
@@ -0,0 +1,204 @@
1
+ ## introduction
2
+
3
+ this package provides an implementation of the OPCUA File Transfer
4
+ specification as per OPCUA 1.0.4 part 5 Annex C (page 99)
5
+
6
+
7
+ ## installation
8
+
9
+ ```bash
10
+ $ npm install node-opcua
11
+ $ npm install node-opcua-file-transfer
12
+ ```
13
+
14
+ ## exposing a File node in the addressSpace
15
+
16
+ ```javascript
17
+ import { OPCUAServer, UAFileType } from "node-opcua";
18
+ import { installFileType } from "node-opcua-file-transfer";
19
+
20
+ ```
21
+
22
+ ```typescript
23
+
24
+ const server = new OPCUAServer({
25
+
26
+ });
27
+
28
+ await server.initialize();
29
+
30
+
31
+ // let say we want to create a access to this file:
32
+ const my_data_filename = "/data/someFile.txt";
33
+ await promisify(fs.writeFile)(my_data_filename, "some content", "utf8");
34
+
35
+
36
+ // now add a file object in the address Space
37
+ const addressSpace = server.engine.addressSpace;
38
+
39
+ // retrieve the FileType UAObjectType
40
+ const fileType = addressSpace.findObjectType("FileType")!;
41
+
42
+ // create a instance of FileType
43
+ const myFile = fileType.instantiate({
44
+ nodeId: "s=MyFile",
45
+ browseName: "MyFile",
46
+ organizedBy: addressSpace.rootFolder.objects
47
+ }) as UAFileType;
48
+
49
+ // now bind the opcuaFile object with our file
50
+ installFileType(opcuaFile, {
51
+ filename: my_data_filename
52
+ });
53
+
54
+ ```
55
+
56
+
57
+ ## accessing a File node from a node-opcua client : ClientFile
58
+
59
+ We assume that we have a valid OPCUA Client Session
60
+
61
+ ```javascript
62
+ import { OPCUAClient } from "node-opcua";
63
+ import { ClientFile } from "node-opcua-file-transfer";
64
+
65
+ const client = OPCUAClient.create({});
66
+
67
+ await client.connect();
68
+ const session = await client.createSession();
69
+ ```
70
+
71
+ Let's assume that the nodeId of the file object is "ns=1;s=MyFile"
72
+
73
+ ```javascript
74
+ import {ClientFile,OpenFileMode } from "node-opcua-file-transfer";
75
+
76
+ //
77
+ const fileNodeId = "ns=1;s=MyFile";
78
+
79
+ // let's create a client file object from the session and nodeId
80
+ const clientFile = new ClientFile(session, fileNodeId);
81
+
82
+ // let's open the file
83
+ const mode = OpenFileMode.ReadWriteAppend;
84
+ await clientFile.open(mode);
85
+
86
+ // ... do some reading or writing
87
+
88
+ // don't forget to close the file when done
89
+ await clientFile.close();
90
+ ```
91
+
92
+ ### operations
93
+
94
+ #### ClientFile#size(): Promise<UInt64> : get file size
95
+
96
+ You can read the size of the file at any time.
97
+ ``` javascript
98
+ const clientFile = new ClientFile(session, fileNodeId);
99
+
100
+ const size = await clientFile.size();
101
+ console.log("the current file size is : ",size," bytes);
102
+ ```
103
+
104
+ #### ClientFile#open(mode: OpenFileMode) : open a file
105
+
106
+ ```javascript
107
+ const mode = OpenFileMode.ReadWriteAppend;
108
+ await clientFile.open(mode);
109
+ ```
110
+
111
+ #### OpenFileMode enumeration
112
+
113
+ | Mode | Description |
114
+ |------|-------------|
115
+ | Read |The file is opened for reading. the Write method cannot be executed. |
116
+ | Write|The file is opened for writing. the Read cannot be executed. |
117
+ | ReadWrite| The file is opened for reading an writing |
118
+ | WriteEraseExisting | File is opened for writing only. The existing content of the file is erased and an empty file is provided. |
119
+ | ReadWriteEraseExisting | File is opened for reading and writing. The existing content of the file is erased and an empty file is provided.|
120
+ | WriteAppend | File is opened for writing only and position is set at the end of the file |
121
+ | ReadWriteAppend | File is opened for reading and writing and position is set at the end of the file |
122
+ }
123
+ #### ClientFile#close() : close a file
124
+
125
+ ```javascript
126
+ const mode = OpenFileMode.ReadWriteAppend;
127
+ await clientFile.open(mode);
128
+
129
+ // ... do some reading or writing
130
+
131
+ // don't forget to close the file when done
132
+ await clientFile.close();
133
+ ```
134
+ #### ClientFile#setPosition(pos: UInt64) : Setting read/write position
135
+
136
+ Once the file is opened, the position for reading or writing can be set
137
+ using setPosition.
138
+ * setPosition expect a UInt64 parameter (see note below)
139
+ * setPosition will throw an exception if file is not opened
140
+
141
+ ``` javascript
142
+ const clientFile = new ClientFile(session, fileNodeId);
143
+
144
+ const mode = OpenFileMode.ReadWriteAppend;
145
+ await clientFile.open(mode);
146
+
147
+ await client.setPosition([0,32]);
148
+
149
+ ```
150
+
151
+ #### ClientFile#getPosition(): Promise<UInt64> Getting read/write position
152
+ Once the file is opened, the position for reading or writing can be retrieved
153
+ using getPosition.
154
+ * getPosition returns a UInt64 parameter (see note below)
155
+ * getPosition will throw an exception if file is not opened
156
+
157
+ ``` javascript
158
+ const clientFile = new ClientFile(session, fileNodeId);
159
+
160
+ const mode = OpenFileMode.ReadWriteAppend;
161
+ await clientFile.open(mode);
162
+
163
+ await client.setPosition(32);
164
+
165
+ ```
166
+
167
+ #### ClientFile#write(buf: Buffer) : Writing data to the file
168
+
169
+ Data can be writing to the file at the current cursor position.
170
+ Data must be passed in a buffer.
171
+ * write will throw an exception if file is not opened
172
+
173
+ ```javascript
174
+ const dataToWrite = Buffer.from("Some data");
175
+
176
+ const mode = OpenFileMode.ReadWriteAppend;
177
+ await clientFile.open(mode);
178
+ await clientFile.write(dataToWrite);
179
+ ```
180
+
181
+
182
+ #### ClientFile#read(): Promise<Buffer> :reading data to the file
183
+
184
+ Data can be writing to the file at the current cursor position.
185
+ Data must be passed in a buffer.
186
+ * read will throw an exception if file is not opened
187
+
188
+ ```javascript
189
+
190
+ const mode = OpenFileMode.ReadWriteAppend;
191
+ await clientFile.open(mode);
192
+
193
+ // read 200 bytes from position 32
194
+ await clientFile.setPosition([0,32]);
195
+ const data: Buffer = await clientFile.read(200);
196
+ ```
197
+
198
+ ### notes
199
+
200
+ UInt64
201
+ * At this time BigInt, is not supported by all version of nodeJs that are targeted by node-opcua.
202
+ UInt64 values are currently stored into a array of 2 32bits numbers : [ High,Low]
203
+
204
+
@@ -0,0 +1,322 @@
1
+ /**
2
+ * @module node-opcua-file-transfer
3
+ */
4
+ import { Byte, Int32, UInt16, UInt32, UInt64 } from "node-opcua-basic-types";
5
+ import { AttributeIds } from "node-opcua-data-model";
6
+ import { NodeId, resolveNodeId } from "node-opcua-nodeid";
7
+ import { IBasicSession } from "node-opcua-pseudo-session";
8
+ import { ReadValueIdOptions } from "node-opcua-service-read";
9
+ import { BrowsePath, makeBrowsePath } from "node-opcua-service-translate-browse-path";
10
+ import { StatusCodes } from "node-opcua-status-code";
11
+ import { DataType, VariantArrayType } from "node-opcua-variant";
12
+ import { MethodIds } from "node-opcua-constants";
13
+
14
+ import { checkDebugFlag, make_debugLog, make_errorLog } from "node-opcua-debug";
15
+
16
+ const debugLog = make_debugLog("FileType");
17
+ const errorLog = make_errorLog("FileType");
18
+ const doDebug = checkDebugFlag("FileType");
19
+
20
+ import { OpenFileMode } from "../open_mode";
21
+ export { OpenFileMode } from "../open_mode";
22
+
23
+ /**
24
+ *
25
+ *
26
+ */
27
+ export class ClientFile {
28
+
29
+ public static useGlobalMethod = false;
30
+
31
+ public fileHandle = 0;
32
+ protected session: IBasicSession;
33
+ protected readonly fileNodeId: NodeId;
34
+
35
+ private openMethodNodeId?: NodeId;
36
+ private closeMethodNodeId?: NodeId;
37
+ private setPositionNodeId?: NodeId;
38
+ private getPositionNodeId?: NodeId;
39
+ private readNodeId?: NodeId;
40
+ private writeNodeId?: NodeId;
41
+ private openCountNodeId?: NodeId;
42
+ private sizeNodeId?: NodeId;
43
+
44
+ constructor(session: IBasicSession, nodeId: NodeId) {
45
+ this.session = session;
46
+ this.fileNodeId = nodeId;
47
+ }
48
+
49
+ public async open(mode: OpenFileMode): Promise<number> {
50
+
51
+ if (mode === null || mode === undefined) {
52
+ throw new Error("expecting a validMode " + OpenFileMode[mode]);
53
+ }
54
+ if (this.fileHandle) {
55
+ throw new Error("File has already be opened");
56
+ }
57
+ await this.ensureInitialized();
58
+
59
+ const result = await this.session.call({
60
+ inputArguments: [
61
+ { dataType: DataType.Byte, value: mode as Byte }
62
+ ],
63
+ methodId: this.openMethodNodeId,
64
+ objectId: this.fileNodeId
65
+ });
66
+ if (result.statusCode !== StatusCodes.Good) {
67
+ debugLog("Cannot open file : ");
68
+ throw new Error("cannot open file statusCode = " + result.statusCode.toString() + " mode = " + OpenFileMode[mode]);
69
+ }
70
+
71
+ this.fileHandle = result.outputArguments![0].value;
72
+
73
+ return this.fileHandle;
74
+ }
75
+
76
+ public async close(): Promise<void> {
77
+ if (!this.fileHandle) {
78
+ throw new Error("File has not been opened yet");
79
+ }
80
+ await this.ensureInitialized();
81
+
82
+ const result = await this.session.call({
83
+ inputArguments: [
84
+ { dataType: DataType.UInt32, value: this.fileHandle }
85
+ ],
86
+ methodId: this.closeMethodNodeId,
87
+ objectId: this.fileNodeId
88
+ });
89
+ if (result.statusCode !== StatusCodes.Good) {
90
+ debugLog("Cannot close file : ");
91
+ throw new Error("cannot close file statusCode = " + result.statusCode.toString());
92
+ }
93
+
94
+ this.fileHandle = 0;
95
+ }
96
+
97
+ public async getPosition(): Promise<UInt64> {
98
+ await this.ensureInitialized();
99
+ if (!this.fileHandle) {
100
+ throw new Error("File has not been opened yet");
101
+ }
102
+
103
+ const result = await this.session.call({
104
+ inputArguments: [
105
+ { dataType: DataType.UInt32, value: this.fileHandle }
106
+ ],
107
+ methodId: this.getPositionNodeId,
108
+ objectId: this.fileNodeId
109
+ });
110
+ if (result.statusCode !== StatusCodes.Good) {
111
+ throw new Error("Error " + result.statusCode.toString());
112
+ }
113
+ return result.outputArguments![0].value as UInt64;
114
+ }
115
+
116
+ public async setPosition(position: UInt64 | UInt32): Promise<void> {
117
+ await this.ensureInitialized();
118
+ if (!this.fileHandle) {
119
+ throw new Error("File has not been opened yet");
120
+ }
121
+ if (typeof position === "number") {
122
+ position = [0, position];
123
+ }
124
+ const result = await this.session.call({
125
+ inputArguments: [
126
+ { dataType: DataType.UInt32, value: this.fileHandle },
127
+ {
128
+ arrayType: VariantArrayType.Scalar,
129
+ dataType: DataType.UInt64,
130
+ value: position
131
+ }
132
+ ],
133
+ methodId: this.setPositionNodeId,
134
+ objectId: this.fileNodeId
135
+ });
136
+ if (result.statusCode !== StatusCodes.Good) {
137
+ throw new Error("Error " + result.statusCode.toString());
138
+ }
139
+ return;
140
+ }
141
+
142
+ public async read(bytesToRead: Int32): Promise<Buffer> {
143
+ await this.ensureInitialized();
144
+ if (!this.fileHandle) {
145
+ throw new Error("File has not been opened yet");
146
+ }
147
+ const result = await this.session.call({
148
+ inputArguments: [
149
+ { dataType: DataType.UInt32, value: this.fileHandle },
150
+ {
151
+ arrayType: VariantArrayType.Scalar,
152
+ dataType: DataType.Int32,
153
+ value: bytesToRead
154
+ }
155
+ ],
156
+ methodId: this.readNodeId,
157
+ objectId: this.fileNodeId
158
+ });
159
+ if (result.statusCode !== StatusCodes.Good) {
160
+ throw new Error("Error " + result.statusCode.toString());
161
+ }
162
+ if (!result.outputArguments || result.outputArguments[0].dataType !== DataType.ByteString) {
163
+ throw new Error("Error invalid output");
164
+ }
165
+ return result.outputArguments![0].value as Buffer;
166
+ }
167
+
168
+ public async write(data: Buffer): Promise<void> {
169
+ await this.ensureInitialized();
170
+ if (!this.fileHandle) {
171
+ throw new Error("File has not been opened yet");
172
+ }
173
+ const result = await this.session.call({
174
+ inputArguments: [
175
+ { dataType: DataType.UInt32, value: this.fileHandle },
176
+ {
177
+ arrayType: VariantArrayType.Scalar,
178
+ dataType: DataType.ByteString,
179
+ value: data
180
+ }
181
+ ],
182
+ methodId: this.writeNodeId,
183
+ objectId: this.fileNodeId
184
+ });
185
+ if (result.statusCode !== StatusCodes.Good) {
186
+ throw new Error("Error " + result.statusCode.toString());
187
+ }
188
+ return;
189
+ }
190
+
191
+ public async openCount(): Promise<UInt16> {
192
+ await this.ensureInitialized();
193
+ const nodeToRead: ReadValueIdOptions = { nodeId: this.openCountNodeId!, attributeId: AttributeIds.Value };
194
+ const dataValue = await this.session.read(nodeToRead);
195
+
196
+ if (doDebug) {
197
+ debugLog(" OpenCount ", nodeToRead.nodeId!.toString(), dataValue.toString());
198
+ }
199
+ return dataValue.value.value;
200
+ }
201
+
202
+ public async size(): Promise<UInt64> {
203
+ await this.ensureInitialized();
204
+ const nodeToRead = { nodeId: this.sizeNodeId, attributeId: AttributeIds.Value };
205
+ const dataValue = await this.session.read(nodeToRead);
206
+ return dataValue.value.value;
207
+ }
208
+
209
+ // eslint-disable-next-line max-statements
210
+ protected async extractMethodsIds(): Promise<void> {
211
+
212
+ if (ClientFile.useGlobalMethod) {
213
+ debugLog("Using GlobalMethodId");
214
+ this.openMethodNodeId = resolveNodeId(MethodIds.FileType_Open);
215
+ this.closeMethodNodeId = resolveNodeId(MethodIds.FileType_Close);
216
+ this.setPositionNodeId = resolveNodeId(MethodIds.FileType_SetPosition);
217
+ this.getPositionNodeId = resolveNodeId(MethodIds.FileType_GetPosition);
218
+ this.writeNodeId = resolveNodeId(MethodIds.FileType_Write);
219
+ this.readNodeId = resolveNodeId(MethodIds.FileType_Read);
220
+ const browsePaths: BrowsePath[] = [
221
+ makeBrowsePath(this.fileNodeId, "/OpenCount"),
222
+ makeBrowsePath(this.fileNodeId, "/Size")
223
+ ];
224
+ const results = await this.session.translateBrowsePath(browsePaths);
225
+ if (results[0].statusCode !== StatusCodes.Good) {
226
+ throw new Error("fileType object does not expose mandatory OpenCount Property");
227
+ }
228
+ if (results[1].statusCode !== StatusCodes.Good) {
229
+ throw new Error("fileType object does not expose mandatory Size Property");
230
+ }
231
+ this.openCountNodeId = results[0].targets![0].targetId;
232
+ this.sizeNodeId = results[1].targets![0].targetId;
233
+ return;
234
+ }
235
+ const browsePaths: BrowsePath[] = [
236
+ makeBrowsePath(this.fileNodeId, "/Open"),
237
+ makeBrowsePath(this.fileNodeId, "/Close"),
238
+ makeBrowsePath(this.fileNodeId, "/SetPosition"),
239
+ makeBrowsePath(this.fileNodeId, "/GetPosition"),
240
+ makeBrowsePath(this.fileNodeId, "/Write"),
241
+ makeBrowsePath(this.fileNodeId, "/Read"),
242
+ makeBrowsePath(this.fileNodeId, "/OpenCount"),
243
+ makeBrowsePath(this.fileNodeId, "/Size")
244
+ ];
245
+
246
+ const results = await this.session.translateBrowsePath(browsePaths);
247
+
248
+ if (results[0].statusCode !== StatusCodes.Good) {
249
+ throw new Error("fileType object does not expose mandatory Open Method");
250
+ }
251
+ if (results[1].statusCode !== StatusCodes.Good) {
252
+ throw new Error("fileType object does not expose mandatory Close Method");
253
+ }
254
+ if (results[2].statusCode !== StatusCodes.Good) {
255
+ throw new Error("fileType object does not expose mandatory SetPosition Method");
256
+ }
257
+ if (results[3].statusCode !== StatusCodes.Good) {
258
+ throw new Error("fileType object does not expose mandatory GetPosition Method");
259
+ }
260
+ if (results[4].statusCode !== StatusCodes.Good) {
261
+ throw new Error("fileType object does not expose mandatory Write Method");
262
+ }
263
+ if (results[5].statusCode !== StatusCodes.Good) {
264
+ throw new Error("fileType object does not expose mandatory Read Method");
265
+ }
266
+ if (results[6].statusCode !== StatusCodes.Good) {
267
+ throw new Error("fileType object does not expose mandatory OpenCount Variable");
268
+ }
269
+ if (results[7].statusCode !== StatusCodes.Good) {
270
+ throw new Error("fileType object does not expose mandatory Size Variable");
271
+ }
272
+
273
+ if (false && doDebug) {
274
+ results.map((x: any) => debugLog(x.toString()));
275
+ }
276
+ this.openMethodNodeId = results[0].targets![0].targetId;
277
+ this.closeMethodNodeId = results[1].targets![0].targetId;
278
+ this.setPositionNodeId = results[2].targets![0].targetId;
279
+ this.getPositionNodeId = results[3].targets![0].targetId;
280
+ this.writeNodeId = results[4].targets![0].targetId;
281
+ this.readNodeId = results[5].targets![0].targetId;
282
+ this.openCountNodeId = results[6].targets![0].targetId;
283
+ this.sizeNodeId = results[7].targets![0].targetId;
284
+ }
285
+
286
+ protected async ensureInitialized(): Promise<void> {
287
+ if (!this.openMethodNodeId) {
288
+ await this.extractMethodsIds();
289
+ }
290
+ }
291
+ }
292
+
293
+ /**
294
+ * 5.2.10 UserRolePermissions
295
+ *
296
+ * The optional UserRolePermissions Attribute specifies the Permissions that apply to a Node for
297
+ * all Roles granted to current Session. The value of the Attribute is an array of
298
+ * RolePermissionType Structures (see Table 8).
299
+ * Clients may determine their effective Permissions by logically ORing the Permissions for each
300
+ * Role in the array.
301
+ * The value of this Attribute is derived from the rules used by the Server to map Sessions to
302
+ * Roles. This mapping may be vendor specific or it may use the standard Role model defined in 4.8.
303
+ * This Attribute shall not be writeable.
304
+ * If not specified, the value of DefaultUserRolePermissions Property from the Namespace
305
+ * Metadata Object associated with the Node is used instead. If the NamespaceMetadata Object
306
+ * does not define the Property or does not exist, then the Server does not publish any information
307
+ * about Roles mapped to the current Session.
308
+ *
309
+ *
310
+ * 5.2.11 AccessRestrictions
311
+ * The optional AccessRestrictions Attribute specifies the AccessRestrictions that apply to a Node.
312
+ * Its data type is defined in 8.56. If a Server supports AccessRestrictions for a particular
313
+ * Namespace it adds the DefaultAccessRestrictions Property to the NamespaceMetadata Object
314
+ * for that Namespace (see Figure 8). If a particular Node in the Namespace needs to override
315
+ * the default value the Server adds the AccessRestrictions Attribute to the Node.
316
+ * If a Server implements a vendor specific access restriction model for a Namespace, it does not
317
+ * add the DefaultAccessRestrictions Property to the NamespaceMetadata Object.
318
+ *
319
+ *
320
+ * DefaultAccessRestrictions
321
+ *
322
+ */
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @module node-opcua-file-transfer
3
+ */
4
+ export * from "./client/client_file";
5
+ export * from "./server/file_type_helpers";
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @module node-opcua-file-transfer-server
3
+ */
4
+ export enum OpenFileModeMask {
5
+ ReadBit = 0x01,
6
+ WriteBit = 0x02,
7
+ EraseExistingBit = 0x04,
8
+ AppendBit = 0x08,
9
+ }
10
+
11
+ export enum OpenFileMode {
12
+ /**
13
+ * Read bit 0 The file is opened for reading. If this bit is not
14
+ * set the Read Method cannot be executed.
15
+ */
16
+ Read = OpenFileModeMask.ReadBit,
17
+ /**
18
+ * Write bit 1 The file is opened for writing. If this bit is not
19
+ * set the Write Method cannot be executed.
20
+ *
21
+ */
22
+ Write = OpenFileModeMask.WriteBit,
23
+ ReadWrite = OpenFileModeMask.ReadBit + OpenFileModeMask.WriteBit,
24
+
25
+ /**
26
+ *
27
+ * WriteEraseExisting
28
+ * EraseExisting 2 This bit can only be set if the file is opened for writing
29
+ * (Write bit is set). The existing content of the file is
30
+ * erased and an empty file is provided.
31
+ */
32
+ WriteEraseExisting = OpenFileModeMask.EraseExistingBit + OpenFileModeMask.WriteBit,
33
+ ReadWriteEraseExisting = OpenFileModeMask.EraseExistingBit + OpenFileModeMask.WriteBit + OpenFileModeMask.ReadBit,
34
+ /**
35
+ * Append 3 When the Append bit is set the file is opened at end
36
+ * of the file, otherwise at begin of the file.
37
+ * The SetPosition Method can be used to change the position.
38
+ */
39
+ WriteAppend = OpenFileModeMask.AppendBit + OpenFileModeMask.WriteBit,
40
+ ReadWriteAppend = OpenFileModeMask.AppendBit + OpenFileModeMask.WriteBit + OpenFileModeMask.ReadBit,
41
+ }