function __validateDataChangeFilter(filter,itemToMonitor,node) {

    assert(itemToMonitor.attributeId  === AttributeIds.Value);
    assert(filter instanceof subscription_service.DataChangeFilter);


    if (!(node instanceof UAVariable)) {
        return StatusCodes.BadNodeIdInvalid;
    }

    assert(node instanceof UAVariable);

    // if node is not Numerical=> DataChangeFilter
    assert(node.dataType instanceof NodeId);
    var dataType = node.addressSpace.findDataType(node.dataType);

    var dataTypeNumber = node.addressSpace.findDataType("Number");
    if (!dataType.isSupertypeOf(dataTypeNumber)) {
        return StatusCodes.BadFilterNotAllowed;
    }


    if (filter.deadbandType === subscription_service.DeadbandType.Percent) {
        if (filter.deadbandValue < 0 || filter.deadbandValue > 100) {
            return StatusCodes.BadDeadbandFilterInvalid;
        }

        // node must also have a valid euRange
        if (!node.euRange) {
            console.log(" node has no euRange ! DeadbandPercent cannot be used on node "+ node.nodeId.toString());
            return StatusCodes.BadMonitoredItemFilterUnsupported;
        }
    }
    return StatusCodes.Good;
}
function _decode_member_(value, field, stream, options) {

    var tracer = options.tracer;
    var cursor_before = stream.length;
    var fieldType = field.fieldType;

    if (field.category === "basic") {

        value = field.schema.decode(stream);
        tracer.trace("member", options.name, value, cursor_before, stream.length, fieldType);

    } else if (field.category === "enumeration") {

        value = field.schema.decode(stream);
        tracer.trace("member", options.name, value, cursor_before, stream.length, fieldType);

    } else {
        assert(field.category === "complex");
        assert(_.isFunction(field.schema));
        var Constructor = field.schema;
        value = new Constructor();
        value.decode_debug(stream, options);

    }
    return value;
}
Exemple #3
0
var makeNodeId = function makeNodeId(value, namespace) {

    value = value || 0;
    namespace = namespace || 0;

    var identifierType = NodeIdType.NUMERIC;
    if (typeof value === "string") {
        //            1         2         3
        //  012345678901234567890123456789012345
        // "72962B91-FA75-4AE6-8D28-B404DC7DAF63"
        if (isValidGuid(value)) {
            identifierType = NodeIdType.GUID;
        } else {
            identifierType = NodeIdType.STRING;
            // detect accidental string of form "ns=x;x";
            assert(value.indexOf("ns=") === -1, " makeNodeId(string) ? did you mean using coerceNodeId instead? ");
        }
    } else if (value instanceof Buffer) {
        identifierType = NodeIdType.BYTESTRING;
    }

    var nodeId = new NodeId(identifierType, value, namespace);

    assert(nodeId.hasOwnProperty("identifierType"));

    return nodeId;
};
    AddressSpace.prototype.addState = function (component, stateName, stateNumber, isInitialState) {

        var addressSpace = this;

        isInitialState = !!isInitialState;

        assert(component instanceof UAObjectType);
        assert(_.isString(stateName));
        assert(_.isBoolean(isInitialState));

        var initialStateType = addressSpace.findObjectType("InitialStateType");
        var stateType = addressSpace.findObjectType("StateType");

        var state;
        if (isInitialState) {
            state = initialStateType.instantiate({
                browseName: stateName,
                componentOf: component
            });
        } else {
            state = stateType.instantiate({
                browseName: stateName,
                componentOf: component
            });
        }
        // ensure state number is unique
        state.stateNumber.setValueFromSource({
            dataType: DataType.UInt32,
            value: stateNumber
        });
        return state;
    };
function NumericRange(value, second_value) {

    var self = this;

    function _construct_from_string(value) {
        var nr = construct_numeric_range_from_string(value);
        self.type = nr.type;
        self.value = nr.value;
    }

    function _construct_from_values(value, second_value) {
        if (_.isUndefined(second_value)) {
            self._set_single_value(value);

        } else {

            if (!_.isFinite(second_value)) {
                throw new Error(" invalid second argument, expecting a number");
            }
            self._set_range_value(value, second_value);
        }

    }

    function _construct_from_array(value) {
        assert(value.length === 2);
        if (_.isFinite(value[0])) {
            if (!_.isFinite(value[1])) {
                throw new Error(" invalid range in " + value);
            }
            self._set_range_value(value[0], value[1]);
        }
    }

    function _construct_from_NumericRange(value) {
        self.value = _.clone(value);
        self.type = value.type;
    }

    assert(!value || !(value instanceof NumericRange), "use coerce to create a NumericRange");

    if (typeof value === "string") {
        _construct_from_string(value);

    } else if (_.isFinite(value) && !_.isUndefined(value)) {
        _construct_from_values(value, second_value);

    } else if (_.isArray(value)) {
        _construct_from_array(value);

    } else if (value instanceof NumericRange) {
        _construct_from_NumericRange(value);

    } else {
        this.value = "<invalid>";
        this.type = NumericRangeType.Empty;
    }

    assert((this.type !== NumericRangeType.ArrayRange) || _.isArray(this.value));
}
Exemple #6
0
UADataType.prototype._getDefinition = function () {

    var self = this;
    var definition = [];
    if (self.enumStrings) {
        var enumStrings = self.enumStrings.readValue().value.value;
        assert(_.isArray(enumStrings));
        definition = enumStrings.map(function(e,index){
            return {
                value: index,
                name: e.text
            };
        });
    } else if (self.enumValues) {
        assert(self.enumValues,"must have a enumValues property");
        var enumValues = self.enumValues.readValue().value.value;
        assert(_.isArray(enumValues));
        definition = _.map(enumValues,function(e) {
            return { name: e.displayName.text, value: e.value[1]};
        });
    }

    // construct nameIndex and valueIndex
    var indexes = {
        nameIndex: {},
        valueIndex: {}
    };
    definition.forEach(function(e){
        indexes.nameIndex[e.name]   =e;
        indexes.valueIndex[e.value] =e;
    });
    return indexes;
};
NumericRange.overlap = function (nr1, nr2) {
    nr1 = nr1 || empty;
    nr2 = nr2 || empty;
    assert(nr1 instanceof NumericRange);
    assert(nr2 instanceof NumericRange);

    if (NumericRangeType.Empty === nr1.type || NumericRangeType.Empty === nr2.type) {
        return true;
    }
    if (NumericRangeType.SingleValue === nr1.type && NumericRangeType.SingleValue === nr2.type) {
        return nr1.value === nr2.value;
    }
    if (NumericRangeType.ArrayRange === nr1.type && NumericRangeType.ArrayRange === nr2.type) {
        // +-----+        +------+     +---+       +------+
        //     +----+       +---+    +--------+  +---+
        var l1 = nr1.value[0];
        var h1 = nr1.value[1];
        var l2 = nr2.value[0];
        var h2 = nr2.value[1];
        return _overlap(l1, h1, l2, h2);
    }
    console.log(" NR1 = ", nr1.toEncodeableString());
    console.log(" NR2 = ", nr2.toEncodeableString());
    assert(false, "not implemented yet "); // TODO
};
NodeCrawler.prototype._push_task = function (name, task) {
    var self = this;
    assert(_.isFunction(task.func));
    task.comment = "P:" + name;
    assert(task.func.length === 2);
    self.q.push(task);
};
OPCUAClient.prototype.withSubscription = function (endpointUrl, subscriptionParameters, innerFunc, callback) {

    assert(_.isFunction(innerFunc));
    assert(_.isFunction(callback));

    this.withSession(endpointUrl, function (session, done) {
        assert(_.isFunction(done));

        const subscription = new ClientSubscription(session, subscriptionParameters);

        try {
            innerFunc(session, subscription, function () {

                subscription.terminate(function (err) {
                    done(err);
                });
            });

        }
        catch (err) {
            console.log(err);
            done(err);
        }
    }, callback);
};
    global.setInterval = function (func, delay) {
        assert(arguments.length === 2);
        assert(delay !== undefined);
        assert(_.isFinite(delay));
        if (delay <= 10) {
            throw new Error("GLOBAL#setInterval called with a too small delay = " + delay.toString());
        }

        // increase number of pending timers
        self.setIntervalCallCount += 1;

        var key = self.setIntervalCallCount;

        var intervalId = self.setInterval_old(func, delay);

        self.interval_map[key] = {
            intervalId: intervalId,
            disposed: false,
            stack: get_stack()
        };

        if (trace) {
            console.log("setInterval \n", get_stack().red, "\n");
        }

        return key;
    };
    this._activateSession(session, function (err) {
        if (!err) {

            if (old_client !== self) {
                // remove session from old client:
                if (old_client) {
                    old_client._removeSession(session);
                    assert(!_.contains(old_client._sessions, session));
                 }

                self._addSession(session);
                assert(session._client === self);
                assert(!session.closed,"session should not vbe closed");
                assert(_.contains(self._sessions, session));
            }

        } else {

            // istanbul ignore next
            if (doDebug) {
                console.log("reactivateSession has failed !".red.bgWhite, err.message);
            }
        }
        callback(err);
    });
Exemple #12
0
ChunkManager.prototype.write = function (buffer, length) {

    length = length || buffer.length;
    assert(buffer instanceof Buffer || (buffer === null));
    assert(length > 0);

    var l = length;
    var input_cursor = 0;

    while (l > 0) {
        assert(length - input_cursor !== 0);

        if (this.cursor === 0) {
            this._push_pending_chunk(false);
        }

        // space left in current chunk
        var space_left = this.maxBodySize - this.cursor;

        var nb_to_write = Math.min(length - input_cursor, space_left);

        if (buffer) {
            buffer.copy(this.chunk, this.cursor + this.dataOffset, input_cursor, input_cursor + nb_to_write);
        }

        input_cursor += nb_to_write;
        this.cursor += nb_to_write;

        if (this.cursor >= this.maxBodySize) {
            this._postprocess_current_chunk();
        }
        l -= nb_to_write;
    }
};
/**
 * @method extractEventFields
 * extract a array of eventFields from a event node, matching the selectClauses
 * @param selectClauses
 * @param eventData : a pseudo Node that provides a browse Method and a readValue(nodeId)
 */
function extractEventFields(selectClauses,eventData) {

    assert_valid_event_data(eventData);
    assert(_.isArray(selectClauses));
    assert(selectClauses.length===0 || selectClauses[0] instanceof SimpleAttributeOperand);
    return selectClauses.map(extractEventField.bind(null, eventData));
}
ClientSecureChannelLayer.prototype._construct_security_header = function () {

    var self = this;
    assert(self.hasOwnProperty("securityMode"));
    assert(self.hasOwnProperty("securityPolicy"));


    self.receiverCertificate = self.serverCertificate;

    var securityHeader = null;
    switch (self.securityMode.value) {
        case MessageSecurityMode.SIGN.value:
        case MessageSecurityMode.SIGNANDENCRYPT.value:
            assert(self.securityPolicy !== SecurityPolicy.None);
            // get the thumbprint of the client certificate
            var thumbprint = self.receiverCertificate ? crypto_utils.makeSHA1Thumbprint(self.receiverCertificate) : null;
            securityHeader = new AsymmetricAlgorithmSecurityHeader({
                securityPolicyUri: securityPolicy_m.toURI(self.securityPolicy),
                senderCertificate: self.getCertificateChain(),  // certificate of the private key used to sign the message
                receiverCertificateThumbprint: thumbprint       // thumbprint of the public key used to encrypt the message
            });
            break;
        default:
            /* istanbul ignore next */
            assert(false, "invalid security mode");
    }
    //xx console.log("xxxx security Header",securityHeader.toJSON());
    //xx console.log("xxxx receiverCertificate",self.receiverCertificate.toString("base64").cyan);
    self.securityHeader = securityHeader;
};
function verifySignature(receiverCertificate, receiverNonce, signature, senderCertificate, securityPolicy) {

    if (securityPolicy === SecurityPolicy.None) {
        return true;
    }
    var crypto_factory = getCryptoFactory(securityPolicy);
    if (!crypto_factory) {
        return false;
    }
    assert(receiverNonce instanceof  Buffer);
    assert(receiverCertificate instanceof  Buffer);
    assert(signature instanceof SignatureData);

    assert(senderCertificate instanceof Buffer);

    if (!(signature.signature instanceof Buffer)) {
        // no signature provided
        return false;
    }

    assert(signature.signature instanceof Buffer);
    // This parameter is calculated by appending the clientNonce to the clientCertificate
    var buffer = Buffer.concat([receiverCertificate, receiverNonce]);

    return crypto_factory.asymmetricVerify(buffer, signature.signature, senderCertificate);
}
function _install_security_token_watchdog() {

    /* jshint validthis: true */
    var self = this;

    //
    // install timer event to raise a 'lifetime_75' when security token is about to expired
    // so that client can request for a new security token
    // note that, for speedup in test,
    // it is possible to tweak this interval for test by specifying a tokenRenewalInterval value
    //
    var liveTime = self.securityToken.revisedLifeTime;
    assert(liveTime && liveTime > 20);
    var timeout = self.tokenRenewalInterval || liveTime * 75 / 100;
    timeout = Math.min(timeout, liveTime * 75 / 100);

    if (doDebug) {
        debugLog(" time until next security token renal = ".red.bold, timeout, "( lifefime = ", liveTime + ")");
    }

    assert(self._securityTokenTimeoutId === null);
    self._securityTokenTimeoutId = setTimeout(function () {
        self._securityTokenTimeoutId = null;
        _on_security_token_about_to_expire.call(self);
    }, timeout);
}
OPCUAClient.prototype._closeSession = function (session, deleteSubscriptions, callback) {

    var self = this;
    assert(_.isFunction(callback));
    assert(_.isBoolean(deleteSubscriptions));

    // istanbul ignore next
    if (!self._secureChannel) {
        return callback(new Error("no channel"));
    }
    assert(self._secureChannel);

    var request = new CloseSessionRequest({
        deleteSubscriptions: deleteSubscriptions
    });

    if (!self._secureChannel.isValid()) {
        return callback();
    }
    session.performMessageTransaction(request, function (err, response) {

        if (err) {
            //xx console.log("xxx received : ", err, response);
            //xx self._secureChannel.close(function () {
            //xx     callback(err, null);
            //xx });
            callback(err, null);
        } else {
            callback(err, response);
        }
    });
};
Exemple #18
0
/**
 * Construct a node ID
 *
 * @class NodeId
 * @param {NodeIdType}                identifierType   - the nodeID type
 * @param {Number|String|GUID|Buffer} value            - the node id value. The type of Value depends on identifierType.
 * @param {Number}                    namespace        - the index of the related namespace (optional , default value = 0 )
 * @example
 *
 *    ``` javascript
 *    var nodeId = new NodeId(NodeIdType.NUMERIC,123,1);
 *    ```
 * @constructor
 */
function NodeId(identifierType, value, namespace) {

    /**
     * @property identifierType
     * @type {NodeIdType}
     */
    this.identifierType = NodeIdType.get(identifierType.value);

    assert(this.identifierType);
    /**
     * @property  value
     * @type  {*}
     */

    this.value = value;
    /**
     * @property namespace
     * @type {Number}
     */
    this.namespace = namespace || 0;

    // namespace shall be a UInt16
    assert(this.namespace >= 0 && this.namespace <= 0xFFFF);

    assert(this.identifierType !== NodeIdType.NUMERIC || (this.value >= 0 && this.value <= 0xFFFFFFFF));
    assert(this.identifierType !== NodeIdType.GUID || isValidGuid(this.value));
    assert(this.identifierType !== NodeIdType.STRING || typeof this.value === "string");

}
UAStateMachine.prototype.findTransitionNode = function(fromStateNode,toStateNode) {

    var self = this;
    var addressSpace = self.addressSpace;

    fromStateNode = self._coerceNode(fromStateNode);
    if (!fromStateNode) { return null; }

    toStateNode = self._coerceNode(toStateNode);

    assert(fromStateNode instanceof UAObject);
    assert(toStateNode   instanceof UAObject);

    var stateType = addressSpace.findObjectType("StateType");

    assert(fromStateNode.typeDefinitionObj.isSupertypeOf(stateType));
    assert(toStateNode.typeDefinitionObj.isSupertypeOf(stateType));

    var transitions = fromStateNode.findReferencesAsObject("FromState",false);

    transitions = transitions.filter(function(transition){
        assert(transition.toStateNode instanceof UAObject);
        return transition.toStateNode === toStateNode;
    });
    if (transitions.length ===0 ) {
        // cannot find a transition from fromState to toState
        return null;
    }
    assert(transitions.length === 1);
    return transitions[0];
};
function difference(v1,v2,absoluteDeadband) {

    assert(_.isFinite(absoluteDeadband));

    if (v1.arrayType === VariantArrayType.Array) {

        if (v1.dataType !== v2.dataType) {
            return true;
        }
        if (v1.value.length !== v2.value.length) {
            return true;
        }

        var n = v1.value.length;
        var i =0;
        for (i=0;i<n;i++) {
            if (_differenceScalar(v1.value[i],v2.value[i],v1.dataType,absoluteDeadband)) {
                return true;
            }
        }
        return false;

    } else {
        assert(v1.arrayType === VariantArrayType.Scalar);
        assert(v1.dataType === v2.dataType);
        return _differenceScalar(v1.value,v2.value,v1.dataType,absoluteDeadband);
    }
}
UAAcknowledgeableConditionBase.prototype._confirm_branch = function _confirm_branch(eventId,comment,branch,message) {

    assert(typeof(message) === "string");
    assert(comment instanceof LocalizedText);

    var conditionNode = this;
    //xx var eventId = branch.getEventId();
    assert(branch.getEventId().toString("hex") === eventId.toString("hex"));
    branch.setConfirmedState(true);
    branch.setRetain(false);

    branch.setComment(comment);

    conditionNode._raiseAuditConditionCommentEvent(message,eventId,comment);
    conditionNode._raiseAuditConditionConfirmEvent(branch);

    conditionNode.raiseNewBranchState(branch);

    /**
     * @event confirmed
     * @param  eventId
     * @param  comment
     * @param  eventId
     * raised when the alarm branch has been confirmed
     */
    conditionNode.emit("confirmed",eventId,comment,branch);

};
/**
 * 
 * @class HistoryReadRequest
 * @constructor
 * @extends BaseUAObject
 * @param  options {Object}
 * @param  [options.requestHeader] {RequestHeader} 
 * @param  [options.historyReadDetails] {ExtensionObject} Maximum age of the value to be read in milliseconds
 * @param  [options.timestampsToReturn = 3] {TimestampsToReturn} An enumeration that specifies the Timestamps to be returned for each requested Variable Value Attribute.
 * @param  [options.releaseContinuationPoints] {Boolean} 
 * @param  [options.nodesToRead] {HistoryReadValueId[]} List of Nodes and their Attributes to read. For each entry in this list, a StatusCode is returned, and if it indicates success, the Attribute Value is also returned.
 */
function HistoryReadRequest(options)
{
    options = options || {};
    /* istanbul ignore next */
    if (schema_helpers.doDebug) { check_options_correctness_against_schema(this,schema,options); }
    var self = this;
    assert(this instanceof BaseUAObject); //  ' keyword "new" is required for constructor call')
    resolve_schema_field_types(schema);

    BaseUAObject.call(this,options);
    if (options === null) { 
        BaseUAObject.call(this,options);
        self.requestHeader =  null; /* new RequestHeader(null); */
        self.nodesToRead =  null; /* null array */
        return ;
    }

    /**
      * 
      * @property requestHeader
      * @type {RequestHeader}
      */
    self.requestHeader =  new RequestHeader( options.requestHeader);

    /**
      * Maximum age of the value to be read in milliseconds
      * @property historyReadDetails
      * @type {ExtensionObject}
      */
    self.historyReadDetails = initialize_field(schema.fields[1], options.historyReadDetails);

    /**
      * An enumeration that specifies the Timestamps to be returned for each requested Variable Value Attribute.
      * @property timestampsToReturn
      * @type {TimestampsToReturn}
      * @default  3
      */
    self.setTimestampsToReturn(initialize_field(schema.fields[2], options.timestampsToReturn));

    /**
      * 
      * @property releaseContinuationPoints
      * @type {Boolean}
      */
    self.releaseContinuationPoints = initialize_field(schema.fields[3], options.releaseContinuationPoints);

    /**
      * List of Nodes and their Attributes to read. For each entry in this list, a StatusCode is returned, and if it indicates success, the Attribute Value is also returned.
      * @property nodesToRead
      * @type {HistoryReadValueId[]}
      */
    self.nodesToRead = [];
    if (options.nodesToRead) {
        assert(_.isArray(options.nodesToRead));
        self.nodesToRead = options.nodesToRead.map(function(e){ return new HistoryReadValueId(e); } );
    }

   // Object.preventExtensions(self);
}
Exemple #23
0
function callConditionRefresh(subscription,callback) {

    var the_session    = subscription.publish_engine.session;
    var subscriptionId = subscription.subscriptionId;

    assert(_.isFinite(subscriptionId),"May be subscription is not yet initialized");
    assert(_.isFunction(callback));

    var conditionTypeNodeId = resolveNodeId("ConditionType");

    var browsePath = [
        makeBrowsePath(conditionTypeNodeId,".ConditionRefresh")
    ];
    var conditionRefreshId  = resolveNodeId("ConditionType_ConditionRefresh");

    //xx console.log("browsePath ", browsePath[0].toString({addressSpace: server.engine.addressSpace}));

    async.series([

        // find conditionRefreshId
        function (callback) {

            the_session.translateBrowsePath(browsePath, function (err, results) {
                if(!err ) {
                    // istanbul ignore else
                    if (results[0].targets.length > 0){
                        conditionRefreshId = results[0].targets[0].targetId;
                    } else {
                        // cannot find conditionRefreshId
                        console.log("cannot find conditionRefreshId",results[0].toString());
                        err = new Error(" cannot find conditionRefreshId");
                    }
                }
                callback(err);
            });
        },
        function (callback) {

            var methodsToCall = [{
                objectId: conditionTypeNodeId,
                methodId: conditionRefreshId,
                inputArguments: [
                    new Variant({ dataType: DataType.UInt32, value: subscriptionId })
                ]
            }];

            the_session.call(methodsToCall,function(err,results) {
                if (err) {
                    return callback(err);
                }
                // istanbul ignore next
                if (results[0].statusCode !== StatusCodes.Good) {
                    return callback(new Error("Error " + results[0].statusCode.toString()));
                }
                callback();
            });
        }
    ],callback);
}
Exemple #24
0
function verify_message_chunk(message_chunk) {
    assert(message_chunk);
    assert(message_chunk instanceof Buffer);
    var header = readMessageHeader(new BinaryStream(message_chunk));
    if (message_chunk.length !== header.length) {
        throw new Error(" chunk length = " + message_chunk.length + " message  length " + header.length);
    }
}
UATwoStateVariable.prototype.getValueAsString = function TwoStateVariable_getValue() {
    var node = this;
    var dataValue = node.readValue();
    assert(dataValue.statusCode === StatusCodes.Good);
    assert(dataValue.value.dataType === DataType.LocalizedText);
    return dataValue.value.value.text.toString();

};
/*=
 *
 * @param arr
 * @param maxNode
 * @private
 * @return {*}
 */
function _fetch_elements(arr, maxNode) {
    assert(_.isArray(arr));
    assert(arr.length > 0);
    var high_limit = ( maxNode <= 0) ? arr.length : maxNode;
    var tmp = arr.splice(0, high_limit);
    assert(tmp.length > 0);
    return tmp;
}
ClientSubscription.prototype._remove = function (monitoredItem) {
    var self = this;
    var clientHandle = monitoredItem.monitoringParameters.clientHandle;
    assert(clientHandle);
    assert(self.monitoredItems.hasOwnProperty(clientHandle));
    monitoredItem.removeAllListeners();
    delete self.monitoredItems[clientHandle];
};
UATwoStateVariable.prototype.getValue = function TwoStateVariable_getValue() {

    var node = this;
    var dataValue = node.id.readValue();
    assert(dataValue.statusCode === StatusCodes.Good);
    assert(dataValue.value.dataType === DataType.Boolean);
    return dataValue.value.value;
};
OPCUADiscoveryServer.prototype._on_RegisterServerRequest = function (message, channel) {
    var server = this;
    var request = message.request;

    assert(request._schema.name === "RegisterServerRequest");
    assert(request instanceof RegisterServerRequest);

    function sendError(statusCode) {
        console.log("_on_RegisterServerRequest error".red, statusCode.toString());
        var response = new RegisterServerResponse({responseHeader: {serviceResult: statusCode}});
        return channel.send_response("MSG", response, message);
    }

    // check serverType is valid
    if (!_isValideServerType(request.server.serverType)) {
        return sendError(StatusCodes.BadInvalidArgument);
    }

    // BadServerUriInvalid
    // TODO

    // BadServerNameMissing
    if (request.server.serverNames.length === 0) {
        return sendError(StatusCodes.BadServerNameMissing);
    }

    // BadDiscoveryUrlMissing
    if (request.server.discoveryUrls.length === 0) {
        return sendError(StatusCodes.BadDiscoveryUrlMissing);
    }

    var key = request.server.serverUri;

    if (request.server.isOnline) {
        console.log(" registering server : ".cyan, request.server.serverUri.yellow);
        server.registered_servers[key] = request.server;

        // prepare serverInfo which will be used by FindServers
        var serverInfo = {};
        serverInfo.applicationUri = request.server.serverUri;
        serverInfo.applicationType = request.server.serverType;
        serverInfo.productUri = request.server.productUri;
        serverInfo.applicationName = request.server.serverNames[0]; // which one shall we use ?
        serverInfo.gatewayServerUri = request.server.gatewayServerUri;
        // XXX ?????? serverInfo.discoveryProfileUri = serverInfo.discoveryProfileUri;
        serverInfo.discoveryUrls = request.server.discoveryUrls;
        server.registered_servers[key].serverInfo = serverInfo;

    } else {
        if (key in server.registered_servers) {
            console.log(" unregistering server : ".cyan, request.server.serverUri.yellow);
            delete server.registered_servers[key];
        }
    }

    var response = new RegisterServerResponse({});
    channel.send_response("MSG", response, message);
};
    self._performMessageTransaction(msgType, msg, function (error, response) {


        if (response && response.responseHeader.serviceResult !== StatusCodes.Good) {
            error = new Error(response.responseHeader.serviceResult.toString());
        }
        if (!error) {

            /* istanbul ignore next */
            if (false && doDebug) {
                debugLog(response.toString());
            }
            assert(response instanceof OpenSecureChannelResponse);
            //xx assert(!is_initial || self.securityToken.secureChannelId === response.securityToken.secureChannelId);

            // todo : verify that server certificate is  valid
            // A self-signed application instance certificate does not need to be verified with a CA.
            // todo : verify that Certificate URI matches the ApplicationURI of the server

            self.securityToken = response.securityToken;
            assert(self.securityToken.tokenId > 0 || msgType === "OPN", "_sendSecureOpcUARequest: invalid token Id ");
            assert(response.hasOwnProperty("serverNonce"));

            self.serverNonce = response.serverNonce;

            if (self.securityMode !== MessageSecurityMode.NONE) {
                // verify that server nonce if provided is at least 32 bytes long

                /* istanbul ignore next */
                if (!self.serverNonce) {
                    console.log(" client : server nonce is invalid !");
                    return callback(new Error(" Invalid server nonce"));
                }
                // This parameter shall have a length equal to key size used for the symmetric
                // encryption algorithm that is identified by the securityPolicyUri.
                if (self.serverNonce.length !== self.clientNonce.length) {
                    console.log(" client : server nonce is invalid !");
                    return callback(new Error(" Invalid server nonce length"));
                }
            }


            var cryptoFactory = self.messageBuilder.cryptoFactory;
            if (cryptoFactory) {
                assert(self.serverNonce instanceof Buffer);
                self.derivedKeys = cryptoFactory.compute_derived_keys(self.serverNonce, self.clientNonce);
            }

            var derivedServerKeys = self.derivedKeys ? self.derivedKeys.derivedServerKeys : null;
            self.messageBuilder.pushNewToken(self.securityToken, derivedServerKeys);

            _install_security_token_watchdog.call(self);

            self._isOpened = true;

        }
        callback(error);
    });