CVE-2022-24375 (published 2022-08-24T05:15Z)

The package node-opcua before 2.74.0 are vulnerable to Denial of Service (DoS) when bypassing the limitations for excessive memory consumption by sending multiple CloseSession requests with the deleteSubscription parameter equal to False.

CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)

Weakness information:
cwe_id: CWE-400
cwe_name: Uncontrolled Resource Consumption
description: The product does not properly control the allocation and maintenance of a limited resource, thereby enabling an actor to influence the amount of resources consumed, eventually leading to the exhaustion of available resources.
extended_description: 
            Limited resources include memory, file system storage, database connection pool entries, and CPU. If an attacker can trigger the allocation of these limited resources, but the number or size of the resources is not controlled, then the attacker could cause a denial of service that consumes all available resources. This would prevent valid users from accessing the product, and it could potentially have an impact on the surrounding environment. For example, a memory exhaustion attack against an application could slow down the application as well as its host operating system.
            There are at least three distinct scenarios which can commonly lead to resource exhaustion:
               
                  Lack of throttling for the number of allocated resources
                  Losing all references to a resource before reaching the shutdown stage
                  Not closing/returning a resource after processing
               
            Resource exhaustion problems are often result due to an incorrect implementation of the following situations:
               
                  Error conditions and other exceptional circumstances.
                  Confusion over which part of the program is responsible for releasing the resource.
               
         
         

Fix information:
repo_url: https://github.com/node-opcua/node-opcua
commit_details: repo_url: https://github.com/node-opcua/node-opcua
author: Etienne Rossignon
author_date: 2022-08-12 11:37:50+02:00
msg: server: fix Subscription.modify assert
repository: repo_url: https://github.com/node-opcua/node-opcua
repo_name: node-opcua/node-opcua
description: Unlocking the Full Potential of OPC UA with Typescript and NodeJS - http://node-opcua.github.io/
repo_language: TypeScript
owner: node-opcua
file_changes: filename: server_subscription.ts
change_type: ModificationType.MODIFY
diff: @@ -629,7 +629,7 @@ export class Subscription extends EventEmitter {
         this.messageSent = false;
 
         this.timerId = null;
-        this._start_timer();
+        this._start_timer({ firstTime: true });
 
         debugLog(chalk.green(`creating subscription ${this.id}`));
 
@@ -686,7 +686,7 @@ export class Subscription extends EventEmitter {
             // todo
         }
         this._stop_timer();
-        this._start_timer();
+        this._start_timer({ firstTime: false });
     }
 
     /**
@@ -732,7 +732,7 @@ export class Subscription extends EventEmitter {
     public increaseLifeTimeCounter(): void {
         this._life_time_counter += 1;
         if (this._life_time_counter >= this.lifeTimeCount) {
-           this.emit("lifeTimeExpired");
+            this.emit("lifeTimeExpired");
         }
         this.emit("lifeTimeCounterChanged", this._life_time_counter);
     }
@@ -1458,7 +1458,7 @@ export class Subscription extends EventEmitter {
         }
     }
 
-    private _start_timer() {
+    private _start_timer({ firstTime }: { firstTime: boolean }) {
         debugLog(
             chalk.bgWhite.blue("Subscription#_start_timer  subscriptionId="),
             this.id,
@@ -1478,8 +1478,11 @@ export class Subscription extends EventEmitter {
         // make sure that a keep-alive Message will be send at the end of the first publishing cycle
         // if there are no Notifications ready.
         this._keep_alive_counter = 0; // this.maxKeepAliveCount;
-        assert(this.messageSent === false);
-        assert(this.state === SubscriptionState.CREATING);
+
+        if (firstTime) {
+            assert(this.messageSent === false);
+            assert(this.state === SubscriptionState.CREATING);
+        }
 
         assert(this.publishingInterval >= Subscription.minimumPublishingInterval);
         this.timerId = setInterval(this._tick.bind(this), this.publishingInterval);

programming_language: TypeScript
filename: test_subscription.js
change_type: ModificationType.MODIFY
diff: @@ -1062,6 +1062,71 @@ describe("Subscriptions", function () {
         subscription.dispose();
     });
 
+    it("T17 - should be possible to  modify subscription publishing interval", function(){
+        // pretend the client has sent many pending PublishRequests
+        fake_publish_engine.pendingPublishRequestCount = 1000;
+
+        /**
+         * When a Subscription is created, the first Message is sent at the end of the first publishing cycle to
+         * inform the Client that the Subscription is operational. A Notification Message is sent if there are
+         * Notifications ready to be reported. If there are none, a keep-alive Message is sent instead that
+         * contains a sequence number of 1, indicating that the first Notification Message has not yet been
+         * sent. This is the only time a keep-alive Message is sent without waiting for the maximum keep-alive
+         * count to be reached, as specified in (f) above.
+         *
+         */
+        const subscription = new Subscription({
+            publishingInterval: 1000,
+            maxKeepAliveCount: 20,
+            //
+            publishEngine: fake_publish_engine,
+            globalCounter: { totalMonitoredItemCount: 0 },
+            serverCapabilities: { maxMonitoredItems: 10000, maxMonitoredItemsPerSubscription: 1000 }
+        });
+        const monitoredItem = add_mock_monitored_item(subscription);
+
+        // pretend that we already have notification messages
+        // a notification finally arrived !
+        monitoredItem.simulateMonitoredItemAddingNotification();
+
+        const notification_event_spy = sinon.spy();
+        const keepalive_event_spy = sinon.spy();
+        const expire_event_spy = sinon.spy();
+
+        subscription.on("notification", notification_event_spy);
+        subscription.on("keepalive", keepalive_event_spy);
+        subscription.on("expired", expire_event_spy);
+
+        test.clock.tick(200);
+        keepalive_event_spy.callCount.should.equal(0);
+        notification_event_spy.callCount.should.eql(0);
+
+        test.clock.tick(1000);
+        keepalive_event_spy.callCount.should.equal(0);
+        notification_event_spy.callCount.should.eql(1);
+
+        test.clock.tick(1000);
+        keepalive_event_spy.callCount.should.equal(0);
+        notification_event_spy.callCount.should.eql(1);
+
+        monitoredItem.simulateMonitoredItemAddingNotification();
+        // now change the publishin Intervale
+        subscription.modify({ requestedPublishingInterval: 2000});
+       
+        test.clock.tick(1000);
+        keepalive_event_spy.callCount.should.equal(0);
+        notification_event_spy.callCount.should.eql(1);
+
+        test.clock.tick(1010);
+        keepalive_event_spy.callCount.should.equal(0);
+        notification_event_spy.callCount.should.eql(2);
+
+        subscription.terminate();
+        subscription.dispose();
+
+    });
+
+
     xit("closing a Subscription causes its MonitoredItems to be deleted. ", function () {
         /** */
     });

programming_language: JavaScript
repo_url: https://github.com/node-opcua/node-opcua
commit_details: repo_url: https://github.com/node-opcua/node-opcua
author: Etienne Rossignon
author_date: 2022-08-13 00:06:23+02:00
msg: server: use ServiceFault on request error
repository: repo_url: https://github.com/node-opcua/node-opcua
repo_name: node-opcua/node-opcua
description: Unlocking the Full Potential of OPC UA with Typescript and NodeJS - http://node-opcua.github.io/
repo_language: TypeScript
owner: node-opcua
file_changes: filename: client_session_impl.ts
change_type: ModificationType.MODIFY
diff: @@ -10,7 +10,7 @@ import {
     ExtraDataTypeManager,
     getExtensionObjectConstructor,
     getExtraDataTypeManager,
-    promoteOpaqueStructure,
+    promoteOpaqueStructure
 } from "node-opcua-client-dynamic-extension-object";
 import { Certificate, Nonce } from "node-opcua-crypto";
 import { attributeNameById, BrowseDirection, LocalizedTextLike } from "node-opcua-data-model";
@@ -82,7 +82,13 @@ import {
 import { WriteRequest, WriteResponse, WriteValue } from "node-opcua-service-write";
 import { StatusCode, StatusCodes, Callback, CallbackT } from "node-opcua-status-code";
 import { ErrorCallback } from "node-opcua-status-code";
-import { BrowseNextRequest, BrowseNextResponse, HistoryReadValueIdOptions, WriteValueOptions } from "node-opcua-types";
+import {
+    BrowseNextRequest,
+    BrowseNextResponse,
+    HistoryReadValueIdOptions,
+    ServiceFault,
+    WriteValueOptions
+} from "node-opcua-types";
 import { buffer_ellipsis, getFunctionParameterNames, isNullOrUndefined, lowerFirstLetter } from "node-opcua-utils";
 import { DataType, Variant, VariantLike } from "node-opcua-variant";
 
@@ -1540,6 +1546,7 @@ export class ClientSessionImpl extends EventEmitter implements ClientSession {
             if (err && err.message.match(/BadSessionIdInvalid/) && request.constructor.name !== "ActivateSessionRequest") {
                 debugLog("Transaction on Invalid Session ", request.constructor.name);
                 request.requestHeader.requestHandle = requestHandleNotSetValue;
+                warningLog("client is now attempting to recreate a session");
                 this.recreate_session_and_reperform_transaction(request, callback);
                 return;
             }
@@ -1595,6 +1602,7 @@ export class ClientSessionImpl extends EventEmitter implements ClientSession {
                 }
                 return callback(err);
             }
+
             /* istanbul ignore next */
             if (!response) {
                 return callback(new Error("internal Error"));

programming_language: TypeScript
filename: client_subscription_impl.ts
change_type: ModificationType.MODIFY
diff: @@ -471,8 +471,12 @@ export class ClientSubscriptionImpl extends EventEmitter implements ClientSubscr
         });
         session.setTriggering(setTriggeringRequest, (err: Error | null, response?: SetTriggeringResponse) => {
             if (err) {
-                // use soft error, no exceptions
-                return callback(null, response);
+                if (response) {
+                    // use soft error, no exceptions
+                    return callback(null, response);
+                } else {
+                    return callback(err);
+                }
             }
             // istanbul ignore next
             if (!response) {

programming_language: TypeScript
filename: u_test_discovery_server.ts
change_type: ModificationType.MODIFY
diff: @@ -18,7 +18,8 @@ import {
     StatusCodes,
     RegisterServerMethod,
     makeApplicationUrn,
-    OPCUADiscoveryServer
+    OPCUADiscoveryServer,
+    ServiceFault
 } from "node-opcua";
 import { readCertificate, exploreCertificate } from "node-opcua-crypto";
 import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
@@ -132,13 +133,15 @@ export function t(test: any) {
                 }
             });
 
-            function check_response(err: Error | null, response: any): void {
-                should.not.exist(err);
+            function check_error_response(err: Error | null, response: any): void {
+                should.exist(err);
+                should.not.exist(response);
+                (err as any).response.should.be.instanceOf(ServiceFault);
                 //xx console.log(response.toString());
-                response.responseHeader.serviceResult.should.eql(StatusCodes.BadDiscoveryUrlMissing);
+                (err as any).response.responseHeader.serviceResult.should.eql(StatusCodes.BadDiscoveryUrlMissing);
             }
 
-            await send_registered_server_request(discoveryServerEndpointUrl, request, check_response);
+            await send_registered_server_request(discoveryServerEndpointUrl, request, check_error_response);
         });
 
         it("DISCO1-2 should fail to register server to the discover server if server type is Client (BadInvalidArgument)", async () => {
@@ -161,13 +164,15 @@ export function t(test: any) {
                 }
             });
 
-            function check_response(err: Error | null, response: any) {
-                should.not.exist(err);
+            function check_error_response(err: Error | null, response: any) {
+                should.exist(err);
+                should.not.exist(response);
                 //xx debugLog(response.toString());
-                response.responseHeader.serviceResult.should.eql(StatusCodes.BadInvalidArgument);
+                (err as any).response.should.be.instanceOf(ServiceFault);
+                (err as any).response.responseHeader.serviceResult.should.eql(StatusCodes.BadInvalidArgument);
             }
 
-            await send_registered_server_request(discoveryServerEndpointUrl, request, check_response);
+            await send_registered_server_request(discoveryServerEndpointUrl, request, check_error_response);
         });
 
         it("DISCO1-3 should fail to register server to the discover server if server name array is empty (BadServerNameMissing)", async () => {
@@ -190,12 +195,14 @@ export function t(test: any) {
                 }
             });
 
-            function check_response(err: Error | null, response: any) {
-                should.not.exist(err);
-                response.responseHeader.serviceResult.should.eql(StatusCodes.BadServerNameMissing);
+            function check_error_response(err: Error | null, response: any) {
+                should.exist(err);
+                should.not.exist(response);
+                (err as any).response.should.be.instanceOf(ServiceFault);
+                (err as any).response.responseHeader.serviceResult.should.eql(StatusCodes.BadServerNameMissing);
             }
 
-            await send_registered_server_request(discoveryServerEndpointUrl, request, check_response);
+            await send_registered_server_request(discoveryServerEndpointUrl, request, check_error_response);
         });
     });
 
@@ -207,7 +214,8 @@ export function t(test: any) {
         let server: OPCUAServer;
 
         before(() => {
-            OPCUAServer.registry.count().should.eql(0);1162
+            OPCUAServer.registry.count().should.eql(0);
+            1162;
         });
 
         after(() => {

programming_language: TypeScript
filename: u_test_e2e_BrowseRequest.js
change_type: ModificationType.MODIFY
diff: @@ -164,10 +164,10 @@ module.exports = function(test) {
                     const browseNextRequest = new BrowseNextRequest({
                         continuationPoints: null
                     });
-                    g_session.performMessageTransaction(browseNextRequest, function(err, response) {
+                    g_session.performMessageTransaction(browseNextRequest, function(err, _response) {
                         err.message.should.match(/BadNothingToDo/);
                         // console.log(response.toString());
-                        response.responseHeader.serviceResult.should.equal(StatusCodes.BadNothingToDo);
+                        err.response.responseHeader.serviceResult.should.equal(StatusCodes.BadNothingToDo);
                         callback();
                     });
                 }

programming_language: JavaScript
filename: u_test_e2e_SubscriptionUseCase.js
change_type: ModificationType.MODIFY
diff: @@ -39,10 +39,10 @@ const {
     Variant,
     Subscription,
     SubscriptionState,
-    installSessionLogging
+    installSessionLogging,
+    ServiceFault
 } = require("node-opcua");
 
-
 const { make_debugLog, checkDebugFlag } = require("node-opcua-debug");
 
 const {
@@ -726,7 +726,8 @@ module.exports = function (test) {
                                 },
                                 function (callback) {
                                     function publish_callback(err, response) {
-                                        should.exist(response);
+                                        should.not.exist(response);
+                                        err.response.should.be.instanceOf(ServiceFault);
                                         should(err.message).match(/BadNoSubscription/);
                                     }
 
@@ -825,7 +826,8 @@ module.exports = function (test) {
                                         subscriptionIds: []
                                     },
                                     function (err, response) {
-                                        should.exist(response);
+                                        should.not.exist(response);
+                                        should.exist(err);
                                         err.message.should.match(/BadNothingToDo/);
                                         callback();
                                     }
@@ -1518,7 +1520,8 @@ module.exports = function (test) {
                     });
                     session.createMonitoredItems(createMonitoredItemsRequest, function (err, createMonitoredItemsResponse) {
                         should(err.message).match(/BadNothingToDo/);
-                        createMonitoredItemsResponse.responseHeader.serviceResult.should.eql(StatusCodes.BadNothingToDo);
+                        should.not.exist(createMonitoredItemsResponse);
+                        err.response.responseHeader.serviceResult.should.eql(StatusCodes.BadNothingToDo);
                         callback();
                     });
                 },
@@ -1581,7 +1584,8 @@ module.exports = function (test) {
                     });
                     session.modifyMonitoredItems(modifyMonitoredItemsRequest, function (err, modifyMonitoredItemsResponse) {
                         should(err.message).match(/BadNothingToDo/);
-                        modifyMonitoredItemsResponse.responseHeader.serviceResult.should.eql(StatusCodes.BadNothingToDo);
+                        err.response.responseHeader.serviceResult.should.eql(StatusCodes.BadNothingToDo);
+                        should.not.exist(modifyMonitoredItemsResponse);
                         callback();
                     });
                 },
@@ -1600,7 +1604,8 @@ module.exports = function (test) {
                     });
                     session.deleteMonitoredItems(deleteMonitoredItemsRequest, function (err, deleteMonitoredItemsResponse) {
                         should(err.message).match(/BadNothingToDo/);
-                        deleteMonitoredItemsResponse.responseHeader.serviceResult.should.eql(StatusCodes.BadNothingToDo);
+                        err.response.responseHeader.serviceResult.should.eql(StatusCodes.BadNothingToDo);
+                        should.not.exist(deleteMonitoredItemsResponse);
                         callback();
                     });
                 },
@@ -1793,6 +1798,7 @@ module.exports = function (test) {
                             requestedPublishingInterval: -1
                         },
                         function (err, createSubscriptionResponse) {
+                            should.not.exist(err);
                             createSubscriptionResponse.revisedPublishingInterval.should.be.greaterThan(10);
 
                             inner_done(err);
@@ -1903,7 +1909,6 @@ module.exports = function (test) {
 
             installSessionLogging(server);
 
-
             endpointUrl = test.endpointUrl;
             temperatureVariableId = server.temperatureVariableId;
 
@@ -2139,13 +2144,13 @@ module.exports = function (test) {
                             })
                         ],
                         (err) => {
-                            tracelog("inner", err ? err.message: "");
+                            tracelog("inner", err ? err.message : "");
                             inner_done(err);
                         }
                     );
                 },
                 (err) => {
-                    tracelog("done", err ? err.message: "");
+                    tracelog("done", err ? err.message : "");
                     done(err);
                 }
             );
@@ -4064,8 +4069,9 @@ module.exports = function (test) {
                     });
                     g_session.republish(request, function (err, response) {
                         should.exist(err);
-                        response.should.be.instanceof(RepublishResponse);
-                        response.responseHeader.serviceResult.should.eql(StatusCodes.BadMessageNotAvailable);
+                        should.not.exist(response);
+                        err.response.should.be.instanceof(ServiceFault);
+                        err.response.responseHeader.serviceResult.should.eql(StatusCodes.BadMessageNotAvailable);
                         done();
                     });
                 }, done);
@@ -4081,8 +4087,9 @@ module.exports = function (test) {
                     });
                     g_session.republish(request, function (err, response) {
                         should.exist(err);
-                        response.should.be.instanceof(RepublishResponse);
-                        response.responseHeader.serviceResult.should.eql(StatusCodes.BadSubscriptionIdInvalid);
+                        should.not.exist(response);
+                        err.response.should.be.instanceof(ServiceFault);
+                        err.response.responseHeader.serviceResult.should.eql(StatusCodes.BadSubscriptionIdInvalid);
                         done();
                     });
                 }, done);

programming_language: JavaScript
filename: u_test_e2e_Subscription_Transfer.js
change_type: ModificationType.MODIFY
diff: @@ -456,10 +456,17 @@ module.exports = function (test) {
                         const response1 = spy_publish_session2.getCall(1).args[1];
                         const response2 = spy_publish_session2.getCall(2).args[1];
                         const response3 = spy_publish_session2.getCall(3).args[1];
-
-                        response1.responseHeader.serviceResult.should.eql(StatusCodes.BadNoSubscription);
-                        response2.responseHeader.serviceResult.should.eql(StatusCodes.BadNoSubscription);
-                        response3.responseHeader.serviceResult.should.eql(StatusCodes.BadNoSubscription);
+                        should.not.exist(response1);
+                        should.not.exist(response2);
+                        should.not.exist(response3);
+
+                        const err1 = spy_publish_session2.getCall(1).args[0];
+                        const err2 = spy_publish_session2.getCall(2).args[0];
+                        const err3 = spy_publish_session2.getCall(3).args[0];
+                        
+                        err1.response.responseHeader.serviceResult.should.eql(StatusCodes.BadNoSubscription);
+                        err2.response.responseHeader.serviceResult.should.eql(StatusCodes.BadNoSubscription);
+                        err3.response.responseHeader.serviceResult.should.eql(StatusCodes.BadNoSubscription);
                         //xx console.log(response1.toString())
                         //xx console.log(response2.toString())
                         //xx console.log(response3.toString())

programming_language: JavaScript
filename: u_test_e2e_browse_read.js
change_type: ModificationType.MODIFY
diff: @@ -29,6 +29,8 @@ const fail_fast_connectivity_strategy = {
 const describe = require("node-opcua-leak-detector").describeWithLeakDetector;
 module.exports = function (test) {
     describe("Browse-Read-Write Services", function () {
+
+        /** @type {ClientSession} */
         let g_session = null;
 
         // use fail fast connectionStrategy
@@ -170,7 +172,7 @@ module.exports = function (test) {
 
             g_session.read(nodesToRead, function (err, dataValues) {
                 if (err) {
-                    const response = dataValues;
+                    const response = err.response;
                     //dataValues.length.should.be(1);
                     response.responseHeader.serviceResult.should.eql(StatusCodes.BadNothingToDo);
                     done();

programming_language: JavaScript
filename: u_test_e2e_registerNodes.js
change_type: ModificationType.MODIFY
diff: @@ -2,20 +2,11 @@
 const should = require("should");
 const async = require("async");
 
+const { OPCUAClient, StatusCodes, UnregisterNodesRequest, RegisterNodesRequest, DataType, AttributeIds, ServiceFault } = require("node-opcua");
 const { perform_operation_on_client_session } = require("../../test_helpers/perform_operation_on_client_session");
 
-const opcua = require("node-opcua");
-const StatusCodes = opcua.StatusCodes;
-const OPCUAClient = opcua.OPCUAClient;
-const UnregisterNodesRequest = opcua.UnregisterNodesRequest;
-const RegisterNodesRequest = opcua.RegisterNodesRequest;
-const AttributeIds = opcua.AttributeIds;
-const DataType = opcua.DataType;
-
 module.exports = function (test) {
-
     describe("end-to-end testing registerNodes", function () {
-
         let client, endpointUrl;
 
         beforeEach(function (done) {
@@ -30,135 +21,147 @@ module.exports = function (test) {
         });
 
         it("should register nodes - BadNothingToDo", function (done) {
-
-            perform_operation_on_client_session(client, endpointUrl, function (session, inner_done) {
-
-                const request = new RegisterNodesRequest({
-                    nodesToRegister: []
-                });
-                session.performMessageTransaction(request, function (err/*, response*/) {
-                    err.message.should.match(/BadNothingToDo/);
-                    inner_done();
-                });
-
-            }, done);
+            perform_operation_on_client_session(
+                client,
+                endpointUrl,
+                function (session, inner_done) {
+                    const request = new RegisterNodesRequest({
+                        nodesToRegister: []
+                    });
+                    session.performMessageTransaction(request, function (err /*, response*/) {
+                        err.message.should.match(/BadNothingToDo/);
+                        inner_done();
+                    });
+                },
+                done
+            );
         });
 
         it("should register nodes - Good", function (done) {
-
-            perform_operation_on_client_session(client, endpointUrl, function (session, inner_done) {
-
-                const request = new RegisterNodesRequest({
-                    nodesToRegister: [
-                        "ns=0;i=1"
-                    ]
-                });
-                session.performMessageTransaction(request, function (err, response) {
-                    should.not.exist(err);
-                    response.registeredNodeIds.length.should.eql(1);
-                    inner_done();
-                });
-
-            }, done);
+            perform_operation_on_client_session(
+                client,
+                endpointUrl,
+                function (session, inner_done) {
+                    const request = new RegisterNodesRequest({
+                        nodesToRegister: ["ns=0;i=1"]
+                    });
+                    session.performMessageTransaction(request, function (err, response) {
+                        should.not.exist(err);
+                        response.registeredNodeIds.length.should.eql(1);
+                        inner_done();
+                    });
+                },
+                done
+            );
         });
 
         it("should unregister nodes - BadNothingToDo", function (done) {
-            perform_operation_on_client_session(client, endpointUrl, function (session, inner_done) {
-
-                const request = new UnregisterNodesRequest({
-                    nodesToUnregister: []
-                });
-                session.performMessageTransaction(request, function (err, response) {
-                    should.exist(response);
-                    err.message.should.match(/BadNothingToDo/);
-                    inner_done();
-                });
-
-            }, done);
+            perform_operation_on_client_session(
+                client,
+                endpointUrl,
+                function (session, inner_done) {
+                    const request = new UnregisterNodesRequest({
+                        nodesToUnregister: []
+                    });
+                    session.performMessageTransaction(request, function (err, response) {
+                        should.not.exist(response);
+                        should.exist(err);
+                        err.response.should.be.instanceOf(ServiceFault);
+                        err.message.should.match(/BadNothingToDo/);
+                        err.response.responseHeader.serviceResult.should.eql(StatusCodes.BadNothingToDo);
+                        inner_done();
+                    });
+                },
+                done
+            );
         });
 
         it("should unregister nodes - Good", function (done) {
-            perform_operation_on_client_session(client, endpointUrl, function (session, inner_done) {
-
-                const request = new UnregisterNodesRequest({
-                    nodesToUnregister: [
-                        "ns=0;i=1"
-                    ]
-                });
-                session.performMessageTransaction(request, function (err, response) {
-                    should.exist(response);
-                    should.not.exist(err);
-                    inner_done();
-                });
-
-            }, done);
+            perform_operation_on_client_session(
+                client,
+                endpointUrl,
+                function (session, inner_done) {
+                    const request = new UnregisterNodesRequest({
+                        nodesToUnregister: ["ns=0;i=1"]
+                    });
+                    session.performMessageTransaction(request, function (err, response) {
+                        should.exist(response);
+                        should.not.exist(err);
+                        inner_done();
+                    });
+                },
+                done
+            );
         });
 
-
         it("should register nodes and provide a alias that can be used on all operations", function (done) {
-            perform_operation_on_client_session(client, endpointUrl, function (session, inner_done) {
-
-                const nodesToRegister = ["ns=2;s=Static_Scalar_Double"];
-                let registeredNodeIds = [];
-                let dataValue1, dataValue2;
-                async.series([
-                    function register_some_node(callback) {
-
-                        session.registerNodes(nodesToRegister, function (err, _registeredNodeIds) {
-                            if (err) {
-                                return callback(err);
+            perform_operation_on_client_session(
+                client,
+                endpointUrl,
+                function (session, inner_done) {
+                    const nodesToRegister = ["ns=2;s=Static_Scalar_Double"];
+                    let registeredNodeIds = [];
+                    let dataValue1, dataValue2;
+                    async.series(
+                        [
+                            function register_some_node(callback) {
+                                session.registerNodes(nodesToRegister, function (err, _registeredNodeIds) {
+                                    if (err) {
+                                        return callback(err);
+                                    }
+                                    registeredNodeIds = _registeredNodeIds;
+                                    callback();
+                                });
+                            },
+                            function (callback) {
+                                const nodeToWrite = {
+                                    nodeId: registeredNodeIds[0],
+                                    attributeId: AttributeIds.Value,
+                                    value: { value: { dataType: DataType.Double, value: 1000 } }
+                                };
+                                session.write(nodeToWrite, function (err, statusCode) {
+                                    statusCode.should.eql(StatusCodes.Good);
+                                    callback(err);
+                                });
+                            },
+                            function (callback) {
+                                const nodeToRead = { nodeId: nodesToRegister[0], attributeId: 13 };
+                                session.read(nodeToRead, function (err, dataValue) {
+                                    if (err) {
+                                        return callback(err);
+                                    }
+                                    dataValue.statusCode.should.eql(StatusCodes.Good);
+                                    dataValue1 = dataValue;
+                                    callback();
+                                });
+                            },
+                            function (callback) {
+                                const nodeToRead = { nodeId: registeredNodeIds[0], attributeId: 13 };
+                                session.read(nodeToRead, function (err, dataValue) {
+                                    if (err) {
+                                        return callback(err);
+                                    }
+                                    dataValue.statusCode.should.eql(StatusCodes.Good);
+                                    dataValue2 = dataValue;
+                                    callback();
+                                });
+                            },
+                            function (callback) {
+                                registeredNodeIds[0].toString().should.not.eql(nodesToRegister[0].toString());
+                                dataValue1.statusCode.toString().should.eql(dataValue2.statusCode.toString());
+
+                                //xx console.log(dataValue1.toString());
+                                //xx console.log(dataValue2.toString());
+
+                                dataValue1.value.toString().should.eql(dataValue2.value.toString());
+                                callback();
                             }
-                            registeredNodeIds = _registeredNodeIds;
-                            callback();
-                        });
-                    },
-                    function (callback) {
-
-                        const nodeToWrite = {
-                            nodeId: registeredNodeIds[0],
-                            attributeId: AttributeIds.Value,
-                            value: {value: {dataType: DataType.Double, value: 1000}}
-                        };
-                        session.write(nodeToWrite, function (err, statusCode) {
-                            statusCode.should.eql(StatusCodes.Good);
-                            callback(err);
-                        });
-                    },
-                    function (callback) {
-                        const nodeToRead = {nodeId: nodesToRegister[0], attributeId: 13};
-                        session.read(nodeToRead, function (err, dataValue) {
-                            if (err) {
-                                return callback(err);
-                            }
-                            dataValue.statusCode.should.eql(StatusCodes.Good);
-                            dataValue1 = dataValue;
-                            callback();
-                        });
-                    },
-                    function (callback) {
-                        const nodeToRead = {nodeId: registeredNodeIds[0], attributeId: 13};
-                        session.read(nodeToRead, function (err, dataValue) {
-                            if (err) {
-                                return callback(err);
-                            }
-                            dataValue.statusCode.should.eql(StatusCodes.Good);
-                            dataValue2 = dataValue;
-                            callback();
-                        });
-                    },
-                    function (callback) {
-                        registeredNodeIds[0].toString().should.not.eql(nodesToRegister[0].toString());
-                        dataValue1.statusCode.toString().should.eql(dataValue2.statusCode.toString());
-
-                        //xx console.log(dataValue1.toString());
-                        //xx console.log(dataValue2.toString());
-
-                        dataValue1.value.toString().should.eql(dataValue2.value.toString());
-                        callback();
-                    }
-                ], inner_done);
-
-            }, done);
+                        ],
+                        inner_done
+                    );
+                },
+                done
+            );
         });
     });
 };

programming_language: JavaScript
filename: u_test_e2e_set_triggering.ts
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,4 @@
+/* eslint-disable max-statements */
 // tslint:disable: no-shadowed-variable
 // tslint:disable: no-console
 import {
@@ -29,6 +30,7 @@ import {
     OPCUAClient,
     Range,
     ServerSidePublishEngine,
+    ServiceFault,
     SetTriggeringRequestOptions,
     StatusCode,
     StatusCodes,
@@ -241,16 +243,14 @@ export function t(test: any) {
 
             try {
                 const result = await subscription.setTriggering(t, [], []);
-
-                //  console.log(result.toString());
-
-                result.removeResults?.length.should.eql(0);
-                result.addResults?.length.should.eql(0);
-                result.responseHeader.serviceResult.should.eql(StatusCodes.BadNothingToDo);
+                console.log(result.toString());
             } catch (err) {
                 _err = err as Error;
             }
-            should.not.exist(_err, "not expecting any exception");
+            should.exist(_err, "expecting a ServiceFault exception");
+            const response = (_err as any).response as ServiceFault;
+            response.should.be.instanceOf(ServiceFault);
+            response.responseHeader.serviceResult.should.eql(StatusCodes.BadNothingToDo);
 
             /*
              */

programming_language: TypeScript
filename: u_test_e2e_test_accessing_service_before_session_is_activated.js
change_type: ModificationType.MODIFY
diff: @@ -29,6 +29,7 @@ module.exports = function(test) {
 
             let session1;
             let last_response;
+            let last_response_err;
             let activate_error;
             async.series([
 
@@ -58,6 +59,7 @@ module.exports = function(test) {
                         console.log(err ? err.toString() : "null");
                         console.log(response ? response.toString() : "null");
                         last_response = response;
+                        last_response_err = err;
                         callback();
                     });
                 },
@@ -81,7 +83,7 @@ module.exports = function(test) {
                 if (err) {
                     return done(err);
                 }
-                last_response.responseHeader.serviceResult.should.eql(StatusCodes.BadSessionNotActivated);
+                last_response_err.response.responseHeader.serviceResult.should.eql(StatusCodes.BadSessionNotActivated);
                 should.exist(activate_error, 
                     "Activate Session should return an error if there has been an attempt to use it before being activated");
                 activate_error.message.should.match(/BadSessionIdInvalid|BadSessionClosed/);

programming_language: JavaScript
filename: u_test_e2e_transfer_session.js
change_type: ModificationType.MODIFY
diff: @@ -1,724 +1,712 @@
 "use strict";
+const path = require("path");
+const fs = require("fs");
 const should = require("should");
 const async = require("async");
 const _ = require("underscore");
-const path = require("path");
-const fs = require("fs");
 
-const { 
-  OPCUAClient, 
-  StatusCodes, 
-  PublishRequest,
-  CreateSubscriptionRequest,
-  CloseSessionRequest,
-  ReadRequest,
-  TimestampsToReturn, 
-  MessageSecurityMode,
-  SecurityPolicy,
-  UserNameIdentityToken
-} = require("node-opcua");
 const {
-  readCertificate,
-  readCertificateRevocationList
-} = require("node-opcua-crypto");
+    OPCUAClient,
+    StatusCodes,
+    PublishRequest,
+    CreateSubscriptionRequest,
+    CloseSessionRequest,
+    ReadRequest,
+    TimestampsToReturn,
+    MessageSecurityMode,
+    SecurityPolicy,
+    UserNameIdentityToken,
+    ServiceFault,
+    ReadResponse
+} = require("node-opcua");
+const { readCertificate, readCertificateRevocationList } = require("node-opcua-crypto");
 
 const certificateFolder = path.join(__dirname, "../../../node-opcua-samples/certificates");
 fs.existsSync(certificateFolder).should.eql(true, "expecting certificate store at " + certificateFolder);
 
 function sendPublishRequest(session, callback) {
-  const publishRequest = new PublishRequest({});
-  session.performMessageTransaction(publishRequest, function(err, response) {
-    callback(err, response);
-  });
+    const publishRequest = new PublishRequest({});
+    session.performMessageTransaction(publishRequest, function (err, response) {
+        callback(err, response);
+    });
 }
 
 function createSubscription(session, callback) {
-  const publishingInterval = 1000;
-  const createSubscriptionRequest = new CreateSubscriptionRequest({
-    requestedPublishingInterval: publishingInterval,
-    requestedLifetimeCount: 60,
-    requestedMaxKeepAliveCount: 10,
-    maxNotificationsPerPublish: 10,
-    publishingEnabled: true,
-    priority: 6
-  });
-
-  session.performMessageTransaction(createSubscriptionRequest, function(err/*, response*/) {
-    callback(err);
-  });
-}
-
-const describe = require("node-opcua-leak-detector").describeWithLeakDetector;
-
-module.exports = function(test) {
-
-  describe("testing session  transfer to different channel", function() {
-
-    it("RQC1 - It should be possible to close a session that has not be activated yet", function(done) {
-      let client1;
-      let session1;
-      async.series([
-
-        function(callback) {
-          client1 = OPCUAClient.create({});
-          client1.connect(test.endpointUrl, callback);
-        },
-
-        // create a session using client1, without activating it
-        function(callback) {
-          test.server.engine.currentSessionCount.should.eql(0);
-          client1._createSession(function(err, session) {
-            if (err) {
-              return callback(err);
-            }
-            session1 = session;
-            callback();
-          });
-        },
-
-        function(callback) {
-
-          // Question: ? Should a unactivated session be accounted for
-          //             in the currentSessionCount ?
-          test.server.engine.currentSessionCount.should.eql(1);
-
-          // however client shall not record session yet
-          client1._sessions.length.should.eql(0);
-
-          // in fact, let make sure that close Session is not harmfull
-          client1.closeSession(session1, /* deleteSubscriptions =*/true, function(err) {
-            client1._sessions.length.should.eql(0);
-            if (err) {
-              // if treated as a Failure , close session expected to return BadSessionNotActivated
-              err.message.match(/BadSessionNotActivated/);
-            }
-            callback();
-          });
-
-        },
-        function(callback) {
-          test.server.engine.currentSessionCount.should.eql(0);
-          callback();
-        },
-        function(callback) {
-          client1.disconnect(callback);
-        }
-      ], done);
-
+    const publishingInterval = 1000;
+    const createSubscriptionRequest = new CreateSubscriptionRequest({
+        requestedPublishingInterval: publishingInterval,
+        requestedLifetimeCount: 60,
+        requestedMaxKeepAliveCount: 10,
+        maxNotificationsPerPublish: 10,
+        publishingEnabled: true,
+        priority: 6
     });
 
-    it("RQB1 - calling CreateSession and CloseSession &  CloseSession again should return BadSessionIdInvalid", function(done) {
-      let client1;
-      let session1;
-      async.series([
-
-        function(callback) {
-          client1 = OPCUAClient.create();
-          client1.connect(test.endpointUrl, callback);
-        },
-        // create a session using client1
-        function(callback) {
-          client1._createSession(function(err, session) {
-            if (err) {
-              return callback(err);
-            }
-            session1 = session;
-            callback();
-          });
-        },
-
-        function(callback) {
-          session1.close(function() {
-            callback();
-          });
-        },
-        function(callback) {
-          session1.close(function(err) {
-            // now session close do not return error if session in invalid
-            // err.message.should.match(/SessionIdInvalid/);
-            should.not.exist(err);
-            callback();
-          });
-        },
-        //
-        function(callback) {
-          test.server.engine.currentSessionCount.should.eql(0);
-          client1.disconnect(callback);
-        }
-
-      ], done);
-
-    });
-
-    it("RQB2 - calling CloseSession without calling CreateSession first", function(done) {
-      let client1;
-
-      async.series([
-
-        function(callback) {
-          client1 = OPCUAClient.create({});
-          client1.connect(test.endpointUrl, callback);
-        },
-        function(callback) {
-          const request = new CloseSessionRequest({
-            deleteSubscriptions: true
-          });
-          client1.performMessageTransaction(request, function(err, response) {
-            should.not.exist(err);
-            //err.message.should.match(/BadSessionIdInvalid/);
-            response.responseHeader.serviceResult.should.eql(StatusCodes.BadSessionIdInvalid);
-            callback();
-          });
-        },
-        function(callback) {
-          client1.disconnect(callback);
-        },
-        function(callback) {
-          test.server.engine.currentSessionCount.should.eql(0);
-          callback();
-        }
-      ], done);
-
+    session.performMessageTransaction(createSubscriptionRequest, function (err /*, response*/) {
+        callback(err);
     });
+}
 
-    it("RQB3 - calling CreateSession,  CloseSession  and CloseSession again", function(done) {
-      let client1;
-      let session1;
-      async.series([
-
-        function(callback) {
-          client1 = OPCUAClient.create();
-          client1.connect(test.endpointUrl, callback);
-        },
-        // create a session using client1
-        function(callback) {
-          client1.createSession(function(err, session) {
-            if (err) {
-              return callback(err);
-            }
-            session1 = session;
-            callback();
-          });
-        },
-
-        // first call to close session should be OK
-        function(callback) {
-          client1.closeSession(session1, /* deleteSubscriptions =*/true, function(err) {
-            callback(err);
-          });
-        },
-
-        // second call to close session should raise an error
-        function(callback) {
-          const request = new CloseSessionRequest({
-            deleteSubscriptions: true
-          });
-          client1.performMessageTransaction(request, function(err, response) {
-            should.not.exist(err);
-            //err.message.should.match(/BadSessionIdInvalid/);
-            response.responseHeader.serviceResult.should.eql(StatusCodes.BadSessionIdInvalid);
-            callback();
-          });
-        },
-
-        function(callback) {
-          client1.disconnect(callback);
-        },
-        function(callback) {
-          test.server.engine.currentSessionCount.should.eql(0);
-          callback();
-        }
+const describe = require("node-opcua-leak-detector").describeWithLeakDetector;
 
-      ], function final(err) {
-        client1.disconnect(function() {
-          done(err);
+module.exports = function (test) {
+    describe("testing session  transfer to different channel", function () {
+        it("RQC1 - It should be possible to close a session that has not be activated yet", function (done) {
+            let client1;
+            let session1;
+            async.series(
+                [
+                    function (callback) {
+                        client1 = OPCUAClient.create({});
+                        client1.connect(test.endpointUrl, callback);
+                    },
+
+                    // create a session using client1, without activating it
+                    function (callback) {
+                        test.server.engine.currentSessionCount.should.eql(0);
+                        client1._createSession(function (err, session) {
+                            if (err) {
+                                return callback(err);
+                            }
+                            session1 = session;
+                            callback();
+                        });
+                    },
+
+                    function (callback) {
+                        // Question: ? Should a unactivated session be accounted for
+                        //             in the currentSessionCount ?
+                        test.server.engine.currentSessionCount.should.eql(1);
+
+                        // however client shall not record session yet
+                        client1._sessions.length.should.eql(0);
+
+                        // in fact, let make sure that close Session is not harmfull
+                        client1.closeSession(session1, /* deleteSubscriptions =*/ true, function (err) {
+                            client1._sessions.length.should.eql(0);
+                            if (err) {
+                                // if treated as a Failure , close session expected to return BadSessionNotActivated
+                                err.message.match(/BadSessionNotActivated/);
+                            }
+                            callback();
+                        });
+                    },
+                    function (callback) {
+                        test.server.engine.currentSessionCount.should.eql(0);
+                        callback();
+                    },
+                    function (callback) {
+                        client1.disconnect(callback);
+                    }
+                ],
+                done
+            );
         });
-      });
 
-    });
-
-    it("RQ0 - call ActiveSession on a session that has been transferred to a different channel", function(done) {
-
-      // this test verifies that the following requirement can be met
-      // OpcUA 1.02 part 3 $5.5 Secure Channel Set page 20
-      // Once a  Client  has established a  Session  it may wish to access the  Session  from a different
-      // SecureChannel. The Client can do this by validating the new  SecureChannel  with the
-      // ActivateSession  Service  described in 5.6.3.
-      let client1, client2;
-      let session1;
-      async.series([
-
-        // create a first channel (client1)
-        function(callback) {
-          client1 = OPCUAClient.create();
-          client1.connect(test.endpointUrl, callback);
-        },
-        // create a session using client1
-        function(callback) {
-          client1._createSession(function(err, session) {
-            if (err) {
-              return callback(err);
-            }
-            session1 = session;
-            callback();
-          });
-        },
-        // activate the session as expected on same channel used to create it
-        function(callback) {
-          client1._activateSession(session1, function(err) {
-            callback(err);
-          });
-        },
-        // let verify that it is now possible to send a request on client1's session
-        function(callback) {
-          // coerce nodeIds
-          const request = new ReadRequest({
-            nodesToRead: [{ nodeId: "i=2255", attributeId: 13 }],
-            maxAge: 0,
-            timestampsToReturn: TimestampsToReturn.Both
-          });
-          request.requestHeader.authenticationToken = session1.authenticationToken;
-          client1.performMessageTransaction(request, function(err, response) {
-            should.not.exist(err);
-            response.responseHeader.serviceResult.should.eql(StatusCodes.Good);
-            callback();
-          });
-        },
-
-        // create a second channel (client2)
-        function(callback) {
-          client2 = OPCUAClient.create();
-          client2.connect(test.endpointUrl, callback);
-        },
-
-        // reactivate session on second channel
-        function(callback) {
-          client2.reactivateSession(session1, function(err) {
-            callback(err);
-          });
-        },
-
-        // now that session has been assigned to client 1,
-        // server shall refuse any requests on channel1
-        function(callback) {
-          // coerce nodeIds
-          const request = new ReadRequest({
-            nodesToRead: [{ nodeId: "i=2255", attributeId: 13 }],
-            maxAge: 0,
-            timestampsToReturn: TimestampsToReturn.Both
-          });
-          request.requestHeader.authenticationToken = session1.authenticationToken;
-          client1.performMessageTransaction(request, function(err, response) {
-            if (!err) {
-              response.responseHeader.serviceResult.should.eql(StatusCodes.BadSecureChannelIdInvalid);
-            }
-            callback(err);
-          });
-        },
-        // but server shall access request on new channel
-        function(callback) {
-          // coerce nodeIds
-          const request = new ReadRequest({
-            nodesToRead: [{ nodeId: "i=2255", attributeId: 13 }],
-            maxAge: 0,
-            timestampsToReturn: TimestampsToReturn.Both
-          });
-          request.requestHeader.authenticationToken = session1.authenticationToken;
-          client2.performMessageTransaction(request, function(err, response) {
-            if (!err) {
-              response.responseHeader.serviceResult.should.eql(StatusCodes.Good);
-            }
-            callback(err);
-          });
-        },
-
-
-        // terminate
-        function(callback) {
-          client2.disconnect(callback);
-        },
-        function(callback) {
-          client1.disconnect(callback);
-        },
-        function(callback) {
-          test.server.engine.currentSessionCount.should.eql(0);
-          callback();
-        }
-
-      ], done);
-    });
-
-    // OpcUA 1.02 part 3 $5.6.3.1 ActiveSession Set page 29
-    // When the ActivateSession  Service  is called f or the first time then the Server shall reject the request
-    // if the  SecureChannel  is not same as the one associated with the CreateSession  request.
-    it("RQ1 - should reject if the channel used to activate the session for the first time is not the same as the channel used to create the session", function(done) {
-
-      let client1, client2;
-      let session1;
-
-      let initialChannelCount = 0;
-      async.series([
-
-        // create a first channel (client1)
-        function(callback) {
-          initialChannelCount = test.server.getChannels().length;
-          test.server.getChannels().length.should.equal(initialChannelCount);
-          client1 = OPCUAClient.create();
-          client1.connect(test.endpointUrl, callback);
-        },
-
-        // create a session using client1
-        //   ( without activating it)
-        function(callback) {
-          client1._createSession(function(err, session) {
-            if (err) {
-              return callback(err);
-            }
-            session1 = session;
-            test.server.getChannels().length.should.equal(initialChannelCount + 1);
-            callback();
-          });
-        },
-        // create a second channel (client2)
-        function(callback) {
-          client2 = OPCUAClient.create();
-          client2.connect(test.endpointUrl, callback);
-        },
-
-        // activate the session created with client1 using client2 !!
-        // this should be detected by server and server shall return an error
-        function(callback) {
-          test.server.getChannels().length.should.equal(initialChannelCount + 2);
-          //xx console.log(" ID1 =", client1._secureChannel.channelId);
-          //xx console.log(" ID2 =", client2._secureChannel.channelId);
-
-          client2.reactivateSession(session1, function(err) {
-
-            if (!err) {
-              callback(new Error("_activateSession shall return an error "));
-            }
-            err.message.should.match(/BadSessionNotActivated/);
-            callback();
-          });
-        },
-
-        // terminate
-        function(callback) {
-          client2.disconnect(callback);
-        },
-        // activate the session as expected on same channel used to create it
-        // so we can close it properly
-        function(callback) {
-          client1._activateSession(session1, function(err) {
-            should.not.exist(err);
-            session1.close(callback);
-          });
-        },
-        function(callback) {
-          client1.disconnect(callback);
-        },
-        function(callback) {
-          test.server.engine.currentSessionCount.should.eql(0);
-          callback();
-        }
-
-      ], done);
-    });
+        it("RQB1 - calling CreateSession and CloseSession &  CloseSession again should return BadSessionIdInvalid", function (done) {
+            let client1;
+            let session1;
+            async.series(
+                [
+                    function (callback) {
+                        client1 = OPCUAClient.create();
+                        client1.connect(test.endpointUrl, callback);
+                    },
+                    // create a session using client1
+                    function (callback) {
+                        client1._createSession(function (err, session) {
+                            if (err) {
+                                return callback(err);
+                            }
+                            session1 = session;
+                            callback();
+                        });
+                    },
+
+                    function (callback) {
+                        session1.close(function () {
+                            callback();
+                        });
+                    },
+                    function (callback) {
+                        session1.close(function (err) {
+                            // now session close do not return error if session in invalid
+                            // err.message.should.match(/SessionIdInvalid/);
+                            should.not.exist(err);
+                            callback();
+                        });
+                    },
+                    //
+                    function (callback) {
+                        test.server.engine.currentSessionCount.should.eql(0);
+                        client1.disconnect(callback);
+                    }
+                ],
+                done
+            );
+        });
 
+        it("RQB2 - calling CloseSession without calling CreateSession first", function (done) {
+            let client1;
+
+            async.series(
+                [
+                    function (callback) {
+                        client1 = OPCUAClient.create({});
+                        client1.connect(test.endpointUrl, callback);
+                    },
+                    function (callback) {
+                        const request = new CloseSessionRequest({
+                            deleteSubscriptions: true
+                        });
+                        client1.performMessageTransaction(request, function (err, response) {
+                            should.exist(err);
+                            should.not.exist(response);
+                            err.response.should.be.instanceOf(ServiceFault);
+                            err.message.should.match(/BadSessionIdInvalid/);
+                            err.response.responseHeader.serviceResult.should.eql(StatusCodes.BadSessionIdInvalid);
+                            callback();
+                        });
+                    },
+                    function (callback) {
+                        client1.disconnect(callback);
+                    },
+                    function (callback) {
+                        test.server.engine.currentSessionCount.should.eql(0);
+                        callback();
+                    }
+                ],
+                done
+            );
+        });
 
-    function m(file) {
-      const p = path.join(certificateFolder, file);
-      if (!fs.existsSync(p)) {
-        console.error(" cannot find ", p);
-      }
-      return p;
-    }
-
-    // OpcUA 1.02 part 3 $5.6.3.1 ActiveSession Set page 29
-    // Subsequent calls to  ActivateSession  may be associated with different  SecureChannels.  If this is the
-    // case then  the  Server  shall verify that the  Certificate  the  Client  used to create the new
-    // SecureChannel  is the same as the  Certificate  used to create the original  SecureChannel.
-    it("RQ2 -server should raise an error if a existing session is reactivated from a channel that have different certificate than the original channel", function(done) {
-
-      const serverCertificate = test.server.getCertificateChain();
-
-      let client1, client2;
-      let session1;
-      async.series([
-
-        // create a first channel (client1) with
-        function(callback) {
-          //xx console.log(" creating initial channel with some certificate");
-          const certificateFile1 = m("client_cert_2048.pem");
-          const privateKeyFile1 = m("client_key_2048.pem");
-          console.log(certificateFile1);
-
-          client1 = OPCUAClient.create({
-            certificateFile: certificateFile1,
-            privateKeyFile: privateKeyFile1,
-            securityMode: MessageSecurityMode.Sign,
-            securityPolicy: SecurityPolicy.Basic128Rsa15,
-            serverCertificate: serverCertificate
-          });
-
-   
-          const certificate = readCertificate(certificateFile1);
-
-          async function doIt() {
-            await test.server.serverCertificateManager.trustCertificate(certificate);
-            const issuerCertificateFile = m("CA/public/cacert.pem");
-            const issuerCertificateRevocationListFile = m("CA/crl/revocation_list.der");
-            const issuerCertificate = readCertificate(issuerCertificateFile);
-            const issuerCrl = await readCertificateRevocationList(issuerCertificateRevocationListFile);
-            await test.server.serverCertificateManager.addIssuer(issuerCertificate);
-            await test.server.serverCertificateManager.addRevocationList(issuerCrl);
-            callback();
-          }
-          doIt();
-        },
-        function(callback) {
-          client1.connect(test.endpointUrl, callback);
-        },
-        // create a session using client1
-        function(callback) {
-          //xx console.log(" create session");
-          client1._createSession(function(err, session) {
-            if (err) {
-              return callback(err);
-            }
-            session1 = session;
-            callback();
-          });
-        },
-        // activate the session as expected on same channel used to create it
-        function(callback) {
-          //xx console.log(" activate session");
-          client1._activateSession(session1, function(err) {
-            callback(err);
-          });
-        },
-
-        // create a second channel (client2)
-        // with a different certificate ....
-        function(callback) {
-
-          // creating second channel with different credential
-          console.log(" creating second channel with different certificate");
-          const certificateFile2 = m("client_cert_3072.pem");
-          const privateKeyFile2 = m("client_key_3072.pem");
-          client2 = OPCUAClient.create({
-            certificateFile: certificateFile2,
-            privateKeyFile: privateKeyFile2,
-            securityMode: MessageSecurityMode.Sign,
-            securityPolicy: SecurityPolicy.Basic256,
-            serverCertificate: serverCertificate
-          });
-          const certificate = readCertificate(certificateFile2);
-          test.server.serverCertificateManager.trustCertificate(certificate, callback);
-
-        },
-        function(callback) {
-          client2.connect(test.endpointUrl, callback);
-        },
-        function(callback) {
-          // reactivate session on second channel
-          // Reactivate should fail because certificate is not the same as the original one
-          client2.reactivateSession(session1, function(err) {
-            if (err) {
-              err.message.should.match(/BadNoValidCertificates/);
-              callback();
-            } else {
-              callback(new Error("expecting reactivateSession to fail"));
-            }
-          });
-        },
-        // terminate
-        function(callback) {
-          client2.disconnect(callback);
-        },
-        function(callback) {
-          session1.close(callback);
-        },
-        function(callback) {
-          client1.disconnect(callback);
-        },
-
-        function(callback) {
-          test.server.engine.currentSessionCount.should.eql(0);
-          callback();
-        }
+        it("RQB3 - calling CreateSession,  CloseSession  and CloseSession again", function (done) {
+            let client1;
+            let session1;
+            async.series(
+                [
+                    function (callback) {
+                        client1 = OPCUAClient.create();
+                        client1.connect(test.endpointUrl, callback);
+                    },
+                    // create a session using client1
+                    function (callback) {
+                        client1.createSession(function (err, session) {
+                            if (err) {
+                                return callback(err);
+                            }
+                            session1 = session;
+                            callback();
+                        });
+                    },
+
+                    // first call to close session should be OK
+                    function (callback) {
+                        client1.closeSession(session1, /* deleteSubscriptions =*/ true, function (err) {
+                            callback(err);
+                        });
+                    },
+
+                    // second call to close session should raise an error
+                    function (callback) {
+                        const request = new CloseSessionRequest({
+                            deleteSubscriptions: true
+                        });
+                        client1.performMessageTransaction(request, function (err, response) {
+                            should.exist(err);
+                            should.not.exist(response);
+                            err.message.should.match(/BadSessionIdInvalid/);
+                            err.response.should.be.instanceOf(ServiceFault);
+                            err.response.responseHeader.serviceResult.should.eql(StatusCodes.BadSessionIdInvalid);
+                            callback();
+                        });
+                    },
+
+                    function (callback) {
+                        client1.disconnect(callback);
+                    },
+                    function (callback) {
+                        test.server.engine.currentSessionCount.should.eql(0);
+                        callback();
+                    }
+                ],
+                function final(err) {
+                    client1.disconnect(function () {
+                        done(err);
+                    });
+                }
+            );
+        });
 
-      ], done);
+        it("RQ0 - call ActiveSession on a session that has been transferred to a different channel", function (done) {
+            // this test verifies that the following requirement can be met
+            // OpcUA 1.02 part 3 $5.5 Secure Channel Set page 20
+            // Once a  Client  has established a  Session  it may wish to access the  Session  from a different
+            // SecureChannel. The Client can do this by validating the new  SecureChannel  with the
+            // ActivateSession  Service  described in 5.6.3.
+            let client1, client2;
+            let session1;
+            async.series(
+                [
+                    // create a first channel (client1)
+                    function (callback) {
+                        client1 = OPCUAClient.create();
+                        client1.connect(test.endpointUrl, callback);
+                    },
+                    // create a session using client1
+                    function (callback) {
+                        client1._createSession(function (err, session) {
+                            if (err) {
+                                return callback(err);
+                            }
+                            session1 = session;
+                            callback();
+                        });
+                    },
+                    // activate the session as expected on same channel used to create it
+                    function (callback) {
+                        client1._activateSession(session1, function (err) {
+                            callback(err);
+                        });
+                    },
+                    // let verify that it is now possible to send a request on client1's session
+                    function (callback) {
+                        // coerce nodeIds
+                        const request = new ReadRequest({
+                            nodesToRead: [{ nodeId: "i=2255", attributeId: 13 }],
+                            maxAge: 0,
+                            timestampsToReturn: TimestampsToReturn.Both
+                        });
+                        request.requestHeader.authenticationToken = session1.authenticationToken;
+                        client1.performMessageTransaction(request, function (err, response) {
+                            should.not.exist(err);
+                            response.responseHeader.serviceResult.should.eql(StatusCodes.Good);
+                            callback();
+                        });
+                    },
+
+                    // create a second channel (client2)
+                    function (callback) {
+                        client2 = OPCUAClient.create();
+                        client2.connect(test.endpointUrl, callback);
+                    },
+
+                    // reactivate session on second channel
+                    function (callback) {
+                        client2.reactivateSession(session1, function (err) {
+                            callback(err);
+                        });
+                    },
+
+                    // now that session has been assigned to client 1,
+                    // server shall refuse any requests on channel1
+                    function (callback) {
+                        // coerce nodeIds
+                        const request = new ReadRequest({
+                            nodesToRead: [{ nodeId: "i=2255", attributeId: 13 }],
+                            maxAge: 0,
+                            timestampsToReturn: TimestampsToReturn.Both
+                        });
+                        request.requestHeader.authenticationToken = session1.authenticationToken;
+                        client1.performMessageTransaction(request, function (err, response) {
+                            should.not.exist(err);
+                            should.exist(response);
+                            response.should.be.instanceOf(ReadResponse);
+                            response.responseHeader.serviceResult.should.eql(StatusCodes.BadSecureChannelIdInvalid);
+                            callback();
+                        });
+                    },
+                    // but server shall access request on new channel
+                    function (callback) {
+                        // coerce nodeIds
+                        const request = new ReadRequest({
+                            nodesToRead: [{ nodeId: "i=2255", attributeId: 13 }],
+                            maxAge: 0,
+                            timestampsToReturn: TimestampsToReturn.Both
+                        });
+                        request.requestHeader.authenticationToken = session1.authenticationToken;
+                        client2.performMessageTransaction(request, function (err, response) {
+                            if (!err) {
+                                response.responseHeader.serviceResult.should.eql(StatusCodes.Good);
+                            }
+                            callback(err);
+                        });
+                    },
+
+                    // terminate
+                    function (callback) {
+                        client2.disconnect(callback);
+                    },
+                    function (callback) {
+                        client1.disconnect(callback);
+                    },
+                    function (callback) {
+                        test.server.engine.currentSessionCount.should.eql(0);
+                        callback();
+                    }
+                ],
+                done
+            );
+        });
 
-    });
+        // OpcUA 1.02 part 3 $5.6.3.1 ActiveSession Set page 29
+        // When the ActivateSession  Service  is called f or the first time then the Server shall reject the request
+        // if the  SecureChannel  is not same as the one associated with the CreateSession  request.
+        it("RQ1 - should reject if the channel used to activate the session for the first time is not the same as the channel used to create the session", function (done) {
+            let client1, client2;
+            let session1;
+
+            let initialChannelCount = 0;
+            async.series(
+                [
+                    // create a first channel (client1)
+                    function (callback) {
+                        initialChannelCount = test.server.getChannels().length;
+                        test.server.getChannels().length.should.equal(initialChannelCount);
+                        client1 = OPCUAClient.create();
+                        client1.connect(test.endpointUrl, callback);
+                    },
+
+                    // create a session using client1
+                    //   ( without activating it)
+                    function (callback) {
+                        client1._createSession(function (err, session) {
+                            if (err) {
+                                return callback(err);
+                            }
+                            session1 = session;
+                            test.server.getChannels().length.should.equal(initialChannelCount + 1);
+                            callback();
+                        });
+                    },
+                    // create a second channel (client2)
+                    function (callback) {
+                        client2 = OPCUAClient.create();
+                        client2.connect(test.endpointUrl, callback);
+                    },
+
+                    // activate the session created with client1 using client2 !!
+                    // this should be detected by server and server shall return an error
+                    function (callback) {
+                        test.server.getChannels().length.should.equal(initialChannelCount + 2);
+                        //xx console.log(" ID1 =", client1._secureChannel.channelId);
+                        //xx console.log(" ID2 =", client2._secureChannel.channelId);
+
+                        client2.reactivateSession(session1, function (err) {
+                            if (!err) {
+                                callback(new Error("_activateSession shall return an error "));
+                            }
+                            err.message.should.match(/BadSessionNotActivated/);
+                            callback();
+                        });
+                    },
+
+                    // terminate
+                    function (callback) {
+                        client2.disconnect(callback);
+                    },
+                    // activate the session as expected on same channel used to create it
+                    // so we can close it properly
+                    function (callback) {
+                        client1._activateSession(session1, function (err) {
+                            should.not.exist(err);
+                            session1.close(callback);
+                        });
+                    },
+                    function (callback) {
+                        client1.disconnect(callback);
+                    },
+                    function (callback) {
+                        test.server.engine.currentSessionCount.should.eql(0);
+                        callback();
+                    }
+                ],
+                done
+            );
+        });
 
-    // In addition,the Server shall verify that the  Client  supplied a  UserIdentityToken  that is   identical to the token
-    // currently associated with the  Session.
-    it("RQ3 - server should raise an error if a session is reactivated with different user identity tokens", function(done) {
-      let client1, client2;
-      let session1;
-
-      const user1 = {
-        userName: "user1", password: "password1"
-      };
-      const user2 = new UserNameIdentityToken({
-        userName: "user1", password: "password1"
-      });
-      //xx console.log(" user1 ", user1.toString());
-      async.series([
-
-        // given a established session with a subscription and some publish request
-
-        function(callback) {
-          client1 = OPCUAClient.create();
-          client1.connect(test.endpointUrl, callback);
-        },
-        // create a session using client1
-        function(callback) {
-
-          client1.createSession(user1, function(err, session) {
-            if (err) {
-              return callback(err);
+        function m(file) {
+            const p = path.join(certificateFolder, file);
+            if (!fs.existsSync(p)) {
+                console.error(" cannot find ", p);
             }
-            session1 = session;
-            callback();
-          });
-        },
-        // when the session is transferred to a different channel
-        // create a second channel (client2)
-        function(callback) {
-          client2 = OPCUAClient.create();
-          client2.connect(test.endpointUrl, callback);
-        },
-        function(callback) {
-          // reactivate session on second channel
-          // alter session1.userIdentityInfo
-          session1.userIdentityInfo = user2;
-          session1.userIdentityInfo.userName.should.eql("user1");
-
-          client2.reactivateSession(session1, function(err) {
-            err.message.should.match(/BadIdentityChangeNotSupported/);
-            _.contains(client1._sessions, session1).should.eql(true);// should have failed
-            callback();
-          });
-        },
-        // terminate
-        function(callback) {
-          client2.disconnect(callback);
-        },
-        function(callback) {
-          client1.disconnect(callback);
-        },
-        function(callback) {
-          test.server.engine.currentSessionCount.should.eql(0);
-          callback();
+            return p;
         }
 
-      ], done);
-
-    });
-    // Once the Server accepts the new  SecureChannel  it shall reject requests sent via the old  SecureChannel.
-    xit("RQ4 - server should reject request send via old channel when session has been transferred to new channel", function(done) {
-      async.series([], done);
-    });
-
-    // unprocessed pending Requests such as PublishRequest shall be be denied by the server
-    // Once the Server accepts the new  SecureChannel  it shall reject requests sent via the old  SecureChannel
-    it("RQ5 - server should reject pending requests send to old channel when session has been transferred to new channel", function(done) {
-
-      const sinon = require("sinon");
-
-      const collectPublishResponse = sinon.spy();
-
-      let client1, client2;
-      let session1;
-      async.series([
-
-        // given a established session with a subscription and some publish request
-
-        function(callback) {
-          client1 = OPCUAClient.create();
-          client1.connect(test.endpointUrl, callback);
-        },
-        // create a session using client1
-        function(callback) {
-          client1._createSession(function(err, session) {
-            if (err) {
-              return callback(err);
-            }
-            session1 = session;
-            callback();
-          });
-        },
-        // activate the session as expected on same channel used to create it
-        function(callback) {
-          client1._activateSession(session1, function(err) {
-            callback(err);
-          });
-        },
-
-        // creaet a subscription,
-        function(callback) {
-          createSubscription(session1, callback);
-        },
-
-
-        // when the session is transferred to a different channel
-        // create a second channel (client2)
-        function(callback) {
-          client2 = OPCUAClient.create();
-          client2.connect(test.endpointUrl, callback);
-          collectPublishResponse.callCount.should.eql(0);
-        },
-
-        // provision 3 publish requests and wait for the first keep alive
-        function(callback) {
-
-          sendPublishRequest(session1, function(err) {
-            should.not.exist(err);
-            collectPublishResponse.callCount.should.eql(0);
-            callback();
-          });
-          sendPublishRequest(session1, collectPublishResponse);
-          sendPublishRequest(session1, collectPublishResponse);
-        },
-
-
-        function(callback) {
-          // reactivate session on second channel
-          client2.reactivateSession(session1, function(err) {
-            callback(err);
-          });
-        },
-        function(callback) {
-          setTimeout(callback, 100);
-        },
-
-        function(callback) {
-
-          collectPublishResponse.callCount.should.eql(2);
-          collectPublishResponse.getCall(0).args[0].message.should.match(/BadSecureChannelClosed/);
-          collectPublishResponse.getCall(1).args[0].message.should.match(/BadSecureChannelClosed/);
-          callback();
-        },
-
-
-        // terminate
-        function(callback) {
-          client2.disconnect(callback);
-        },
-        function(callback) {
-          client1.disconnect(callback);
-        },
-        function(callback) {
-          test.server.engine.currentSessionCount.should.eql(0);
-          callback();
-        }
+        // OpcUA 1.02 part 3 $5.6.3.1 ActiveSession Set page 29
+        // Subsequent calls to  ActivateSession  may be associated with different  SecureChannels.  If this is the
+        // case then  the  Server  shall verify that the  Certificate  the  Client  used to create the new
+        // SecureChannel  is the same as the  Certificate  used to create the original  SecureChannel.
+        it("RQ2 -server should raise an error if a existing session is reactivated from a channel that have different certificate than the original channel", function (done) {
+            const serverCertificate = test.server.getCertificateChain();
+
+            let client1, client2;
+            let session1;
+            async.series(
+                [
+                    // create a first channel (client1) with
+                    function (callback) {
+                        //xx console.log(" creating initial channel with some certificate");
+                        const certificateFile1 = m("client_cert_2048.pem");
+                        const privateKeyFile1 = m("client_key_2048.pem");
+                        console.log(certificateFile1);
+
+                        client1 = OPCUAClient.create({
+                            certificateFile: certificateFile1,
+                            privateKeyFile: privateKeyFile1,
+                            securityMode: MessageSecurityMode.Sign,
+                            securityPolicy: SecurityPolicy.Basic128Rsa15,
+                            serverCertificate: serverCertificate
+                        });
+
+                        const certificate = readCertificate(certificateFile1);
+
+                        async function doIt() {
+                            await test.server.serverCertificateManager.trustCertificate(certificate);
+                            const issuerCertificateFile = m("CA/public/cacert.pem");
+                            const issuerCertificateRevocationListFile = m("CA/crl/revocation_list.der");
+                            const issuerCertificate = readCertificate(issuerCertificateFile);
+                            const issuerCrl = await readCertificateRevocationList(issuerCertificateRevocationListFile);
+                            await test.server.serverCertificateManager.addIssuer(issuerCertificate);
+                            await test.server.serverCertificateManager.addRevocationList(issuerCrl);
+                            callback();
+                        }
+                        doIt();
+                    },
+                    function (callback) {
+                        client1.connect(test.endpointUrl, callback);
+                    },
+                    // create a session using client1
+                    function (callback) {
+                        //xx console.log(" create session");
+                        client1._createSession(function (err, session) {
+                            if (err) {
+                                return callback(err);
+                            }
+                            session1 = session;
+                            callback();
+                        });
+                    },
+                    // activate the session as expected on same channel used to create it
+                    function (callback) {
+                        //xx console.log(" activate session");
+                        client1._activateSession(session1, function (err) {
+                            callback(err);
+                        });
+                    },
+
+                    // create a second channel (client2)
+                    // with a different certificate ....
+                    function (callback) {
+                        // creating second channel with different credential
+                        console.log(" creating second channel with different certificate");
+                        const certificateFile2 = m("client_cert_3072.pem");
+                        const privateKeyFile2 = m("client_key_3072.pem");
+                        client2 = OPCUAClient.create({
+                            certificateFile: certificateFile2,
+                            privateKeyFile: privateKeyFile2,
+                            securityMode: MessageSecurityMode.Sign,
+                            securityPolicy: SecurityPolicy.Basic256,
+                            serverCertificate: serverCertificate
+                        });
+                        const certificate = readCertificate(certificateFile2);
+                        test.server.serverCertificateManager.trustCertificate(certificate, callback);
+                    },
+                    function (callback) {
+                        client2.connect(test.endpointUrl, callback);
+                    },
+                    function (callback) {
+                        // reactivate session on second channel
+                        // Reactivate should fail because certificate is not the same as the original one
+                        client2.reactivateSession(session1, function (err) {
+                            if (err) {
+                                err.message.should.match(/BadNoValidCertificates/);
+                                callback();
+                            } else {
+                                callback(new Error("expecting reactivateSession to fail"));
+                            }
+                        });
+                    },
+                    // terminate
+                    function (callback) {
+                        client2.disconnect(callback);
+                    },
+                    function (callback) {
+                        session1.close(callback);
+                    },
+                    function (callback) {
+                        client1.disconnect(callback);
+                    },
+
+                    function (callback) {
+                        test.server.engine.currentSessionCount.should.eql(0);
+                        callback();
+                    }
+                ],
+                done
+            );
+        });
 
-      ], done);
+        // In addition,the Server shall verify that the  Client  supplied a  UserIdentityToken  that is   identical to the token
+        // currently associated with the  Session.
+        it("RQ3 - server should raise an error if a session is reactivated with different user identity tokens", function (done) {
+            let client1, client2;
+            let session1;
+
+            const user1 = {
+                userName: "user1",
+                password: "password1"
+            };
+            const user2 = new UserNameIdentityToken({
+                userName: "user1",
+                password: "password1"
+            });
+            //xx console.log(" user1 ", user1.toString());
+            async.series(
+                [
+                    // given a established session with a subscription and some publish request
+
+                    function (callback) {
+                        client1 = OPCUAClient.create();
+                        client1.connect(test.endpointUrl, callback);
+                    },
+                    // create a session using client1
+                    function (callback) {
+                        client1.createSession(user1, function (err, session) {
+                            if (err) {
+                                return callback(err);
+                            }
+                            session1 = session;
+                            callback();
+                        });
+                    },
+                    // when the session is transferred to a different channel
+                    // create a second channel (client2)
+                    function (callback) {
+                        client2 = OPCUAClient.create();
+                        client2.connect(test.endpointUrl, callback);
+                    },
+                    function (callback) {
+                        // reactivate session on second channel
+                        // alter session1.userIdentityInfo
+                        session1.userIdentityInfo = user2;
+                        session1.userIdentityInfo.userName.should.eql("user1");
+
+                        client2.reactivateSession(session1, function (err) {
+                            err.message.should.match(/BadIdentityChangeNotSupported/);
+                            _.contains(client1._sessions, session1).should.eql(true); // should have failed
+                            callback();
+                        });
+                    },
+                    // terminate
+                    function (callback) {
+                        client2.disconnect(callback);
+                    },
+                    function (callback) {
+                        client1.disconnect(callback);
+                    },
+                    function (callback) {
+                        test.server.engine.currentSessionCount.should.eql(0);
+                        callback();
+                    }
+                ],
+                done
+            );
+        });
+        // Once the Server accepts the new  SecureChannel  it shall reject requests sent via the old  SecureChannel.
+        xit("RQ4 - server should reject request send via old channel when session has been transferred to new channel", function (done) {
+            async.series([], done);
+        });
 
+        // unprocessed pending Requests such as PublishRequest shall be be denied by the server
+        // Once the Server accepts the new  SecureChannel  it shall reject requests sent via the old  SecureChannel
+        it("RQ5 - server should reject pending requests send to old channel when session has been transferred to new channel", function (done) {
+            const sinon = require("sinon");
+
+            const collectPublishResponse = sinon.spy();
+
+            let client1, client2;
+            let session1;
+            async.series(
+                [
+                    // given a established session with a subscription and some publish request
+
+                    function (callback) {
+                        client1 = OPCUAClient.create();
+                        client1.connect(test.endpointUrl, callback);
+                    },
+                    // create a session using client1
+                    function (callback) {
+                        client1._createSession(function (err, session) {
+                            if (err) {
+                                return callback(err);
+                            }
+                            session1 = session;
+                            callback();
+                        });
+                    },
+                    // activate the session as expected on same channel used to create it
+                    function (callback) {
+                        client1._activateSession(session1, function (err) {
+                            callback(err);
+                        });
+                    },
+
+                    // creaet a subscription,
+                    function (callback) {
+                        createSubscription(session1, callback);
+                    },
+
+                    // when the session is transferred to a different channel
+                    // create a second channel (client2)
+                    function (callback) {
+                        client2 = OPCUAClient.create();
+                        client2.connect(test.endpointUrl, callback);
+                        collectPublishResponse.callCount.should.eql(0);
+                    },
+
+                    // provision 3 publish requests and wait for the first keep alive
+                    function (callback) {
+                        sendPublishRequest(session1, function (err) {
+                            should.not.exist(err);
+                            collectPublishResponse.callCount.should.eql(0);
+                            callback();
+                        });
+                        sendPublishRequest(session1, collectPublishResponse);
+                        sendPublishRequest(session1, collectPublishResponse);
+                    },
+
+                    function (callback) {
+                        // reactivate session on second channel
+                        client2.reactivateSession(session1, function (err) {
+                            callback(err);
+                        });
+                    },
+                    function (callback) {
+                        setTimeout(callback, 100);
+                    },
+
+                    function (callback) {
+                        collectPublishResponse.callCount.should.eql(2);
+                        collectPublishResponse.getCall(0).args[0].message.should.match(/BadSecureChannelClosed/);
+                        collectPublishResponse.getCall(1).args[0].message.should.match(/BadSecureChannelClosed/);
+                        callback();
+                    },
+
+                    // terminate
+                    function (callback) {
+                        client2.disconnect(callback);
+                    },
+                    function (callback) {
+                        client1.disconnect(callback);
+                    },
+                    function (callback) {
+                        test.server.engine.currentSessionCount.should.eql(0);
+                        callback();
+                    }
+                ],
+                done
+            );
+        });
     });
-
-  });
-
 };

programming_language: JavaScript
filename: client_secure_channel_layer.ts
change_type: ModificationType.MODIFY
diff: @@ -119,7 +119,8 @@ function process_request_callback(requestData: RequestData, err?: Error | null,
         err = new Error(" serviceResult = " + response.responseHeader.serviceResult.toString());
         //  "  returned by server \n response:" + response.toString() + "\n  request: " + request.toString());
         (err as any).response = response;
-        ((err as any).request = request), (response = undefined);
+        (err as any).request = request;
+        response = undefined;
     }
 
     const theCallbackFunction = requestData.callback;
@@ -1721,7 +1722,7 @@ export class ClientSecureChannelLayer extends EventEmitter {
         // istanbul ignore next
         if (!this.derivedKeys || !this.derivedKeys.derivedClientKeys) {
             errorLog("derivedKeys not set but security mode = ", MessageSecurityMode[this.securityMode]);
-            return null; // 
+            return null; //
             // throw new Error("internal error expecting valid derivedKeys while security mode is " + MessageSecurityMode[this.securityMode]);
         }
 

programming_language: TypeScript
filename: opcua_discovery_server.ts
change_type: ModificationType.MODIFY
diff: @@ -15,7 +15,7 @@ import { UAString } from "node-opcua-basic-types";
 import { makeApplicationUrn } from "node-opcua-common";
 import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
 import { extractFullyQualifiedDomainName, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
-import { Message, Response, ServerSecureChannelLayer } from "node-opcua-secure-channel";
+import { Message, Response, ServerSecureChannelLayer, ServiceFault } from "node-opcua-secure-channel";
 import { OPCUABaseServer, OPCUABaseServerOptions, OPCUAServerEndPoint } from "node-opcua-server";
 
 import {
@@ -390,7 +390,7 @@ export class OPCUADiscoveryServer extends OPCUABaseServer {
 
         function sendError(statusCode: StatusCode): Response {
             debugLog(chalk.red("_on_RegisterServer(2)Request error"), statusCode.toString());
-            const response1 = new RegisterServerXResponse({
+            const response1 = new ServiceFault({
                 responseHeader: { serviceResult: statusCode }
             });
             return response1;

programming_language: TypeScript
filename: opcua_server.ts
change_type: ModificationType.MODIFY
diff: @@ -145,7 +145,8 @@ import {
     BuildInfoOptions,
     MonitoredItemCreateResult,
     IssuedIdentityToken,
-    BrowseResultOptions
+    BrowseResultOptions,
+    ServiceFault
 } from "node-opcua-types";
 import { DataType } from "node-opcua-variant";
 import { VariantArrayType } from "node-opcua-variant";
@@ -184,7 +185,7 @@ const warningLog = make_warningLog(__filename);
 const default_maxConnectionsPerEndpoint = 10;
 
 function g_sendError(channel: ServerSecureChannelLayer, message: Message, ResponseClass: any, statusCode: StatusCode): void {
-    const response = new ResponseClass({
+    const response = new ServiceFault({
         responseHeader: { serviceResult: statusCode }
     });
     return channel.send_response("MSG", response, message);
@@ -2233,7 +2234,7 @@ export class OPCUAServer extends OPCUABaseServer {
 
         function sendResponse(response1: Response) {
             try {
-                assert(response1 instanceof ResponseClass);
+                assert(response1 instanceof ResponseClass || response1 instanceof ServiceFault);
                 if (message.session) {
                     const counterName = ResponseClass.name.replace("Response", "");
                     message.session.incrementRequestTotalCounter(counterName);
@@ -3071,7 +3072,7 @@ export class OPCUAServer extends OPCUABaseServer {
             (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
                 assert(session);
                 assert(session.publishEngine); // server.publishEngine doesn't exists, OPCUAServer has probably shut down already
-                session.publishEngine._on_PublishRequest(request, (request1: PublishRequest, response: PublishResponse) => {
+                session.publishEngine._on_PublishRequest(request, (_request1, response) => {
                     sendResponse(response);
                 });
             }
@@ -3180,16 +3181,20 @@ export class OPCUAServer extends OPCUABaseServer {
                     linksToAdd,
                     linksToRemove
                 );
-                const response = new SetTriggeringResponse({
-                    responseHeader: { serviceResult: statusCode },
-
-                    addResults,
-                    removeResults,
-                    addDiagnosticInfos: null,
-                    removeDiagnosticInfos: null
-                });
+                if (statusCode !== StatusCodes.Good) {
+                    const response = new ServiceFault({ responseHeader: { serviceResult: statusCode } });
+                    sendResponse(response);
+                } else {
+                    const response = new SetTriggeringResponse({
+                        responseHeader: { serviceResult: statusCode },
 
-                sendResponse(response);
+                        addResults,
+                        removeResults,
+                        addDiagnosticInfos: null,
+                        removeDiagnosticInfos: null
+                    });
+                    sendResponse(response);
+                }
             }
         );
     }
@@ -3390,11 +3395,8 @@ export class OPCUAServer extends OPCUABaseServer {
             message,
             channel,
             (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
-                let response;
-
                 if (!request.nodesToRegister || request.nodesToRegister.length === 0) {
-                    response = new RegisterNodesResponse({ responseHeader: { serviceResult: StatusCodes.BadNothingToDo } });
-                    return sendResponse(response);
+                    return sendError(StatusCodes.BadNothingToDo);
                 }
                 if (this.engine.serverCapabilities.operationLimits.maxNodesPerRegisterNodes > 0) {
                     if (request.nodesToRegister.length > this.engine.serverCapabilities.operationLimits.maxNodesPerRegisterNodes) {
@@ -3410,7 +3412,7 @@ export class OPCUAServer extends OPCUABaseServer {
                 // NodeId from the request.
                 const registeredNodeIds = request.nodesToRegister.map((nodeId) => session.registerNode(nodeId));
 
-                response = new RegisterNodesResponse({
+                const response = new RegisterNodesResponse({
                     registeredNodeIds
                 });
                 sendResponse(response);
@@ -3427,13 +3429,10 @@ export class OPCUAServer extends OPCUABaseServer {
             message,
             channel,
             (session: ServerSession, sendResponse: (response: Response) => void, sendError: (statusCode: StatusCode) => void) => {
-                let response;
-
                 request.nodesToUnregister = request.nodesToUnregister || [];
 
                 if (!request.nodesToUnregister || request.nodesToUnregister.length === 0) {
-                    response = new UnregisterNodesResponse({ responseHeader: { serviceResult: StatusCodes.BadNothingToDo } });
-                    return sendResponse(response);
+                    return sendError(StatusCodes.BadNothingToDo);
                 }
 
                 if (this.engine.serverCapabilities.operationLimits.maxNodesPerRegisterNodes > 0) {
@@ -3446,7 +3445,7 @@ export class OPCUAServer extends OPCUABaseServer {
 
                 request.nodesToUnregister.map((nodeId: NodeId) => session.unRegisterNode(nodeId));
 
-                response = new UnregisterNodesResponse({});
+                const response = new UnregisterNodesResponse({});
                 sendResponse(response);
             }
         );

programming_language: TypeScript
filename: server_publish_engine.ts
change_type: ModificationType.MODIFY
diff: @@ -11,7 +11,7 @@ import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
 import { ObjectRegistry } from "node-opcua-object-registry";
 import { StatusCode, StatusCodes } from "node-opcua-status-code";
 
-import { PublishRequest, PublishResponse, SubscriptionAcknowledgement } from "node-opcua-types";
+import { PublishRequest, PublishResponse, ServiceFault, SubscriptionAcknowledgement } from "node-opcua-types";
 import { Subscription } from "./server_subscription";
 import { SubscriptionState } from "./server_subscription";
 import { IServerSidePublishEngine, INotifMsg, IClosedOrTransferredSubscription } from "./i_server_side_publish_engine";
@@ -36,7 +36,7 @@ interface PublishData {
     request: PublishRequest;
     serverTimeWhenReceived: number;
     results: StatusCode[];
-    callback: (request: PublishRequest, response: PublishResponse) => void;
+    callback: (request: PublishRequest, response: PublishResponse | ServiceFault) => void;
 }
 
 function _assertValidPublishData(publishData: PublishData) {
@@ -137,7 +137,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
 
         const tmp = srcPublishEngine.detach_subscription(subscription);
         destPublishEngine.add_subscription(tmp);
-        
+
         subscription.resetLifeTimeCounter();
         if (sendInitialValues) {
             /*  A Boolean parameter with the following values:
@@ -327,11 +327,12 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
     public on_close_subscription(subscription: IClosedOrTransferredSubscription): void {
         doDebug && debugLog("ServerSidePublishEngine#on_close_subscription", subscription.id);
         if (subscription.hasPendingNotifications) {
-            doDebug && debugLog(
-                "ServerSidePublishEngine#on_close_subscription storing subscription",
-                subscription.id,
-                " to _closed_subscriptions because it has pending notification"
-            );
+            doDebug &&
+                debugLog(
+                    "ServerSidePublishEngine#on_close_subscription storing subscription",
+                    subscription.id,
+                    " to _closed_subscriptions because it has pending notification"
+                );
             this._closed_subscriptions.push(subscription);
         } else {
             doDebug && debugLog("ServerSidePublishEngine#on_close_subscription disposing subscription", subscription.id);
@@ -402,7 +403,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
      */
     public _on_PublishRequest(
         request: PublishRequest,
-        callback?: (request1: PublishRequest, response: PublishResponse) => void
+        callback?: (request1: PublishRequest, response: PublishResponse| ServiceFault) => void
     ): void {
         callback = callback || dummy_function;
         assert(typeof callback === "function");
@@ -483,9 +484,10 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
                                 s.timeToKeepAlive +
                                 " m?=" +
                                 s.hasUncollectedMonitoredItemNotifications +
-                                " " + 
+                                " " +
                                 SubscriptionState[s.state] +
-                                " " + s.messageSent + 
+                                " " +
+                                s.messageSent +
                                 "]"
                         )
                         .join(" \n")
@@ -508,7 +510,8 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
             }
             const starving_subscription = this._find_starving_subscription();
             if (starving_subscription) {
-                doDebug && debugLog(chalk.bgWhite.red("feeding most late subscription subscriptionId  = "), starving_subscription.id);
+                doDebug &&
+                    debugLog(chalk.bgWhite.red("feeding most late subscription subscriptionId  = "), starving_subscription.id);
                 starving_subscription.process_subscription();
             }
         });
@@ -537,10 +540,10 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
 
     private _send_error_for_request(publishData: PublishData, statusCode: StatusCode): void {
         _assertValidPublishData(publishData);
-        const publishResponse = new PublishResponse({
+        const response = new ServiceFault({
             responseHeader: { serviceResult: statusCode }
         });
-        this._send_response_for_request(publishData, publishResponse);
+        this._send_response_for_request(publishData, response);
     }
 
     private _cancelPendingPublishRequest(statusCode: StatusCode): void {
@@ -629,7 +632,7 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
         assert(this.pendingPublishRequestCount > 0);
         assert(response.subscriptionId !== 0xffffff);
         const publishData = this._publish_request_queue.shift()!;
-        this._send_response_for_request(publishData, response);
+        this._send_valid_response_for_request(publishData, response);
     }
 
     public _on_tick(): void {
@@ -654,15 +657,17 @@ export class ServerSidePublishEngine extends EventEmitter implements IServerSide
             this._send_error_for_request(publishData, StatusCodes.BadTimeout);
         }
     }
-
-    public _send_response_for_request(publishData: PublishData, response: PublishResponse): void {
+    public _send_response_for_request(publishData: PublishData, response: PublishResponse | ServiceFault): void {
+        response.responseHeader.requestHandle = publishData.request.requestHeader.requestHandle;
+        publishData.callback(publishData.request, response);
+    }
+    public _send_valid_response_for_request(publishData: PublishData, response: PublishResponse): void {
         if (doDebug) {
             debugLog("_send_response_for_request ", response.toString());
         }
         _assertValidPublishData(publishData);
         // xx assert(response.responseHeader.requestHandle !== 0,"expecting a valid requestHandle");
         response.results = publishData.results;
-        response.responseHeader.requestHandle = publishData.request.requestHeader.requestHandle;
-        publishData.callback(publishData.request, response);
+        this._send_response_for_request(publishData, response);
     }
 }

programming_language: Java
filename: test_server_engine_subscription.js
change_type: ModificationType.MODIFY
diff: @@ -6,6 +6,7 @@ const { MonitoringMode, PublishRequest } = require("node-opcua-service-subscript
 const { StatusCodes, StatusCode } = require("node-opcua-status-code");
 const { TimestampsToReturn } = require("node-opcua-service-read");
 const { MonitoredItemCreateRequest } = require("node-opcua-service-subscription");
+const { ServiceFault } = require("node-opcua-types");
 
 const { get_mini_nodeset_filename } = require("node-opcua-address-space/testHelpers");
 
@@ -402,19 +403,22 @@ describe("ServerEngine Subscriptions service", function () {
             publishSpy.callCount.should.eql(4);
 
             publishSpy.getCall(1).args[1].responseHeader.serviceResult.should.eql(StatusCodes.BadNoSubscription);
-            publishSpy.getCall(1).args[1].subscriptionId.should.eql(0xffffffff);
-            publishSpy.getCall(1).args[1].notificationMessage.sequenceNumber.should.eql(0);
-            publishSpy.getCall(1).args[1].notificationMessage.notificationData.length.should.eql(0);
+            publishSpy.getCall(1).args[1].should.be.instanceOf(ServiceFault);
+            // publishSpy.getCall(1).args[1].subscriptionId.should.eql(0xffffffff);
+            // publishSpy.getCall(1).args[1].notificationMessage.sequenceNumber.should.eql(0);
+            // publishSpy.getCall(1).args[1].notificationMessage.notificationData.length.should.eql(0);
 
             publishSpy.getCall(2).args[1].responseHeader.serviceResult.should.eql(StatusCodes.BadNoSubscription);
-            publishSpy.getCall(2).args[1].subscriptionId.should.eql(0xffffffff);
-            publishSpy.getCall(2).args[1].notificationMessage.sequenceNumber.should.eql(0);
-            publishSpy.getCall(2).args[1].notificationMessage.notificationData.length.should.eql(0);
+            publishSpy.getCall(2).args[1].should.be.instanceOf(ServiceFault);
+            // publishSpy.getCall(2).args[1].subscriptionId.should.eql(0xffffffff);
+            // publishSpy.getCall(2).args[1].notificationMessage.sequenceNumber.should.eql(0);
+            // publishSpy.getCall(2).args[1].notificationMessage.notificationData.length.should.eql(0);
 
             publishSpy.getCall(3).args[1].responseHeader.serviceResult.should.eql(StatusCodes.BadNoSubscription);
-            publishSpy.getCall(3).args[1].subscriptionId.should.eql(0xffffffff);
-            publishSpy.getCall(3).args[1].notificationMessage.sequenceNumber.should.eql(0);
-            publishSpy.getCall(3).args[1].notificationMessage.notificationData.length.should.eql(0);
+            publishSpy.getCall(3).args[1].should.be.instanceOf(ServiceFault);
+            // publishSpy.getCall(3).args[1].subscriptionId.should.eql(0xffffffff);
+            // publishSpy.getCall(3).args[1].notificationMessage.sequenceNumber.should.eql(0);
+            // publishSpy.getCall(3).args[1].notificationMessage.notificationData.length.should.eql(0);
 
             await engine.closeSession(session.authenticationToken, true, "CloseSession");
         });

programming_language: JavaScript
filename: test_server_publish_engine.ts
change_type: ModificationType.MODIFY
diff: @@ -185,7 +185,7 @@ describe("Testing the server publish engine", function (this: any) {
         serverSidePublishEngine.dispose();
     });
 
-    it("ZDZ-5 a server should return BadNoSubscription as a response for a publish Request if there is no subscription available for this session. ", () => {
+    it("ZDZ-5 a server should return ServiceFault(BadNoSubscription) as a response for a publish Request if there is no subscription available for this session. ", () => {
         // create a server - server has no subscription
         const publish_server = new ServerSidePublishEngine();
 
@@ -199,7 +199,7 @@ describe("Testing the server publish engine", function (this: any) {
         flushPending();
 
         send_response_for_request_spy.callCount.should.equal(1);
-        send_response_for_request_spy.getCall(0).args[1].schema.name.should.equal("PublishResponse");
+        send_response_for_request_spy.getCall(0).args[1].schema.name.should.equal("ServiceFault");
         send_response_for_request_spy.getCall(0).args[1].responseHeader.serviceResult.should.eql(StatusCodes.BadNoSubscription);
 
         publish_server.shutdown();
@@ -309,25 +309,23 @@ describe("Testing the server publish engine", function (this: any) {
         // xx console.log(send_response_for_request_spy.getCall(0).args[1].responseHeader.toString());
         // xx console.log(send_response_for_request_spy.getCall(1).args[1].responseHeader.toString());
 
-        send_response_for_request_spy.getCall(1).args[1].schema.name.should.equal("PublishResponse");
+        send_response_for_request_spy.getCall(1).args[1].schema.name.should.equal("ServiceFault");
         send_response_for_request_spy
             .getCall(1)
             .args[1].responseHeader.serviceResult.should.eql(StatusCodes.BadTooManyPublishRequests);
         send_response_for_request_spy.getCall(1).args[1].responseHeader.requestHandle.should.eql(2);
-        send_response_for_request_spy.getCall(1).args[1].results!.should.eql([]);
-
+       
         publish_server._on_PublishRequest(new PublishRequest({ requestHeader: { requestHandle: 8 } }));
         flushPending();
 
         send_response_for_request_spy.callCount.should.be.equal(3);
         // xx console.log(send_response_for_request_spy.getCall(2).args[1].responseHeader.toString());
-        send_response_for_request_spy.getCall(2).args[1].schema.name.should.equal("PublishResponse");
+        send_response_for_request_spy.getCall(2).args[1].schema.name.should.equal("ServiceFault");
         send_response_for_request_spy
             .getCall(2)
             .args[1].responseHeader.serviceResult.should.eql(StatusCodes.BadTooManyPublishRequests);
         send_response_for_request_spy.getCall(2).args[1].responseHeader.requestHandle.should.eql(3);
-        send_response_for_request_spy.getCall(2).args[1].results!.should.eql([]);
-
+    
         subscription.terminate();
         subscription.dispose();
         publish_server.shutdown();

programming_language: Java
filename: parallel_test.js
change_type: ModificationType.MODIFY
diff: @@ -302,6 +302,10 @@ if (isMainThread) {
                 if (key.name === "l") {
                     dumpRunningTests();
                 }
+                if (key.name === "i") {
+                    dumpRunningTests();
+                    dumpRunningTestsLogs();
+                }
             });
         }
 
@@ -315,14 +319,7 @@ if (isMainThread) {
         const infoTimer = setInterval(() => {
             console.log("----------------------------------------------- RUNNING TESTS ");
             dumpRunningTests();
-            const runningTests = [...runningPages].map((i) => testFiles[i]);
-            for (let file of runningTests) {
-                const outputs = outputFor[file];
-                if (outputs && outputs.length) {
-                    console.log(chalk.green("log for", file));
-                    console.log(outputs.join("\n"));
-                }
-            }
+            dumpRunningTestsLogs();
         }, testWatchDogTimeout);
 
         fileMax = testFiles.length;
@@ -342,3 +339,13 @@ if (isMainThread) {
     const { workerThread } = require("./parallel_test_worker");
     workerThread();
 }
+function dumpRunningTestsLogs() {
+    const runningTests = [...runningPages].map((i) => testFiles[i]);
+    for (let file of runningTests) {
+        const outputs = outputFor[file];
+        if (outputs && outputs.length) {
+            console.log(chalk.green("log for", file));
+            console.log(outputs.join("\n"));
+        }
+    }
+}

programming_language: TypeScript