Skip to content

Commit a8539bd

Browse files
EveGunrobertsLando
andauthored
fix: decode getEventInformation ack and allow optional object filter (#68)
## Summary This PR consolidates event/alarm fixes in `getEventInformation`: - decode ACK payload using the correct decoder path - support optional `objectId` filter (`null` allowed) - harden decode loop for tagged timestamps and closing-tag termination ## Problem Event/alarm handling could fail or return incomplete data because: - ACK payload decode path was incorrect - calls without object filter were not supported - decoder could break on valid tagged timestamp/closing-tag patterns ## Changes - `src/lib/client.ts` - updated `getEventInformation(...)` to accept optional `objectId` - encode object-id context tag only when provided - decode response with `GetEventInformation.decodeAcknowledge(...)` - return `result.events` - `src/lib/services/EventInformation.ts` - hardened decoder iteration/termination for tagged timestamp structures ## Impact - More robust `getEventInformation` behavior across BACnet devices - Works with both filtered and unfiltered queries - Improves reliability for event/alarm pipelines downstream ## Compatibility Backward compatible. No public API break. ## Validation Verified with real gateway flow: - event/alarm fetch succeeds without object filter - decoded events are returned consistently - downstream event handling receives expected payload structure --------- Co-authored-by: Daniel Lando <daniel.sorridi@gmail.com>
1 parent 9fb90f1 commit a8539bd

3 files changed

Lines changed: 112 additions & 37 deletions

File tree

src/lib/client.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import ServicesMap, {
1717
DeleteObject,
1818
DeviceCommunicationControl,
1919
EventInformation,
20+
GetEventInformation,
2021
EventNotifyData,
2122
GetEnrollmentSummary,
2223
IAm,
@@ -1776,7 +1777,7 @@ export default class BACnetClient extends TypedEventEmitter<BACnetClientEvents>
17761777
*/
17771778
async getEventInformation(
17781779
receiver: BACNetAddress,
1779-
objectId: BACNetObjectID,
1780+
objectId?: BACNetObjectID | null,
17801781
options: ServiceOptions = {},
17811782
): Promise<BACNetEventInformation[]> {
17821783
const settings: ServiceOptions = {
@@ -1805,23 +1806,25 @@ export default class BACnetClient extends TypedEventEmitter<BACnetClientEvents>
18051806
0,
18061807
0,
18071808
)
1808-
baAsn1.encodeContextObjectId(
1809-
buffer,
1810-
0,
1811-
objectId.type,
1812-
objectId.instance,
1813-
)
1809+
if (objectId) {
1810+
baAsn1.encodeContextObjectId(
1811+
buffer,
1812+
0,
1813+
objectId.type,
1814+
objectId.instance,
1815+
)
1816+
}
18141817
this.sendBvlc(receiver, buffer)
18151818
const data = await this._requestManager.add(settings.invokeId)
1816-
const result = EventInformation.decode(
1819+
const result = GetEventInformation.decodeAcknowledge(
18171820
data.buffer,
18181821
data.offset,
18191822
data.length,
18201823
)
18211824
if (!result) {
18221825
throw new Error('INVALID_DECODING')
18231826
}
1824-
return result.alarms
1827+
return result.events
18251828
}
18261829

18271830
/**

src/lib/services/EventInformation.ts

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as baAsn1 from '../asn1'
2-
import { ApplicationTag } from '../enum'
32
import { EncodeBuffer, BACNetEvent } from '../types'
43
import { BacnetService } from './AbstractServices'
54

@@ -51,7 +50,10 @@ export default class EventInformation extends BacnetService {
5150
len++
5251
const alarms = []
5352

54-
while (apduLen - 3 - len > 0) {
53+
while (
54+
len < apduLen &&
55+
!baAsn1.decodeIsClosingTagNumber(buffer, offset + len, 0)
56+
) {
5557
const value: any = {}
5658

5759
result = baAsn1.decodeTagNumberAndValue(buffer, offset + len)
@@ -88,31 +90,21 @@ export default class EventInformation extends BacnetService {
8890
value.eventTimeStamps = []
8991

9092
for (let i = 0; i < 3; i++) {
91-
if (result.tagNumber !== ApplicationTag.NULL) {
92-
decodedValue = baAsn1.decodeApplicationDate(
93-
buffer,
94-
offset + len,
95-
)
96-
len += decodedValue.len
97-
const date = decodedValue.value
98-
decodedValue = baAsn1.decodeApplicationTime(
99-
buffer,
100-
offset + len,
101-
)
102-
len += decodedValue.len
103-
const time = decodedValue.value
104-
value.eventTimeStamps[i] = new Date(
105-
date.getFullYear(),
106-
date.getMonth(),
107-
date.getDate(),
108-
time.getHours(),
109-
time.getMinutes(),
110-
time.getSeconds(),
111-
time.getMilliseconds(),
112-
)
113-
} else {
114-
len += result.value
115-
}
93+
decodedValue = baAsn1.decodeApplicationDate(buffer, offset + len)
94+
len += decodedValue.len
95+
const date = decodedValue.value
96+
decodedValue = baAsn1.decodeApplicationTime(buffer, offset + len)
97+
len += decodedValue.len
98+
const time = decodedValue.value
99+
value.eventTimeStamps[i] = new Date(
100+
date.getFullYear(),
101+
date.getMonth(),
102+
date.getDate(),
103+
time.getHours(),
104+
time.getMinutes(),
105+
time.getSeconds(),
106+
time.getMilliseconds(),
107+
)
116108
}
117109

118110
len++

test/unit/client.spec.ts

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import test from 'node:test'
22
import assert from 'node:assert'
33

44
import BACnetClient from '../../src/lib/client'
5-
import { ServicesSupported } from '../../src'
5+
import * as baNpdu from '../../src/lib/npdu'
6+
import * as baApdu from '../../src/lib/apdu'
7+
import { GetEventInformation } from '../../src/lib/services'
8+
import { EventState, NotifyType, ServicesSupported, TimeStamp } from '../../src'
69

710
test.describe('bacnet - client', () => {
811
test('should successfuly encode a bitstring > 32 bits', () => {
@@ -36,4 +39,81 @@ test.describe('bacnet - client', () => {
3639
bitsUsed: 1,
3740
})
3841
})
42+
43+
test('getEventInformation should omit optional objectId when not provided', async () => {
44+
const client = Object.create(BACnetClient.prototype) as BACnetClient & {
45+
_requestManager: { add: (invokeId: number) => Promise<any> }
46+
_getInvokeId: () => number
47+
_getApduBuffer: () => { buffer: Buffer; offset: number }
48+
sendBvlc: (
49+
receiver: { address: string } | null,
50+
buffer: { buffer: Buffer; offset: number },
51+
) => void
52+
}
53+
54+
const response = {
55+
buffer: Buffer.alloc(1482),
56+
offset: 0,
57+
}
58+
GetEventInformation.encodeAcknowledge(
59+
response,
60+
[
61+
{
62+
objectId: { type: 0, instance: 32 },
63+
eventState: EventState.NORMAL,
64+
acknowledgedTransitions: { value: [14], bitsUsed: 6 },
65+
eventTimeStamps: [
66+
{ type: TimeStamp.SEQUENCE_NUMBER, value: 1 },
67+
{ type: TimeStamp.SEQUENCE_NUMBER, value: 2 },
68+
{ type: TimeStamp.SEQUENCE_NUMBER, value: 3 },
69+
],
70+
notifyType: NotifyType.EVENT,
71+
eventEnable: { value: [15], bitsUsed: 7 },
72+
eventPriorities: [2, 3, 4],
73+
},
74+
],
75+
false,
76+
)
77+
const expected = GetEventInformation.decodeAcknowledge(
78+
response.buffer,
79+
0,
80+
response.offset,
81+
)
82+
assert.ok(expected)
83+
84+
let sentRequest: Buffer | undefined
85+
client._getInvokeId = () => 7
86+
client._getApduBuffer = () => ({
87+
buffer: Buffer.alloc(1482),
88+
offset: 4,
89+
})
90+
client.sendBvlc = (_receiver, buffer) => {
91+
sentRequest = Buffer.from(buffer.buffer.subarray(0, buffer.offset))
92+
}
93+
client._requestManager = {
94+
add: async (invokeId: number) => {
95+
assert.strictEqual(invokeId, 7)
96+
return {
97+
buffer: response.buffer,
98+
offset: 0,
99+
length: response.offset,
100+
}
101+
},
102+
}
103+
104+
const events = await client.getEventInformation({
105+
address: '127.0.0.1',
106+
})
107+
108+
assert.ok(sentRequest)
109+
const npdu = baNpdu.decode(sentRequest, 4)
110+
assert.ok(npdu)
111+
const apdu = baApdu.decodeConfirmedServiceRequest(
112+
sentRequest,
113+
4 + npdu.len,
114+
)
115+
const payloadOffset = 4 + npdu.len + apdu.len
116+
assert.strictEqual(payloadOffset, sentRequest.length)
117+
assert.deepStrictEqual(events, expected.events)
118+
})
39119
})

0 commit comments

Comments
 (0)