node-opcua-transport 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/.mocharc.yml +10 -0
- package/LICENSE +20 -0
- package/dist/source/AcknowledgeMessage.d.ts +27 -0
- package/dist/source/AcknowledgeMessage.js +79 -0
- package/dist/source/AcknowledgeMessage.js.map +1 -0
- package/dist/source/HelloMessage.d.ts +27 -0
- package/dist/source/HelloMessage.js +95 -0
- package/dist/source/HelloMessage.js.map +1 -0
- package/dist/source/TCPErrorMessage.d.ts +18 -0
- package/dist/source/TCPErrorMessage.js +47 -0
- package/dist/source/TCPErrorMessage.js.map +1 -0
- package/dist/source/client_tcp_transport.d.ts +68 -0
- package/dist/source/client_tcp_transport.js +315 -0
- package/dist/source/client_tcp_transport.js.map +1 -0
- package/dist/source/index.d.ts +11 -0
- package/dist/source/index.js +24 -0
- package/dist/source/index.js.map +1 -0
- package/dist/source/message_builder_base.d.ts +61 -0
- package/dist/source/message_builder_base.js +207 -0
- package/dist/source/message_builder_base.js.map +1 -0
- package/dist/source/server_tcp_transport.d.ts +45 -0
- package/dist/source/server_tcp_transport.js +232 -0
- package/dist/source/server_tcp_transport.js.map +1 -0
- package/dist/source/tcp_transport.d.ts +117 -0
- package/dist/source/tcp_transport.js +350 -0
- package/dist/source/tcp_transport.js.map +1 -0
- package/dist/source/tools.d.ts +13 -0
- package/dist/source/tools.js +99 -0
- package/dist/source/tools.js.map +1 -0
- package/dist/source/utils.d.ts +2 -0
- package/dist/source/utils.js +9 -0
- package/dist/source/utils.js.map +1 -0
- package/dist/test-fixtures/fixture_full_tcp_packets.d.ts +21 -0
- package/dist/test-fixtures/fixture_full_tcp_packets.js +413 -0
- package/dist/test-fixtures/fixture_full_tcp_packets.js.map +1 -0
- package/dist/test-fixtures/index.d.ts +1 -0
- package/dist/test-fixtures/index.js +14 -0
- package/dist/test-fixtures/index.js.map +1 -0
- package/dist/test_helpers/direct_transport.d.ts +14 -0
- package/dist/test_helpers/direct_transport.js +63 -0
- package/dist/test_helpers/direct_transport.js.map +1 -0
- package/dist/test_helpers/fake_server.d.ts +15 -0
- package/dist/test_helpers/fake_server.js +56 -0
- package/dist/test_helpers/fake_server.js.map +1 -0
- package/dist/test_helpers/half_com_channel.d.ts +10 -0
- package/dist/test_helpers/half_com_channel.js +35 -0
- package/dist/test_helpers/half_com_channel.js.map +1 -0
- package/dist/test_helpers/index.d.ts +4 -0
- package/dist/test_helpers/index.js +17 -0
- package/dist/test_helpers/index.js.map +1 -0
- package/dist/test_helpers/socket_transport.d.ts +8 -0
- package/dist/test_helpers/socket_transport.js +30 -0
- package/dist/test_helpers/socket_transport.js.map +1 -0
- package/package.json +50 -0
- package/source/AcknowledgeMessage.ts +112 -0
- package/source/HelloMessage.ts +133 -0
- package/source/TCPErrorMessage.ts +57 -0
- package/source/client_tcp_transport.ts +366 -0
- package/source/index.ts +11 -0
- package/source/message_builder_base.ts +263 -0
- package/source/server_tcp_transport.ts +284 -0
- package/source/tcp_transport.ts +450 -0
- package/source/tools.ts +113 -0
- package/source/utils.ts +4 -0
- package/test_helpers/direct_transport.ts +78 -0
- package/test_helpers/fake_server.ts +71 -0
- package/test_helpers/half_com_channel.ts +38 -0
- package/test_helpers/index.ts +4 -0
- package/test_helpers/socket_transport.ts +34 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module node-opcua-transport
|
|
3
|
+
*/
|
|
4
|
+
// tslint:disable:class-name
|
|
5
|
+
// system
|
|
6
|
+
import * as chalk from "chalk";
|
|
7
|
+
import { Socket } from "net";
|
|
8
|
+
import { assert } from "node-opcua-assert";
|
|
9
|
+
|
|
10
|
+
// opcua requires
|
|
11
|
+
import { BinaryStream } from "node-opcua-binary-stream";
|
|
12
|
+
import { verify_message_chunk } from "node-opcua-chunkmanager";
|
|
13
|
+
import { StatusCode, StatusCodes } from "node-opcua-status-code";
|
|
14
|
+
import { ErrorCallback } from "node-opcua-status-code";
|
|
15
|
+
|
|
16
|
+
// this package requires
|
|
17
|
+
import { AcknowledgeMessage } from "./AcknowledgeMessage";
|
|
18
|
+
import { HelloMessage } from "./HelloMessage";
|
|
19
|
+
import { TCP_transport } from "./tcp_transport";
|
|
20
|
+
import { TCPErrorMessage } from "./TCPErrorMessage";
|
|
21
|
+
import { decodeMessage, packTcpMessage } from "./tools";
|
|
22
|
+
|
|
23
|
+
import * as debug from "node-opcua-debug";
|
|
24
|
+
import { doTraceHelloAck } from "./utils";
|
|
25
|
+
|
|
26
|
+
const hexDump = debug.hexDump;
|
|
27
|
+
const debugLog = debug.make_debugLog(__filename);
|
|
28
|
+
const errorLog = debug.make_errorLog(__filename);
|
|
29
|
+
const doDebug = debug.checkDebugFlag(__filename);
|
|
30
|
+
|
|
31
|
+
type CallbackFunc = (err: null | Error) => void;
|
|
32
|
+
|
|
33
|
+
function clamp_value(value: number, minVal: number, maxVal: number): number {
|
|
34
|
+
assert(minVal < maxVal);
|
|
35
|
+
if (value === 0) {
|
|
36
|
+
return maxVal;
|
|
37
|
+
}
|
|
38
|
+
if (value < minVal) {
|
|
39
|
+
return minVal;
|
|
40
|
+
}
|
|
41
|
+
/* istanbul ignore next*/
|
|
42
|
+
if (value >= maxVal) {
|
|
43
|
+
return maxVal;
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const minimumBufferSize = 8192;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @class ServerTCP_transport
|
|
52
|
+
* @extends TCP_transport
|
|
53
|
+
* @constructor
|
|
54
|
+
*
|
|
55
|
+
*/
|
|
56
|
+
export class ServerTCP_transport extends TCP_transport {
|
|
57
|
+
public static throttleTime: number = 1000;
|
|
58
|
+
|
|
59
|
+
public receiveBufferSize: number;
|
|
60
|
+
public sendBufferSize: number;
|
|
61
|
+
public maxMessageSize: number;
|
|
62
|
+
public maxChunkCount: number;
|
|
63
|
+
public protocolVersion: number;
|
|
64
|
+
|
|
65
|
+
private _aborted: number;
|
|
66
|
+
private _helloReceived: boolean;
|
|
67
|
+
|
|
68
|
+
constructor() {
|
|
69
|
+
super();
|
|
70
|
+
this._aborted = 0;
|
|
71
|
+
this._helloReceived = false;
|
|
72
|
+
this.receiveBufferSize = 0;
|
|
73
|
+
this.sendBufferSize = 0;
|
|
74
|
+
this.maxMessageSize = 0;
|
|
75
|
+
this.maxChunkCount = 0;
|
|
76
|
+
this.protocolVersion = 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected _write_chunk(messageChunk: Buffer): void {
|
|
80
|
+
if (this.sendBufferSize > 0 && messageChunk.length > this.sendBufferSize) {
|
|
81
|
+
errorLog(
|
|
82
|
+
"write chunk exceed sendBufferSize messageChunk length = ",
|
|
83
|
+
messageChunk.length,
|
|
84
|
+
"sendBufferSize = ",
|
|
85
|
+
this.sendBufferSize
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
super._write_chunk(messageChunk);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Initialize the server transport.
|
|
92
|
+
*
|
|
93
|
+
*
|
|
94
|
+
* The ServerTCP_transport initialization process starts by waiting for the client to send a "HEL" message.
|
|
95
|
+
*
|
|
96
|
+
* The ServerTCP_transport replies with a "ACK" message and then start waiting for further messages of any size.
|
|
97
|
+
*
|
|
98
|
+
* The callback function received an error:
|
|
99
|
+
* - if no message from the client is received within the ```self.timeout``` period,
|
|
100
|
+
* - or, if the connection has dropped within the same interval.
|
|
101
|
+
* - if the protocol version specified within the HEL message is invalid or is greater
|
|
102
|
+
* than ```self.protocolVersion```
|
|
103
|
+
*
|
|
104
|
+
*
|
|
105
|
+
*/
|
|
106
|
+
public init(socket: Socket, callback: ErrorCallback) {
|
|
107
|
+
if (debugLog) {
|
|
108
|
+
debugLog(chalk.cyan("init socket"));
|
|
109
|
+
}
|
|
110
|
+
assert(!this._socket, "init already called!");
|
|
111
|
+
assert(typeof callback === "function", "expecting a valid callback ");
|
|
112
|
+
this._install_socket(socket);
|
|
113
|
+
this._install_HEL_message_receiver(callback);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public abortWithError(statusCode: StatusCode, extraErrorDescription: string, callback: ErrorCallback) {
|
|
117
|
+
return this._abortWithError(statusCode, extraErrorDescription, callback);
|
|
118
|
+
}
|
|
119
|
+
private _abortWithError(statusCode: StatusCode, extraErrorDescription: string, callback: ErrorCallback) {
|
|
120
|
+
if (debugLog) {
|
|
121
|
+
debugLog(chalk.cyan("_abortWithError"));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
assert(typeof callback === "function", "expecting a callback");
|
|
125
|
+
|
|
126
|
+
/* istanbul ignore else */
|
|
127
|
+
if (!this._aborted) {
|
|
128
|
+
this._aborted = 1;
|
|
129
|
+
setTimeout(() => {
|
|
130
|
+
// send the error message and close the connection
|
|
131
|
+
assert(StatusCodes.hasOwnProperty(statusCode.name));
|
|
132
|
+
|
|
133
|
+
/* istanbul ignore next*/
|
|
134
|
+
if (doDebug) {
|
|
135
|
+
debugLog(chalk.red(" Server aborting because ") + chalk.cyan(statusCode.name));
|
|
136
|
+
debugLog(chalk.red(" extraErrorDescription ") + chalk.cyan(extraErrorDescription));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const errorResponse = new TCPErrorMessage({
|
|
140
|
+
reason: statusCode.description,
|
|
141
|
+
statusCode
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const messageChunk = packTcpMessage("ERR", errorResponse);
|
|
145
|
+
|
|
146
|
+
this.write(messageChunk);
|
|
147
|
+
|
|
148
|
+
this.disconnect(() => {
|
|
149
|
+
this._aborted = 2;
|
|
150
|
+
callback(new Error(extraErrorDescription + " StatusCode = " + statusCode.name));
|
|
151
|
+
});
|
|
152
|
+
}, ServerTCP_transport.throttleTime);
|
|
153
|
+
} else {
|
|
154
|
+
callback(new Error(statusCode.name));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private _send_ACK_response(helloMessage: HelloMessage) {
|
|
159
|
+
assert(helloMessage.receiveBufferSize >= minimumBufferSize);
|
|
160
|
+
assert(helloMessage.sendBufferSize >= minimumBufferSize);
|
|
161
|
+
|
|
162
|
+
this.receiveBufferSize = clamp_value(helloMessage.receiveBufferSize, 8192, 512 * 1024);
|
|
163
|
+
this.sendBufferSize = clamp_value(helloMessage.sendBufferSize, 8192, 512 * 1024);
|
|
164
|
+
this.maxMessageSize = clamp_value(helloMessage.maxMessageSize, 100000, 64 * 1024 * 1024);
|
|
165
|
+
this.maxChunkCount = clamp_value(helloMessage.maxChunkCount, 0, 65535);
|
|
166
|
+
|
|
167
|
+
// istanbul ignore next
|
|
168
|
+
if (doTraceHelloAck) {
|
|
169
|
+
console.log(`received Hello \n${helloMessage.toString()}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
debugLog("Client accepts only message of size => ", this.maxMessageSize);
|
|
173
|
+
|
|
174
|
+
const acknowledgeMessage = new AcknowledgeMessage({
|
|
175
|
+
maxChunkCount: this.maxChunkCount,
|
|
176
|
+
maxMessageSize: this.maxMessageSize,
|
|
177
|
+
protocolVersion: this.protocolVersion,
|
|
178
|
+
receiveBufferSize: this.receiveBufferSize,
|
|
179
|
+
sendBufferSize: this.sendBufferSize
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// istanbul ignore next
|
|
183
|
+
if (doTraceHelloAck) {
|
|
184
|
+
console.log(`sending Ack \n${acknowledgeMessage.toString()}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const messageChunk = packTcpMessage("ACK", acknowledgeMessage);
|
|
188
|
+
|
|
189
|
+
/* istanbul ignore next*/
|
|
190
|
+
if (doDebug) {
|
|
191
|
+
verify_message_chunk(messageChunk);
|
|
192
|
+
debugLog("server send: " + chalk.yellow("ACK"));
|
|
193
|
+
debugLog("server send: " + hexDump(messageChunk));
|
|
194
|
+
debugLog("acknowledgeMessage=", acknowledgeMessage);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// send the ACK reply
|
|
198
|
+
this.write(messageChunk);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private _install_HEL_message_receiver(callback: ErrorCallback) {
|
|
202
|
+
if (debugLog) {
|
|
203
|
+
debugLog(chalk.cyan("_install_HEL_message_receiver "));
|
|
204
|
+
}
|
|
205
|
+
this._install_one_time_message_receiver((err?: Error | null, data?: Buffer) => {
|
|
206
|
+
if (err) {
|
|
207
|
+
this._abortWithError(StatusCodes.BadConnectionRejected, err.message, callback);
|
|
208
|
+
} else {
|
|
209
|
+
// handle the HEL message
|
|
210
|
+
this._on_HEL_message(data!, callback);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private _on_HEL_message(data: Buffer, callback: ErrorCallback) {
|
|
216
|
+
if (debugLog) {
|
|
217
|
+
debugLog(chalk.cyan("_on_HEL_message"));
|
|
218
|
+
}
|
|
219
|
+
assert(!this._helloReceived);
|
|
220
|
+
const stream = new BinaryStream(data);
|
|
221
|
+
const msgType = data.slice(0, 3).toString("ascii");
|
|
222
|
+
|
|
223
|
+
/* istanbul ignore next*/
|
|
224
|
+
if (doDebug) {
|
|
225
|
+
debugLog("SERVER received " + chalk.yellow(msgType));
|
|
226
|
+
debugLog("SERVER received " + hexDump(data));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (msgType === "HEL") {
|
|
230
|
+
try {
|
|
231
|
+
assert(data.length >= 24);
|
|
232
|
+
const helloMessage = decodeMessage(stream, HelloMessage) as HelloMessage;
|
|
233
|
+
assert(isFinite(this.protocolVersion));
|
|
234
|
+
|
|
235
|
+
// OPCUA Spec 1.03 part 6 - page 41
|
|
236
|
+
// The Server shall always accept versions greater than what it supports.
|
|
237
|
+
if (helloMessage.protocolVersion !== this.protocolVersion) {
|
|
238
|
+
debugLog(
|
|
239
|
+
`warning ! client sent helloMessage.protocolVersion = ` +
|
|
240
|
+
` 0x${helloMessage.protocolVersion.toString(16)} ` +
|
|
241
|
+
`whereas server protocolVersion is 0x${this.protocolVersion.toString(16)}`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (helloMessage.protocolVersion === 0xdeadbeef || helloMessage.protocolVersion < this.protocolVersion) {
|
|
246
|
+
// Note: 0xDEADBEEF is our special version number to simulate BadProtocolVersionUnsupported in tests
|
|
247
|
+
// invalid protocol version requested by client
|
|
248
|
+
return this._abortWithError(
|
|
249
|
+
StatusCodes.BadProtocolVersionUnsupported,
|
|
250
|
+
"Protocol Version Error" + this.protocolVersion,
|
|
251
|
+
callback
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// OPCUA Spec 1.04 part 6 - page 45
|
|
256
|
+
// UASC is designed to operate with different TransportProtocols that may have limited buffer
|
|
257
|
+
// sizes. For this reason, OPC UA Secure Conversation will break OPC UA Messages into several
|
|
258
|
+
// pieces (called ‘MessageChunks’) that are smaller than the buffer size allowed by the
|
|
259
|
+
// TransportProtocol. UASC requires a TransportProtocol buffer size that is at least 8 192 bytes
|
|
260
|
+
if (helloMessage.receiveBufferSize < minimumBufferSize || helloMessage.sendBufferSize < minimumBufferSize) {
|
|
261
|
+
return this._abortWithError(
|
|
262
|
+
StatusCodes.BadConnectionRejected,
|
|
263
|
+
"Buffer size too small (should be at least " + minimumBufferSize,
|
|
264
|
+
callback
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
// the helloMessage shall only be received once.
|
|
268
|
+
this._helloReceived = true;
|
|
269
|
+
this._send_ACK_response(helloMessage);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
// connection rejected because of malformed message
|
|
272
|
+
return this._abortWithError(StatusCodes.BadConnectionRejected, err instanceof Error ? err.message: "", callback);
|
|
273
|
+
}
|
|
274
|
+
callback(); // no Error
|
|
275
|
+
} else {
|
|
276
|
+
// invalid packet , expecting HEL
|
|
277
|
+
/* istanbul ignore next*/
|
|
278
|
+
if (doDebug) {
|
|
279
|
+
debugLog(chalk.red("BadCommunicationError ") + "Expecting 'HEL' message to initiate communication");
|
|
280
|
+
}
|
|
281
|
+
this._abortWithError(StatusCodes.BadCommunicationError, "Expecting 'HEL' message to initiate communication", callback);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|