Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ following services are already supported at this point in time:
| Create Object | yes¹ | yes¹ |
| Delete Object | yes¹ | yes¹ |
| Subscribe COV | yes¹ | yes¹ |
| Confirmed COV Notification | yes¹ | yes¹ |
| Subscribe Property | yes¹ | yes¹ |
| Atomic Read File | yes¹ | yes¹ |
| Atomic Write File | yes¹ | yes¹ |
Expand All @@ -56,6 +57,8 @@ following services are already supported at this point in time:
| Unconfirmed Event Notification | yes¹ | yes¹ |
| Unconfirmed Private Transfer | yes¹ | yes¹ |
| Confirmed Private Transfer | yes¹ | yes¹ |
| Register Foreign Device | no | yes¹ |
| Distribute Broadcast to Network| no | yes¹ |

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

Expand Down
15 changes: 15 additions & 0 deletions lib/apdu.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

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

const getDecodedType = module.exports.getDecodedType = (buffer, offset) => {
Expand Down Expand Up @@ -153,6 +154,20 @@ module.exports.decodeSegmentAck = (buffer, offset) => {
};
};

module.exports.encodeResult = (buffer, /* BvlcResultFormat */ resultCode) => {
baAsn1.encodeUnsigned(buffer, resultCode, 2);
};

module.exports.decodeResult = (buffer, offset) => {
const orgOffset = offset;
const decode = baAsn1.decodeUnsigned(buffer, offset, 2);
offset += decode.len;
return {
len: offset - orgOffset,
resultCode: decode.value, // BvlcResultFormat
};
};

module.exports.encodeError = (buffer, type, service, invokeId) => {
buffer.buffer[buffer.offset++] = type;
buffer.buffer[buffer.offset++] = invokeId;
Expand Down
6 changes: 4 additions & 2 deletions lib/asn1.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const getEncodingType = (encoding, decodingBuffer, decodingOffset) => {
}
};

const encodeUnsigned = (buffer, value, length) => {
const encodeUnsigned = module.exports.encodeUnsigned = (buffer, value, length) => {
buffer.buffer.writeUIntBE(value, buffer.offset, length, true);
buffer.offset += length;
};
Expand Down Expand Up @@ -484,8 +484,10 @@ const bacappEncodeApplicationData = module.exports.bacappEncodeApplicationData =
case baEnum.ApplicationTags.READ_ACCESS_SPECIFICATION:
encodeReadAccessSpecification(buffer, value.value);
break;
case undefined:
throw new Error('Cannot encode a value if the type has not been specified');
default:
throw 'Unknown type';
throw 'Unknown ApplicationTags type: ' + baEnum.getEnumName(baEnum.ApplicationTags, value.type);
}
};

Expand Down
55 changes: 48 additions & 7 deletions lib/bvlc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,36 @@

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

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

module.exports.encode = (buffer, func, msgLength, originatingIP) => {
buffer[0] = baEnum.BVLL_TYPE_BACNET_IP;
buffer[1] = func;
buffer[2] = (msgLength & 0xFF00) >> 8;
buffer[3] = (msgLength & 0x00FF) >> 0;
if (originatingIP) {
// This is always a FORWARDED_NPDU regardless of the 'func' parameter.
if (func !== baEnum.BvlcResultPurpose.FORWARDED_NPDU) {
throw new Error('Cannot specify originatingIP unless '
+ 'BvlcResultPurpose.FORWARDED_NPDU is used.');
}
// Encode the IP address and optional port into bytes.
const [ipstr, portstr] = originatingIP.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;
} else {
if (func === baEnum.BvlcResultPurpose.FORWARDED_NPDU) {
throw new Error('Must specify originatingIP if '
+ 'BvlcResultPurpose.FORWARDED_NPDU is used.');
}
}
return baEnum.BVLC_HEADER_LENGTH;
};

Expand All @@ -15,31 +40,47 @@ module.exports.decode = (buffer, offset) => {
const func = buffer[1];
const msgLength = (buffer[2] << 8) | (buffer[3] << 0);
if (buffer[0] !== baEnum.BVLL_TYPE_BACNET_IP || buffer.length !== msgLength) return;
let originatingIP = null;
switch (func) {
case baEnum.BvlcResultPurpose.BVLC_RESULT:
case baEnum.BvlcResultPurpose.ORIGINAL_UNICAST_NPDU:
case baEnum.BvlcResultPurpose.ORIGINAL_BROADCAST_NPDU:
case baEnum.BvlcResultPurpose.DISTRIBUTE_BROADCAST_TO_NETWORK:
len = 4;
break;
case baEnum.BvlcResultPurpose.FORWARDED_NPDU:
len = 10;
break;
case baEnum.BvlcResultPurpose.REGISTER_FOREIGN_DEVICE:
case baEnum.BvlcResultPurpose.READ_FOREIGN_DEVICE_TABLE:
case baEnum.BvlcResultPurpose.DELETE_FOREIGN_DEVICE_TABLE_ENTRY:
case baEnum.BvlcResultPurpose.READ_BROADCAST_DISTRIBUTION_TABLE:
case baEnum.BvlcResultPurpose.WRITE_BROADCAST_DISTRIBUTION_TABLE:
case baEnum.BvlcResultPurpose.READ_BROADCAST_DISTRIBUTION_TABLE_ACK:
case baEnum.BvlcResultPurpose.READ_FOREIGN_DEVICE_TABLE_ACK:
len = 4;
break;
case baEnum.BvlcResultPurpose.FORWARDED_NPDU:
// Work out where the packet originally came from before the BBMD
// forwarded it to us, so we can tell the BBMD where to send any reply to.
const port = (buffer[8] << 8) | buffer[9];
originatingIP = buffer.slice(4, 8).join('.');

// Only add the port if it's not the usual one.
if (port !== DefaultBACnetPort) {
originatingIP += ':' + port;
}

len = 10;
break;
case baEnum.BvlcResultPurpose.SECURE_BVLL:
// unimplemented
return;
default:
return;
}
return {
len: len,
func: func,
msgLength: msgLength
msgLength: msgLength,
// Originating IP is set to the IP address of the node that originally
// sent the packet, when it has been forwarded to us by a BBMD (since the
// BBMD's IP address will be in the sender field.
originatingIP: originatingIP,
};
};
Loading