Skip to content

Fix WritePropertyMultiple decoder to support multiple objects per request#158

Open
imurasawa wants to merge 3 commits into
ela-compil:masterfrom
imurasawa:fix/write-property-multiple
Open

Fix WritePropertyMultiple decoder to support multiple objects per request#158
imurasawa wants to merge 3 commits into
ela-compil:masterfrom
imurasawa:fix/write-property-multiple

Conversation

@imurasawa

Copy link
Copy Markdown

Fix WritePropertyMultiple decoder to support multiple objects per request

Summary

DecodeWritePropertyMultiple in Serialize/Services.cs was only decoding a single object,
despite the BACnet specification defining WritePropertyMultiple as a
SEQUENCE OF WriteAccessSpecifications — meaning a single request can target multiple objects.

This PR fixes the decoder and updates the delegate signature in BACnetClient.cs,
following the same design as ReadPropertyMultiple (RPM), which delegates to
ASN1.decode_read_access_specification. WPM now delegates to the newly added
ASN1.decode_write_access_specification, making the two implementations fully symmetric.


Background

In a production environment, a commercial BMS client sent WritePropertyMultiple requests
containing multiple objects to a BACnet server built with this library. The server failed
to process them.

The issue was isolated using the bacwpm tool from bacnet-stack:

BACNET_IFACE=eth0 ./bacwpm 22 2 1017 85 15 4 50.0 2 1016 85 15 4 40.0
State Result
Before fix Reject: Unrecognized Service
After fix WriteProperty Acknowledged! (SimpleAck)

Root Cause

The BACnet specification (ASHRAE 135) defines the WritePropertyMultiple request PDU as a
SEQUENCE OF WriteAccessSpecifications, allowing multiple objects in a single request.

The existing DecodeWritePropertyMultiple decoded only the first object and ignored
any subsequent ones, causing the request to be rejected.


Changes

Design

Mirrors the RPM design: just as DecodeReadPropertyMultiple delegates to
ASN1.decode_read_access_specification, DecodeWritePropertyMultiple now delegates to
the newly added ASN1.decode_write_access_specification.
This follows the design philosophy of the original bacnet-stack.

Files changed (4 files)

1. Base/BacnetWriteAccessSpecification.cs (new file)

A new struct symmetric to BacnetReadAccessSpecification:

public struct BacnetWriteAccessSpecification
{
    public BacnetObjectId objectIdentifier;
    public ICollection<BacnetPropertyValue> propertyValues;
}

2. Serialize/ASN1.cs

Added decode_write_access_specification immediately after decode_read_access_specification,
using the same variable names (p_ref, propertyIdAndValues) and code style.

3. Serialize/Services.cs

Replaced the single-object decoder with a while loop, symmetric to DecodeReadPropertyMultiple:

public static int DecodeWritePropertyMultiple(BacnetAddress address, byte[] buffer, int offset, int apduLen,
    out ICollection<BacnetWriteAccessSpecification> properties)
{
    var len = 0;
    var values = new List<BacnetWriteAccessSpecification>();
    properties = null;

    while (apduLen - len > 0)
    {
        var tmp = ASN1.decode_write_access_specification(address, buffer, offset + len, apduLen - len, out var value);
        if (tmp < 0) return -1;
        len += tmp;
        values.Add(value);
    }

    properties = values;
    return len;
}

4. BACnetClient.cs

Updated the WritePropertyMultipleRequestHandler delegate signature:

// Before
public delegate void WritePropertyMultipleRequestHandler(
    BacnetClient sender, BacnetAddress adr, byte invokeId,
    BacnetObjectId objectId,
    ICollection<BacnetPropertyValue> values,
    BacnetMaxSegments maxSegments);

// After
public delegate void WritePropertyMultipleRequestHandler(
    BacnetClient sender, BacnetAddress adr, byte invokeId,
    ICollection<BacnetWriteAccessSpecification> properties,
    BacnetMaxSegments maxSegments);

⚠️ Breaking Changes

The signature of WritePropertyMultipleRequestHandler has changed.
Any code subscribing to OnWritePropertyMultipleRequest must be updated:

// Before
client.OnWritePropertyMultipleRequest += (sender, adr, invokeId, objectId, values, maxSegments) =>
{
    // objectId and values received separately
};

// After
client.OnWritePropertyMultipleRequest += (sender, adr, invokeId, properties, maxSegments) =>
{
    foreach (var spec in properties)
    {
        var objectId = spec.objectIdentifier;
        var values   = spec.propertyValues;
        // ...
    }
};

Test Environment

  • Server: Raspberry Pi 4 (Linux)
  • Client: Raspberry Pi 4 (separate device), bacwpm from bacnet-stack
  • Verified: multiple objects in a single WPM request received and acknowledged
  • Verified: concurrent operation with RPM from a commercial BMS client

Notes

Dependency on #157:
This fix was verified on Linux (Raspberry Pi 4). On Linux, WPM packets only reach
the server after the UDP socket fix in #157. The decoding bug itself is OS-independent
and affects Windows as well.

Note on BACnetClient.cs diff:
BACnetClient.cs was also normalized from CRLF to LF (as mentioned in #157).
To see only the functional changes, compare against
imurasawa:fix/normalize-line-endings branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant