Skip to content

Commit

Permalink
feat(client): Add support for sending messages as if they are coming …
Browse files Browse the repository at this point in the history
…from a BBMD
  • Loading branch information
adam-nielsen committed Feb 1, 2019
1 parent 3d0a9db commit ed90716
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 26 deletions.
21 changes: 19 additions & 2 deletions lib/bvlc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,28 @@

const baEnum = require('./enum');

module.exports.encode = (buffer, func, msgLength) => {
const DefaultBACnetPort = 47808;

module.exports.encode = (buffer, func, msgLength, forwardedFrom) => {
buffer[0] = baEnum.BVLL_TYPE_BACNET_IP;
buffer[1] = func;
// buffer[1] set below
buffer[2] = (msgLength & 0xFF00) >> 8;
buffer[3] = (msgLength & 0x00FF) >> 0;
if (forwardedFrom) {
// This is always a FORWARDED_NPDU regardless of the 'func' parameter.
buffer[1] = baEnum.BvlcResultPurpose.FORWARDED_NPDU;
const [ipstr, portstr] = forwardedFrom.split(':');
const port = parseInt(portstr) || DefaultBACnetPort;
const ip = ipstr.split('.');
buffer[4] = parseInt(ip[0]);
buffer[5] = parseInt(ip[1]);
buffer[6] = parseInt(ip[2]);
buffer[7] = parseInt(ip[3]);
buffer[8] = (port & 0xFF00) >> 8;
buffer[9] = (port & 0x00FF) >> 0;
return 6 + baEnum.BVLC_HEADER_LENGTH;
}
buffer[1] = func;
return baEnum.BVLC_HEADER_LENGTH;
};

Expand Down
118 changes: 94 additions & 24 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const baEnum = require('./enum');

const DEFAULT_HOP_COUNT = 0xFF;
const BVLC_HEADER_LENGTH = 4;
const BVLC_FWD_HEADER_LENGTH = 10; // FORWARDED_NPDU

/**
* To be able to communicate to BACNET devices, you have to initialize a new bacstack instance.
Expand Down Expand Up @@ -91,10 +92,10 @@ class Client extends EventEmitter {
};
}

_getBuffer() {
_getBuffer(isForwarded) {
return {
buffer: Buffer.alloc(this._transport.getMaxPayload()),
offset: BVLC_HEADER_LENGTH
offset: isForwarded ? BVLC_FWD_HEADER_LENGTH : BVLC_HEADER_LENGTH
};
}

Expand Down Expand Up @@ -797,13 +798,15 @@ class Client extends EventEmitter {
const settings = {
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
invokeId: options.invokeId || this._getInvokeId()
invokeId: options.invokeId || this._getInvokeId(),
isForwarded: !!options.forwardedFrom,
forwardedFrom: options.forwardedFrom || null,
};
const buffer = this._getBuffer();
const buffer = this._getBuffer(settings.isForwarded);
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.CONFIRMED_COV_NOTIFICATION, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
baServices.covNotify.encode(buffer, subscribeId, initiatingDeviceId, monitoredObject, lifetime, values);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset, settings.forwardedFrom);
this._transport.send(buffer.buffer, buffer.offset, address);
this._addCallback(settings.invokeId, (err, data) => {
if (err) return next(err);
Expand Down Expand Up @@ -1203,63 +1206,130 @@ class Client extends EventEmitter {
}

// Public Device Functions
readPropertyResponse(receiver, invokeId, objectId, property, value) {
const buffer = this._getBuffer();

/**
* The readPropertyResponse call sends a response with information about one of our properties.
* @function bacstack.readPropertyResponse
* @param {string} receiver - IP address of the target device.
* @param {number} invokeId - ID of the original readProperty request.
* @param {object} objectId - objectId from the original request,
* @param {object} property - property being read, taken from the original request.
* @param {object=} options varying behaviour for special circumstances
* @param {string=} options.forwardedFrom - If functioning as a BBMD, the IP address this message originally came from.
*/
readPropertyResponse(receiver, invokeId, objectId, property, value, options = {}) {
const settings = {
isForwarded: !!options.forwardedFrom,
forwardedFrom: options.forwardedFrom || null,
};

const buffer = this._getBuffer(settings.isForwarded);
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
baApdu.encodeComplexAck(buffer, baEnum.PduTypes.COMPLEX_ACK, baEnum.ConfirmedServiceChoice.READ_PROPERTY, invokeId);
baServices.readProperty.encodeAcknowledge(buffer, objectId, property.id, property.index, value);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset, settings.forwardedFrom);
this._transport.send(buffer.buffer, buffer.offset, receiver);
}

readPropertyMultipleResponse(receiver, invokeId, values) {
const buffer = this._getBuffer();
readPropertyMultipleResponse(receiver, invokeId, values, options = {}) {
const settings = {
isForwarded: !!options.forwardedFrom,
forwardedFrom: options.forwardedFrom || null,
};

const buffer = this._getBuffer(settings.isForwarded);
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
baApdu.encodeComplexAck(buffer, baEnum.PduTypes.COMPLEX_ACK, baEnum.ConfirmedServiceChoice.READ_PROPERTY_MULTIPLE, invokeId);
baServices.readPropertyMultiple.encodeAcknowledge(buffer, values);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset, settings.forwardedFrom);
this._transport.send(buffer.buffer, buffer.offset, receiver);
}

iAmResponse(deviceId, segmentation, vendorId) {
const buffer = this._getBuffer();
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, this._transport.getBroadcastAddress());
/**
* The iAmResponse command is sent as a reply to a whoIs request.
* @function bacstack.iAmResponse
* @param {number} deviceId - Our device ID.
* @param {number} segmentation - an enum.Segmentation value.
* @param {number} vendorId - The numeric ID assigned to the organisation providing this application.
* @param {object=} options varying behaviour for special circumstances
* @param {string=} options.forwardedFrom - If functioning as a BBMD, the IP address this message originally came from.
* @param {string=} options.receiver - If functioning as a BBMD, the upstream device to send this message to. By default it is broadcasted to the local subnet, but this can be overridden here. An object like {net: 65535} is also permitted.
* @param {number=} options.hops - Number of hops until packet should be dropped, default 255.
*/
iAmResponse(deviceId, segmentation, vendorId, options) {
const settings = {
isForwarded: !!options.forwardedFrom,
forwardedFrom: options.forwardedFrom || null,
receiver: options.receiver || this._transport.getBroadcastAddress(),
hops: options.hops || DEFAULT_HOP_COUNT,
};

const buffer = this._getBuffer(settings.isForwarded);
baNpdu.encode(
buffer,
baEnum.NpduControlPriority.NORMAL_MESSAGE,
settings.receiver,
undefined,
settings.hops
);
baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.I_AM);
baServices.iAmBroadcast.encode(buffer, deviceId, this._transport.getMaxPayload(), segmentation, vendorId);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, this._transport.getBroadcastAddress());
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU, buffer.offset, settings.forwardedFrom);
this._transport.send(buffer.buffer, buffer.offset, settings.receiver);
}

iHaveResponse(deviceId, objectId, objectName) {
const buffer = this._getBuffer();
iHaveResponse(deviceId, objectId, objectName, options = {}) {
const settings = {
isForwarded: !!options.forwardedFrom,
forwardedFrom: options.forwardedFrom || null,
};

const buffer = this._getBuffer(settings.isForwarded);
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, this._transport.getBroadcastAddress());
baApdu.EecodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.I_HAVE);
baServices.EncodeIhaveBroadcast(buffer, deviceId, objectId, objectName);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, this._transport.getBroadcastAddress());
}

simpleAckResponse(receiver, service, invokeId) {
const buffer = this._getBuffer();
simpleAckResponse(receiver, service, invokeId, options = {}) {
const settings = {
isForwarded: !!options.forwardedFrom,
forwardedFrom: options.forwardedFrom || null,
};

const buffer = this._getBuffer(settings.isForwarded);
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
baApdu.encodeSimpleAck(buffer, baEnum.PduTypes.SIMPLE_ACK, service, invokeId);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset, settings.forwardedFrom);
this._transport.send(buffer.buffer, buffer.offset, receiver);
}

/**
* The resultResponse is a BVLC-Result message used to respond to certain events, such as BBMD registration.
* This message cannot be wrapped for passing through a BBMD, as it is used as a BBMD control message.
* @function bacstack.resultResponse
* @param {string} receiver - IP address of the target device.
* @param {number} resultCode - Single value from BvlcResultFormat enum.
*/
resultResponse(receiver, resultCode) {
const buffer = this._getBuffer();
baApdu.encodeResult(buffer, resultCode);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.BVLC_RESULT, buffer.offset);
this._transport.send(buffer.buffer, buffer.offset, receiver);
}

errorResponse(receiver, service, invokeId, errorClass, errorCode) {
const buffer = this._getBuffer();
errorResponse(receiver, service, invokeId, errorClass, errorCode, options = {}) {
const settings = {
isForwarded: !!options.forwardedFrom,
forwardedFrom: options.forwardedFrom || null,
};

const buffer = this._getBuffer(settings.isForwarded);
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
baApdu.encodeError(buffer, baEnum.PduTypes.ERROR, service, invokeId);
baServices.error.encode(buffer, errorClass, errorCode);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset, settings.forwardedFrom);
this._transport.send(buffer.buffer, buffer.offset, receiver);
}

Expand Down

0 comments on commit ed90716

Please sign in to comment.