New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

node-opcua

Package Overview
Dependencies
Maintainers
1
Versions
355
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-opcua - npm Package Compare versions

Comparing version 0.0.1 to 0.0.2

.travis.yml

145

lib/binaryStream.js
var assert = require("assert");
var util = require("util");
var Writable = require("stream").Writable;
var Readable = require("stream").Readable;
function BinaryReader(buffer) {
this._buffer = buffer;
Readable.call(this, {});
}
util.inherits(BinaryReader, Readable);
exports.BinaryReader = BinaryReader;
BinaryReader.prototype._read = function() {
this.push(this._buffer);
this.push(null);
};
function BinaryStream(data) {
if (data === undefined) {
this.stream = new Buffer(1000);
this._buffer = new Buffer(1024);
} else {
this.stream = new Buffer(data, "binary");
if (data instanceof Buffer) {
this._buffer = data;
} else {
this._buffer = new Buffer(data, "binary");
}
}

@@ -17,5 +37,11 @@ this.length = 0;

BinaryStream.prototype.writeUByte = function (value) {
assert(value >= 0 && value < 256);
this._buffer.writeInt8(value, this.length);
this.length += 1;
};
BinaryStream.prototype.writeByte = function (value) {
assert(value >= 0 && value < 256);
this.stream.writeInt8(value, this.length);
this._buffer.writeInt8(value, this.length);
this.length += 1;

@@ -26,3 +52,3 @@ };

assert(value >= 0 && value < 256);
this.stream.writeUInt8(value, this.length);
this._buffer.writeUInt8(value, this.length);
this.length += 1;

@@ -32,3 +58,3 @@ };

BinaryStream.prototype.writeInt16 = function (value) {
this.stream.writeInt16LE(value, this.length);
this._buffer.writeInt16LE(value, this.length);
this.length += 2;

@@ -38,3 +64,3 @@ };

BinaryStream.prototype.writeInteger = function (value) {
this.stream.writeInt32LE(value, this.length);
this._buffer.writeInt32LE(value, this.length);
this.length += 4;

@@ -44,3 +70,3 @@ };

BinaryStream.prototype.writeUInt32 = function (value) {
this.stream.writeUInt32LE(value, this.length);
this._buffer.writeUInt32LE(value, this.length);
this.length += 4;

@@ -50,3 +76,3 @@ };

BinaryStream.prototype.writeUInt16 = function (value) {
this.stream.writeUInt16LE(value, this.length);
this._buffer.writeUInt16LE(value, this.length);
this.length += 2;

@@ -56,3 +82,3 @@ };

BinaryStream.prototype.writeFloat = function (value) {
this.stream.writeFloatLE(value, this.length);
this._buffer.writeFloatLE(value, this.length);
this.length += 4;

@@ -62,3 +88,3 @@ };

BinaryStream.prototype.writeDouble = function (value) {
this.stream.writeDoubleLE(value, this.length);
this._buffer.writeDoubleLE(value, this.length);
this.length += 8;

@@ -68,3 +94,3 @@ };

BinaryStream.prototype.readByte = function () {
var retVal = this.stream.readInt8(this.length);
var retVal = this._buffer.readInt8(this.length);
this.length += 1;

@@ -75,3 +101,3 @@ return retVal;

BinaryStream.prototype.readUInt8 = function () {
var retVal = this.stream.readUInt8(this.length);
var retVal = this._buffer.readUInt8(this.length);
this.length += 1;

@@ -82,3 +108,3 @@ return retVal;

BinaryStream.prototype.readInt16 = function () {
var retVal = this.stream.readInt16LE(this.length);
var retVal = this._buffer.readInt16LE(this.length);
this.length += 2;

@@ -89,3 +115,3 @@ return retVal;

BinaryStream.prototype.readInteger = function () {
var retVal = this.stream.readInt32LE(this.length);
var retVal = this._buffer.readInt32LE(this.length);
this.length += 4;

@@ -96,3 +122,3 @@ return retVal;

BinaryStream.prototype.readUInt16 = function () {
var retVal = this.stream.readUInt16LE(this.length);
var retVal = this._buffer.readUInt16LE(this.length);
this.length += 2;

@@ -103,3 +129,3 @@ return retVal;

BinaryStream.prototype.readUInt32 = function () {
var retVal = this.stream.readUInt32LE(this.length);
var retVal = this._buffer.readUInt32LE(this.length);
this.length += 4;

@@ -110,3 +136,3 @@ return retVal;

BinaryStream.prototype.readFloat = function () {
var retVal = this.stream.readFloatLE(this.length);
var retVal = this._buffer.readFloatLE(this.length);
this.length += 4;

@@ -117,3 +143,3 @@ return retVal;

BinaryStream.prototype.readDouble = function () {
var retVal = this.stream.readDoubleLE(this.length);
var retVal = this._buffer.readDoubleLE(this.length);
this.length += 8;

@@ -123,13 +149,31 @@ return retVal;

BinaryStream.prototype.writeRaw = function (buf,length) {
this._buffer.write(buf,this.length,length);
this.length += length;
};
BinaryStream.prototype.writeByteStream = function (buf) {
if (!buf) {
this.writeUInt32(0xFFFFFFFF);
return;
}
var bufLen = buf.length;
this.writeUInt32(buf.length);
buf.copy(this.stream, this.length,0,buf.length);
buf.copy(this._buffer, this.length,0,buf.length);
this.length += bufLen;
};
BinaryStream.prototype.readByteStream = function () {
var bufLen = this.readUInt32();
if (bufLen === 0xFFFFFFFF) {
return null;
}
if (bufLen === 0) {
return new Buffer(0);
}
var buf = new Buffer(bufLen);
this.stream.copy(buf,0,this.length,this.length+ bufLen);
this._buffer.copy(buf,0,this.length,this.length+ bufLen);
this.length += bufLen;

@@ -140,1 +184,62 @@ return buf;

exports.BinaryStream = BinaryStream;
function BinaryStreamSizeCalculator() {
this.length = 0;
}
BinaryStreamSizeCalculator.prototype.rewind = function () {
this.length = 0;
};
BinaryStreamSizeCalculator.prototype.writeByte = function (value) {
this.length += 1;
};
BinaryStreamSizeCalculator.prototype.writeUInt8 = function (value) {
this.length += 1;
};
BinaryStreamSizeCalculator.prototype.writeInt16 = function (value) {
this.length += 2;
};
BinaryStreamSizeCalculator.prototype.writeInteger = function (value) {
this.length += 4;
};
BinaryStreamSizeCalculator.prototype.writeUInt32 = function (value) {
this.length += 4;
};
BinaryStreamSizeCalculator.prototype.writeUInt16 = function (value) {
this.length += 2;
};
BinaryStreamSizeCalculator.prototype.writeFloat = function (value) {
this.length += 4;
};
BinaryStreamSizeCalculator.prototype.writeDouble = function (value) {
this.length += 8;
};
BinaryStreamSizeCalculator.prototype.writeByteStream = function (buf) {
if(!buf) {
this.writeUInt32(0);
}else {
this.writeUInt32(buf.length);
this.length += buf.length;
}
};
BinaryStreamSizeCalculator.prototype.writeRaw = function (buf,length) {
this.length += length;
};
exports.BinaryStreamSizeCalculator = BinaryStreamSizeCalculator;

@@ -13,3 +13,3 @@ var BinaryStream = require("./binaryStream").BinaryStream;

} else {
value = stream.stream.toString(encoding = 'binary', stream.length, stream.length + length);
value = stream._buffer.toString('binary', stream.length, stream.length + length);
stream.length += length;

@@ -22,3 +22,3 @@ }

if (value === undefined) {
if (value === undefined || value === null) {
stream.writeInteger(-1);

@@ -28,6 +28,23 @@ return;

stream.writeInteger(value.length);
stream.stream.write(value, stream.length);
stream.length += value.length;
stream.writeRaw(value, value.length);
};
exports.encodeUInt16 = function (value, stream) {
stream.writeUInt16(value);
};
exports.decodeUInt16 = function (stream) {
return stream.readUInt16();
};
exports.encodeInt16 = function (value, stream) {
stream.writeInt16(value);
};
exports.decodeInt16 = function (stream) {
return stream.readInt16();
};
exports.encodeInt32 = function (value, stream) {

@@ -41,2 +58,3 @@ stream.writeInteger(value);

exports.encodeUInt32 = function (value, stream) {

@@ -56,3 +74,3 @@ stream.writeUInt32(value);

exports.decodeBoolean = function (stream) {
return value = stream.readByte() ? true : false;
return stream.readByte() ? true : false;
};

@@ -65,5 +83,13 @@

exports.decodeByte = function (stream) {
return value = stream.readByte();
return stream.readByte();
};
exports.encodeUInt8 = function (value, stream) {
stream.writeUInt8(value);
};
exports.decodeUInt8 = function (stream) {
return stream.readUInt8();
};
exports.encodeFloat = function (value, stream) {

@@ -74,3 +100,3 @@ stream.writeFloat(value);

exports.decodeFloat = function (stream) {
return value = stream.readFloat();
return stream.readFloat();
};

@@ -83,45 +109,80 @@

exports.decodeDouble = function (stream) {
return value = stream.readDouble();
return stream.readDouble();
};
//xx var int64 = require("int64-native");
var offset1601 = function () {
var offset_factor_1601 = function () {
// utc = Date.UTC(this.value.getYear(),this.value.getMonth()); // Number of second since January 1970
var utc1600 = new Date(1601, 1, 1);
var utc1970 = new Date(1970, 1, 1);
var t1970 = utc1970.getTime();
var t1600 = utc1600.getTime();
return (t1970 - t1600);
var utc_offset = (new Date().getTimezoneOffset() * 60000);
var utc1600 = new Date(1601, 0, 1);
var t1600 = utc1600.getTime() + utc_offset;
var utc1600_plus_one_day = new Date(1601, 0, 1+1);
var t1600_1d = utc1600_plus_one_day.getTime() + utc_offset;
var factor = (24*60*60*1000)*10000/(t1600_1d-t1600);
var utc1970 = new Date(1970, 0, 1, 0, 0);
var t1970 = utc1970.getTime() + utc_offset;
var offset = -t1600 + t1970;
assert( factor === 10000);
assert( offset === 11644473600000);
return [offset,factor];
}();
exports.encodeDateTime = function (date, stream) {
var t = date.getTime(); // number of millisecond since 1/1/70
assert(new Date(t).getTime() == t);
function dateToHundredNanoSecondFrom1601(date) {
var span = t + offset1601;
var high = Math.floor(span / 0xffffffff);
var low = span % 0xffffffff;
assert( date instanceof Date);
var t = date.getTime(); // number of milliseconds since 1/1/70
assert(new Date(t).getTime() === t);
var check_value = high * 0xffffffff + low;
assert(check_value === span);
var offset = offset_factor_1601[0];
var factor = offset_factor_1601[1];
stream.writeUInt32(high);
stream.writeUInt32(low);
return (t + offset)*factor;
}
};
exports.dateToHundredNanoSecondFrom1601 = dateToHundredNanoSecondFrom1601;
exports.decodeDateTime = function (stream) {
function hundredNanoSecondFrom1601ToDate(value) {
var offset = offset_factor_1601[0];
var factor = offset_factor_1601[1];
value = value/factor - offset;
return new Date(value);
}
var high = stream.readUInt32();
var low = stream.readUInt32();
var value = (high * 0xffffffff) + low;
value -= offset1601;
exports.hundredNanoSecondFrom1601ToDate = hundredNanoSecondFrom1601ToDate;
var d = new Date(value);
return d;
// 0123456789012345
var MAX_INT64 = 0x8FFFFFFFFFFFFFFF;
exports.encodeDateTime = function(date, stream) {
assert(date instanceof Date);
var longValue = dateToHundredNanoSecondFrom1601(date);
assert(longValue>=0 && longValue <= MAX_INT64);
var lo = longValue % 0xFFFFFFFF;
var hi = Math.floor(longValue / 0xFFFFFFFF);
stream.writeUInt32(lo);
stream.writeUInt32(hi);
};
exports.decodeDateTime = function(stream) {
var lo= stream.readUInt32();
var hi= stream.readUInt32();
var value = hi * 0xFFFFFFFF + lo;
return hundredNanoSecondFrom1601ToDate(value);
};
exports.isValidGUID = function (guid) {

@@ -185,27 +246,120 @@ assert(guid.length == 36)

exports.makeNodeId = function makeNodeId(value,namespace) {
var Enum = require("enum");
var NodeIdType = new Enum({
NUMERIC: 0x01,
STRING: 0x02,
GUID: 0x03,
BYTESTRING: 0x04
});
exports.NodeIdType = NodeIdType;
var re = new RegExp("\"", 'g');
function nodeId_toString() {
return JSON.stringify(this).replace(re,"");
}
var makeNodeId = function makeNodeId(value,namespace) {
value = value || 0;
namespace = namespace || 0;
return { value: value, namespace: namespace};
var identifierType = NodeIdType.NUMERIC;
if (typeof value == "string" ) {
identifierType= NodeIdType.STRING;
} else if ( value instanceof Buffer) {
identifierType= NodeIdType.BYTESTRING;
}
var nodeId = { identifierType: identifierType , value: value, namespace: namespace};
nodeId.toString = nodeId_toString;
assert(nodeId.hasOwnProperty("identifierType"));
return nodeId;
};
exports.makeNodeId = makeNodeId;
var EnumNodeIdEncoding = new Enum({
TwoBytes: 0x00, // A numeric value that fits into the two byte representation.
FourBytes: 0x01, // A numeric value that fits into the four byte representation.
Numeric: 0x02, // A numeric value that does not fit into the two or four byte representations.
String: 0x03, // A String value.
Guid: 0x04, // A Guid value.
ByteString: 0x05, // An opaque (ByteString) value.
NamespaceUriFlag: 0x80, // NamespaceUriFlag on ExpandedNodeId is present
ServerIndexFlag: 0x40 // NamespaceUriFlag on ExpandedNodeId is present
});
function nodeID_encodingByte(nodeId)
{
assert(nodeId.hasOwnProperty("identifierType"));
var encodingByte = 0;
if (nodeId.identifierType === NodeIdType.NUMERIC) {
if (nodeId.value < 255 && (!nodeId.namespace) && !nodeId.namespaceUri && !nodeId.serverIndex) {
return EnumNodeIdEncoding.TwoBytes;
} else if (nodeId.value <= 65535 && nodeId.namespace >= 0 && nodeId.namespace <= 255 && !nodeId.namespaceUri && !nodeId.serverIndex) {
return EnumNodeIdEncoding.FourBytes;
} else {
encodingByte = EnumNodeIdEncoding.Numeric;
}
} else if (nodeId.identifierType === NodeIdType.STRING) {
encodingByte = EnumNodeIdEncoding.String;
} else if (nodeId.identifierType === NodeIdType.BYTESTRING ) {
encodingByte = EnumNodeIdEncoding.ByteString;
} else if (nodeId.identifierType === NodeIdType.GUID ) {
encodingByte = EnumNodeIdEncoding.Guid;
}
if (nodeId.hasOwnProperty("namespaceUri") && nodeId.namespaceUri ) {
encodingByte |= EnumNodeIdEncoding.NamespaceUriFlag;
}
if (nodeId.hasOwnProperty("serverIndexFlag") && nodeId.serverIndexFlag ) {
encodingByte |= EnumNodeIdEncoding.ServerIndexFlag;
}
return encodingByte;
}
exports.encodeNodeId = function(nodeId,stream)
{
if (typeof nodeId.value === 'number') {
if (nodeId.value < 255 && nodeId.namespace == 0) {
// 2 byte encoding
stream.writeByte(0);// encoding byte
var encoding_byte = nodeID_encodingByte(nodeId);
stream.writeByte(encoding_byte.value);// encoding byte
switch(encoding_byte) {
case EnumNodeIdEncoding.TwoBytes:
stream.writeByte(nodeId.value);
} else if (nodeId.value <= 65535 && nodeId.namespace >= 0 && nodeId.namespace <= 255) {
stream.writeByte(1); // encoding byte
stream.writeByte(nodeId.namespace); // encoding byte
break;
case EnumNodeIdEncoding.FourBytes:
stream.writeByte(nodeId.namespace);
stream.writeInt16(nodeId.value);
} else {
throw " cannot encode node id "+ util.inspect(nodeId);
}
} else {
stream.writeByte(3); // UAString
stream.writeInt16(nodeId.namespace);
ec.encodeUAString(nodeId.value,stream);
break;
case EnumNodeIdEncoding.Numeric:
stream.writeInt16(nodeId.namespace);
stream.writeUInt32(nodeId.value);
break;
case EnumNodeIdEncoding.String:
stream.writeInt16(nodeId.namespace);
ec.encodeUAString(nodeId.value,stream);
break;
case EnumNodeIdEncoding.ByteString:
assert(nodeId.value instanceof Buffer);
stream.writeInt16(nodeId.namespace);
ec.encodeByteString(nodeId.value,stream);
break;
default:
throw new Error("encodeNodeId: invalid nodeId { or Not Implemented yet} " + encoding_byte);
break;
}

@@ -215,8 +369,115 @@

var _decodeNodeId = function(encoding_byte,stream) {
var nodeId = { value: 0, namespace: null};
var value,namespace;
switch(encoding_byte) {
case EnumNodeIdEncoding.TwoBytes:
value = stream.readByte();
break;
case EnumNodeIdEncoding.FourBytes:
namespace = stream.readByte();
value = stream.readInt16();
break;
case EnumNodeIdEncoding.Numeric:
namespace = stream.readInt16();
value = stream.readUInt32(stream);
break;
case EnumNodeIdEncoding.String:
namespace = stream.readInt16();
value = ec.decodeUAString(stream);
break;
case EnumNodeIdEncoding.ByteString:
namespace = stream.readInt16();
value = ec.decodeByteString(stream);
break;
default:
throw new Error("_decodeNodeId : invalid nodeId { or Not Implemented yet} "+encoding_byte );
break;
}
return makeNodeId(value,namespace);
};
exports.decodeNodeId = function(stream) {
var encoding_byte = EnumNodeIdEncoding.get(stream.readByte());
return _decodeNodeId(encoding_byte,stream);
};
//------
// An ExpandedNodeId extends the NodeId structure by allowing the NamespaceUri to be
// explicitly specified instead of using the NamespaceIndex. The NamespaceUri is optional. If it
// is specified then the NamespaceIndex inside the NodeId shall be ignored.
//
// The ExpandedNodeId is encoded by first encoding a NodeId as described in Clause 5 .2.2.9
// and then encoding NamespaceUri as a String.
//
// An instance of an ExpandedNodeId may still use the NamespaceIndex instead of the
// NamespaceUri. In this case, the NamespaceUri is not encoded in the stream. The presence of
// the NamespaceUri in the stream is indicated by setting the NamespaceUri flag in the encoding
// format byte for the NodeId.
//
// If the NamespaceUri is present then the encoder shall encode the NamespaceIndex as 0 in
// the stream when the NodeId portion is encoded. The unused NamespaceIndex is included in
// the stream for consistency,
//
// An ExpandedNodeId may also have a ServerIndex which is encoded as a UInt32 after the
// NamespaceUri. The ServerIndex flag in the NodeId encoding byte indicates whether the
// ServerIndex is present in the stream. The ServerIndex is omitted if it is equal to zero.
//
exports.makeExpandedNodeId = function makeExpandedNodeId(value,namespace) {
value = parseInt(value) || 0;
namespace = namespace || 0;
serverIndex = 0;
namespaceUri = null;
var nodeId= {
identifierType: NodeIdType.NUMERIC,
value: value,
namespace: namespace,
namespaceUri: namespaceUri,
serverIndex: serverIndex
};
nodeId.toString = nodeId_toString;
return nodeId;
};
exports.encodeExpandedNodeId = function(expandedNodeId, stream) {
var encodingByte = nodeID_encodingByte(expandedNodeId);
exports.encodeNodeId(expandedNodeId,stream);
if (encodingByte.has(EnumNodeIdEncoding.NamespaceUriFlag)) {
ec.encodeString(expandedNodeId.namespaceUri,stream);
}
if(encodingByte.has(EnumNodeIdEncoding.ServerIndexFlag)) {
ec.encodeUInt32(expandedNodeId.serverIndex,stream);
}
};
exports.decodeExpandedNodeId = function(stream) {
var encoding_byte = EnumNodeIdEncoding.get(stream.readByte());
var expandedNodeId = _decodeNodeId(encoding_byte,stream);
expandedNodeId.namespaceUri = null;
expandedNodeId.serverIndex = 0;
if (encoding_byte.has(EnumNodeIdEncoding.NamespaceUriFlag)) {
expandedNodeId.namespaceUri = ec.encodeString(stream);
}
if(encoding_byte.has(EnumNodeIdEncoding.ServerIndexFlag)) {
expandedNodeId.serverIndex = ec.encodeUInt32(stream);
}
return expandedNodeId;
};
exports.encodeLocaleId = ec.encodeUAString;
exports.decodeLocaleId = ec.decodeUAString;
exports.validateLocaleId = function( value) {
// TODO : check that localeID is wellformed
exports.decodeLocaleId = ec.decodeUAString;
exports.validateLocaleId = function( /*value*/) {
// TODO : check that localeID is well-formed
// see part 3 $8.4 page 63
return true;

@@ -223,0 +484,0 @@ };

var assert = require("assert");
var ec = require("./encode_decode");
var util =require("util");
var util = require("util");
require('enum').register();
var _ = require("underscore");
var hexDump = require("./utils").hexDump;
var objectNodeIds = require("./opcua_node_ids").Object;
factories = {};

@@ -12,11 +17,20 @@ _enumerations = {};

{ name: "UAString" , encode: ec.encodeUAString, decode: ec.decodeUAString , defaultValue: ""},
{ name: "Byte", encode: ec.encodeByte, decode: ec.decodeByte, defaultValue: 0 },
{ name: "Integer", encode: ec.encodeInt32, decode: ec.decodeInt32, defaultValue: 0 },
{ name: "Int32", encode: ec.encodeInt32, decode: ec.decodeInt32, defaultValue: 0 },
{ name: "UInt32", encode: ec.encodeUInt32, decode: ec.decodeUInt32, defaultValue: 0 },
{ name: "Double", encode: ec.encodeDouble, decode: ec.decodeDouble, defaultValue: 0.0 },
{ name: "Float", encode: ec.encodeFloat, decode: ec.decodeFloat, defaultValue: 0.0 },
{ name: "UAString", encode: ec.encodeUAString, decode: ec.decodeUAString, defaultValue: ""},
{ name: "String", encode: ec.encodeUAString, decode: ec.decodeUAString, defaultValue: ""},
{ name: "Byte", encode: ec.encodeByte, decode: ec.decodeByte, defaultValue: 0 },
{ name: "Integer", encode: ec.encodeInt32, decode: ec.decodeInt32, defaultValue: 0 },
{ name: "Int32", encode: ec.encodeInt32, decode: ec.decodeInt32, defaultValue: 0 },
{ name: "UInt32", encode: ec.encodeUInt32, decode: ec.decodeUInt32, defaultValue: 0 },
{ name: "Int16", encode: ec.encodeInt16, decode: ec.decodeInt16, defaultValue: 0 },
{ name: "UInt16", encode: ec.encodeUInt16, decode: ec.decodeUInt16, defaultValue: 0 },
{ name: "Double", encode: ec.encodeDouble, decode: ec.decodeDouble, defaultValue: 0.0 },
{ name: "Boolean", encode: ec.encodeBoolean, decode: ec.decodeBoolean, defaultValue: false },
// OPC Unified Architecture, part 3.0 $8.13 page 65
{ name: "Duration", encode: ec.encodeDouble, decode: ec.decodeDouble, defaultValue: 0.0 },
{ name: "Float", encode: ec.encodeFloat, decode: ec.decodeFloat, defaultValue: 0.0 },
// OPC Unified Architecture, part 3.0 $8.26 page 67
{ name: "UtcTime", encode: ec.encodeDateTime, decode: ec.decodeDateTime, defaultValue: new Date() },
{ name: "UtcTime", encode: ec.encodeDateTime, decode: ec.decodeDateTime, defaultValue: new Date() },

@@ -26,21 +40,67 @@ // OPC Unified Architecture, part 4.0 $7.13

// except for 0, are valid.
{ name: "IntegerId", encode: ec.encodeUInt32, decode: ec.decodeUInt32, defaultValue: 0xFFFFFFFF },
{ name: "IntegerId", encode: ec.encodeUInt32, decode: ec.decodeUInt32, defaultValue: 0xFFFFFFFF },
// string in the form "en-US" or "de-DE" or "fr" etc...
{ name: "LocaleId", encode: ec.encodeLocaleId, decode: ec.decodeLocaleId, validate:ec.validateLocaleId, defaultValue: "en" },
{ name: "LocaleId", encode: ec.encodeLocaleId, decode: ec.decodeLocaleId, validate: ec.validateLocaleId, defaultValue: "en" },
{ name: "NodeId", encode: ec.encodeNodeId, decode: ec.decodeNodeId, defaultValue: ec.makeNodeId() },
{ name: "NodeId", encode: ec.encodeNodeId, decode: ec.decodeNodeId, defaultValue: ec.makeNodeId() },
{ name: "ExpandedNodeId", encode: ec.encodeExpandedNodeId, decode: ec.decodeExpandedNodeId, defaultValue: ec.makeExpandedNodeId() },
{ name: "ByteString", encode: ec.encodeByteString, decode: ec.decodeByteString, defaultValue: null }
{ name: "ByteString", encode: ec.encodeByteString, decode: ec.decodeByteString, defaultValue: function () {
return new Buffer(0);
} },
{ name: "ExtensionObject", encode: encodeExtensionObject, decode: decodeExtensionObject, defaultValue: function () {
return null;
} }
];
var _defaultTypeMap = {}; _defaultType.forEach(function(d) {_defaultTypeMap[d.name] = d;});
function registerType(name,encodeFunc,decodeFunc,defaultValue)
{
// OPC-UA Part 6 - $5.2.2.15 ExtensionObject
// AnExtensionObjectis encoded as sequence of bytes prefixed by the NodeIdof its
// DataTypeEncodingand the number of bytes encoded.
function encodeExtensionObject(object, stream) {
if (!object) {
ec.encodeNodeId(object.makeNodeId(0), stream);
stream.writeByte(0x00); // no body is encoded
stream.writeUInt32(0);
} else {
ec.encodeNodeId(object.encodingDefaultBinary, stream);
stream.writeByte(0x01); // 0x01 The body is encoded as a ByteString.
stream.writeUInt32(object.binaryStoreSize());
object.encode(stream);
}
}
function decodeExtensionObject(stream) {
var nodeId = ec.decodeNodeId(stream);
var encodingType = stream.readByte();
var length = stream.readUInt32();
if (nodeId.value == 0 || encodingType == 0) {
return null;
}
var object = exports.constructObject(nodeId);
if (object === null) {
// this object is unknown to us ..
stream.length += length;
return null;
}
object.decode(stream);
return object;
}
var _defaultTypeMap = {};
_defaultType.forEach(function (d) {
_defaultTypeMap[d.name] = d;
});
function registerType(name, encodeFunc, decodeFunc, defaultValue) {
assert(_.isFunction(encodeFunc));
assert(_.isFunction(decodeFunc));
var obj = {
name: name,encode:encodeFunc,decode:decodeFunc,defaultValue:defaultValue
};
name: name, encode: encodeFunc, decode: decodeFunc, defaultValue: defaultValue
};
_defaultType.push(obj);

@@ -51,2 +111,3 @@ _defaultTypeMap[name] = obj;

var constructorMap = {};

@@ -58,55 +119,226 @@

}
throw "cannot find " + name + " in "+ util.inspect(_defaultTypeMap);
throw "cannot find " + name + " in " + util.inspect(_defaultTypeMap);
};
function _encode_by_type(obj,fieldType,stream)
{
var _t = _defaultTypeMap[fieldType];
_t.encode(obj,stream);
function _encode_by_type(obj, fieldType, stream) {
try {
var _t = _defaultTypeMap[fieldType];
_t.encode(obj, stream);
}
catch (err) {
console.error("ERROR in _encode_by_type ".red + "cannot encode " + fieldType + " on " + util.inspect(obj));
console.error(util.inspect(err));
console.error(err.stack);
}
}
function _decode_by_type(obj,fieldType,stream)
{
function _decode_by_type(obj, fieldType, stream) {
var _t = _defaultTypeMap[fieldType];
obj = _t.decode(stream);
if (!_t.hasOwnProperty("decode") || (!_t.decode instanceof Function)) {
console.error(" _decode_by_type :" , util.inspect(_t), util.inspect(obj));
}
var obj = _t.decode(stream);
return obj;
}
function _encode_member_(member, fieldType, stream) {
if (_defaultTypeMap[fieldType]) {
function _encode_(obj,objDesc,stream)
{
objDesc.fields.forEach(function(field) {
_encode_by_type(member, fieldType, stream);
} else if (factories[fieldType]) {
member.encode(stream);
} else if (_enumerations[fieldType]) {
// OPC Unified Architecture, Part 3 page 34
// Enumerations are always encoded as Int32 on the wire as defined in Part 6.
stream.writeInteger(member.value);
} else {
throw new Error(" Invalid field" + fieldType);
}
}
function _decode_member_(member, fieldType, stream, options) {
var tracer = undefined;
if (options) {
tracer = options.tracer;
}
var cursor_before = stream.length;
if (_defaultTypeMap[fieldType]) {
member = _decode_by_type(member, fieldType, stream);
if (tracer) {
tracer.trace("member", options.name, member, cursor_before, stream.length,fieldType);
}
return member;
} else if (factories[fieldType]) {
//xx console.log(" decoding fieldType=",fieldType);
member.decode(stream, options);
} else if (_enumerations[fieldType]) {
var typedEnum = _enumerations[fieldType].typedEnum;
member = typedEnum.get(stream.readInteger());
if (tracer) {
tracer.trace("member", options.name, member, cursor_before, stream.length,fieldType);
}
return member;
} else {
throw new Error(" Invalid field" + field.fieldType);
}
return member;
}
function _encode_(obj, objDesc, stream) {
objDesc.fields.forEach(function (field) {
if (obj.hasOwnProperty(field.name)) {
_encode_by_type(obj[field.name],field.fieldType,stream);
var member = obj[field.name];
var fieldType = field.fieldType;
if (_.isArray(member)) {
stream.writeUInt32(member.length);
member.forEach(function (element) {
_encode_member_(element, fieldType, stream);
});
} else {
_encode_member_(member, fieldType, stream);
}
} else {
throw new Error(" Missing field " + field.name + " in object " + util.inspect(obj));
}
});
}
function _decode_(obj,objDesc,stream)
{
objDesc.fields.forEach(function(field) {
function _resolve_defaultValue(type_userValue, defaultValue) {
defaultValue = defaultValue || type_userValue;
if (_.isFunction(defaultValue)) {
defaultValue = defaultValue.call();
}
// this is a default type such as UAString or Integer
return defaultValue;
}
function _build_default_value(fieldType, isArray, defaultValue) {
if (isArray) {
return [];
}
var sub_options, _constructor;
if (_defaultTypeMap[fieldType]) {
var _type = _defaultTypeMap[fieldType];
return _resolve_defaultValue(_type.defaultValue, defaultValue);
} else if (factories[fieldType]) {
sub_options = sub_options || {};
_constructor = factories[fieldType];
return callConstructor(_constructor, defaultValue);
} else if (_enumerations[fieldType]) {
console.error("Missing Enumeration");
} else {
throw new Error(" Invalid field" + field.fieldType);
}
return null;
}
function _decode_(obj, objDesc, stream, options) {
var tracer = undefined;
if (options) {
tracer = options.tracer;
}
if (tracer) {
tracer.trace("start", options.name + "(" + objDesc.name + ")", stream.length, stream.length);
}
objDesc.fields.forEach(function (field) {
if (obj.hasOwnProperty(field.name)) {
obj[field.name] = _decode_by_type(obj[field.name],field.fieldType,stream);
//xx console.error(" field.name " , field.name);
var member = obj[field.name];
var fieldType = field.fieldType;
if (_.isArray(member)) {
assert(member.length === 0);
var cursor_before = stream.length;
var nb = stream.readUInt32();
if (options) {
options.name = field.name + []
}
if (tracer) {
tracer.trace("start_array", field.name, nb, cursor_before, stream.length);
}
for (var i = 0; i < nb; i++) {
var element = _build_default_value(fieldType, false, field.defaultValue);
element = _decode_member_(element, fieldType, stream, options) || member;
member.push(element);
}
if (tracer) {
tracer.trace("end_array", field.name, stream.length - 4);
}
} else {
if (options) {
options.name = field.name;
}
obj[field.name] = _decode_member_(member, fieldType, stream, options) || member;
}
}
});
if (tracer) {
tracer.trace("end", objDesc.name, stream.length, stream.length);
}
}
function installEnumProp(obj,name,Enum){
var private_name ="__"+name;
function installEnumProp(obj, name, Enum) {
var private_name = "__" + name;
obj[private_name] = Enum.enums[0];
if (typeof(obj[param]) === "function") {
return; // cannot do it !!!
}
if (Object.getOwnPropertyDescriptor(obj, name)) {
return;
}
// create a array of possible value for the enum
param = {};
var param = {};
param[name] = {
set : function (value){
set: function (value) {
if (!(value in Enum )) {
throw "Invalid value provided for Enum " + name+ ": " + value;
throw "Invalid value provided for Enum " + name + ": " + value;
}
this[private_name] = value;
},
get : function() {
get: function () {
return this[private_name];

@@ -117,4 +349,4 @@ },

};
Object.defineProperties(obj,param);
Object.defineProperty(obj,private_name,{ hidden: true, enumerable: false});
Object.defineProperties(obj, param);
Object.defineProperty(obj, private_name, { hidden: true, enumerable: false});
}

@@ -130,6 +362,176 @@

function _addSimpleField(fieldType, isArray, defaultValue) {
function UAObjectFactoryBuild(_description)
{
var description =_description;
var _type = _defaultTypeMap[fieldType];
return _build_default_value(fieldType, isArray, defaultValue);
}
var constructObject = function (kind_of_field, self, field, extra, data) {
var fieldType = field.fieldType;
var fieldName = field.name;
switch (kind_of_field) {
case "enumeration":
var typedEnum = _enumerations[fieldType].typedEnum;
installEnumProp(self, fieldName, typedEnum);
if (field.defaultValue) {
self[fieldName] = _resolve_defaultValue(field.defaultValue);
}
break;
case "basic":
self[fieldName] = _addSimpleField(fieldType, field.isArray, field.defaultValue);
break;
case "complex":
if (field.subtype) {
// this is a synonymous
fieldType = field.subType;
callback("basic", self, fieldName, field);
self[fieldName] = _addSimpleField(fieldType, field.isArray, field.defaultValue);
} else {
if (field.defaultValue == "null") {
self[fieldName] = null;
} else {
// this is a complex type
// find options related to this filed in options
var sub_options = {};
if (fieldName in data.options) {
sub_options = data.options[fieldName];
data.sub_option_to_ignore.push(fieldName);
}
self[fieldName] = _build_default_value(fieldType, field.isArray, sub_options);
}
}
break;
default:
throw new Error("internal error kind_of_field");
}
};
function r(str) {
return (str + " ").substr(0, 30);
}
var _exploreObject = function (kind_of_field, self, field, extra, data) {
var fieldType = field.fieldType;
var fieldName = field.name;
var padding = data.padding;
var value = self[fieldName];
var str;
switch (kind_of_field) {
case "enumeration":
var typedEnum = _enumerations[fieldType].typedEnum;
str = r(padding + fieldName, 30) + " " + r(fieldType, 15) + " " + value.key + " ( " + value.value + ")";
data.lines.push(str);
break;
case "basic":
if (value instanceof Buffer) {
var _hexDump = hexDump(value);
value = "<BUFFER>";
data.lines.push(r(padding + fieldName, 30) + " " + r(fieldType, 15));
data.lines.push(_hexDump);
} else {
if (fieldType == "IntegerId" || fieldType == "UInt32") {
value = "" + value + " 0x" + value.toString(16);
}
str = r(padding + fieldName, 30) + " " + r(fieldType, 15) + " " + value;
data.lines.push(str);
}
break;
case "complex":
if (field.subtype) {
// this is a synonymous
fieldType = field.subType;
str = r(padding + fieldName, 30) + " " + r(fieldType, 15) + " " + value;
data.lines.push(str);
} else {
var _new_desc = factories[fieldType].prototype._description;
if (field.isArray) {
data.lines.push(r(padding + fieldName, 30) + r(fieldType, 15) + ': [');
var i = 0;
value.forEach(function (element) {
var data1 = { padding: padding + " ", lines: []};
objectVisitor(element, _new_desc, data1, _exploreObject);
data.lines.push(padding + i + ": {");
data.lines = data.lines.concat(data1.lines);
data.lines.push(padding + "}");
i++;
});
data.lines.push(r(padding + "", 30) + "]");
} else {
data.lines.push(r(padding + fieldName, 30) + r(fieldType, 15) + "{")
var data1 = { padding: padding + " ", lines: []};
objectVisitor(value, _new_desc, data1, _exploreObject);
data.lines = data.lines.concat(data1.lines);
data.lines.push(padding + "}")
}
}
break;
default:
throw new Error("internal error: unknown kind_of_field");
}
};
var explore = function () {
var self = this;
var data = { padding: " ", lines: []};
data.lines.push("message /*" + this._description.name + "*/ : {");
objectVisitor(self, self._description, data, _exploreObject);
data.lines.push(" };");
return data.lines.join("\n");
};
var objectVisitor = function (object, description, data, callback) {
var self = object;
assert(description);
description.fields.forEach(function (field) {
var fieldType = field.fieldType;
var fieldName = field.name;
if (fieldType in _enumerations) {
var typedEnum = _enumerations[fieldType].typedEnum;
callback("enumeration", self, field, typedEnum, data);
} else if (fieldType in _defaultTypeMap) {
callback("basic", self, field, null, data);
} else if (fieldType in factories) {
callback("complex", self, field, null, data);
} else {
console.error(description);
console.error("field = ",field);
throw new Error("Invalid field type : " + fieldType + JSON.stringify(field) + " is not a default type nor a registered complex struct");
}
});
};
function UAObjectFactoryBuild(_description) {
var description = _description;
var name = description.name;

@@ -139,7 +541,9 @@

// create a new Enum
var myEnum = new Enum(description.enumValues);
var typedEnum = new Enum(description.enumValues);
// xx if ( name in enumerations) { throw " already inserted"; }
_enumerations[name] = description;
return myEnum;
_enumerations[name].typedEnum = typedEnum;
return typedEnum;
}
if (description.hasOwnProperty("subtype")) {

@@ -149,4 +553,7 @@

if (t === undefined) {
throw " "+ util.inspect(description,{color: true}) +" cannot find subtype " + description.subtype;
throw " " + util.inspect(description, {color: true}) + " cannot find subtype " + description.subtype;
}
//xx console.log(util.inspect(description));
assert(_.isFunction(t.encode));
assert(_.isFunction(t.decode));
registerType(name, t.encode, t.decode, t.defaultValue);

@@ -156,6 +563,5 @@ return;

var ClassConstructor = function (options) {
var classConstructor;
classConstructor = function (options) {
assert(this != undefined, " keyword 'new' is required for constructor call");
var self = this;

@@ -165,86 +571,103 @@

// construct default properties
var sub_option_to_ignore = [];
if (!description.fields) {
console.log("description",description)
throw new Error("fields missing in description " + description)
}
description.fields.forEach( function(field) {
var fieldType = field.fieldType;
var fieldName = field.name;
var data = {
options: options,
sub_option_to_ignore: []
};
objectVisitor(self, description, data, constructObject);
function _addSimpleField(fieldType,isArray,defaultValue) {
_type = _defaultTypeMap[fieldType];
if(isArray) {
return [];
} else {
defaultValue = defaultValue || _type.defaultValue;
// this is a default type such as UAString or Integer
return defaultValue;
}
for (var option in options) {
if (options.hasOwnProperty(option) && data.sub_option_to_ignore.indexOf(option) == -1) {
assert(this.hasOwnProperty(option), " invalid option specified " + option);
assert(!(this[options] instanceof Object)); //
this[option] = options[option];
}
}
if ( fieldType in _enumerations ) {
// Prevents code from adding or deleting properties, or changing the descriptors of any property on an object.
// Property values can be changed however.
Object.seal(this);
};
var typedEnum = new Enum(_enumerations[fieldType].enumValues);
installEnumProp(self,fieldName,typedEnum);
ClassConstructor.prototype.binaryStoreSize = function () {
} else if ( fieldType in _defaultTypeMap) {
var BinaryStreamSizeCalculator = require("../lib/binaryStream").BinaryStreamSizeCalculator;
var stream = new BinaryStreamSizeCalculator();
this.encode(stream);
return stream.length;
};
self[fieldName] = _addSimpleField(fieldType,field.isArray, field.defaultValue);
ClassConstructor.prototype.encode = function (stream) {
if (description.encode) {
// use the encode function specified in the description object instead of default one
description.encode(this, stream);
} else {
_encode_(this, description, stream);
}
};
} else if (fieldType in factories) {
ClassConstructor.prototype.decode = function (stream, options) {
if (description.decode) {
// use the decode function specified in the description object instead of default one
description.decode(this, stream, options);
} else {
_decode_(this, description, stream, options);
}
};
if (field.subtype) {
// this is a synonymous
fieldType = field.subType;
self[fieldName] = _addSimpleField(fieldType,field.isArray,field.defaultValue);
} else {
// this is a complex type
// find options related to this filed in options
sub_options = {};
if (fieldName in options) {
sub_options = options[fieldName];
sub_option_to_ignore.push(fieldName);
}
if(field.isArray) {
self[fieldName] = [];
} else {
_constructor = factories[fieldType];
self[fieldName] = callConstructor(_constructor, sub_options);
}
}
var id = description.id;
if (!id) {
var encode_name = name + "_Encoding_DefaultBinary";
id = objectNodeIds[encode_name];
if (!id) {
throw new Error(" " + name + " has no _Encoding_DefaultBinary id \n"
+" please add a Id field in the structure definition" );
}
}
var expandedNodeId = ec.makeExpandedNodeId(id);
} else {
throw new Error("Invalid field type : " + fieldType + " is not a default type nor a registered complex struct");
}
ClassConstructor.prototype.encodingDefaultBinary = expandedNodeId;
ClassConstructor.prototype.constructor.name = name;
ClassConstructor.prototype._description = description;
ClassConstructor.prototype.explore = explore;
});
if (name in factories) {
throw new Error(" Class " + name + " already in factories");
}
factories[name] = ClassConstructor;
for (option in options) {
if (options.hasOwnProperty(option) && sub_option_to_ignore.indexOf(option) == -1) {
assert(!(this[options] instanceof Object)); //
this[option] = options[option];
}
if (expandedNodeId.value != 0) {
if (expandedNodeId.value in constructorMap) {
throw new Error(" Class " + name + " with ID " + expandedNodeId.value + " already in constructorMap");
}
};
}
constructorMap[expandedNodeId.value] = ClassConstructor;
return ClassConstructor;
}
var getConstructor = function (expandedId) {
if (!expandedId || !(expandedId.value in constructorMap)) {
return null;
}
return constructorMap[expandedId.value];
};
exports.getConstructor = getConstructor;
classConstructor.prototype.encode = function(stream) {
_encode_(this,description,stream);
};
classConstructor.prototype.decode = function(stream) {
_decode_(this,description,stream);
};
exports.constructObject = function (expandedId) {
if (!expandedId || !(expandedId.value in constructorMap)) {
throw new Error("constructObject: cannot find constructor for " + expandedId);
}
return new constructorMap[expandedId.value]();
};
factories[name] = classConstructor;
exports.UAObjectFactoryBuild = UAObjectFactoryBuild;
return classConstructor;
}
exports.UAObjectFactoryBuild = UAObjectFactoryBuild;
var _next_available_id = 0xFFFE0000;
exports.next_available_id = function(){
_next_available_id +=1;
return _next_available_id;
}

224

lib/nodeopcua.js
var assert = require("assert");
var BinaryStream = require("./binaryStream").BinaryStream;
var ec = require("./encode_decode");
var s = require("./structures");
var factories = require("../lib/factories");
//===========================================================
function GUID(value) {
// pattern should be
// “72962B91-FA75-4AE6-8D28-B404DC7DAF63”
this.guid = value;
}
GUID.prototype.encode = function (stream) {
ec.encodeGUID(this.guid,stream);
};
function Enum(possibleValues) {
this.possibleValues = possibleValues;
this.value = possibleValues[0];
}
Enum.prototype.encode = function (binaryStream) {
};
function NodeId(name) {
this.namespace = 0;
this.value = name;
}
NodeId.prototype.encode = function (binaryStream) {
ec.encodeNodeId(this,binaryStream);
};
function LocalizeText() {
this.encodingMask = new Byte();
this.locale = "";
this.text = "";
}
LocalizeText.prototype.encode = function (binaryStream) {
};
LocalizeText.prototype.decode = function (binaryStream) {
};
opcua = exports;

@@ -56,9 +13,10 @@

name: "HelloMessage",
id: factories.next_available_id(),
fields: [
{ name: "protocolVersion" , fieldType: "Integer" },
{ name: "receiveBufferSize" , fieldType: "Integer" },
{ name: "sendBufferSize" , fieldType: "Integer" },
{ name: "maxMessageSize" , fieldType: "Integer" },
{ name: "maxChunkCount" , fieldType: "Integer" },
{ name: "endpointUrl" , fieldType: "UAString"}
{ name: "protocolVersion" , fieldType: "UInt32" , description: "The latest version of the OPC UA TCP protocol supported by the Client"},
{ name: "receiveBufferSize" , fieldType: "UInt32" , description: "The largest message that the sender can receive."},
{ name: "sendBufferSize" , fieldType: "UInt32" , description: "The largest message that the sender will send." },
{ name: "maxMessageSize" , fieldType: "UInt32" , description: "The maximum size for any response message." },
{ name: "maxChunkCount" , fieldType: "UInt32" , description: "The maximum number of chunks in any response message"},
{ name: "endpointUrl" , fieldType: "UAString",description: "The URL of the Endpoint which the Client wished to connect to."}
]

@@ -69,8 +27,9 @@ };

name: "AcknowledgeMessage",
id: factories.next_available_id(),
fields: [
{ name: "protocolVersion" , fieldType: "Integer" },
{ name: "receiveBufferSize" , fieldType: "Integer" },
{ name: "sendBufferSize" , fieldType: "Integer" },
{ name: "maxMessageSize" , fieldType: "Integer" },
{ name: "maxChunkCount" , fieldType: "Integer" }
{ name: "protocolVersion" , fieldType: "UInt32" , description: "The latest version of the OPC UA TCP protocol supported by the Server." },
{ name: "receiveBufferSize" , fieldType: "UInt32" },
{ name: "sendBufferSize" , fieldType: "UInt32" },
{ name: "maxMessageSize" , fieldType: "UInt32" , description: "The maximum size for any request message."},
{ name: "maxChunkCount" , fieldType: "UInt32" , description: "The maximum number of chunks in any request message." }
]

@@ -81,9 +40,9 @@ };

name: "ErrorMessage",
id: factories.next_available_id(),
fields: [
{name: "Error", fieldType: "UInt32", comment: "The numeric code for the error. This shall be one of the values listed in Table 40." },
{name: "Reason", fieldType: "String", comment: "A more verbose description of the error.This string shall not be more than 4096 characters." }
{name: "Error", fieldType: "UInt32", description: "The numeric code for the error. This shall be one of the values listed in Table 40." },
{name: "Reason", fieldType: "String", description: "A more verbose description of the error.This string shall not be more than 4096 characters." }
]
};
var factories = require("../lib/factories");
var HelloMessage = factories.UAObjectFactoryBuild(HelloMessage_Description);

@@ -93,3 +52,52 @@ var AcknowledgeMessage = factories.UAObjectFactoryBuild(AcknowledgeMessage_Description);

exports.AcknowledgeMessage = AcknowledgeMessage;
function is_valid_msg_type(msgType) {
assert([ 'HEL', 'ACK', 'ERR', // Connection Layer
'OPN', 'MSG', 'CLO' // OPC Unified Architecture, Part 6 page 36
].indexOf(msgType) >= 0, "invalid message type " + msgType);
return true;
}
function readMessageHeader(stream) {
assert(stream instanceof BinaryStream);
var msgType = String.fromCharCode(stream.readUInt8()) +
String.fromCharCode(stream.readUInt8()) +
String.fromCharCode(stream.readUInt8());
var isFinal = String.fromCharCode(stream.readUInt8());
var length = stream.readUInt32();
return { msgType: msgType, isFinal: isFinal, length: length };
}
exports.readMessageHeader = readMessageHeader;
function decodeMessage(stream, className) {
assert(stream instanceof BinaryStream);
assert(className instanceof Function , " expecting a function for " + className);
var header = readMessageHeader(stream);
assert(stream.length == 8);
var obj;
if (header.msgType == "ERR") {
//xx console.log(" received an error");
obj = new opcua.TCPErrorMessage();
obj.decode(stream);
return obj;
} else {
obj = new className();
obj.decode(stream);
return obj;
}
}
/**

@@ -103,66 +111,72 @@ * @name encodeMessage

*/
encodeMessage = function (msgType, messageContent, stream) {
// console.log(msgType)
// console.log(['HEL','ACK','ERR']);
assert(['HEL', 'ACK', 'ERR'].indexOf(msgType) >= 0);
var writeTCPMessageHeader = function(msgType,chunkType,total_length,stream){
if (stream instanceof Buffer) {
stream = new BinaryStream(stream)
}
assert(is_valid_msg_type(msgType));
assert(['A','F','C'].indexOf(chunkType) !== -1);
stream.writeByte(msgType.charCodeAt(0));
stream.writeByte(msgType.charCodeAt(1));
stream.writeByte(msgType.charCodeAt(2));
stream.writeByte(0); // reserved
stream.writeInteger(0); //the length of the message, in bytes. (includes the 8 bytes of the message header)
messageContent.encode(stream);
length = stream.length;
stream.stream.writeUInt32LE(length, 4);
// Chunk type
stream.writeByte(chunkType.charCodeAt(0)); // reserved
stream.writeUInt32(total_length);
};
exports.writeTCPMessageHeader = writeTCPMessageHeader;
function readMessageHeader(stream) {
var encodeMessage = function (msgType, messageContent, stream) {
assert(stream instanceof opcua.BinaryStream);
//the length of the message, in bytes. (includes the 8 bytes of the message header)
var total_length = messageContent.binaryStoreSize() + 8;
msgType = String.fromCharCode(stream.readByte()) +
String.fromCharCode(stream.readByte()) +
String.fromCharCode(stream.readByte());
padding = stream.readByte();
writeTCPMessageHeader(msgType,"F",total_length,stream);
messageContent.encode(stream);
assert(total_length == stream.length , "invalid message size");
};
length = stream.readInteger(4);
function packTcpMessage(msgType,encodableObject) {
return { msgType: msgType, length: length };
}
assert(is_valid_msg_type(msgType));
function decodeMessage(stream, className) {
assert(stream instanceof opcua.BinaryStream);
var messageChunk = new Buffer(encodableObject.binaryStoreSize() + 8);
// encode encodeableObject in a packet
var stream = new BinaryStream(messageChunk);
encodeMessage(msgType, encodableObject, stream);
return messageChunk;
header = readMessageHeader(stream);
obj = new className();
obj.decode(stream);
return obj;
}
exports.BinaryStream = BinaryStream;
exports.GUID = GUID;
exports.NodeId = NodeId;
exports.HelloMessage = HelloMessage;
exports.AcknowledgeMessage = AcknowledgeMessage;
// opc.tcp://xleuri11022:51210/UA/SampleServer
function parseEndpointUrl(endpoint_url)
{
var r = /([a-z.]*):\/\/([a-zA-Z\.\-0-9]*):([0-9]*)(\/.*)/
try {
var matches = r.exec(endpoint_url);
return {
protocol: matches[1],
hostname: matches[2],
port: parseInt(matches[3]),
address: matches[4]
}
exports.encodeMessage = encodeMessage;
exports.decodeMessage = decodeMessage;
function sendMessage(socket, msgType, msg, callback) {
assert(['HEL', 'ACK', 'ERR'].indexOf(msgType) >= 0);
// encode message in a packet
var stream = new opcua.BinaryStream();
opcua.encodeMessage(msgType, msg, stream);
s = stream.stream.slice(0, stream.length);
// write data to socket
socket.write(s, callback);
}
catch(err) {
console.log(" invalid endpoint URL " + endpoint_url);
}
return {};
}
exports.sendMessage = sendMessage;
exports.HelloMessage = HelloMessage;
exports.AcknowledgeMessage = AcknowledgeMessage;
exports.TCPErrorMessage = s.TCPErrorMessage;
exports.decodeMessage = decodeMessage;
//xx exports.encodeMessage = encodeMessage;
exports.packTcpMessage = packTcpMessage;
exports.parseEndpointUrl = parseEndpointUrl;

@@ -0,82 +1,431 @@

var util = require("util");
var EventEmitter = require("events").EventEmitter;
var net = require("net");
var opcua = require("./nodeopcua");
var colors = require('colors');
var util = require('util');
var read_certificate = require("../lib/crypto_utils").read_certificate;
var crypto = require("crypto");
var async = require("async");
var _ = require("underscore");
var assert= require("assert");
var ClientSecureChannelLayer =require("./client/client_secure_channel_layer").ClientSecureChannelLayer;
var s = require("./structures");
var nodeids = require("./opcua_node_ids").DataType;
var ec = require("./encode_decode");
var debugLog = require("../lib/utils").make_debugLog(__filename);
function makeHeader(options)
{
var header = new s.RequestHeader();
header;
return header;
}
exports.makeHeader = makeHeader;
/**
*
* @constructor OPCUAClient
*/
function OPCUAClient() {
EventEmitter.call(this);
this.protocolVersion = 1;
this._sessions = [];
this._clientNonce = crypto.randomBytes(32);
this._certificate = read_certificate("certificates/client_cert.pem");
this._server_endpoints =[];
function OPCUAClient()
}
util.inherits(OPCUAClient, EventEmitter);
/**
* connect OPCUA client to server
*
* @param endpoint_url
* @param callback
*/
OPCUAClient.prototype.connect = function(endpoint_url, callback)
{
this._client = new net.Socket();
assert(_.isFunction(callback), "expecting a callback");
// Add a 'data' event handler for the client socket
// data is what the server sent to this socket
this._client.on('data', function(data) {
var self = this;
_stream = new opcua.BinaryStream(data);
var acknowledgeMessage = opcua.decodeMessage(_stream,opcua.AcknowledgeMessage);
//xx console.log(util.inspect(acknowledgeMessage,{ colors: true}));
// prevent illegal call to connect
if ( this._secureChannel) {
process.nextTick(function() { callback(new Error("connect already called"),null);});
return;
}
self._connection_callback = callback;
//todo: make sure endpoint_url exists in the list of endpoints send by the server
async.series([
//------------------------------------------------- STEP 2 : OpenSecureChannel
function(callback) {
assert( self._secureChannel === undefined );
self._secureChannel = new ClientSecureChannelLayer();
self._secureChannel.on("send_chunk",function(message_chunk) {
self.emit("send_chunk",message_chunk); });
self._secureChannel.on("receive_chunk",function(message_chunk) {
self.emit("receive_chunk",message_chunk); });
self._secureChannel.on("send_request",function(message) {
self.emit("send_request",message); });
self._secureChannel.on("receive_response",function(message) {
self.emit("receive_response",message); });
self._secureChannel.protocolVersion = self.protocolVersion;
self._secureChannel.create(endpoint_url,function(err){
if (err) {
self._secureChannel = null;
} else {
}
callback(err);
});
},
//------------------------------------------------- STEP 3 : GetEndpointsRequest
function(callback) {
self.getEndPointRequest(function(err,endpoints){
callback(err);
});
}
], function(err) {
if (err) {
self.disconnect(function() {
if (self._connection_callback) {
setImmediate(self._connection_callback,err); // OK
self._connection_callback = null;
}
});
self._secureChannel = null;
} else {
if (self._connection_callback) {
setImmediate(self._connection_callback,err); // OK
self._connection_callback = null;
}
}
});
// Add a 'close' event handler for the client socket
this._client.on('close', function() {
//xx console.log('Connection closed');
};
/**
* return the endpoint information from a URI
* @param endpoint_url
*/
OPCUAClient.prototype.findEndpoint = function(endpoint_url) {
for (var i in this._server_endpoints) {
var endpoint =this._server_endpoints[i];
if (endpoint.endpointUrl === endpoint_url) {
return endpoint;
}
}
return null;
};
OPCUAClient.prototype.getEndPointRequest = function(callback) {
var self = this;
// OpenSecureChannel
var request = new s.GetEndpointsRequest(
{
endpointUrl: self.endpoint_url,
localeIds: [],
requestHeader: {
auditEntryId: null
}
}
);
self._secureChannel.performMessageTransaction(request, s.GetEndpointsResponse,function(err,response){
if (!err) {
self._server_endpoints = response.endpoints;
callback(null,response.endpoints);
} else {
self._server_endpoints = [];
callback(err,null);
}
});
}
};
var OPCUASession = function(client) {
OPCUAClient.prototype.sendMessage = function(msg,callback)
assert(client instanceof OPCUAClient);
this._client = client;
};
var browse_service = require("./browse_service");
/**
* session.browse([{
* nodeId: ec.makeNodeId(85),
* includeSubTypes: true,
* browseDirection: browse_service.BrowseDirection.Both,
* }],function(err,nodes) {} );
*
* @param nodesToBrowse
* @param callback
*/
OPCUASession.prototype.browse = function(nodesToBrowse,callback) {
assert(typeof(callback) === "function");
var request = new browse_service.BrowseRequest({
});
nodesToBrowse.forEach(function(node) {
var b = new browse_service.BrowseDescription(node);
b.resultMask = b.resultMask || 63;
request.nodesToBrowse.push(b);
});
request.requestHeader.authenticationToken = this.authenticationToken;
var self = this;
self._client._secureChannel.performMessageTransaction(request, browse_service.BrowseResponse,function(err,response){
if(err) {
callback(err,null,response);
} else {
// console.log(JSON.stringify(response.results,null," ").yellow.bold);
callback(null,response.results,response.diagnosticInfos);
}
});
};
OPCUASession.prototype.close = function(callback) {
this._client.closeSession(this,callback);
};
OPCUAClient.prototype._nextSessionName = function()
{
opcua.sendMessage(this._client,"HEL",msg,callback);
if (!this.___sessionName_counter) {
this.___sessionName_counter = 0;
}
this.___sessionName_counter += 1;
return 'Session' + this.___sessionName_counter;
};
OPCUAClient.prototype._createSession = function(callback) {
assert(typeof(callback) === "function");
if (!this._secureChannel) {
callback(new Error(" No secure channel"));
return;
}
var endpoint = this.findEndpoint(this._secureChannel.endpoint_url);
if (!endpoint) {
callback(new Error( " End point must exist " + this._secureChannel.endpoint_url));
return;
}
this.serverUri = endpoint.server.applicationUri;
this.endpoint_url =this._secureChannel.endpoint_url;
this.endpoint_url = "opc.tcp://localhost:51210/UA/SampleServer";
var applicationDescription = new s.ApplicationDescription({
applicationUri: "urn:localhost:application:",
productUri: "http://localhost/application",
applicationName: { text: "MyApplication"},
applicationType: s.EnumApplicationType.CLIENT,
gatewayServerUri: undefined,
discoveryProfileUri: undefined,
discoveryUrls: []
});
assert(this.serverUri," must have a valid server URI");
assert(this.endpoint_url," must have a valid server endpoint_url");
var request = new s.CreateSessionRequest({
clientDescription: applicationDescription,
serverUri: this.serverUri,
endpointUrl: this.endpoint_url,
sessionName: this._nextSessionName(),
clientNonce: this._clientNonce,
clientCertificate: null, //xx this._certificate,
requestedSessionTimeout: 300000,
maxResponseMessageSize: 800000
});
// console.log(JSON.stringify(request,null," "));
var self = this;
self._secureChannel.performMessageTransaction(request, s.CreateSessionResponse,function(err,response){
if (!err) {
assert( response instanceof s.CreateSessionResponse);
//
// todo: verify SignedSoftwareCertificates and response.serverSignature
//
var session = new OPCUASession(self);
session.name = request.sessionName;
session.sessionId = response.sessionId;
session.authenticationToken = response.authenticationToken;
session.timeout = response.revisedSessionTimeout;
session.serverNonce = response.serverNonce;
session.serverCertificate = response.serverCertificate;
session.serverSignature = response.serverSignature;
callback(null,session);
} else {
callback(err,null);
}
});
};
OPCUAClient.prototype.connect = function(host, port , callback)
{
// see OPCUA Part 4 - $7.35
OPCUAClient.prototype._activateSession = function(session,callback) {
assert(typeof(callback) === "function");
if (!this._secureChannel) {
callback(new Error(" No secure channel"));
}
var request = new s.ActivateSessionRequest({
clientSignature: { algorithm: null, signature: null },
clientSoftwareCertificates: [
],
localeIds: [
],
userIdentityToken: new s.AnonymousIdentityToken({
policyId: "0"
}), // extension object
userTokenSignature: {
algorithm: null,
signature: null
}
});
request.requestHeader.authenticationToken = session.authenticationToken;
var self = this;
self._secureChannel.performMessageTransaction(request, s.ActivateSessionResponse,function(err,response){
if (!err) {
assert( response instanceof s.ActivateSessionResponse);
var serverNonce = response.serverNonce;
var results = response.results;
callback(null,session);
} else {
callback(err,null);
}
});
};
/**
* create and activate a new session
*
* @param callback
*/
OPCUAClient.prototype.createSession = function(callback) {
var self = this;
this._client.connect(port, host, function() {
//xx console.log('Client connected to : ' + host + ':' + port);
self._createSession(function(err,session){
if(err) {
callback(err);
} else {
self._activateSession(session,function(err,session){
if (!err) {
self._sessions.push(session);
}
callback(err,session);
});
}
});
// Write a message to the socket as soon as the client is connected,
// the server will receive it as message from the client
msg = new opcua.HelloMessage();
self.sendMessage(msg,function(err) {
};
if (err) {
OPCUAClient.prototype._closeSession= function(session,callback) {
} else {
callback();
assert(typeof(callback) === "function");
if (!this._secureChannel) {
callback(new Error(" No secure channel"));
}
// msg = new OpenSecureChannel();
// self.sendMessage(msg,function callback(err) {
// callback();
// });
}
});
var request = new s.CloseSessionRequest({
deleteSubscriptions: true
});
var self = this;
self._secureChannel.performMessageTransaction(request, s.CreateSessionResponse,function(err,response){
if(err) {
callback(err,null);
} else {
self._secureChannel.close(callback);
}
});
};
OPCUAClient.prototype.disconnect = function(callback)
{
this._client.end();
callback();
/**
* @param callback
* @param session
*/
OPCUAClient.prototype.closeSession = function(session,callback) {
var self = this;
//todo : send close session on secure channel
var index = this._sessions.indexOf(session);
if (index >=0 ) {
this._sessions.splice(index, 1);
}
self._closeSession(session,function(err){
callback(err);
});
};
/**
* property transactionInProgress
*
* @returns {boolean} true if a transaction has already been initiated and if the client
* is waiting for a reply from the server, false if the client is ready
* to initiate a new transaction with the server.
*/
OPCUAClient.prototype.__defineGetter__("transactionInProgress" ,function() {
return this._secureChannel.transactionInProgress;
});
/**
* disconnect client from server
* @param callback
*/
OPCUAClient.prototype.disconnect = function(callback) {
if (this._secureChannel) {
this._secureChannel.close(callback);
} else {
callback();
}
};
exports.OPCUAClient = OPCUAClient;

@@ -1,83 +0,270 @@

var net = require('net');
var colors = require('colors');
var util = require('util');
var s = require("./structures");
var StatusCodes = require("./opcua_status_code").StatusCodes;
var assert = require("assert");
var hexDump = require("../lib/utils").hexDump;
var async = require('async');
var messageHeaderToString = require("../lib/packet_analyzer").messageHeaderToString;
var debugLog = require("../lib/utils").make_debugLog(__filename);
function OPCUAServer()
{
var ServerSecureChannelLayer = require("../lib/server/server_secure_channel_layer").ServerSecureChannelLayer;
var _ = require("underscore");
/**
* OPCUAServerEndPoint a ServerEndPoint, listening to one port
*
* when a client is connecting, a SecureChannelIsCreated
*
* @param server
* @param port
* @constructor
*/
function OPCUAServerEndPoint(server, port) {
assert(server instanceof OPCUAServer);
var self = this;
self.port = parseInt(port);
self._helloreceived = false;
self.server = server;
self._channels = [];
this._server = net.createServer(
self._server = net.createServer(
function (socket) {
function(socket) {
// a client is attempting a connection on the socket
//'connection' listener
//xx console.log('server connected');
var channel = new ServerSecureChannelLayer();
channel.timeout = 100;
var _stream = new Buffer(8192);
channel.init(socket, function (err) {
if (err) {
socket.end();
} else {
self._registerChannel(channel);
debugLog('server receiving a client connection');
socket.on('data',function(data) {
}
});
channel.on("message", function (message) {
debugLog("--------------------------------------------------------".green.bold, message._description.name);
if (message instanceof s.GetEndpointsRequest) {
self._on_GetEndpointsRequest(message, channel);
} else if (message instanceof s.CreateSessionRequest) {
self._on_CreateSessionRequest(message, channel);
} else if (message instanceof s.ActivateSessionRequest) {
self._on_ActivateSessionRequest(message, channel);
} else if (message instanceof s.CloseSessionRequest) {
self._on_CloseSessionRequest(message, channel);
} else {
var errMessage = "UNSUPPORTED MESSAGE !! " + message._description.name;
debugLog(errMessage.red.bold);
self._abortWithError(StatusCodes.Bad_ProtocolVersionUnsupported, errMessage, channel);
}
});
channel.on("abort",function(){
// the channel has aborted
self._unregisterChannel(channel);
});
}
);
self._server.on("connection", function (socket) {
debugLog('server connected with : ' + socket.remoteAddress + ':' + socket.remotePort);
});
}
_stream = new opcua.BinaryStream(data);
var helloMessage = opcua.decodeMessage(_stream,opcua.HelloMessage);
OPCUAServerEndPoint.prototype.endpointDescription = function () {
var self = this;
//xx console.log(util.inspect(helloMessage,{ colors: true}));
var server = self.server;
var hostname = require("os").hostname();
// the helloMessage shall only be received once.
self._helloreceived = true;
var acknowledgeMessage = new opcua.AcknowledgeMessage();
opcua.sendMessage(socket,"ACK",acknowledgeMessage,function callback() {
// return the endpoint object
var endpoint = new s.EndpointDescription({
endpointUrl: "opc.tcp://" + hostname + ":" + this.port + "/UA/SampleServer",
server: {
applicationUri: "SampleServer",
productUri: "SampleServer",
applicationName: { text: "SampleServer", locale: null },
applicationType: s.EnumApplicationType.SERVER,
gatewayServerUri: "",
discoveryProfileUri: "",
discoveryUrls: []
},
serverCertificate: server.getCertificate(),
securityMode: s.MessageSecurityMode.NONE,
securityPolicyUri: "http://opcfoundation.org/UA/SecurityPolicy#None",
userIdentityTokens: [
{
policyId: "0",
tokenType: s.UserIdentityTokenType.ANONYMOUS,
issuedTokenType: "",
issuerEndpointUrl: "",
securityPolicyUri: ""
}
],
transportProfileUri: "http://opcfoundation.org/UA-Profile/Transport/uatcp-uasc-uabinary",
securityLevel: 3
});
//xx console.log("send reply");
});
return endpoint;
};
/**
*
* @param channel
* @private
*/
OPCUAServerEndPoint.prototype._registerChannel = function (channel) {
var self = this;
self._channels[channel.securityToken.secureChannelId] = channel;
};
});
OPCUAServerEndPoint.prototype._unregisterChannel = function (channel) {
var self = this;
delete self._channels[channel.securityToken.secureChannelId];
};
socket.on('close', function() {
//xx console.log('server disconnected (CLOSE)');
});
OPCUAServerEndPoint.prototype.listen = function () {
socket.on('end', function() {
//xx console.log('server disconnected (END)');
});
var self = this;
self._started = true;
self._server.listen(self.port, function () { //'listening' listener
debugLog('server bound');
});
};
// socket.write('hello\r\n');
// socket.pipe(socket);
OPCUAServerEndPoint.prototype.shutdown = function (callback) {
var self = this;
if (self._started) {
self._started = false;
self._server.close(function () {
callback();
});
} else {
callback();
}
};
self.connected_client_count+=1;
OPCUAServerEndPoint.prototype.start = function (callback) {
this.listen();
process.nextTick(callback);
};
}
);
this._server.on("connection",function(socket){
//xx console.log('CONNECTED: ' + socket.remoteAddress +':'+ socket.remotePort);
});
/**
*
* @param statusCode
* @param description
* @param channel
* @private
*/
OPCUAServerEndPoint.prototype._abortWithError = function (statusCode, description, channel) {
channel.send_error_and_abort(statusCode, description);
};
this.connected_client_count = 0;
}
/**
*
* @param request
* @param channel
* @private
*/
OPCUAServerEndPoint.prototype._on_GetEndpointsRequest = function (request, channel) {
OPCUAServer.prototype.listen = function(port)
{
port = parseInt(port);
var self = this;
var server = self.server;
assert(request._description.name == "GetEndpointsRequest");
this._server.listen(port, function() { //'listening' listener
//xx console.log('server bound');
var response = new s.GetEndpointsResponse({});
server.endpoints.forEach(function (endpoint) {
response.endpoints.push(endpoint.endpointDescription());
});
channel.send_response("MSG", response);
};
OPCUAServer.prototype.shutdown = function(callback)
{
OPCUAServerEndPoint.prototype._on_CreateSessionRequest = function(request,channel) {
var self = this;
var server = self.server;
assert(request._description.name == "CreateSessionRequest");
var response = new s.CreateSessionResponse({});
channel.send_response("MSG", response);
};
OPCUAServerEndPoint.prototype._on_ActivateSessionRequest = function(request,channel) {
var self = this;
var server = self.server;
assert(request._description.name == "ActivateSessionRequest");
var response = new s.ActivateSessionResponse({});
channel.send_response("MSG", response);
};
delete this._server;
callback();
OPCUAServerEndPoint.prototype._on_CloseSessionRequest = function(request,channel) {
var self = this;
assert(request._description.name == "CloseSessionRequest");
var response = new s.CloseSessionResponse({});
channel.send_response("MSG", response);
};
OPCUAServer = function () {
var self = this;
self.endpoints = [];
self.protocolVersion = 1;
self.connected_client_count = 0;
// add the tcp/ip endpoint with no security
self.endpoints.push(new OPCUAServerEndPoint(this, 65432));
};
/**
* Initiate the server by starting all its endpoints
*/
OPCUAServer.prototype.start = function (done) {
var tasks = [];
this.endpoints.forEach(function (endpoint) {
tasks.push(function (callback) {
endpoint.start(callback);
});
});
async.parallel(tasks, done);
};
OPCUAServer.prototype.shutdown = function (done) {
assert(_.isFunction(done));
var tasks = [];
this.endpoints.forEach(function (endpoint) {
tasks.push(function (callback) {
debugLog(" shutting down endpoint " + endpoint.endpointDescription().endpointUrl);
endpoint.shutdown(callback);
});
});
async.parallel(tasks, function(err) {
done(err);
debugLog("shutdown completed");
});
};
OPCUAServer.prototype.getCertificate = function () {
if (!this.certificate) {
// create fake certificate
var read_certificate = require("../lib/crypto_utils").read_certificate;
this.certificate = read_certificate("certificates/cert.pem");
}
return this.certificate;
};
exports.OPCUAServerEndPoint = OPCUAServerEndPoint;
exports.OPCUAServer = OPCUAServer;
// A SecureChannel is a long-running logical connection between a single Client and a single Server.
// This channel maintains a set of keys known only to the Client and Server, which are used to
// authenticate and encrypt Messages sent across the network. The SecureChannel Services allow
// authenticate and encrypt Messages sent across the network. The ClientSecureChannelLayer Services allow
// the Client and Server to securely negotiate the keys to use.
var util = require("util");
var assert = require("assert");
var color = require("colors");
function SecureChannel()
{
var factories = require("./factories");
var BinaryStream = require("./binaryStream").BinaryStream;
var s= require("./structures");
var ec = require("./encode_decode");
// OPC UA Secure Conversation Message Header : Part 6 page 36
//Asymmetric algorithms are used to secure the OpenSecureChannel messages.
var AsymmetricAlgorithmSecurityHeader_Description = {
name: "AsymmetricAlgorithmSecurityHeader",
id: factories.next_available_id(),
fields: [
// length shall not exceed 256
// The URI of the security policy used to secure the message.
// This field is encoded as a UTF8 string without a null terminator
{ name:"securityPolicyUri" , fieldType:"String" },
// The X509v3 certificate assigned to the sending application instance.
// This is a DER encoded blob.
// This indicates what private key was used to sign the MessageChunk.
// This field shall be null if the message is not signed.
{ name:"senderCertificate" , fieldType:"ByteString" },
// The thumbprint of the X509v3 certificate assigned to the receiving application
// The thumbprint is the SHA1 digest of the DER encoded form of the certificate.
// This indicates what public key was used to encrypt the MessageChunk
// This field shall be null if the message is not encrypted.
{ name:"receiverCertificateThumbprint" , fieldType:"ByteString"}
]
};
AsymmetricAlgorithmSecurityHeader = exports.AsymmetricAlgorithmSecurityHeader = factories.UAObjectFactoryBuild(AsymmetricAlgorithmSecurityHeader_Description);
// Symmetric algorithms are used to secure all messages other than the OpenSecureChannel messages
// OPC UA Secure Conversation Message Header : Part 6 page 37
var SymmetricAlgorithmSecurityHeader_Description= {
name: "SymmetricAlgorithmSecurityHeader",
id: factories.next_available_id(),
fields: [
// A unique identifier for the ClientSecureChannelLayer token used to secure the message
// This identifier is returned by the server in an OpenSecureChannel response message. If a
// Server receives a TokenId which it does not recognize it shall return an appropriate
// transport layer error.
{ name: "tokenId" , fieldType: "UInt32" , defaultValue: 0xDEADBEEF }
]
};
SymmetricAlgorithmSecurityHeader = exports.SymmetricAlgorithmSecurityHeader = factories.UAObjectFactoryBuild(SymmetricAlgorithmSecurityHeader_Description);
var SequenceHeader_Description = {
name: "SequenceHeader",
id: factories.next_available_id(),
fields: [
// A monotonically increasing sequence number assigned by the sender to each
// MessageChunk sent over the ClientSecureChannelLayer.
{ name: "sequenceNumber", fieldType: "UInt32" },
// An identifier assigned by the client to OPC UA request Message. All MessageChunks for
// the request and the associated response use the same identifier.
{ name: "requestId", fieldType: "UInt32" }
]
};
exports.SequenceHeader = SequenceHeader = factories.UAObjectFactoryBuild(SequenceHeader_Description);
var Padding_Description = {
name: "Padding",
id: factories.next_available_id(),
fields: [
// The number of padding bytes (not including the byte for the PaddingSize).
{ name: "paddingSize", fieldType: "Byte" },
// Padding added to the end of the message to ensure length of the data to encrypt is an
// integer multiple of the encryption block size.
// The value of each byte of the padding is equal to PaddingSize.
{ name: "padding", fieldType: "Byte*" },
// The signature for the MessageChunk.
// The signature includes the all headers, all message data, the PaddingSize and the Padding.
{ name: "Signatures", fieldType: "Byte*" }
]
};
function calculateMaxBodySize() {
// The formula to calculate the amount of padding depends on the amount of data that needs to
// be sent (called BytesToWrite). The sender shall first calculate the maximum amount of space
// available in the MessageChunk (called MaxBodySize) using the following formula:
// MaxBodySize = PlainTextBlockSize * Floor((MessageChunkSize –
// HeaderSize – SignatureSize - 1)/CipherTextBlockSize) –
// SequenceHeaderSize;
}
exports.SecureChannel = SecureChannel();
var MessageChunkManager = require("./chunk_manager").MessageChunkManager;
var SequenceNumberGenerator = require("./sequence_number_generator").SequenceNumberGenerator;
function makeAlgorithmSecurityHeader(msgType) {
var securityHeader = new AsymmetricAlgorithmSecurityHeader();
securityHeader.securityPolicyUri = "http://opcfoundation.org/UA/SecurityPolicy#None";
securityHeader.senderCertificate = null;
securityHeader.receiverCertificateThumbprint = null;
return securityHeader;
}
function chooseSecurityHeader(msgType) {
var securityHeader = (msgType === "OPN") ?
new AsymmetricAlgorithmSecurityHeader() :
new SymmetricAlgorithmSecurityHeader();
return securityHeader;
}
exports.chooseSecurityHeader = chooseSecurityHeader;
var SecureMessageChunkManager = function (msgType, options, securityHeader,sequenceNumberGenerator) {
msgType = msgType || "OPN";
this.chunkSize = options.chunkSize || 8192;
var secureChannelId = options.secureChannelId;
var requestId = options.requestId;
this.sequenceNumberGenerator = sequenceNumberGenerator;
securityHeader = securityHeader || chooseSecurityHeader(msgType);
this.securityHeader = securityHeader;
assert(requestId >0 , "expecting a valid request ID");
this.sequenceHeader = new SequenceHeader({ requestId: requestId, sequenceNumber: -1});
var securityHeaderSize = this.securityHeader.binaryStoreSize();
var sequenceHeaderSize = this.sequenceHeader.binaryStoreSize();
var extraHeaderSize = securityHeaderSize + sequenceHeaderSize;
MessageChunkManager.call(this, this.chunkSize,msgType,secureChannelId,extraHeaderSize);
assert(this.bodySize > 0);
};
util.inherits(SecureMessageChunkManager, MessageChunkManager);
SecureMessageChunkManager.prototype.write_header_and_footer = function(finalC,buf,length) {
assert(finalC.length === 1);
// reserve space for header
var self = this;
assert(buf instanceof Buffer);
var bs = new BinaryStream(buf);
// message header --------------------------
// ---------------------------------------------------------------
// OPC UA Secure Conversation Message Header : Part 6 page 36
// MessageType Byte[3]
// IsFinal Byte[1] C : intermediate, F: Final , A: Final with Error
// MessageSize UInt32 The length of the MessageChunk, in bytes. This value includes size of the message header.
// SecureChannelId UInt32 A unique identifier for the ClientSecureChannelLayer assigned by the server.
bs.writeUInt8(this.msgType.charCodeAt(0));
bs.writeUInt8(this.msgType.charCodeAt(1));
bs.writeUInt8(this.msgType.charCodeAt(2));
bs.writeUInt8(finalC.charCodeAt(0));
bs.writeUInt32(length);
bs.writeUInt32(this.secureChannelId);
assert(bs.length === 12 );
// write Security Header -----------------
this.securityHeader.encode(bs);
// write Sequence Header -----------------
this.sequenceHeader.sequenceNumber = this.sequenceNumberGenerator.next();
this.sequenceHeader.encode(bs);
assert(bs.length === this.sizeOfHeader);
// body + padding already written
// sign
// write signature
// encrypt
};
/**
* wrap a message body into one or more message_chunks
* ( use this method to build fake data bloc in tests)
* @param message_body
* @param msgType
* @param chunk_size
* @returns {Array}
*/
function decompose_message_body_in_chunks(message_body,msgType,chunk_size) {
assert(chunk_size>24,"expecting chunk_size");
assert(msgType.length===3," invalid msgType "+ msgType);
assert(message_body instanceof Buffer && message_body.length >0 , " invalid buffer");
var sequenceNumberGenerator = new SequenceNumberGenerator();
var options = {
secureChannelId: 10,
requestId: 36
};
var msgChunkManager = new SecureMessageChunkManager(msgType,options,null,sequenceNumberGenerator);
var chunks = [];
msgChunkManager.on("chunk",function(chunk){
if(chunk) {
assert(chunk.length>0);
chunks.push(chunk);
}
});
msgChunkManager.write(message_body);
msgChunkManager.end();
assert(chunks.length>0,"decompose_message_body_in_chunks: must produce at least one chunk");
return chunks;
}
exports.decompose_message_body_in_chunks = decompose_message_body_in_chunks;
function clone_buffer(buffer) {
var clone = new Buffer(buffer.length);
buffer.copy(clone,0,0);
return clone;
}
exports.clone_buffer = clone_buffer;
function MessageChunker() {
this.sequenceNumberGenerator = new SequenceNumberGenerator();
}
MessageChunker.prototype.chunkSecureMessage = function(msgType,options,message,messageChunkCallback) {
var chunkStream = require("./chunk_manager").chunkStream;
// calculate message size ( with its encodingDefaultBinary)
var binSize = message.binaryStoreSize() + 4;
var stream = new BinaryStream(binSize);
ec.encodeExpandedNodeId(message.encodingDefaultBinary,stream);
message.encode(stream);
exports.fullbuf = clone_buffer(stream._buffer);
var r = require("stream").Readable();
r.push(stream._buffer);
r.push(null);
var securityHeader;
if (msgType == "OPN") {
securityHeader = makeAlgorithmSecurityHeader();
} else {
securityHeader = new SymmetricAlgorithmSecurityHeader({
tokenId: options.tokenId
});
}
r.pipe(chunkStream(new SecureMessageChunkManager(msgType, options, securityHeader,this.sequenceNumberGenerator))
).on("data",function (messageChunk) {
messageChunkCallback(messageChunk);
}).on("finish",function(){
messageChunkCallback(null);
});
};
exports.MessageChunker = MessageChunker;

@@ -1,4 +0,5 @@

var factories = require("./factories.js");
var factories = require("./factories");
var ec = require("./encode_decode");
var assert = require("assert");
//The StatusCode is a 32-bit unsigned integer. The top 16 bits represent the numeric value of the

@@ -14,9 +15,73 @@ //code that shall be used for detecting specific errors or conditions. The bottom 16 bits are bit flags

// TCP Error Message OPC Unified Architecture, Part 6 page 46
// the server always close the connection after sending the TCPError message
var TCPErrorMessage_Description = {
name: "TCPErrorMessage",
id: factories.next_available_id(),
fields: [
{ name :"name", fieldType: "UInt32"},
{ name :"reason", fieldType: "String"} // A more verbose description of the error.
]
};
exports.TCPErrorMessage = factories.UAObjectFactoryBuild(TCPErrorMessage_Description);
// see Part 3 $8.3 and Part 6 $5.2.213
var QualifiedName_Description = {
name: "QualifiedName",
id: factories.next_available_id(),
fields: [
{ name: "namespaceIndex", fieldType: "UInt16" , comment: "The namespace index" },
{ name: "name", fieldType: "String" , comment: "The name" }
]
};
exports.QualifiedName = factories.UAObjectFactoryBuild(QualifiedName_Description);
function getLocalizeText_EncodingByte(localizedText) {
var encoding_mask = 0;
if (localizedText.locale) {
encoding_mask +=1;
}
if (localizedText.text) {
encoding_mask +=2;
}
return encoding_mask;
}
// see Part 3 - $8.5 page 63
var LocalizedText_Description = {
name: "LocalizedText",
id: factories.next_available_id(),
fields: [
{ name: "text", fieldType: "String" },
{ name: "locale", fieldType: "LocaleId" }
]
],
// OPCUA Part 6 $ 5.2.2.14 : localizedText have a special encoding
encode: function(localizedText,stream) {
var encoding_mask= getLocalizeText_EncodingByte(localizedText);
ec.encodeByte(encoding_mask,stream);
if ( ( encoding_mask & 0x01) === 0x01) {
ec.encodeUAString(localizedText.locale,stream);
}
if ( ( encoding_mask & 0x02) == 0x02 ) {
ec.encodeUAString(localizedText.text,stream);
}
},
decode: function(self,stream) {
var encoding_mask = ec.decodeByte(stream);
if ( ( encoding_mask & 0x01) === 0x01) {
self.locale = ec.decodeUAString(stream);
}else {
self.locale = null;
}
if ( ( encoding_mask & 0x02) == 0x02 ) {
self.text = ec.decodeUAString(stream);
} else {
self.text = null;
}
}
};

@@ -28,2 +93,3 @@ exports.LocalizedText = factories.UAObjectFactoryBuild(LocalizedText_Description);

name: "ExtensibleParameter",
id: factories.next_available_id(),
// The extensible parameter types can only be extended by additional parts of this multi-part

@@ -46,4 +112,6 @@ // specification.

name: "ExtensibleParameterAdditionalHeader",
id: factories.next_available_id(),
fields: [
{ name: "parameterTypeId", fieldType: "NodeId" }
{ name: "parameterTypeId", fieldType: "NodeId" },
{ name: "encodingMask" , fieldType: "Byte" }
// TODO: { name: "data"}

@@ -70,3 +138,3 @@ ]

// the Session. The SessionAuthenticationToken type is defined in 7.29.
{ name: "authenticationToken", fieldType: "SessionAuthenticationToken" },
{ name: "authenticationToken", fieldType: "NodeId" },

@@ -111,2 +179,3 @@ // The time the Client sent the request.

{ name: "serviceDiagnostics", fieldType:"Byte" , comment: "The diagnostics associated with the ServiceResult." },
// There is one string in this list for each unique namespace, symbolic identifier,

@@ -147,3 +216,3 @@ // and localized text string contained in all of the diagnostics information

// List of transport profiles that the returned Endpoints shall support.
{ name: "profileUris" , isArray: true, fieldType: "String" },
{ name: "profileUris" , isArray: true, fieldType: "String" }

@@ -203,3 +272,3 @@ ]

};
exports.ApplicationType = factories.UAObjectFactoryBuild(EnumApplicationType_Description);
exports.EnumApplicationType = factories.UAObjectFactoryBuild(EnumApplicationType_Description);

@@ -273,3 +342,4 @@ // OPC Unified Architecture, Part 4 $7.1 page 106

// The application instance Certificate issued to the Server .
{ name: "serverCertificate", fieldType: "ApplicationInstanceCertificate" },
//xx { name: "serverCertificate", fieldType: "ApplicationInstanceCertificate" },
{ name: "serverCertificate", fieldType: "ByteString" },

@@ -319,5 +389,7 @@ // The type of security to apply to the messages. ( see part 4 - $7.14.)

name: "ApplicationInstanceCertificate",
id: factories.next_available_id(),
fields: [
// An identifier for the version of the Certificate encoding.
{ name: "version", fieldType: "ExtensibleParameterAdditionalHeader" },
{ name: "version", fieldType: "String" },

@@ -372,4 +444,15 @@ // A unique identifier for the Certificate assigned by the Issuer.

var EnumSecurityTokenRequestType_Description = {
name:"SecurityTokenRequestType",
isEnum: true,
enumValues: {
ISSUE: 0, // creates a new SecurityToken for a new ClientSecureChannelLayer
RENEW: 1 // creates a new SecurityToken for an existing ClientSecureChannelLayer .
}
};
exports.SecurityTokenRequestType = factories.UAObjectFactoryBuild(EnumSecurityTokenRequestType_Description);
// see OPCUA.Part4. page 22
var OpenSecureChannelRequest_Description = {
var OpenSecureChannelRequest_Description_as_per_SPEC_Part4 = {
name: "OpenSecureChannelRequest",

@@ -387,23 +470,60 @@ fields: [

};
// or OPCUA.Part6. Release 1.0 6.4.4 Establishing a ClientSecureChannelLayer page 39
var OpenSecureChannelRequest_Description_as_per_XMLSCHEMA = {
name: "OpenSecureChannelRequest",
fields: [
{ name: "requestHeader" , fieldType: "RequestHeader" },
{ name: "clientProtocolVersion", fieldType: "UInt32" , description: "The version of protocol used by the client" },
{ name: "requestType", fieldType: "SecurityTokenRequestType", description: "Whether the channel is being created or renewed" },
{ name: "securityMode", fieldType: "EnumMessageSecurityMode" , description: "The security mode to use with the channel." },
{ name: "clientNonce", fieldType: "ByteString" , description: "A random number generated by the client." },
{ name: "requestedLifetime", fieldType: "UInt32" , description: "The channel lifetime in milliseconds." }
]
};
var OpenSecureChannelResponse_Description = {
exports.OpenSecureChannelRequest = factories.UAObjectFactoryBuild(OpenSecureChannelRequest_Description_as_per_XMLSCHEMA);
// OPC Unified Architecture, Part 6 page 36
var ChannelSecurityToken_Description = {
name: "ChannelSecurityToken",
fields: [
{ name: "secureChannelId", fieldType: "UInt32" },
{ name: "tokenId", fieldType: "UInt32" },
{ name: "createdAt", fieldType: "UtcTime" },
{ name: "revisedLifeTime", fieldType: "UInt32" }
]
};
exports.ChannelSecurityToken = factories.UAObjectFactoryBuild(ChannelSecurityToken_Description);
var OpenSecureChannelResponse_Description_as_per_Spec_Part4 = {
name: "OpenSecureChannelResponse",
fields: [
{ name: "responseHeader", fieldType: "responseHeader" },
{ name: "responseHeader", fieldType: "ResponseHeader" },
{ name: "securityToken", fieldType: "ChannelSecurityToken" },
{ name: "channelId", fieldType: "ByteString" },
{ name: "tokenId", fieldType: "ByteString" },
{ name: "createdAt", fieldType: "UTCTime" },
{ name: "revisedLifetime", fieldType: "Duration" },
{ name: "serverNonce", fieldType: "ByteString" }
]
};
var OpenSecureChannelResponse_Description_as_per_Spec_Part6 = {
name: "OpenSecureChannelResponse",
fields: [
{ name: "responseHeader", fieldType: "ResponseHeader" },
{ name: "serverProtocolVersion", fieldType: "UInt32" },
{ name: "securityToken", fieldType: "ChannelSecurityToken" },
{ name: "serverNonce", fieldType: "ByteString" }
]
};
exports.OpenSecureChannelResponse = factories.UAObjectFactoryBuild(OpenSecureChannelResponse_Description_as_per_Spec_Part6);
var CloseSecureChannelRequest_Description = {
name: "CloseSecureChannelRequuest",
name: "CloseSecureChannelRequest",
fields: [
{ name: "requestHeader", fieldType: "RequestHeader" },
{ name: "secureChannelId", fieldType: "ByteString" },
{ name: "requestHeader", fieldType: "RequestHeader" }
// { name: "secureChannelId", fieldType: "ByteString" },
]
};
//
exports.CloseSecureChannelRequest= factories.UAObjectFactoryBuild(CloseSecureChannelRequest_Description);

@@ -413,10 +533,213 @@ var CloseSecureChannelResponse_Description = {

fields: [
{ name: "responseHeader", fieldType: "ResponseHeader" },
{ name: "responseHeader", fieldType: "ResponseHeader" }
]
};
exports.CloseSecureChannelResponse = factories.UAObjectFactoryBuild(CloseSecureChannelResponse_Description);
var factories = require("./factories");
var FindServersRequest_Description = {
comment: "A standard header included in all requests sent to a server.",
name: "FindServersRequest",
fields: [
{ name: "requestHeader" , fieldType: "RequestHeader" },
{ name: "endpointUrl", fieldType: "String", documentation:"The URL used by the client to send the request." },
{ name: "localeIds", isArray: true, fieldType: "LocaleId", documentation:"The locales to use when constructing a response." },
{ name: "serverUris", isArray: true, fieldType: "String", documentation:"The URIs of the servers to return (all servers returned if none specified"}
]
};
exports.FindServersRequest = factories.UAObjectFactoryBuild(FindServersRequest_Description);
var OpenSecureChannelRequest = factories.UAObjectFactoryBuild(OpenSecureChannelRequest_Description);
var FindServersResponse_Description = {
name: "FindServersResponse",
fields: [
{ name: "responseHeader" , fieldType: "ResponseHeader" },
{ name: "servers", isArray: true, fieldType: "ApplicationDescription", documentation:"The servers that met the criteria specified in the request."}
]
};
exports.FindServersResponse = factories.UAObjectFactoryBuild(FindServersResponse_Description);
var ServiceFault_Description = {
comment: "The response returned by all services when there is a service level error.",
name: "ServiceFault",
fields: [
{ name: "responseHeader" , fieldType: "ResponseHeader" }
]
};
exports.ServiceFault = factories.UAObjectFactoryBuild(ServiceFault_Description);
var s2 = require("./session_service");
for( var name in s2) { exports[name] = s2[name]; }
var SignedSoftwareCertificate_Description = {
name: "SignedSoftwareCertificate",
fields: [
{ name: "certificateData" , fieldType: "ByteString", documentation:"The data of the certificate." },
{ name: "signature", fieldType: "ByteString", documentation:"The digital signature."}
]
};
exports.SignedSoftwareCertificate = factories.UAObjectFactoryBuild(SignedSoftwareCertificate_Description);
var SignatureData_Description = {
name: "SignatureData",
fields: [
{ name: "algorithm" , fieldType: "String", documentation:"The cryptography algorithm used to create the signature." },
{ name: "signature", fieldType: "ByteString", documentation:"The digital signature."}
]
};
exports.SignatureData = factories.UAObjectFactoryBuild(SignatureData_Description);
var DiagnosticInfo_EncodingByte_Description = {
name: "DiagnosticInfo_EncodingByte",
isEnum: true,
enumValues: {
SymbolicId: 0x01,
NamespaceUri: 0x02,
LocalizedText: 0x04,
Locale: 0x08,
AdditionalInfo: 0x10,
InnerStatusCode: 0x20,
InnerDiagnosticInfo: 0x40
}
};
var DiagnosticInfo_EncodingByte = exports.DiagnosticInfo_EncodingByte = factories.UAObjectFactoryBuild(DiagnosticInfo_EncodingByte_Description);
// see OPCUA Part 4 $7.8 table 123
var DiagnosticInfoIdentifier_Description = {
name: "DiagnosticInfoIdentifier",
id: factories.next_available_id(),
fields: [
{ name: "namespaceUri", fieldType: "Int32" , defaultValue: null },
{ name: "symbolicId", fieldType: "Int32" , defaultValue: null },
{ name: "locale", fieldType: "Int32" , defaultValue: null },
{ name: "localizedText", fieldType: "Int32" , defaultValue: null }
]
};
exports.DiagnosticInfoIdentifier = factories.UAObjectFactoryBuild(DiagnosticInfoIdentifier_Description);
function getDiagnosticInfoEncodingByte(diagnosticInfo) {
assert(diagnosticInfo);
var encoding_mask= 0;
if (diagnosticInfo.identifier.namespaceUri) {
encoding_mask = set_flag(encoding_mask,DiagnosticInfo_EncodingByte.NamespaceUri);
}
if (diagnosticInfo.identifier.symbolicId) {
encoding_mask = set_flag(encoding_mask,DiagnosticInfo_EncodingByte.SymbolicId);
}
if (diagnosticInfo.identifier.locale) {
encoding_mask = set_flag(encoding_mask,DiagnosticInfo_EncodingByte.Locale);
}
if (diagnosticInfo.identifier.localizedText) {
encoding_mask = set_flag(encoding_mask,DiagnosticInfo_EncodingByte.LocalizedText);
}
if (diagnosticInfo.additionalInfo) {
encoding_mask = set_flag(encoding_mask,DiagnosticInfo_EncodingByte.AdditionalInfo);
}
if (diagnosticInfo.innerStatusCode) {
encoding_mask = set_flag(encoding_mask,DiagnosticInfo_EncodingByte.InnerStatusCode);
}
if (diagnosticInfo.innerDiagnosticInfo !== null) {
encoding_mask = set_flag(encoding_mask,DiagnosticInfo_EncodingByte.InnerDiagnosticInfo);
}
return encoding_mask;
}
var set_flag = require("./utils").set_flag;
var check_flag = require("./utils").check_flag;
exports.set_flag = set_flag;
exports.check_flag = check_flag;
var DiagnosticInfo_Description = {
name: "DiagnosticInfo",
fields: [
{ name: "identifier", fieldType: "DiagnosticInfoIdentifier"},
{ name: "additionalInfo", fieldType: "String", defaultValue: null },
{ name: "innerStatusCode", fieldType: "StatusCode" },
{ name: "innerDiagnosticInfo", fieldType: "DiagnosticInfo", defaultValue: "null" }
],
id: 25,
encode: function(diagnosticInfo,stream) {
var encoding_mask = getDiagnosticInfoEncodingByte(diagnosticInfo);
// write encoding byte
ec.encodeByte(encoding_mask,stream);
// write symbolic id
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.SymbolicId)) {
ec.encodeInt32(diagnosticInfo.identifier.symbolicId,stream);
}
// write namespace uri
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.NamespaceUri)) {
ec.encodeInt32(diagnosticInfo.identifier.namespaceUri,stream);
}
// write locale
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.Locale)) {
ec.encodeInt32(diagnosticInfo.identifier.locale,stream);
}
// write localized text
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.LocalizedText)) {
ec.encodeInt32(diagnosticInfo.identifier.localizedText,stream);
}
// write additional info
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.AdditionalInfo)) {
ec.encodeUAString(diagnosticInfo.additionalInfo,stream);
}
// write inner status code
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.InnerStatusCode)) {
ec.encodeInt32(diagnosticInfo.innerStatusCode,stream);
}
// write innerDiagnosticInfo
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.InnerDiagnosticInfo)) {
assert(diagnosticInfo.innerDiagnosticInfo!=null,"missing innerDiagnosticInfo");
diagnosticInfo.innerDiagnosticInfo.encode(stream);
}
},
decode: function(diagnosticInfo,stream) {
var encoding_mask = ec.decodeByte(stream);
// read symbolic id
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.SymbolicId)) {
diagnosticInfo.identifier.symbolicId = ec.decodeInt32(stream);
}
// read namespace uri
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.NamespaceUri)) {
diagnosticInfo.identifier.namespaceUri = ec.decodeInt32(stream);
}
// read locale
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.Locale)) {
diagnosticInfo.identifier.locale = ec.decodeInt32(stream);
}
// read localized text
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.LocalizedText)) {
diagnosticInfo.identifier.localizedText= ec.decodeInt32(stream);
}
// read additional info
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.AdditionalInfo)) {
diagnosticInfo.additionalInfo = ec.decodeUAString(stream);
}
// read inner status code
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.InnerStatusCode)) {
diagnosticInfo.innerStatusCode = ec.decodeInt32(stream);
}
// read inner status code
if( check_flag(encoding_mask,DiagnosticInfo_EncodingByte.InnerDiagnosticInfo)) {
diagnosticInfo.innerDiagnosticInfo = new exports.DiagnosticInfo({});
diagnosticInfo.innerDiagnosticInfo.decode(stream);
}
}
};
exports.DiagnosticInfo = factories.UAObjectFactoryBuild(DiagnosticInfo_Description);
{
"name": "node-opcua",
"version": "0.0.1",
"description": "pure nodejs OPCUA SDK",
"main": "",
"bin": {},
"directories": {
"test": "test"
},
"scripts": {
"test": "mocha test"
},
"repository": {
"type": "git",
"url": "git://github.com/erossignon/node-opcua.git"
},
"keywords": [
"OPC UA",
"M2M"
],
"author": "Etienne Rossignon",
"license": "MIT",
"bugs" : {
"url": "git://github.com/erossignon/node-opcua/issues"
},
"dependencies": {
"int64-native": "~0.2.1",
"should": "~3.0.1",
"colors": "~0.6.2",
"x509": "0.0.7",
"enum": "~0.2.5"
}
"name": "node-opcua",
"version": "0.0.2",
"description": "pure nodejs OPCUA SDK",
"main": "",
"bin": {},
"directories": {
"test": "test"
},
"scripts": {
"test": "mocha test -R spec --recursive"
},
"repository": {
"type": "git",
"url": "git://github.com/erossignon/node-opcua.git"
},
"keywords": [
"OPC UA",
"M2M"
],
"author": "Etienne Rossignon",
"license": "MIT",
"bugs": {
"url": "git://github.com/erossignon/node-opcua/issues"
},
"devDependencies": {
"mocha": "*",
"should": "*",
"csv": "~0.3.6"
},
"dependencies": {
"colors": "~0.6.2",
"enum": "~0.2.5",
"async": "~0.2.9",
"underscore": "~1.6.0",
"ignite": "~0.1.8",
"machina": "~0.3.4",
"through2": "~0.4.1",
"node-xml": "~1.0.2",
"node-expat": "~2.1.4",
"tmp": "0.0.23",
"sprintf": "~0.1.3",
"hexy": "~0.2.5",
"optimist": "~0.6.0",
"prettyjson": "~0.11.1",
"treeify": "~1.0.1",
"coveralls": "~2.7.0",
"mocha-lcov-reporter": "0.0.1",
"node-int64": "~0.3.0",
"easy-table": "~0.3.0",
"tracer": "~0.6.2",
"base64-js": "0.0.6"
}
}

@@ -7,2 +7,10 @@ node-opcua

[![NPM version](https://badge.fury.io/js/node-opcua.png)](http://badge.fury.io/js/node-opcua)
[![Build Status](https://travis-ci.org/erossignon/node-opcua.png?branch=master)](https://travis-ci.org/erossignon/node-opcua)
[![Dependency Status](https://gemnasium.com/erossignon/node-opcua.png)](https://gemnasium.com/erossignon/node-opcua)
[![Coverage Status](https://coveralls.io/repos/erossignon/node-opcua/badge.png)](https://coveralls.io/r/erossignon/node-opcua)
node-opcua is an experimental OPC-UA stack written in NodeJS.

@@ -15,1 +23,21 @@

Getting started:
================
$ git clone git://github.com/erossignon/node-opcua.git node-opcua
$ cd node-opcua
$ npm install
$ npm test
[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=gadz_er&url=https://github.com/erossignon/node-opcua&title=Node-OPCUA&language=nodejs&tags=github&category=software)
[![NPM](https://nodei.co/npm/node-opcua.png?downloads=true&stars=true)](https://nodei.co/npm/node-opcua/)
[![Project Stats](https://www.ohloh.net/p/713850/widgets/project_thin_badge.gif)](https://www.ohloh.net/p/node-opcua)
var should = require("should");
var ec = require("../lib/encode_decode");
var opcua = require("../lib/nodeopcua");
var BinaryStream = require("../lib/binaryStream").BinaryStream;
describe("testing built-in type encoding",function() {
function test_encode_decode(obj, encode_func, decode_func, expectedLength, verify_buffer_func) {
var binaryStream = new BinaryStream();
binaryStream.length.should.equal(0);
encode_func(obj, binaryStream);
binaryStream.length.should.equal(expectedLength);
it("should encode a boolean as a single byte",function(){
if (verify_buffer_func) {
verify_buffer_func(binaryStream._buffer);
}
binaryStream.rewind();
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
var obj_verif = decode_func(binaryStream);
binaryStream.length.should.equal(expectedLength);
ec.encodeBoolean(true,binaryStream);
binaryStream.length.should.equal(1);
if (obj !== undefined) {
obj_verif.should.eql(obj);
binaryStream.rewind();
var boolValue =ec.decodeBoolean(binaryStream);
boolValue.should.equal(true);
} else {
should.not.exists(obj_verif);
}
}
describe("testing built-in type encoding", function () {
it("should encode and decode a boolean as a single byte", function () {
test_encode_decode(true, ec.encodeBoolean, ec.decodeBoolean, 1);
test_encode_decode(false, ec.encodeBoolean, ec.decodeBoolean, 1);
});
it("should encode a Integer as a 4 byte stream",function(){
it("should encode and decode a Integer (4 bytes)", function () {
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
ec.encodeInt32(1000000000,binaryStream);
binaryStream.length.should.equal(4);
test_encode_decode(1000000000, ec.encodeInt32, ec.decodeInt32, 4, function (buffer) {
// should be little endian
buffer.readUInt8(0).should.equal(0x00);
buffer.readUInt8(1).should.equal(0xCA);
buffer.readUInt8(2).should.equal(0x9A);
buffer.readUInt8(3).should.equal(0x3B);
});
test_encode_decode(-100000000, ec.encodeInt32, ec.decodeInt32, 4);
// should be little endian
//xx console.log(binaryStream.stream.slice(0,4));//toJSON());
binaryStream.stream.readUInt8(0).should.equal(0x00);
binaryStream.stream.readUInt8(1).should.equal(0xCA);
binaryStream.stream.readUInt8(2).should.equal(0x9A);
binaryStream.stream.readUInt8(3).should.equal(0x3B);
binaryStream.rewind();
var check_value =ec.decodeInt32(binaryStream);
check_value.should.equal(1000000000);
});
it("should encode a Floating Point as a 4 byte stream",function(){
// I EEE-754
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
it("should encode and decode a Floating Point (4 bytes)", function () {
var value = -6.5;
ec.encodeFloat(value,binaryStream);
// I EEE-754
test_encode_decode(value, ec.encodeFloat, ec.decodeFloat, 4, function (buffer) {
// should be little endian
buffer.readUInt8(0).should.equal(0x00);
buffer.readUInt8(1).should.equal(0x00);
buffer.readUInt8(2).should.equal(0xD0);
buffer.readUInt8(3).should.equal(0xC0);
});
binaryStream.length.should.equal(4);
// should be little endian
binaryStream.stream.readUInt8(0).should.equal(0x00);
binaryStream.stream.readUInt8(1).should.equal(0x00);
binaryStream.stream.readUInt8(2).should.equal(0xD0);
binaryStream.stream.readUInt8(3).should.equal(0xC0);
binaryStream.rewind();
var check_value =ec.decodeFloat(binaryStream);
check_value.should.equal(value);
});
it("should encode a Double Point as a 8 byte stream",function(){
it("should encode and decode a Double Point (8 bytes)", function () {
// I EEE-754
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
var value = -6.5;
ec.encodeDouble(value,binaryStream);
// I EEE-754
test_encode_decode(value, ec.encodeDouble, ec.decodeDouble, 8, function (buffer) {
// should be little endian
buffer.readUInt8(0).should.equal(0x00);
buffer.readUInt8(1).should.equal(0x00);
buffer.readUInt8(2).should.equal(0x00);
buffer.readUInt8(3).should.equal(0x00);
buffer.readUInt8(4).should.equal(0x00);
buffer.readUInt8(5).should.equal(0x00);
buffer.readUInt8(6).should.equal(0x1a);
buffer.readUInt8(7).should.equal(0xc0);
});
binaryStream.length.should.equal(8);
// should be little endian
//xx console.log(binaryStream.stream.slice(0,8));//toJSON());
binaryStream.stream.readUInt8(0).should.equal(0x00);
binaryStream.stream.readUInt8(1).should.equal(0x00);
binaryStream.stream.readUInt8(2).should.equal(0x00);
binaryStream.stream.readUInt8(3).should.equal(0x00);
binaryStream.stream.readUInt8(4).should.equal(0x00);
binaryStream.stream.readUInt8(5).should.equal(0x00);
binaryStream.stream.readUInt8(6).should.equal(0x1a);
binaryStream.stream.readUInt8(7).should.equal(0xc0);
binaryStream.rewind();
var check_value =ec.decodeDouble(binaryStream);
check_value.should.equal(value);
});
it("should encode a null string" ,function() {
it("should encode and decode a null string", function () {
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
var value = undefined;
ec.encodeUAString(value,binaryStream);
binaryStream.length.should.equal(4);
binaryStream.stream.readUInt8(0).should.equal(0xff);
binaryStream.stream.readUInt8(1).should.equal(0xff);
binaryStream.stream.readUInt8(2).should.equal(0xff);
binaryStream.stream.readUInt8(3).should.equal(0xff);
test_encode_decode(value, ec.encodeUAString, ec.decodeUAString, 4, function (buffer) {
// should be little endian
buffer.readUInt8(0).should.equal(0xff);
buffer.readUInt8(1).should.equal(0xff);
buffer.readUInt8(2).should.equal(0xff);
buffer.readUInt8(3).should.equal(0xff);
});
binaryStream.rewind();
var check_value =ec.decodeUAString(binaryStream);
should.not.exists(check_value);
});
it("should encode a normal string" ,function() {
it("should encode and decode a normal string", function () {
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
var value = "Hello";
var value ="Hello";
ec.encodeUAString(value,binaryStream);
test_encode_decode(value, ec.encodeUAString, ec.decodeUAString, 9, function (buffer) {
// should be little endian
buffer.readUInt8(0).should.equal(0x05);
buffer.readUInt8(1).should.equal(0x00);
buffer.readUInt8(2).should.equal(0x00);
buffer.readUInt8(3).should.equal(0x00);
buffer.readUInt8(4).should.equal('H'.charCodeAt(0));
buffer.readUInt8(5).should.equal('e'.charCodeAt(0));
buffer.readUInt8(6).should.equal('l'.charCodeAt(0));
buffer.readUInt8(7).should.equal('l'.charCodeAt(0));
buffer.readUInt8(8).should.equal('o'.charCodeAt(0));
});
binaryStream.length.should.equal(4 + 5);
// should be little endian
binaryStream.stream.readUInt8(0).should.equal(0x05);
binaryStream.stream.readUInt8(1).should.equal(0x00);
binaryStream.stream.readUInt8(2).should.equal(0x00);
binaryStream.stream.readUInt8(3).should.equal(0x00);
binaryStream.stream.readUInt8(4).should.equal('H'.charCodeAt(0));
binaryStream.stream.readUInt8(5).should.equal('e'.charCodeAt(0));
binaryStream.stream.readUInt8(6).should.equal('l'.charCodeAt(0));
binaryStream.stream.readUInt8(7).should.equal('l'.charCodeAt(0));
binaryStream.stream.readUInt8(8).should.equal('o'.charCodeAt(0));
});
binaryStream.rewind();
var check_value =ec.decodeUAString(binaryStream);
check_value.should.equal(value);
it("should encode and decode a DateTime", function () {
var value = new Date(2014,0,2,15,0);
test_encode_decode(value, ec.encodeDateTime, ec.decodeDateTime, 8, function (buffer) {
// todo
});
});
it("should encode a DateTime" ,function() {
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
it("should encode and decode a GUID", function () {
var value = new Date();
ec.encodeDateTime(value,binaryStream);
var value = "72962B91-FA75-4AE6-8D28-B404DC7DAF63";
binaryStream.length.should.equal(8);
test_encode_decode(value, ec.encodeGUID, ec.decodeGUID, 16, function (buffer) {
buffer.readUInt8(0).should.equal(0x91);
buffer.readUInt8(1).should.equal(0x2B);
buffer.readUInt8(2).should.equal(0x96);
buffer.readUInt8(3).should.equal(0x72);
binaryStream.rewind();
var checked_value = ec.decodeDateTime(binaryStream);
checked_value.should.eql(value);
buffer.readUInt8(4).should.equal(0x75);
buffer.readUInt8(5).should.equal(0xFA);
});
buffer.readUInt8(6).should.equal(0xE6);
buffer.readUInt8(7).should.equal(0x4A);
it("should encode a GUID" ,function() {
buffer.readUInt8(8).should.equal(0x8D);
buffer.readUInt8(9).should.equal(0x28);
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
buffer.readUInt8(10).should.equal(0xB4);
buffer.readUInt8(11).should.equal(0x04);
buffer.readUInt8(12).should.equal(0xDC);
buffer.readUInt8(13).should.equal(0x7D);
buffer.readUInt8(14).should.equal(0xAF);
buffer.readUInt8(15).should.equal(0x63);
});
});
var value = "72962B91-FA75-4AE6-8D28-B404DC7DAF63";
ec.encodeGUID(value,binaryStream);
binaryStream.length.should.equal(16);
it("should encode and decode a ByteString", function () {
var buf = new Buffer(256);
buf.write("THIS IS MY BUFFER");
binaryStream.rewind();
// should be little endian
binaryStream.readUInt8().should.equal(0x91);
binaryStream.readUInt8().should.equal(0x2B);
binaryStream.readUInt8().should.equal(0x96);
binaryStream.readUInt8().should.equal(0x72);
test_encode_decode(buf, ec.encodeByteString, ec.decodeByteString, 256 + 4, function (buffer) {
binaryStream.readUInt8().should.equal(0x75);
binaryStream.readUInt8().should.equal(0xFA);
buffer.readUInt32LE(0).should.equal(256);
});
//xx check_buf.toString('hex').should.equal(buf.toString('hex'));
binaryStream.readUInt8().should.equal(0xE6);
binaryStream.readUInt8().should.equal(0x4A);
binaryStream.readUInt8().should.equal(0x8D);
binaryStream.readUInt8().should.equal(0x28);
});
binaryStream.readUInt8().should.equal(0xB4);
binaryStream.readUInt8().should.equal(0x04);
binaryStream.readUInt8().should.equal(0xDC);
binaryStream.readUInt8().should.equal(0x7D);
binaryStream.readUInt8().should.equal(0xAF);
binaryStream.readUInt8().should.equal(0x63);
it("should encode and decode a two byte NodeId", function () {
binaryStream.rewind();
var checked_value = ec.decodeGUID(binaryStream);
checked_value.should.eql(value);
var nodeId = ec.makeNodeId(25);
nodeId.identifierType.should.eql(ec.NodeIdType.NUMERIC);
test_encode_decode(
nodeId,
ec.encodeNodeId,
ec.decodeNodeId,
2,
function verify_buffer(buffer) {
buffer.readUInt8(0).should.equal(0);
buffer.readUInt8(1).should.equal(25); // namespace
}
);

@@ -194,71 +193,156 @@ });

it("should encode and decode a ByteString" ,function() {
it("should encode and decode a four byte NodeId", function () {
var buf = new Buffer(256);
buf.write("THIS IS MY BUFFER");
var nodeId = ec.makeNodeId(258);
nodeId.identifierType.should.eql(ec.NodeIdType.NUMERIC);
test_encode_decode(
nodeId,
ec.encodeNodeId,
ec.decodeNodeId,
4,
function verify_buffer(buffer) {
buffer.readUInt8(0).should.equal(1);
buffer.readUInt8(1).should.equal(0); // namespace
buffer.readUInt16LE(2).should.equal(258);
}
);
});
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
it("should encode and decode a Numeric NodeId", function () {
ec.encodeByteString(buf,binaryStream);
binaryStream.length.should.equal(256+4);
var nodeId = ec.makeNodeId(545889, 2500);
nodeId.identifierType.should.eql(ec.NodeIdType.NUMERIC);
test_encode_decode(
nodeId,
ec.encodeNodeId,
ec.decodeNodeId,
7
);
});
binaryStream.rewind();
var check_buf = ec.decodeByteString(binaryStream);
check_buf.length.should.equal(buf.length);
it("should encode and decode a String NodeId", function () {
var nodeId = ec.makeNodeId("SomeStuff", 2500);
nodeId.identifierType.should.eql(ec.NodeIdType.STRING);
test_encode_decode(
nodeId,
ec.encodeNodeId,
ec.decodeNodeId,
4 + 9 + 2 + 1
);
});
it("should encode a two byte NodeId" ,function() {
// standard binary encoding
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
xit("should encode and decode a Guid NodeId", function () {
// todo
});
var value = ec.makeNodeId(25);
ec.encodeNodeId(value,binaryStream);
it("should encode and decode a Opaque NodeId", function () {
binaryStream.length.should.equal(2);
binaryStream.stream.readUInt8(0).should.equal(0);
binaryStream.stream.readUInt8(1).should.equal(25);
var value = Buffer(32);
for(var i= 0; i< 32;i++) { value.writeUInt8(i,i); }
var nodeId = ec.makeNodeId(value, 0x1BCD);
nodeId.identifierType.should.equal(ec.NodeIdType.BYTESTRING);
var expectedLength = 1+ 2 + 4 + 32;
test_encode_decode(nodeId, ec.encodeNodeId, ec.decodeNodeId, expectedLength, function (buffer) {
// cod
buffer.readUInt8(0).should.equal(0x05);
// namespace
buffer.readUInt8(1).should.equal(0xCD);
buffer.readUInt8(2).should.equal(0x1B);
// size
buffer.readUInt32LE(3).should.equal(32);
buffer.readUInt8( 7).should.equal(0x00);
buffer.readUInt8( 8).should.equal(0x01);
buffer.readUInt8( 9).should.equal(0x02);
buffer.readUInt8(10).should.equal(0x03);
buffer.readUInt8(11).should.equal(0x04);
buffer.readUInt8(12).should.equal(0x05);
// ...
buffer.readUInt8(38).should.equal( 31);
});
});
it("should encode a four byte NodeId" ,function() {
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
var value = ec.makeNodeId(258);
ec.encodeNodeId(value,binaryStream);
it("should encode and decode a Expanded NodeId - TwoBytes", function () {
binaryStream.length.should.equal(4);
binaryStream.stream.readUInt8(0).should.equal(1);
binaryStream.stream.readUInt8(1).should.equal(0); // namespace
binaryStream.stream.readUInt16LE(2).should.equal(258);
test_encode_decode(
ec.makeExpandedNodeId(10),
ec.encodeExpandedNodeId,
ec.decodeExpandedNodeId,
2
);
});
it("should encode and decode a Expanded NodeId - FourBytes", function () {
test_encode_decode(
ec.makeExpandedNodeId(32000),
ec.encodeExpandedNodeId,
ec.decodeExpandedNodeId,
4
);
});
it("should encode a Numeric NodeId" ,function() {
});
it("should encode a long NodeId" ,function() {
// standard binary encoding
var binaryStream = new opcua.BinaryStream();
binaryStream.length.should.equal(0);
});
var value = new ec.makeNodeId("SomeStuff",2500);
ec.encodeNodeId(value,binaryStream);
binaryStream.stream.readUInt8(0).should.equal(3);
describe("check OPCUA Date Type ", function () {
it("should convert date in 2014 ",function(){
var date = new Date(2014,0,1);
var nano = ec.dateToHundredNanoSecondFrom1601(date);
var date2 = ec.hundredNanoSecondFrom1601ToDate(nano);
date2.toString().should.equal(date.toString());
});
it("should encode a Guid NodeId" ,function() {
it("dateToHundredNanoSecondFrom1601 should return 0 for 1st of January 1601",function(){
var date = new Date(Date.UTC(1601,0,1,0,0));
var nano = ec.dateToHundredNanoSecondFrom1601(date);
nano.should.equal(0);
});
it("should encode a Opaque NodeId" ,function() {
it("dateToHundredNanoSecondFrom1601 should return xx nanos for 2st of January 1601",function(){
var date = new Date(Date.UTC(1601,0,2,0,0));
var nano = ec.dateToHundredNanoSecondFrom1601(date);
nano.should.equal(24*60*60*1000*10000);
});
it("should encode a Expanded NodeId" ,function() {
it("hundredNanoSecondFrom1601ToDate and dateToHundredNanoSecondFrom1601 ",function(){
var date = new Date(1789,6,14,19,47);
var nano = ec.dateToHundredNanoSecondFrom1601(date);
var date2 = ec.hundredNanoSecondFrom1601ToDate(nano);
date2.toString().should.equal(date.toString());
});
it("should decode 92c253d3 0cf7ce01 DateTime as Dec 12, 2013 08:36:09.747317000 ", function () {
var buf = new Buffer(8);
buf.writeUInt32BE(0x92c253d3, 0);
buf.writeUInt32BE(0x0cf7ce01, 4);
buf.readUInt8(0).should.equal(0x92);
buf.readUInt8(1).should.equal(0xc2);
buf.readUInt8(2).should.equal(0x53);
buf.readUInt8(7).should.equal(0x01);
var stream = new BinaryStream(buf);
var date = ec.decodeDateTime(stream);
stream.rewind();
ec.encodeDateTime(new Date(2013,11,12,9,36,9),stream);
});
//
// =>
});

@@ -5,4 +5,9 @@ var factories = require("../lib/factories");

var util = require("util");
var ec = require("../lib/encode_decode");
var encode_decode_round_trip_test = require("./utils/encode_decode_round_trip_test").encode_decode_round_trip_test;
var Person_Description = {
id: factories.next_available_id(),
name: "Person",

@@ -18,3 +23,3 @@ fields: [

var Employee_Description = {
id: factories.next_available_id(),
name: "Employee",

@@ -28,9 +33,57 @@ fields: [

var Company_Description = {
id: factories.next_available_id(),
name: "Company",
fields: [
{ name: "name", fieldType: "String" },
{ name: "employees", isArray: true, fieldType: "Employee" }
]
};
var Person = factories.UAObjectFactoryBuild(Person_Description);
var Employee = factories.UAObjectFactoryBuild(Employee_Description);
var Company = factories.UAObjectFactoryBuild(Company_Description);
describe("testing object factory", function () {
var ShapeType = factories.UAObjectFactoryBuild( {
name: "EnumShapeType",
isEnum: true,
enumValues: {
CIRCLE: 1,
SQUARE: 2,
RECTANGLE: 3,
HEXAGON: 6
}
});
var Color = factories.UAObjectFactoryBuild( {
name: "EnumColor",
isEnum: true,
enumValues: {
RED: 100,
BLUE: 200,
GREEN: 300
}
});
var Shape = factories.UAObjectFactoryBuild({
id: factories.next_available_id(),
name: "Shape",
fields: [
{ name:"name", fieldType: "String" , defaultValue: function() { return "my shape";} },
{ name:"shapeType", fieldType: "EnumShapeType" },
{ name:"color", fieldType: "EnumColor", defaultValue: Color.GREEN },
{ name:"inner_color", fieldType: "EnumColor", defaultValue: function() { return Color.BLUE; }}
]
});
describe("Factories: testing object factory", function () {
it("should construct a new object from a simple Class Description", function () {

@@ -75,6 +128,4 @@

it("should encode and decode a object created from the Factory",function(){
it("should encode and decode a simple object created from the Factory",function(){
var Person = factories.UAObjectFactoryBuild(Person_Description);
var person = new Person({lastName:"Joe"});

@@ -84,10 +135,4 @@ person.age = 50;

var stream = new BinaryStream();
person.encode(stream);
var person_reloaded = encode_decode_round_trip_test(person);
stream.rewind();
var person_reloaded = new Person();
person_reloaded.decode(stream);
person.lastName.should.equal(person_reloaded.lastName);

@@ -97,3 +142,33 @@ person.age.should.equal(person_reloaded.age);

});
it("should encode and decode a composite object created from the Factory",function(){
var employee = new Employee({ person: { lastName: "John"}, service: "R&D" });
encode_decode_round_trip_test(employee);
});
it("should encode and decode a composite object containing an array",function(){
var company = new Company({name: "ACME" });
company.employees.length.should.equal(0);
var employee = new Employee({ person: { lastName: "John"}, service: "R&D" });
company.employees.push(employee);
company.employees.push(new Employee({ person: { lastName: "Peter"}, service: "R&D" }));
company.employees.length.should.equal(2);
encode_decode_round_trip_test(company);
});
it("should handle subtype properly",function(){

@@ -110,2 +185,3 @@

name: "MyStruct",
id: factories.next_available_id(),
fields: [

@@ -120,19 +196,6 @@ { name: "value", fieldType: "MyInteger" }

});
it('should handle enumeration properly',function(){
var ShapeType = factories.UAObjectFactoryBuild( {
name: "EnumShapeType",
isEnum: true,
enumValues: {
CIRCLE: 1,
SQUARE: 2,
RECTANGLE: 3
}
});
var Shape = factories.UAObjectFactoryBuild({
name: "Shape",
fields: [
{ name:"shapeType" , fieldType: "EnumShapeType" }
]
});

@@ -142,3 +205,7 @@ var shape = new Shape();

shape.shapeType.should.eql(ShapeType.CIRCLE);
shape.name.should.eql("my shape");
shape.color.should.eql(Color.GREEN);
shape.inner_color.should.eql(Color.BLUE);
shape.shapeType = ShapeType.RECTANGLE;

@@ -152,7 +219,26 @@ shape.shapeType.should.equal(ShapeType.RECTANGLE);

});
it('should allow enumeration value to be passed in options during construction',function(){
var shape1 = new Shape({ shapeType: ShapeType.HEXAGON});
shape1.shapeType.should.eql(ShapeType.HEXAGON);
var shape2 = new Shape({ shapeType: ShapeType.RECTANGLE});
shape2.shapeType.should.eql(ShapeType.RECTANGLE);
});
it("should encode and decode a structure containing a enumeration properly",function(){
var shape = new Shape({name: "yo" , shapeType: ShapeType.HEXAGON , color: Color.BLUE });
encode_decode_round_trip_test(shape);
});
});
describe("testing strong typed enums", function(){
it('installEnumProp should create a strong typed enum',function(){
describe("Factories: testing strong typed enums", function(){
it('installEnumProp should append a member as a strong typed enum',function(){
var ShapeType = factories.UAObjectFactoryBuild( {

@@ -182,3 +268,107 @@ name: "EnumShapeType",

});
});
describe("Factories: testing binaryStoreSize",function(){
it("should implement binaryStoreSize",function(){
var shape = new Shape();
shape.binaryStoreSize().should.be.greaterThan(10);
});
});
describe("Factories: testing encodingDefaultBinary and constructObject",function(){
it("a factory object should have a encodingDefaultBinary",function(){
var company = new Company({name: "ACME"});
company.encodingDefaultBinary.should.eql(ec.makeExpandedNodeId(Company_Description.id));
});
it("should create a object from a encodingDefaultBinaryId", function() {
var getObjectClassName = require("../lib/utils").getObjectClassName;
var obj = factories.constructObject(ec.makeExpandedNodeId(Company_Description.id));
should(obj).have.property("_description");
obj._description.name.should.equal("Company");
getObjectClassName(obj).should.equal("Object");
});
it("should encode and decode a Object containing ByteString",function(done){
var Blob_Description = {
id: factories.next_available_id(),
name: "FakeBlob",
fields: [
{ name: "name", fieldType: "String" },
{ name: "buffer0", fieldType: "ByteString" },
{ name: "buffer1", fieldType: "ByteString" }
]
};
var Blob = factories.UAObjectFactoryBuild(Blob_Description);
var blob = new Blob({ buffer0: new Buffer(0), buffer1: new Buffer(1024) });
encode_decode_round_trip_test(blob);
done();
});
it("should pretty print an object ",function(){
var company = new Company({name: "ACME" });
var employee = new Employee({ person: { lastName: "John"}, service: "R&D" });
company.employees.push(employee);
company.employees.push(new Employee({ person: { lastName: "Peter"}, service: "R&D" }));
var str = company.explore();
// console.log(str);
});
it("should handle ExtensionObject ",function(){
var MetaShape_Description = {
id: factories.next_available_id(),
fields: [
{ name: "name", fieldType: "String" },
{ name: "shape", fieldType: "ExtensionObject" },
{ name: "comment", fieldType: "String" }
]
};
var MetaShape = factories.UAObjectFactoryBuild(MetaShape_Description);
var shape = new MetaShape({
name: "MyCircle",
shape: new Shape({name: "circle" , shapeType:ShapeType.CIRCLE , color: Color.BLUE}),
comment: "this is a comment"
});
shape.encodingDefaultBinary.should.eql(ec.makeExpandedNodeId(MetaShape_Description.id));
var stream = new BinaryStream(shape.binaryStoreSize());
shape.encode(stream);
var encode_decode_round_trip_test = require("./utils/encode_decode_round_trip_test").encode_decode_round_trip_test;
encode_decode_round_trip_test(shape);
});
});

@@ -1,3 +0,3 @@

opcua = require("../lib/nodeopcua");
var opcua = require("../lib/nodeopcua");
var BinaryStream = require("../lib/binaryStream").BinaryStream;
var should = require("should");

@@ -13,9 +13,7 @@

var stream = new opcua.BinaryStream(2000);
opcua.encodeMessage('HEL',helloMessage1,stream);
var message = opcua.packTcpMessage('HEL',helloMessage1);
//xx console.log(helloMessage1);
stream.rewind();
var stream = new BinaryStream(message);

@@ -34,1 +32,23 @@ var helloMessage2 = opcua.decodeMessage(stream,opcua.HelloMessage);

});
describe("testing parseEndpointUrl",function(){
it(" should parse a endpoint ",function(){
var ep = opcua.parseEndpointUrl("opc.tcp://abcd1234:51210/UA/SampleServer");
ep.protocol.should.equal("opc.tcp");
ep.hostname.should.equal("abcd1234");
ep.port.should.equal(51210);
ep.address.should.equal("/UA/SampleServer");
});
it(" should parse this endpoint as well",function(){
var ep = opcua.parseEndpointUrl("opc.tcp://ABCD12354:51210/UA/SampleServer");
ep.protocol.should.equal("opc.tcp");
ep.hostname.should.equal("ABCD12354");
ep.port.should.equal(51210);
ep.address.should.equal("/UA/SampleServer");
});
});

@@ -1,34 +0,136 @@

nodeopcua = require("../lib/nodeopcua");
OPCUAServer = require("../lib/opcua-server").OPCUAServer;
OPCUAClient = require("../lib/opcua-client").OPCUAClient;
var OPCUAServer = require("../lib/opcua-server").OPCUAServer;
var OPCUAClient = require("../lib/opcua-client").OPCUAClient;
var should = require("should");
var async = require("async");
var util = require("util");
var opcua = require("../lib/nodeopcua");
var debugLog = require("../lib/utils").make_debugLog(__filename);
function is_valid_endpointUrl(endpointUrl) {
var e = opcua.parseEndpointUrl(endpointUrl);
if (e.hasOwnProperty("hostname")) {
return true;
}
return false;
}
describe("testing basic Client-Server communication",function() {
var server , client;
var endpointUrl ;
beforeEach(function(done){
server = new OPCUAServer();
// we will connect to first server end point
endpointUrl = server.endpoints[0].endpointDescription().endpointUrl;
debugLog("endpointUrl",endpointUrl);
is_valid_endpointUrl(endpointUrl).should.equal(true);
server.start(function() {
done();
});
client = new OPCUAClient();
});
afterEach(function(done){
server.shutdown(function() {
done();
});
});
it("should start a server and accept a connection",function(done){
var server = new OPCUAServer();
server.listen(8345);
server.connected_client_count.should.equal(0);
var client = new OPCUAClient();
client.protocolVersion = 1;
client.connect("localhost",8345, function callback(){
async.series([
function(callback) {
client.connect(endpointUrl,callback);
},
function(callback) {
client.disconnect(callback);
},
function(callback) {
server.shutdown(callback);
}
],done);
server.connected_client_count.should.equal(1);
});
process.nextTick(function() {
client.disconnect(function() {
server.shutdown(function() {
done();
})
})
});
it("Server should not accept connection, if protocol version is incompatible",function(done){
client.protocolVersion = 55555; // set a invalid protocol version
server.connected_client_count.should.equal(0);
async.series([
function(callback) {
debugLog(" connect");
client.connect(endpointUrl,function(err){
debugLog(" Error =".yellow.bold,err);
callback(err);
});
},
function(callback) {
server.shutdown(callback);
}
],function(err) {
server.connected_client_count.should.equal(0);
debugLog(" error : ", err);
server.shutdown(done);
});
});
it("Client shall be able to create a session with a anonymous token",function(done){
server.connected_client_count.should.equal(0);
var g_session ;
async.series([
function(callback) {
debugLog(" connect");
client.connect(endpointUrl,function(err){
debugLog(" Error =".yellow.bold,err);
callback(err);
});
},
function(callback) {
debugLog(" createSession");
client.createSession(function(err,session){
g_session = session;
debugLog(" Error =".yellow.bold,err);
client.transactionInProgress.should.equal(false);
callback(err);
});
client.transactionInProgress.should.equal(true);
},
function(callback) {
debugLog("closing session");
g_session.close(callback);
},
function(callback) {
debugLog("Disconnecting client");
client.disconnect(callback);
}
],function(err) {
debugLog("finally");
server.connected_client_count.should.equal(0);
debugLog(" error : ", err);
done();
});
});
});

@@ -5,2 +5,3 @@ var should = require("should");

var opcua = require("../lib/nodeopcua");
var BinaryStream = require("../lib/binaryStream").BinaryStream;

@@ -16,9 +17,12 @@ describe("testing OPCUA structures ",function() {

ltext.locale.should.equal("en-US");
});
it("should encode and decode a LocalizeText" , function() {
it("should encode and decode a LocalizeText that have both text and locale" , function() {
var ltext = new s.LocalizedText({text: "HelloWorld" , locale: "en-US"});
var stream = new opcua.BinaryStream();
var stream = new BinaryStream();
stream.length.should.equal(0);

@@ -37,2 +41,3 @@

ltext_verif.text.should.equal("HelloWorld");
ltext_verif.locale.should.equal("en-US");

@@ -42,2 +47,53 @@

it("should encode and decode a LocalizeText that have text but no locale" , function() {
var ltext = new s.LocalizedText({text: "HelloWorld" , locale: null });
ltext.should.have.property("locale");
should(ltext.locale).equal(null);
var stream = new BinaryStream();
stream.length.should.equal(0);
ltext.encode(stream);
stream.length.should.be.greaterThan(0);
var ltext_verif = new s.LocalizedText();
stream.rewind();
ltext_verif.decode(stream);
ltext_verif.text.should.equal("HelloWorld");
should(ltext_verif.locale).equal(null);
});
it("should encode and decode a LocalizeText that have no text but a locale" , function() {
var ltext = new s.LocalizedText({text: null , locale: "en-US" });
ltext.should.have.property("text");
should(ltext.text).equal(null);
var stream = new BinaryStream();
stream.length.should.equal(0);
ltext.encode(stream);
stream.length.should.be.greaterThan(0);
var ltext_verif = new s.LocalizedText();
stream.rewind();
ltext_verif.decode(stream);
ltext_verif.should.eql(ltext);
ltext_verif.locale.should.equal("en-US");
ltext_verif.should.have.property("text");
should(ltext_verif.text).equal(null);
});
it("should create a RequestHeader",function(){

@@ -44,0 +100,0 @@

var should = require("should");
var secure_channel = require("../lib/secure_channel_service");
var MessageBuilder = require("../lib/message_builder").MessageBuilder;
var s = require("../lib/structures");
var async = require("async");
var util = require("util");
var compare_buffers = require("./../lib/utils").compare_buffers;
var debugLog = require("../lib/utils").make_debugLog(__filename);
var clone_buffer = secure_channel.clone_buffer;
var MessageChunker = secure_channel.MessageChunker;
describe("SecureMessageChunkManager",function(){
it("should reconstruct a valid message when message is received in multiple chunks",function(done){
// a very large endPointResponse spanning on multiple chunks ...
var endPointResponse = require("./fixtures/fixture_GetEndPointResponse").fixture2;
var requestId = 0x1000;
var chunk_stack = [];
var fullBufferForVerif = null;
async.series([
function (callback) {
var options = {
requestId: requestId,
tokenId: 1
};
endPointResponse.responseHeader.requestHandle = requestId;
var chunker = new MessageChunker();
chunker.chunkSecureMessage("MSG",options,endPointResponse,function(messageChunk) {
if (messageChunk) {
chunk_stack.push(clone_buffer(messageChunk));
} else {
fullBufferForVerif = secure_channel.fullbuf;
callback();
}
});
},
function (callback) {
// let verify that each intermediate chunk is marked with "C" and final chunk is marked with "F"
for (var i = 0; i<chunk_stack.length -1;i++) {
String.fromCharCode(chunk_stack[i].readUInt8(3)).should.equal("C");
}
String.fromCharCode(chunk_stack[i].readUInt8(3)).should.equal("F");
callback();
},
function (callback) {
chunk_stack.length.should.be.greaterThan(0);
// now apply the opposite operation by reconstructing the message from chunk and
// decoding the inner object
// console.log(" message Builder");
var messageBuilder = new MessageBuilder();
messageBuilder.on("full_message_body",function(full_message_body){
compare_buffers(fullBufferForVerif,full_message_body,40);
}).on("message",function(message) {
//xx console.log("message = ", util.inspect(message));
message.should.eql(endPointResponse);
// check also that requestId has been properly installed by chunkSecureMessage
callback();
}).on("error",function(errCode){
callback(new Error("Error : code 0x"+ errCode.toString(16)));
});
// feed messageBuilder with
chunk_stack.forEach(function(chunk){
// let simulate a real TCP communication
// where our messageChunk would be split into several packages....
var l1 = Math.round(chunk.length/3); // arbitrarily split into 2 packets : 1/3 and 2/3
// just for testing the ability to reassemble data block
var data1 = chunk.slice(0,l1);
var data2 = chunk.slice(l1);
messageBuilder.feed(data1);
messageBuilder.feed(data2);
});
}
],done);
});
it("should receive an ERR message",function(done){
var messageBuilder = new MessageBuilder();
messageBuilder.on("full_message_body",function(full_message_body){
debugLog(" On raw Buffer \n" );
debugLog(require("../lib/utils").hexDump(full_message_body));
}).on("message",function(message) {
debugLog(" message ", message);
done();
}).on("error",function(errCode){
debugLog(" errCode ", errCode);
done();
});
var makebuffer_from_trace = require("./utils/makebuffer_from_trace").makebuffer_from_trace;
var packet =makebuffer_from_trace(function(){
/*
00000000: 4d 53 47 46 64 00 00 00 0c 00 00 00 01 00 00 00 04 00 00 00 03 00 00 00 01 00 8d 01 00 00 00 00 MSGFd...........................
00000020: 00 00 00 00 00 00 00 00 00 00 82 80 24 00 00 00 00 00 00 00 80 01 00 00 00 24 00 00 00 55 6e 65 ............$............$...Une
00000040: 78 70 65 63 74 65 64 20 65 72 72 6f 72 20 70 72 6f 63 65 73 73 69 6e 67 20 72 65 71 75 65 73 74 xpected.error.processing.request
00000060: 2e 00 00 00 ....
*/
});
messageBuilder.feed(packet);
});
});

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc