Skip to content

Commit 71480c2

Browse files
committed
feat(client): add support for interfacing with, and acting as, a BBMD device
1 parent d4cb197 commit 71480c2

File tree

13 files changed

+550
-439
lines changed

13 files changed

+550
-439
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ following services are already supported at this point in time:
5656
| Unconfirmed Event Notification | yes¹ | yes¹ |
5757
| Unconfirmed Private Transfer | yes¹ | yes¹ |
5858
| Confirmed Private Transfer | yes¹ | yes¹ |
59+
| Register Foreign Device | no | yes¹ |
60+
| Distribute Broadcast to Network| no | yes¹ |
5961

6062
¹ Support implemented as Beta (untested, undocumented, breaking interface)
6163

lib/apdu.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
const baAsn1 = require('./asn1');
34
const baEnum = require('./enum');
45

56
const getDecodedType = module.exports.getDecodedType = (buffer, offset) => {
@@ -153,6 +154,20 @@ module.exports.decodeSegmentAck = (buffer, offset) => {
153154
};
154155
};
155156

157+
module.exports.encodeResult = (buffer, /* BvlcResultFormat */ resultCode) => {
158+
baAsn1.encodeUnsigned(buffer, resultCode, 2);
159+
};
160+
161+
module.exports.decodeResult = (buffer, offset) => {
162+
const orgOffset = offset;
163+
const decode = baAsn1.decodeUnsigned(buffer, offset, 2);
164+
offset += decode.len;
165+
return {
166+
len: offset - orgOffset,
167+
resultCode: decode.value, // BvlcResultFormat
168+
};
169+
};
170+
156171
module.exports.encodeError = (buffer, type, service, invokeId) => {
157172
buffer.buffer[buffer.offset++] = type;
158173
buffer.buffer[buffer.offset++] = invokeId;

lib/asn1.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const getEncodingType = (encoding, decodingBuffer, decodingOffset) => {
4646
}
4747
};
4848

49-
const encodeUnsigned = (buffer, value, length) => {
49+
const encodeUnsigned = module.exports.encodeUnsigned = (buffer, value, length) => {
5050
buffer.buffer.writeUIntBE(value, buffer.offset, length, true);
5151
buffer.offset += length;
5252
};

lib/bvlc.js

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,36 @@
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, originatingIP) => {
68
buffer[0] = baEnum.BVLL_TYPE_BACNET_IP;
79
buffer[1] = func;
810
buffer[2] = (msgLength & 0xFF00) >> 8;
911
buffer[3] = (msgLength & 0x00FF) >> 0;
12+
if (originatingIP) {
13+
// This is always a FORWARDED_NPDU regardless of the 'func' parameter.
14+
if (func !== baEnum.BvlcResultPurpose.FORWARDED_NPDU) {
15+
throw new Error('Cannot specify originatingIP unless '
16+
+ 'BvlcResultPurpose.FORWARDED_NPDU is used.');
17+
}
18+
// Encode the IP address and optional port into bytes.
19+
const [ipstr, portstr] = originatingIP.split(':');
20+
const port = parseInt(portstr) || DefaultBACnetPort;
21+
const ip = ipstr.split('.');
22+
buffer[4] = parseInt(ip[0]);
23+
buffer[5] = parseInt(ip[1]);
24+
buffer[6] = parseInt(ip[2]);
25+
buffer[7] = parseInt(ip[3]);
26+
buffer[8] = (port & 0xFF00) >> 8;
27+
buffer[9] = (port & 0x00FF) >> 0;
28+
return 6 + baEnum.BVLC_HEADER_LENGTH;
29+
} else {
30+
if (func === baEnum.BvlcResultPurpose.FORWARDED_NPDU) {
31+
throw new Error('Must specify originatingIP if '
32+
+ 'BvlcResultPurpose.FORWARDED_NPDU is used.');
33+
}
34+
}
1035
return baEnum.BVLC_HEADER_LENGTH;
1136
};
1237

@@ -15,31 +40,47 @@ module.exports.decode = (buffer, offset) => {
1540
const func = buffer[1];
1641
const msgLength = (buffer[2] << 8) | (buffer[3] << 0);
1742
if (buffer[0] !== baEnum.BVLL_TYPE_BACNET_IP || buffer.length !== msgLength) return;
43+
let originatingIP = null;
1844
switch (func) {
1945
case baEnum.BvlcResultPurpose.BVLC_RESULT:
2046
case baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU:
2147
case baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU:
2248
case baEnum.BvlcResultPurpose.DISTRIBUTE_BROADCAST_TO_NETWORK:
23-
len = 4;
24-
break;
25-
case baEnum.BvlcResultPurpose.FORWARDED_NPDU:
26-
len = 10;
27-
break;
2849
case baEnum.BvlcResultPurpose.REGISTER_FOREIGN_DEVICE:
2950
case baEnum.BvlcResultPurpose.READ_FOREIGN_DEVICE_TABLE:
3051
case baEnum.BvlcResultPurpose.DELETE_FOREIGN_DEVICE_TABLE_ENTRY:
3152
case baEnum.BvlcResultPurpose.READ_BROADCAST_DISTRIBUTION_TABLE:
3253
case baEnum.BvlcResultPurpose.WRITE_BROADCAST_DISTRIBUTION_TABLE:
3354
case baEnum.BvlcResultPurpose.READ_BROADCAST_DISTRIBUTION_TABLE_ACK:
3455
case baEnum.BvlcResultPurpose.READ_FOREIGN_DEVICE_TABLE_ACK:
56+
len = 4;
57+
break;
58+
case baEnum.BvlcResultPurpose.FORWARDED_NPDU:
59+
// Work out where the packet originally came from before the BBMD
60+
// forwarded it to us, so we can tell the BBMD where to send any reply to.
61+
const port = (buffer[8] << 8) | buffer[9];
62+
originatingIP = buffer.slice(4, 8).join('.');
63+
64+
// Only add the port if it's not the usual one.
65+
if (port != DefaultBACnetPort) {
66+
originatingIP += ':' + port;
67+
}
68+
69+
len = 10;
70+
break;
3571
case baEnum.BvlcResultPurpose.SECURE_BVLL:
72+
// unimplemented
3673
return;
3774
default:
3875
return;
3976
}
4077
return {
4178
len: len,
4279
func: func,
43-
msgLength: msgLength
80+
msgLength: msgLength,
81+
// Originating IP is set to the IP address of the node that originally
82+
// sent the packet, when it has been forwarded to us by a BBMD (since the
83+
// BBMD's IP address will be in the sender field.
84+
originatingIP: originatingIP,
4485
};
4586
};

0 commit comments

Comments
 (0)