Skip to content

Commit ed90716

Browse files
committed
feat(client): Add support for sending messages as if they are coming from a BBMD
1 parent 3d0a9db commit ed90716

2 files changed

Lines changed: 113 additions & 26 deletions

File tree

lib/bvlc.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,28 @@
22

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

5-
module.exports.encode = (buffer, func, msgLength) => {
5+
const DefaultBACnetPort = 47808;
6+
7+
module.exports.encode = (buffer, func, msgLength, forwardedFrom) => {
68
buffer[0] = baEnum.BVLL_TYPE_BACNET_IP;
7-
buffer[1] = func;
9+
// buffer[1] set below
810
buffer[2] = (msgLength & 0xFF00) >> 8;
911
buffer[3] = (msgLength & 0x00FF) >> 0;
12+
if (forwardedFrom) {
13+
// This is always a FORWARDED_NPDU regardless of the 'func' parameter.
14+
buffer[1] = baEnum.BvlcResultPurpose.FORWARDED_NPDU;
15+
const [ipstr, portstr] = forwardedFrom.split(':');
16+
const port = parseInt(portstr) || DefaultBACnetPort;
17+
const ip = ipstr.split('.');
18+
buffer[4] = parseInt(ip[0]);
19+
buffer[5] = parseInt(ip[1]);
20+
buffer[6] = parseInt(ip[2]);
21+
buffer[7] = parseInt(ip[3]);
22+
buffer[8] = (port & 0xFF00) >> 8;
23+
buffer[9] = (port & 0x00FF) >> 0;
24+
return 6 + baEnum.BVLC_HEADER_LENGTH;
25+
}
26+
buffer[1] = func;
1027
return baEnum.BVLC_HEADER_LENGTH;
1128
};
1229

lib/client.js

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const baEnum = require('./enum');
1515

1616
const DEFAULT_HOP_COUNT = 0xFF;
1717
const BVLC_HEADER_LENGTH = 4;
18+
const BVLC_FWD_HEADER_LENGTH = 10; // FORWARDED_NPDU
1819

1920
/**
2021
* To be able to communicate to BACNET devices, you have to initialize a new bacstack instance.
@@ -91,10 +92,10 @@ class Client extends EventEmitter {
9192
};
9293
}
9394

94-
_getBuffer() {
95+
_getBuffer(isForwarded) {
9596
return {
9697
buffer: Buffer.alloc(this._transport.getMaxPayload()),
97-
offset: BVLC_HEADER_LENGTH
98+
offset: isForwarded ? BVLC_FWD_HEADER_LENGTH : BVLC_HEADER_LENGTH
9899
};
99100
}
100101

@@ -797,13 +798,15 @@ class Client extends EventEmitter {
797798
const settings = {
798799
maxSegments: options.maxSegments || baEnum.MaxSegmentsAccepted.SEGMENTS_65,
799800
maxApdu: options.maxApdu || baEnum.MaxApduLengthAccepted.OCTETS_1476,
800-
invokeId: options.invokeId || this._getInvokeId()
801+
invokeId: options.invokeId || this._getInvokeId(),
802+
isForwarded: !!options.forwardedFrom,
803+
forwardedFrom: options.forwardedFrom || null,
801804
};
802-
const buffer = this._getBuffer();
805+
const buffer = this._getBuffer(settings.isForwarded);
803806
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE | baEnum.NpduControlBits.EXPECTING_REPLY, address);
804807
baApdu.encodeConfirmedServiceRequest(buffer, baEnum.PduTypes.CONFIRMED_REQUEST, baEnum.ConfirmedServiceChoice.CONFIRMED_COV_NOTIFICATION, settings.maxSegments, settings.maxApdu, settings.invokeId, 0, 0);
805808
baServices.covNotify.encode(buffer, subscribeId, initiatingDeviceId, monitoredObject, lifetime, values);
806-
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
809+
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset, settings.forwardedFrom);
807810
this._transport.send(buffer.buffer, buffer.offset, address);
808811
this._addCallback(settings.invokeId, (err, data) => {
809812
if (err) return next(err);
@@ -1203,63 +1206,130 @@ class Client extends EventEmitter {
12031206
}
12041207

12051208
// Public Device Functions
1206-
readPropertyResponse(receiver, invokeId, objectId, property, value) {
1207-
const buffer = this._getBuffer();
1209+
1210+
/**
1211+
* The readPropertyResponse call sends a response with information about one of our properties.
1212+
* @function bacstack.readPropertyResponse
1213+
* @param {string} receiver - IP address of the target device.
1214+
* @param {number} invokeId - ID of the original readProperty request.
1215+
* @param {object} objectId - objectId from the original request,
1216+
* @param {object} property - property being read, taken from the original request.
1217+
* @param {object=} options varying behaviour for special circumstances
1218+
* @param {string=} options.forwardedFrom - If functioning as a BBMD, the IP address this message originally came from.
1219+
*/
1220+
readPropertyResponse(receiver, invokeId, objectId, property, value, options = {}) {
1221+
const settings = {
1222+
isForwarded: !!options.forwardedFrom,
1223+
forwardedFrom: options.forwardedFrom || null,
1224+
};
1225+
1226+
const buffer = this._getBuffer(settings.isForwarded);
12081227
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
12091228
baApdu.encodeComplexAck(buffer, baEnum.PduTypes.COMPLEX_ACK, baEnum.ConfirmedServiceChoice.READ_PROPERTY, invokeId);
12101229
baServices.readProperty.encodeAcknowledge(buffer, objectId, property.id, property.index, value);
1211-
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
1230+
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset, settings.forwardedFrom);
12121231
this._transport.send(buffer.buffer, buffer.offset, receiver);
12131232
}
12141233

1215-
readPropertyMultipleResponse(receiver, invokeId, values) {
1216-
const buffer = this._getBuffer();
1234+
readPropertyMultipleResponse(receiver, invokeId, values, options = {}) {
1235+
const settings = {
1236+
isForwarded: !!options.forwardedFrom,
1237+
forwardedFrom: options.forwardedFrom || null,
1238+
};
1239+
1240+
const buffer = this._getBuffer(settings.isForwarded);
12171241
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
12181242
baApdu.encodeComplexAck(buffer, baEnum.PduTypes.COMPLEX_ACK, baEnum.ConfirmedServiceChoice.READ_PROPERTY_MULTIPLE, invokeId);
12191243
baServices.readPropertyMultiple.encodeAcknowledge(buffer, values);
1220-
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
1244+
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset, settings.forwardedFrom);
12211245
this._transport.send(buffer.buffer, buffer.offset, receiver);
12221246
}
12231247

1224-
iAmResponse(deviceId, segmentation, vendorId) {
1225-
const buffer = this._getBuffer();
1226-
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, this._transport.getBroadcastAddress());
1248+
/**
1249+
* The iAmResponse command is sent as a reply to a whoIs request.
1250+
* @function bacstack.iAmResponse
1251+
* @param {number} deviceId - Our device ID.
1252+
* @param {number} segmentation - an enum.Segmentation value.
1253+
* @param {number} vendorId - The numeric ID assigned to the organisation providing this application.
1254+
* @param {object=} options varying behaviour for special circumstances
1255+
* @param {string=} options.forwardedFrom - If functioning as a BBMD, the IP address this message originally came from.
1256+
* @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.
1257+
* @param {number=} options.hops - Number of hops until packet should be dropped, default 255.
1258+
*/
1259+
iAmResponse(deviceId, segmentation, vendorId, options) {
1260+
const settings = {
1261+
isForwarded: !!options.forwardedFrom,
1262+
forwardedFrom: options.forwardedFrom || null,
1263+
receiver: options.receiver || this._transport.getBroadcastAddress(),
1264+
hops: options.hops || DEFAULT_HOP_COUNT,
1265+
};
1266+
1267+
const buffer = this._getBuffer(settings.isForwarded);
1268+
baNpdu.encode(
1269+
buffer,
1270+
baEnum.NpduControlPriority.NORMAL_MESSAGE,
1271+
settings.receiver,
1272+
undefined,
1273+
settings.hops
1274+
);
12271275
baApdu.encodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.I_AM);
12281276
baServices.iAmBroadcast.encode(buffer, deviceId, this._transport.getMaxPayload(), segmentation, vendorId);
1229-
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU, buffer.offset);
1230-
this._transport.send(buffer.buffer, buffer.offset, this._transport.getBroadcastAddress());
1277+
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU, buffer.offset, settings.forwardedFrom);
1278+
this._transport.send(buffer.buffer, buffer.offset, settings.receiver);
12311279
}
12321280

1233-
iHaveResponse(deviceId, objectId, objectName) {
1234-
const buffer = this._getBuffer();
1281+
iHaveResponse(deviceId, objectId, objectName, options = {}) {
1282+
const settings = {
1283+
isForwarded: !!options.forwardedFrom,
1284+
forwardedFrom: options.forwardedFrom || null,
1285+
};
1286+
1287+
const buffer = this._getBuffer(settings.isForwarded);
12351288
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, this._transport.getBroadcastAddress());
12361289
baApdu.EecodeUnconfirmedServiceRequest(buffer, baEnum.PduTypes.UNCONFIRMED_REQUEST, baEnum.UnconfirmedServiceChoice.I_HAVE);
12371290
baServices.EncodeIhaveBroadcast(buffer, deviceId, objectId, objectName);
12381291
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU, buffer.offset);
12391292
this._transport.send(buffer.buffer, buffer.offset, this._transport.getBroadcastAddress());
12401293
}
12411294

1242-
simpleAckResponse(receiver, service, invokeId) {
1243-
const buffer = this._getBuffer();
1295+
simpleAckResponse(receiver, service, invokeId, options = {}) {
1296+
const settings = {
1297+
isForwarded: !!options.forwardedFrom,
1298+
forwardedFrom: options.forwardedFrom || null,
1299+
};
1300+
1301+
const buffer = this._getBuffer(settings.isForwarded);
12441302
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
12451303
baApdu.encodeSimpleAck(buffer, baEnum.PduTypes.SIMPLE_ACK, service, invokeId);
1246-
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
1304+
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset, settings.forwardedFrom);
12471305
this._transport.send(buffer.buffer, buffer.offset, receiver);
12481306
}
12491307

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

1257-
errorResponse(receiver, service, invokeId, errorClass, errorCode) {
1258-
const buffer = this._getBuffer();
1322+
errorResponse(receiver, service, invokeId, errorClass, errorCode, options = {}) {
1323+
const settings = {
1324+
isForwarded: !!options.forwardedFrom,
1325+
forwardedFrom: options.forwardedFrom || null,
1326+
};
1327+
1328+
const buffer = this._getBuffer(settings.isForwarded);
12591329
baNpdu.encode(buffer, baEnum.NpduControlPriority.NORMAL_MESSAGE, receiver);
12601330
baApdu.encodeError(buffer, baEnum.PduTypes.ERROR, service, invokeId);
12611331
baServices.error.encode(buffer, errorClass, errorCode);
1262-
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset);
1332+
baBvlc.encode(buffer.buffer, baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU, buffer.offset, settings.forwardedFrom);
12631333
this._transport.send(buffer.buffer, buffer.offset, receiver);
12641334
}
12651335

0 commit comments

Comments
 (0)