diff --git a/.github/workflows/bsc-tests-linux.yml b/.github/workflows/bsc-tests-linux.yml
index 2d6fc4cb0a..f51a459b18 100644
--- a/.github/workflows/bsc-tests-linux.yml
+++ b/.github/workflows/bsc-tests-linux.yml
@@ -3,7 +3,7 @@ name: BACnet/SC linux tests
on:
push:
branches:
- - master
+ - '*'
pull_request:
branches:
- '*'
@@ -89,4 +89,4 @@ jobs:
cd build
cmake ..
make
- ./test_bsc-datalink
\ No newline at end of file
+ ./test_bsc-datalink
diff --git a/.github/workflows/gcc.yml b/.github/workflows/gcc.yml
index 91e6643b30..33a16cdcd9 100644
--- a/.github/workflows/gcc.yml
+++ b/.github/workflows/gcc.yml
@@ -3,7 +3,7 @@ name: GCC
on:
push:
branches:
- - master
+ - '*'
pull_request:
branches:
- '*'
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 729a960bbb..746de13214 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -3,7 +3,7 @@ name: Quality
on:
push:
branches:
- - master
+ - '*'
pull_request:
branches:
- '*'
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index e5dcf22819..8eccf51997 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -3,7 +3,7 @@ name: CMake
on:
push:
branches:
- - master
+ - '*'
pull_request:
branches:
- '*'
diff --git a/.gitignore b/.gitignore
index ebb160150b..3cf2b20987 100644
--- a/.gitignore
+++ b/.gitignore
@@ -113,3 +113,4 @@ apps/piface/libpifacedigital/
*.vcxproj.user
.venv/
+.agents
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71aec1f573..4dbf58bd3e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,103 @@ The git repositories are hosted at the following sites:
*
*
+## [1.5.2] - Unreleased
+
+### Security
+
+* Secured an MS/TP implementation COBS frame decoding buffer overflow,
+ and added unit test for tight buffer handling. (#1425)
+
+### Fixed
+
+## [1.5.1] - 2026-07-04
+
+### Security
+
+* Secured the basic device object which had a string use after free.
+ Added character string buffer stndup and same/diff functions.
+ Changed all the device object character string handling to use
+ character string buffers, where the API set calls use static memory
+ and WriteProperty uses dynamic memory. This improves memory usage
+ and prevents use after free since the character string buffer tracks
+ allocated vs non-allocated strings. (#1375)
+* Secured xy_color_decode() by adjusting apdu_size calculation to prevent
+ out-of-bounds read, and secured network control handler offset calculation
+ in router applications to prevent buffer overrun. (#1386) (#1387)
+* Secured apps/router-mstp and apps/router-ipv6 routing by introducing
+ routed_npdu_apdu_encode() with explicit oversized-PDU drop checks. (#1392)
+* Secured rpm_ack_decode_service_request buffer overflow by validating data
+ length and remaining bytes. Added decoder-path unit tests. (#1395)
+* Secured the basic RAMFS to prevent heap out-of-bounds read during
+ record replacement in AtomicWriteFile record-access handling. (#1408)
+* Secured the basic RAMFS to prevent buffer overrun during consecutive
+ AtomicWriteFile record appends. (#1408)
+* Secured Life Safety Zone member handling for write property. (#1410)
+* Secured RAMFS by checking read-only property for file size setting
+ and zero new memory during realloc to prevent data leaking. (#1411)
+* Secured lighting_command_decode out-of-bounds read, and enforce the
+ FADE_TO and RAMP_TO required levels. (#1412)
+* Secured rpm_decode_object_property by fixing a DoS vulnerability
+ for malformed RPM requests. (#1374)
+* Secured bsc_node_parse_urls() by fixing buffer overflows by using relative
+ lengths and adding array bounds checks before every write. (#1365)
+* Secured apps/epics by preventing a buffer overflow in ProcessRPMData,
+ resolving destination slot for property values. (#1366)
+* Secured address_list_encode() function buffer overrun by using
+ existing BACnetAddressBinding encoding function for length check
+ and refactoring. Added unit test for validation.(#1363)
+* Secured AtomicReadFile/AtomicWriteFile logic and underlying file backends
+ by adding explicit invalid-start-position guards which reject negative
+ and out-of-range positions/records. (#1362)
+* Secured Loop object internal Read_Property function buffer overflow. (#1355)
+* Secured Life Safety Point and Zone Read_Property of accepted-modes property
+ buffer overflow. (#1354)
+* Secured Notification Class AddListElement and RemoveListElement stack
+ based buffer overflow. (#1353)
+* Secured Device ENDRESTORE so that it does not delete existing objects
+ until after the first record is decoded. (#1352)
+* Secured Timer object State_Change_Values property self-reference that
+ caused uncontrolled recursion. (#1347)
+* Secured Channel object member self-reference that caused uncontrolled
+ recursion. Changed Channel property handling to use device object
+ property reference and unsigned value specific decoders. Fixed handling
+ of Write_Status in Channel_Write_Members to properly reflect success
+ or failure of property writes. (#1345)
+* Secured AtomicReadFile-ACK Record-Access Encoder by initializing
+ BACNET_CHARACTER_STRING and OCTET_STRING to prevent uninitialized
+ usage and conditional information disclosure. (#1344)
+* Secured AtomicReadFile and AtomicWriteFile callbacks into bacfile.c
+ by adding null checks and fixing out-of-bounds read/write.(#1344)
+* Secured WriteProperty to Structured View subordinate-list that caused a NULL
+ pointer dereference in bacnet_device_object_reference_decode(). (#1321)
+* Secured AtomicReadFile handler by implementing bounds checks for
+ RecordCount stack based out-of-bounds write. (#1340)
+
+### Fixed
+
+* Fixed off-by-one error in Life_Safety_Point_Read_Property for
+ accepted modes property. (#1349)
+* Fixed WriteProperty handling across the stack by rejecting zero-length
+ application payloads for non-list properties (returning
+ ERROR_CODE_INVALID_TAG) and by adding defensive wp_data == NULL checks
+ in many object *_Write_Property() handlers. (#1337)
+* Fixed access-doors array to be in array list. (#1331)
+* Fixed Network Port object local IPv4 gateway address configuration for
+ Linux/BSD/Windows. (#1335)
+* Fixed lighting command update notifications to use scaled physical
+ values using min/max actual value. (#1315)
+* Fixed lighting command off to off behavior. (#1314)
+* Fixed lighting command refresh logic in trim set functions. (#1313)
+* Fixed EPICS values for recipient list, empty lists, and authentication
+ factors. Fixed EPICS app to allow target MAC or IP address format. (#1310)
+* Fixed coupling between lighting output object and lighting command structure
+ by using locking callbacks in lighting command that are engaged when accessing
+ any of the lighting command structure data. (#1306)
+* Fixed BBMD_Result handling to avoid false positive error message
+ when no registration is requested. (#1305)
+* Fixed Keylist memory allocation check in CheckArraySize and handle
+ node creation failure in Keylist_Data_Add (#1295)
+
## [1.5.0] - 2026-04-16
### Security
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 41c25272c2..d783beb9af 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(
bacnet-stack
- VERSION 1.5.0
+ VERSION 1.5.2
LANGUAGES C)
#
diff --git a/Makefile b/Makefile
index 10c03e5c25..6563023154 100644
--- a/Makefile
+++ b/Makefile
@@ -570,6 +570,10 @@ test:
retest:
$(MAKE) -s -j -C test retest
+.PHONY: unit
+unit:
+ $(MAKE) -s -j -C test unit
+
.PHONY: test-bsc
test-bsc:
$(MAKE) -s -C test clean
diff --git a/SECURITY.md b/SECURITY.md
index d117c4bd0b..a4df1ca15d 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -3,70 +3,237 @@
## Supported Versions
The following versions of the BACnet Stack C library are
-currently being supported with security updates.
+currently being supported with security updates in this branch:
| Version | Supported |
| ------- | ------------------ |
-| 1.4.x | :white_check_mark: |
-| 1.3.x | :white_check_mark: |
-| 1.2.x | :white_check_mark: |
-| 1.1.x | :white_check_mark: |
-| 1.0.x | :white_check_mark: |
+| 1.5.x | :white_check_mark: |
+| 1.4.x | :x: |
+| 1.3.x | :x: |
+| 1.2.x | :x: |
+| 1.1.x | :x: |
+| 1.0.x | :x: |
| 0.9.x | :x: |
-| 0.8.x | :white_check_mark: |
+| 0.8.x | :x: |
| 0.7.x | :x: |
| < 0.6.x | :x: |
-
## Coordinated Vulnerability Disclosure
-From time to time a vulnerability is disclosed to [CVE](https://www.cve.org/)
+Vulnerabilites are disclosed to [CVE](https://www.cve.org/)
+or [GHSA](https://github.com/bacnet-stack/bacnet-stack/security/advisories?state=published)
and a record is created to identify, define, and catalog publicly disclosed
cybersecurity vulnerabilities.
-Here are the known CVE records:
+MS/TP COBS decode overflow
+[GHSA-8456-m9x4-j6mc](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-8456-m9x4-j6mc).
+Patched versions: 1.4.6, 1.5.2, 1.6.1, 1.7.0
+Pull Request: [#1425](https://github.com/bacnet-stack/bacnet-stack/pull/1425).
+
+[CVE-2026-52790](https://www.cve.org/CVERecord?id=CVE-2026-52790) -
+bacnet_device.c stack-use-after-return in writable Device string properties
+[GHSA-jr7p-rm2x-739x](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-jr7p-rm2x-739x).
+Patched versions: 1.5.1
+Pull Request: [#1375](https://github.com/bacnet-stack/bacnet-stack/pull/1375).
+
+[CVE-2026-45341](https://www.cve.org/CVERecord?id=CVE-2026-45341) -
+WriteProperty to Structured View subordinate-list causes NULL pointer dereference
+[GHSA-fv2r-c2m2-7qhh](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-fv2r-c2m2-7qhh)
+Patched versions: 1.5.1
+Pull Request: [#1321](https://github.com/bacnet-stack/bacnet-stack/pull/1321).
+
+Pre-auth OOB read in xy_color_decode (BACnetXYColor) via WriteGroup/WriteProperty
+[GHSA-mmg6-p4pr-cj6h](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-mmg6-p4pr-cj6h).
+Patched versions: 1.5.1
+Pull Request: [#1386](https://github.com/bacnet-stack/bacnet-stack/pull/1386),
+[#1387](https://github.com/bacnet-stack/bacnet-stack/pull/1387).
+
+Remote global-buffer-overflow in apps/router-ipv6/main.c routed APDU forwarding path
+[GHSA-4p4w-m434-jrhj](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-4p4w-m434-jrhj).
+Patched versions: 1.5.1
+Pull Request: [#1392](https://github.com/bacnet-stack/bacnet-stack/pull/1392).
+
+Remote Global-Buffer-Overflow Read in readpropm via Malformed ReadPropertyMultiple-ACK
+[GHSA-3xxw-jfwm-rq9c](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-3xxw-jfwm-rq9c).
+Patched versions: 1.5.1
+Pull Request: [#1395](https://github.com/bacnet-stack/bacnet-stack/pull/1395).
+
+Replacing a RAMFS record with a shorter one via AtomicWriteFile(record-access) can trigger a heap out-of-bounds read
+[GHSA-cf8g-hp9m-9fvv](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-cf8g-hp9m-9fvv).
+Patched versions: 1.5.1
+Pull Request: [#1408](https://github.com/bacnet-stack/bacnet-stack/pull/1408).
+
+Consecutive AtomicWriteFile(record-access) appends can trigger a heap out-of-bounds read in the RAMFS file backend
+[GHSA-32jj-x86x-w98w](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-32jj-x86x-w98w).
+Patched versions: 1.5.1
+Pull Request: [#1408](https://github.com/bacnet-stack/bacnet-stack/pull/1408).
+
+Remote unauthenticated DoS in Life_Safety_Zone PROP_ZONE_MEMBERS WriteProperty parsing
+[GHSA-2c8x-f46r-8phh](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-2c8x-f46r-8phh).
+Patched versions: 1.5.1
+Pull Request: [#1410](https://github.com/bacnet-stack/bacnet-stack/pull/1410).
+
+WriteProperty(File_Size) can bypass read-only protection and expose uninitialized RAMFS tail bytes through AtomicReadFile
+[GHSA-mwj7-2v5r-v934](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-mwj7-2v5r-v934).
+Patched versions: 1.5.1
+Pull Request: [#1411](https://github.com/bacnet-stack/bacnet-stack/pull/1411).
+
+Out-of-bounds read in lighting_command_decode (full APDU size passed to nested tag decoders)
+[GHSA-9hq3-w3pc-8385](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-9hq3-w3pc-8385).
+Patched versions: 1.5.1
+Pull Request: [#1412](https://github.com/bacnet-stack/bacnet-stack/pull/1412).
+
+[CVE-2026-52789](https://www.cve.org/CVERecord?id=CVE-2026-52789) -
+Denial of Service (Infinite Loop) in handler_read_property_multiple via malformed RPM requests.
+[GHSA-4rf9-4vgq-5gcw](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-4rf9-4vgq-5gcw).
+Patched versions: 1.5.1
+Pull Request: [#1374](https://github.com/bacnet-stack/bacnet-stack/pull/1374).
+
+[CVE-2026-52788](https://www.cve.org/CVERecord?id=CVE-2026-52788) -
+Buffer overflows in bsc_node_parse_urls() (BACnet/SC Address Resolution ACK URL parser).
+[GHSA-rf83-3rr5-v4mj](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-rf83-3rr5-v4mj).
+Patched versions: 1.5.1
+Pull Request: [#1365](https://github.com/bacnet-stack/bacnet-stack/pull/1365).
+
+[CVE-2026-52786](https://www.cve.org/CVERecord?id=CVE-2026-52786) -
+apps/epics: malicious ReadPropertyMultiple-ACK with excessive properties causes global-buffer-overflow in ProcessRPMData().
+[GHSA-c4q6-7827-mfg6](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-c4q6-7827-mfg6).
+Patched versions: 1.5.1
+Pull Request: [#1366](https://github.com/bacnet-stack/bacnet-stack/pull/1366),
+[#1409](https://github.com/bacnet-stack/bacnet-stack/pull/1409).
+
+[CVE-2026-52787](https://www.cve.org/CVERecord?id=CVE-2026-52787) -
+Global APDU transmit buffer out-of-bounds write in device.address-binding response encoding via address_list_encode()
+[GHSA-4fgg-fghm-jm43](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-4fgg-fghm-jm43).
+Patched versions: 1.5.1
+Pull Request: [#1363](https://github.com/bacnet-stack/bacnet-stack/pull/1363).
+
+[CVE-2026-49990](https://www.cve.org/CVERecord?id=CVE-2026-49990) -
+AtomicReadFile/AtomicWriteFile stream fileStartPosition validation flaw causes out-of-bounds read/write in RAM file backends.
+[GHSA-8759-hx7g-94qx](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-8759-hx7g-94qx).
+Patched versions: 1.5.1
+Pull Request: [#1362](https://github.com/bacnet-stack/bacnet-stack/pull/1362).
+
+[CVE-2026-47710](https://www.cve.org/CVERecord?id=CVE-2026-47710) -
+Loop reference to long Structured View description causes stack-buffer-overflow.
+[GHSA-2xm5-gjpc-9m6q](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-2xm5-gjpc-9m6q).
+Patched versions: 1.5.1
+Pull Request: [#1355](https://github.com/bacnet-stack/bacnet-stack/pull/1355).
+
+[CVE-2026-47711](https://www.cve.org/CVERecord?id=CVE-2026-47711) -
+Stack buffer overflow in Loop internal ReadProperty path via Life_Safety_Zone accepted-modes property.
+[GHSA-vrpm-9gm2-x552](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-vrpm-9gm2-x552).
+Patched versions: 1.5.1
+Pull Request: [#1354](https://github.com/bacnet-stack/bacnet-stack/pull/1354),
+[#1355](https://github.com/bacnet-stack/bacnet-stack/pull/1355).
+
+[CVE-2026-47259](https://www.cve.org/CVERecord?id=CVE-2026-47259) -
+Stack-based buffer overflow in Notification Class RemoveListElement recipient-list decoding.
+[GHSA-9w9m-w7w5-rrv3](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-9w9m-w7w5-rrv3).
+Patched versions: 1.5.1
+Pull Request: [#1353](https://github.com/bacnet-stack/bacnet-stack/pull/1353).
+
+[CVE-2026-47258](https://www.cve.org/CVERecord?id=CVE-2026-47258) -
+Stack-based buffer overflow in Notification Class AddListElement recipient-list decoding.
+[GHSA-rjmv-3mcm-r83j](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-rjmv-3mcm-r83j).
+Patched versions: 1.5.1
+Pull Request: [#1353](https://github.com/bacnet-stack/bacnet-stack/pull/1353).
+
+[CVE-2026-47257](https://www.cve.org/CVERecord?id=CVE-2026-47257) -
+ReinitializeDevice ENDRESTORE can delete existing objects on an empty restore file and still return success.
+[GHSA-x6pp-3pf3-f87r](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-x6pp-3pf3-f87r).
+Patched versions: 1.5.1
+Pull Request: [#1352](https://github.com/bacnet-stack/bacnet-stack/pull/1352).
+
+[CVE-2026-49341](https://www.cve.org/CVERecord?id=CVE-2026-49341) -
+Uncontrolled recursion in Timer object writeback path leads to remote server stack overflow.
+[GHSA-7r8r-2rj2-5wvr](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-7r8r-2rj2-5wvr).
+Patched versions: 1.5.1
+Pull Request: [#1347](https://github.com/bacnet-stack/bacnet-stack/pull/1347)
+
+[CVE-2026-47217](https://www.cve.org/CVERecord?id=CVE-2026-47217) -
+Channel member self-reference causes uncontrolled recursion and stack overflow in default BACnet/IP server
+[GHSA-wjw5-q9g6-2764](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-wjw5-q9g6-2764)
+Patched versions: 1.5.1
+Pull Request: [#1345](https://github.com/bacnet-stack/bacnet-stack/pull/1345).
+
+[CVE-2026-46677](https://www.cve.org/CVERecord?id=CVE-2026-46677) -
+Client-Side Out-of-Bounds Read in AtomicReadFile-ACK Record-Access Handling via RecordCount / fileData[] Mismatch
+[GHSA-rv5h-cxwq-q3mh](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-rv5h-cxwq-q3mh)
+Patched versions: 1.5.1
+Pull Request: [#1344](https://github.com/bacnet-stack/bacnet-stack/pull/1344).
+
+[CVE-2026-46676](https://www.cve.org/CVERecord?id=CVE-2026-46676) -
+Uninitialized Value Use in AtomicReadFile-ACK Record-Access Encoder Causes Response Corruption and Conditional Information Disclosure
+[GHSA-2fwp-32cj-g3x4](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-2fwp-32cj-g3x4)
+Patched versions: 1.5.1
+Pull Request: [#1344](https://github.com/bacnet-stack/bacnet-stack/pull/1344).
+
+[CVE-2026-46674](https://www.cve.org/CVERecord?id=CVE-2026-46674) -
+Out-of-Bounds Read in AtomicWriteFile Record Decoder via Unbounded returnedRecordCount
+[GHSA-8384-pwhh-cxjh](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-8384-pwhh-cxjh)
+Patched versions: 1.5.1
+Pull Request: [#1344](https://github.com/bacnet-stack/bacnet-stack/pull/1344).
+
+[CVE-2026-45265](https://www.cve.org/CVERecord?id=CVE-2026-45265) -
+Atomic-Read-File RecordCount Stack-Based Out-of-Bounds Write
+[GHSA-v3gx-mwrp-xvh5](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-v3gx-mwrp-xvh5)
+Patched versions: 1.5.1
+Pull Request: [#1340](https://github.com/bacnet-stack/bacnet-stack/pull/1340).
+
+[CVE-2026-40279](https://www.cve.org/CVERecord?id=CVE-2026-40279) -
+Undefined-behavior signed left shift in `decode_signed32()`
+[GHSA-326g-j95f-gmxv](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-326g-j95f-gmxv)
+Patched versions: [1.5.0](https://github.com/bacnet-stack/bacnet-stack/tree/bacnet-stack-1.5.0)
+Pull Request: [#1300](https://github.com/bacnet-stack/bacnet-stack/pull/1300)
+
+[CVE-2026-41503](https://www.cve.org/CVERecord?id=CVE-2026-41503) -
+Out-of-Bounds Read in ReadPropertyMultiple Property Decoder via Deprecated Tag Parser
+[GHSA-5w2v-mwqj-pr2c](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-5w2v-mwqj-pr2c)
+Patched versions: 1.5.0
+Pull Request: [#1244](https://github.com/bacnet-stack/bacnet-stack/pull/1244)
+
+[CVE-2026-41502](https://www.cve.org/CVERecord?id=CVE-2026-41502) -
+Off-by-One Out-of-Bounds Read in ReadPropertyMultiple Object ID Decoder
+[GHSA-7545-3fpx-4xw3](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-7545-3fpx-4xw3)
+Patched versions: 1.5.0
+Pull Request: [#1244](https://github.com/bacnet-stack/bacnet-stack/pull/1244)
+
+[CVE-2026-41475](https://www.cve.org/CVERecord?id=CVE-2026-41475) -
+Out-of-Bounds Read in WritePropertyMultiple Decoder via Deprecated Tag Parser
+[GHSA-cvv4-v3g6-4jmv](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-cvv4-v3g6-4jmv)
+Patched versions: 1.5.0
+Pull Request: [#1244](https://github.com/bacnet-stack/bacnet-stack/pull/1244)
[CVE-2026-26264](https://www.cve.org/CVERecord?id=CVE-2026-26264) -
WriteProperty decoding length underflow leads to OOB read and crash
[GHSA-phjh-v45p-gmjj](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-phjh-v45p-gmjj)
+Patched versions: 1.5.0
+Pull Request: [#1231](https://github.com/bacnet-stack/bacnet-stack/pull/1231)
[CVE-2026-21870](https://www.cve.org/CVERecord?id=CVE-2026-21870) -
Off-by-one Stack-based Buffer Overflow in tokenizer_string
[GHSA-pc83-wp6w-93mx](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-pc83-wp6w-93mx)
+Patched versions: 1.5.0
+Pull Request: [#1196](https://github.com/bacnet-stack/bacnet-stack/pull/1196)
[CVE-2026-21878](https://www.cve.org/CVERecord?id=CVE-2026-21878) -
Improper Limitation of a Pathname to a Restricted Directory
[GHSA-p8rx-c26w-545j](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-p8rx-c26w-545j)
+Patched versions: 1.5.0
+Pull Request: [#1197](https://github.com/bacnet-stack/bacnet-stack/pull/1197)
[CVE-2025-66624](https://www.cve.org/CVERecord?id=CVE-2025-66624) -
BACnet-stack MS/TP reply matcher OOB read
[GHSA-8wgw-5h6x-qgqg](https://github.com/bacnet-stack/bacnet-stack/security/advisories/GHSA-8wgw-5h6x-qgqg)
-
-[CVE-2023-38341](https://www.cve.org/CVERecord?id=CVE-2023-38341) -
-Multiple out-of-bounds accesses in bacerror code paths
-[#81](https://sourceforge.net/p/bacnet/bugs/81/)
-
-[CVE-2023-38340](https://www.cve.org/CVERecord?id=CVE-2023-38340) -
-Out of bounds accesses in bacnet_npdu_decode
-[#80](https://sourceforge.net/p/bacnet/bugs/80/)
-
-[CVE-2023-38339](https://www.cve.org/CVERecord?id=CVE-2023-38339) -
-Out of bounds jump in h_apdu.c:apdu_handler
-[#79](https://sourceforge.net/p/bacnet/bugs/79/)
-
-[CVE-2019-12480](https://www.cve.org/CVERecord?id=CVE-2019-12480) -
-Invalid read in bacserv when decoding alarm tags
-[#62](https://sourceforge.net/p/bacnet/bugs/62/)
-
-[CVE-2018-10238](https://www.cve.org/CVERecord?id=CVE-2018-10238) -
-Segmentation fault leading to denial of service
-[#61](https://sourceforge.net/p/bacnet/bugs/61/)
+Patched versions: 1.5.0
+Pull Request: [#1178](https://github.com/bacnet-stack/bacnet-stack/pull/1178)
## Reporting a Vulnerability
-Please use the "bugs" feature of Sourceforge.net to report a vulnerability,
-where it will be tracked until it is resolved.
-https://sourceforge.net/p/bacnet/bugs/
+Privately discuss, fix, and publish information about security
+vulnerabilities in this library using Github Security Advisories:
+https://github.com/bacnet-stack/bacnet-stack/security/advisories/new
-Vulnerabilities can also be reported using "issues" at Github.
+Alternatively, vulnerabilities can be reported using "issues" at Github.
https://github.com/bacnet-stack/bacnet-stack/issues
diff --git a/apps/epics/main.c b/apps/epics/main.c
index 6737274179..efbd06f9ea 100644
--- a/apps/epics/main.c
+++ b/apps/epics/main.c
@@ -112,6 +112,24 @@ static uint32_t Property_List_Length = 0;
static uint32_t Property_List_Index = 0;
static int32_t Property_List[MAX_PROPS + 2];
+static bool property_list_append(int32_t property_id)
+{
+ uint32_t capacity =
+ (uint32_t)(sizeof(Property_List) / sizeof(Property_List[0]));
+
+ /* Keep one slot free for the list terminator and cap to MAX_PROPS. */
+ if ((Property_List_Index >= (capacity - 1)) ||
+ (Property_List_Index >= MAX_PROPS)) {
+ return false;
+ }
+
+ Property_List[Property_List_Index] = property_id;
+ Property_List_Index++;
+ Property_List_Length++;
+
+ return true;
+}
+
struct property_value_list_t {
int32_t property_id;
BACNET_APPLICATION_DATA_VALUE *value;
@@ -245,7 +263,7 @@ static void MyReadPropertyAckHandler(
len = rp_ack_fully_decode_service_request(
service_request, service_len, rp_data);
}
- if (len > 0) {
+ if (len >= 0) {
memmove(
&Read_Property_Multiple_Data.service_data, service_data,
sizeof(BACNET_CONFIRMED_SERVICE_ACK_DATA));
@@ -472,7 +490,11 @@ static void PrintReadPropertyArray(
if (Walked_List_Index == 1) {
/* If the array is empty, make it VTS3-friendly */
if (value->tag == BACNET_APPLICATION_TAG_EMPTYLIST) {
- fprintf(stdout, "?\n ");
+ if (ShowValues) {
+ fprintf(stdout, "{}\n");
+ } else {
+ fprintf(stdout, "{?}\n");
+ }
return;
}
@@ -575,7 +597,7 @@ static void PrintReadPropertyData(
* But are we showing Values? We (VTS3) want ? instead of {?,?} to show
* up. */
switch (rpm_property->propertyIdentifier) {
- /* Screen the Properties that can be arrays or Sequences */
+ /* Screen the Properties that can be arrays or Sequences */
case PROP_PRESENT_VALUE:
case PROP_PRIORITY_ARRAY:
if (!ShowValues) {
@@ -822,7 +844,7 @@ ProcessRPMData(BACNET_READ_ACCESS_DATA *rpm_data, EPICS_STATES state)
* wait and put these object lists at the end */
bool bHasObjectList = false;
bool bHasStructuredViewList = false;
- int i = 0;
+ int slot = 0;
while (rpm_data) {
rpm_property = rpm_data->listOfProperties;
@@ -838,10 +860,11 @@ ProcessRPMData(BACNET_READ_ACCESS_DATA *rpm_data, EPICS_STATES state)
bHasStructuredViewList = true;
break;
default:
- Property_List[Property_List_Index] =
- rpm_property->propertyIdentifier;
- Property_List_Index++;
- Property_List_Length++;
+ if (!property_list_append(
+ (int32_t)rpm_property->propertyIdentifier)) {
+ /* Ignore excess properties once local list is full.
+ */
+ }
break;
}
/* Free up the value(s) */
@@ -852,10 +875,29 @@ ProcessRPMData(BACNET_READ_ACCESS_DATA *rpm_data, EPICS_STATES state)
free(old_value);
}
} else if (state == GET_HEADING_RESPONSE) {
- Property_Value_List[i++].value = rpm_property->value;
- /* copy this pointer.
- * On error, the pointer will be null
- * We won't free these values; they will free at exit */
+ /* Resolve destination slot by property identifier to avoid
+ * overflow from unexpected, duplicate, or excess properties */
+ for (slot = 0; Property_Value_List[slot].property_id != -1;
+ slot++) {
+ if (Property_Value_List[slot].property_id ==
+ (int32_t)rpm_property->propertyIdentifier) {
+ break;
+ }
+ }
+ if ((Property_Value_List[slot].property_id != -1) &&
+ (Property_Value_List[slot].value == NULL)) {
+ /* Store only in the matching, empty slot.
+ * We won't free these values; they will free at exit */
+ Property_Value_List[slot].value = rpm_property->value;
+ } else {
+ /* free unknown, duplicate, or excess property values */
+ value = rpm_property->value;
+ while (value) {
+ old_value = value;
+ value = value->next;
+ free(old_value);
+ }
+ }
} else {
fprintf(stdout, " ");
Print_Property_Identifier(rpm_property->propertyIdentifier);
@@ -882,18 +924,24 @@ ProcessRPMData(BACNET_READ_ACCESS_DATA *rpm_data, EPICS_STATES state)
} else if (bSuccess) { /* and GET_LIST_OF_ALL_RESPONSE */
/* Now append the properties we waited on. */
if (bHasStructuredViewList) {
- Property_List[Property_List_Index] = PROP_STRUCTURED_OBJECT_LIST;
- Property_List_Index++;
- Property_List_Length++;
+ if (!property_list_append(PROP_STRUCTURED_OBJECT_LIST)) {
+ /* Ignore when local list is full. */
+ }
}
if (bHasObjectList) {
- Property_List[Property_List_Index] = PROP_OBJECT_LIST;
- Property_List_Index++;
- Property_List_Length++;
+ if (!property_list_append(PROP_OBJECT_LIST)) {
+ /* Ignore when local list is full. */
+ }
}
/* Now insert the -1 list terminator, but don't count it. */
- Property_List[Property_List_Index] = -1;
- assert(Property_List_Length < MAX_PROPS);
+ if (Property_List_Index <
+ (uint32_t)(sizeof(Property_List) / sizeof(Property_List[0]))) {
+ Property_List[Property_List_Index] = -1;
+ } else {
+ Property_List
+ [(sizeof(Property_List) / sizeof(Property_List[0])) - 1] = -1;
+ }
+ assert(Property_List_Length <= MAX_PROPS);
Property_List_Index = 0; /* Will start at top of the list */
nextState = GET_PROPERTY_REQUEST;
}
@@ -922,11 +970,12 @@ static void print_help(const char *filename)
printf("-v: show values instead of '?' \n");
printf("-c: columns break for BACnetARRAY. Default is 0=always\n");
printf("-d: show only device object properties\n");
- printf("-p: Use sport for \"my\" port. 0xBAC0 is default.\n");
+ printf("-p: Use sport for \"my\" port. 47808 is default.\n");
printf(" Allows you to communicate with a localhost target.\n");
- printf("-t: declare target's MAC instead of using Who-Is to bind to \n");
- printf(" device-instance. Format is \"C0:A8:00:18:BA:C0\"\n");
- printf(" Use \"7F:00:00:01:BA:C0\" for loopback testing \n");
+ printf("-t: declare target's MAC or IP address instead of using Who-Is\n");
+ printf(" to bind to device-instance.\n");
+ printf(" Format is \"192.168.1.42:47808\" or \"C0:A8:01:2A:BA:C0\".\n");
+ printf(" Use \"127.0.0.1:47808\" for loopback testing.\n");
printf("-n: specify target's DNET if not local BACnet network \n");
printf(" or on routed Virtual Network \n");
printf("\n");
@@ -938,6 +987,7 @@ static int CheckCommandLineArgs(int argc, char *argv[])
{
int i;
bool bFoundTarget = false;
+ BACNET_MAC_ADDRESS mac = { 0 };
int argi = 0;
const char *filename = NULL;
@@ -1002,22 +1052,8 @@ static int CheckCommandLineArgs(int argc, char *argv[])
break;
case 't':
if (++i < argc) {
- /* decoded MAC addresses */
- unsigned mac[6];
- /* number of successful decodes */
- int count;
- /* loop counter */
- unsigned j;
- count = sscanf(
- argv[i], "%2x:%2x:%2x:%2x:%2x:%2x", &mac[0],
- &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
- if (count == 6) { /* success */
- Target_Address.mac_len = count;
- for (j = 0; j < 6; j++) {
- Target_Address.mac[j] = (uint8_t)mac[j];
- }
- Target_Address.net = 0;
- Target_Address.len = 0; /* No src address */
+ if (bacnet_address_mac_from_ascii(&mac, argv[i])) {
+ bacnet_address_init(&Target_Address, &mac, 0, NULL);
Provided_Targ_MAC = true;
break;
} else {
diff --git a/apps/perl/perl_bindings.c b/apps/perl/perl_bindings.c
index 88d90e2b02..fef5b47fb5 100644
--- a/apps/perl/perl_bindings.c
+++ b/apps/perl/perl_bindings.c
@@ -245,7 +245,7 @@ static void AtomicReadFileAckHandler(
BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data)
{
int len = 0;
- BACNET_ATOMIC_READ_FILE_DATA data;
+ BACNET_ATOMIC_READ_FILE_DATA data = { 0 };
if (address_match(&Target_Address, src) &&
(service_data->invoke_id == Request_Invoke_ID)) {
@@ -295,7 +295,7 @@ static void My_Read_Property_Ack_Handler(
BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data)
{
int len = 0;
- BACNET_READ_PROPERTY_DATA data;
+ BACNET_READ_PROPERTY_DATA data = { 0 };
if (address_match(&Target_Address, src) &&
(service_data->invoke_id == Request_Invoke_ID)) {
diff --git a/apps/piface/device.c b/apps/piface/device.c
index 4b1edf08de..29869a02b7 100644
--- a/apps/piface/device.c
+++ b/apps/piface/device.c
@@ -1687,7 +1687,7 @@ static bool Device_Write_Property_Object_Name(
{
bool status = false; /* return value */
int len = 0;
- BACNET_CHARACTER_STRING value;
+ BACNET_CHARACTER_STRING value = { 0 };
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
uint32_t object_instance = 0;
int apdu_size = 0;
@@ -1750,6 +1750,10 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool status = false; /* Ever the pessamist! */
struct object_functions *pObject = NULL;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* initialize the default return values */
wp_data->error_class = ERROR_CLASS_OBJECT;
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
@@ -1769,7 +1773,17 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
status = Device_Write_Property_Object_Name(
wp_data, pObject->Object_Write_Property);
} else {
- status = pObject->Object_Write_Property(wp_data);
+ if ((wp_data->application_data_len == 0) &&
+ !property_list_bacnet_list_member(
+ wp_data->object_type, wp_data->object_property)) {
+ /* only list properties can be written with
+ an empty application payload */
+ wp_data->error_class = ERROR_CLASS_SERVICES;
+ wp_data->error_code = ERROR_CODE_INVALID_TAG;
+ status = false;
+ } else {
+ status = pObject->Object_Write_Property(wp_data);
+ }
}
} else {
wp_data->error_class = ERROR_CLASS_PROPERTY;
diff --git a/apps/readfile/main.c b/apps/readfile/main.c
index dd0a96e4d2..ce2d422416 100644
--- a/apps/readfile/main.c
+++ b/apps/readfile/main.c
@@ -97,7 +97,7 @@ static void AtomicReadFileAckHandler(
{
int len = 0;
int result = 0;
- BACNET_ATOMIC_READ_FILE_DATA data;
+ BACNET_ATOMIC_READ_FILE_DATA data = { 0 };
FILE *pFile = NULL; /* stream pointer */
size_t octets_written = 0;
size_t octet_count = 0;
diff --git a/apps/router-ipv6/main.c b/apps/router-ipv6/main.c
index 6d764951dc..05c35575e5 100644
--- a/apps/router-ipv6/main.c
+++ b/apps/router-ipv6/main.c
@@ -38,6 +38,22 @@
#include "bacnet/datalink/bvlc.h"
#include "bacnet/basic/bbmd/h_bbmd.h"
+#ifndef DEBUG_LOG_DEBUG
+#define DEBUG_LOG_DISABLED 0
+#define DEBUG_LOG_ERROR 1
+#define DEBUG_LOG_INFO 2
+#define DEBUG_LOG_DEBUG 3
+#endif
+
+#ifndef debug_log_fprintf
+#define debug_log_fprintf(level, stream, ...) \
+ debug_fprintf((stream), __VA_ARGS__)
+#endif
+
+#ifndef debug_log_severity_set
+#define debug_log_severity_set(level) ((void)(level))
+#endif
+
/* current version of the BACnet stack */
static const char *BACnet_Version = BACNET_VERSION_TEXT;
@@ -269,7 +285,8 @@ static void dnet_cleanup(DNET *dnets)
{
DNET *dnet = dnets;
while (dnet != NULL) {
- debug_printf("DNET %u removed\n", (unsigned)dnet->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "DNET %u removed\n", (unsigned)dnet->net);
dnet = dnet->next;
free(dnets);
dnets = dnet;
@@ -313,14 +330,20 @@ static int datalink_send_pdu(
int bytes_sent = 0;
if (snet == 0) {
- debug_printf("BVLC/BVLC6 Send to DNET %u\n", (unsigned)dest->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "BVLC/BVLC6 Send to DNET %u\n",
+ (unsigned)dest->net);
bytes_sent = bip_send_pdu(dest, npdu_data, pdu, pdu_len);
bytes_sent = bip6_send_pdu(dest, npdu_data, pdu, pdu_len);
} else if (snet == BIP_Net) {
- debug_printf("BVLC Send to DNET %u\n", (unsigned)dest->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "BVLC Send to DNET %u\n",
+ (unsigned)dest->net);
bytes_sent = bip_send_pdu(dest, npdu_data, pdu, pdu_len);
} else if (snet == BIP6_Net) {
- debug_printf("BVLC6 Send to DNET %u\n", (unsigned)dest->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "BVLC6 Send to DNET %u\n",
+ (unsigned)dest->net);
bytes_sent = bip6_send_pdu(dest, npdu_data, pdu, pdu_len);
}
@@ -358,7 +381,7 @@ static void send_i_am_router_to_network(uint16_t snet, uint16_t net)
len = encode_unsigned16(&Tx_Buffer[pdu_len], net);
pdu_len += len;
} else {
- debug_printf("I-Am-Router-To-Network ");
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "I-Am-Router-To-Network ");
/* Each router shall broadcast out each port
an I-Am-Router-To-Network message containing the network
numbers of each accessible network except the networks
@@ -369,12 +392,12 @@ static void send_i_am_router_to_network(uint16_t snet, uint16_t net)
port = Router_Table_Head;
while (port != NULL) {
if (port->net != snet) {
- debug_printf("%u,", port->net);
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "%u,", port->net);
len = encode_unsigned16(&Tx_Buffer[pdu_len], port->net);
pdu_len += len;
dnet = port->dnets;
while (dnet != NULL) {
- debug_printf("%u,", dnet->net);
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "%u,", dnet->net);
len = encode_unsigned16(&Tx_Buffer[pdu_len], dnet->net);
pdu_len += len;
dnet = dnet->next;
@@ -382,7 +405,7 @@ static void send_i_am_router_to_network(uint16_t snet, uint16_t net)
}
port = port->next;
}
- debug_printf("from %u\n", snet);
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "from %u\n", snet);
}
datalink_send_pdu(snet, &dest, &npdu_data, &Tx_Buffer[0], pdu_len);
}
@@ -638,13 +661,11 @@ static void network_control_handler(
uint8_t *npdu,
uint16_t npdu_len)
{
- uint16_t npdu_offset = 0;
uint16_t dnet = 0;
- uint16_t len = 0;
const char *msg_name = NULL;
msg_name = bactext_network_layer_msg_name(npdu_data->network_message_type);
- fprintf(stderr, "Received %s\n", msg_name);
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "Received %s\n", msg_name);
switch (npdu_data->network_message_type) {
case NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK:
who_is_router_to_network_handler(
@@ -652,19 +673,8 @@ static void network_control_handler(
break;
case NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK:
/* add its DNETs to our routing table */
- fprintf(stderr, "for Networks: ");
- len = 2;
- while (npdu_len >= len) {
- len = decode_unsigned16(&npdu[npdu_offset], &dnet);
- fprintf(stderr, "%hu", dnet);
- dnet_add(snet, dnet, src);
- npdu_len -= len;
- npdu_offset += len;
- if (npdu_len) {
- fprintf(stderr, ", ");
- }
- }
- fprintf(stderr, ".\n");
+ npdu_i_am_router_to_network_process(
+ snet, src, npdu, npdu_len, dnet_add);
break;
case NETWORK_MESSAGE_I_COULD_BE_ROUTER_TO_NETWORK:
/* Do nothing, same as previous case. */
@@ -672,32 +682,47 @@ static void network_control_handler(
case NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK:
if (npdu_len >= 3) {
decode_unsigned16(&npdu[1], &dnet);
- fprintf(stderr, "for Network:%hu\n", dnet);
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr, "for Network:%hu\n", dnet);
switch (npdu[0]) {
case 0:
- fprintf(stderr, "Reason: Other Error.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr, "Reason: Other Error.\n");
break;
case 1:
- fprintf(stderr, "Reason: Network unreachable.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Network unreachable.\n");
break;
case 2:
- fprintf(stderr, "Reason: Network is busy.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Network is busy.\n");
break;
case 3:
- fprintf(
- stderr, "Reason: Unknown network message type.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Unknown network message type.\n");
break;
case 4:
- fprintf(stderr, "Reason: Message too long.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Message too long.\n");
break;
case 5:
- fprintf(stderr, "Reason: Security Error.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Security Error.\n");
break;
case 6:
- fprintf(stderr, "Reason: Invalid address length.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Invalid address length.\n");
break;
default:
- fprintf(stderr, "Reason: %u\n", (unsigned int)npdu[0]);
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr, "Reason: %u\n",
+ (unsigned int)npdu[0]);
break;
}
}
@@ -709,32 +734,10 @@ static void network_control_handler(
case NETWORK_MESSAGE_INIT_RT_TABLE:
/* If sent with Number of Ports == 0, we respond with
* NETWORK_MESSAGE_INIT_RT_TABLE_ACK and a list of all our
- * reachable networks.
- */
- if (npdu_len > 0) {
- /* If Number of Ports is 0, broadcast our "full" table */
- if (npdu[0] == 0) {
- send_initialize_routing_table_ack(snet, NULL);
- } else {
- /* they sent us a list */
- int net_count = npdu[0];
- while (net_count--) {
- int i = 1;
- /* DNET */
- decode_unsigned16(&npdu[i], &dnet);
- /* update routing table */
- dnet_add(snet, dnet, src);
- if (npdu[i + 3] > 0) {
- /* find next NET value */
- i = npdu[i + 3] + 4;
- } else {
- i += 4;
- }
- }
- send_initialize_routing_table_ack(snet, NULL);
- }
- break;
- }
+ * reachable networks. */
+ npdu_init_routing_table_process(
+ snet, src, npdu, npdu_len, dnet_add);
+ send_initialize_routing_table_ack(snet, NULL);
break;
case NETWORK_MESSAGE_INIT_RT_TABLE_ACK:
/* Do nothing with the routing table info, since don't support
@@ -789,6 +792,46 @@ static void routed_src_address(
}
}
+/**
+ * @brief Encode a routed APDU into the provided PDU buffer
+ * @param pdu[out] The buffer to hold the encoded PDU
+ * @param dest[in] The destination BACNET_ADDRESS
+ * @param src[in] The source BACNET_ADDRESS
+ * @param npdu[in] The NPDU data to encode
+ * @param apdu[in] The APDU data to encode
+ * @param apdu_len[in] The length of the APDU data
+ * @return The total length of the encoded PDU, or 0 on error
+ */
+static int routed_npdu_apdu_encode(
+ uint8_t *pdu,
+ BACNET_ADDRESS *dest,
+ BACNET_ADDRESS *src,
+ const BACNET_NPDU_DATA *npdu,
+ uint8_t *apdu,
+ uint16_t apdu_len)
+{
+ int npdu_len = 0;
+
+ npdu_len = npdu_encode_pdu(pdu, dest, src, npdu);
+ if (npdu_len <= 0) {
+ return 0;
+ }
+ if ((npdu_len + apdu_len) > sizeof(Tx_Buffer)) {
+ /* can't send, message too big */
+ debug_log_fprintf(
+ DEBUG_LOG_ERROR, stderr,
+ "Dropping oversized routed APDU: "
+ "npdu_len=%d apdu_len=%u tx=%lu\n",
+ npdu_len, (unsigned)apdu_len, (unsigned long)sizeof(Tx_Buffer));
+ return 0;
+ }
+ if (apdu && (apdu_len > 0)) {
+ memmove(&pdu[npdu_len], apdu, apdu_len);
+ }
+
+ return npdu_len + apdu_len;
+}
+
/**
* If a BACnet NPDU is received with NPCI indicating that the message
* should be relayed by virtue of the presence of a non-broadcast
@@ -820,7 +863,7 @@ static void routed_apdu_handler(
BACNET_ADDRESS local_dest;
BACNET_ADDRESS remote_dest;
BACNET_ADDRESS router_src;
- int npdu_len = 0;
+ int pdu_len = 0;
/* for broadcast messages no search is needed */
if (dest->net == BACNET_BROADCAST_NETWORK) {
@@ -840,19 +883,21 @@ static void routed_apdu_handler(
npdu->hop_count--;
routed_src_address(&router_src, snet, src);
/* encode both source and destination for broadcast */
- npdu_len =
- npdu_encode_pdu(&Tx_Buffer[0], &local_dest, &router_src, npdu);
- memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
- /* send to my other ports */
- debug_printf("Routing a BROADCAST from %u\n", (unsigned)snet);
- port = Router_Table_Head;
- while (port != NULL) {
- if (port->net != snet) {
- datalink_send_pdu(
- port->net, &local_dest, npdu, &Tx_Buffer[0],
- npdu_len + apdu_len);
+ pdu_len = routed_npdu_apdu_encode(
+ &Tx_Buffer[0], &local_dest, &router_src, npdu, apdu, apdu_len);
+ if (pdu_len > 0) {
+ /* send to my other ports */
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "Routing a BROADCAST from %u\n",
+ (unsigned)snet);
+ port = Router_Table_Head;
+ while (port != NULL) {
+ if (port->net != snet) {
+ datalink_send_pdu(
+ port->net, &local_dest, npdu, &Tx_Buffer[0], pdu_len);
+ }
+ port = port->next;
}
- port = port->next;
}
return;
}
@@ -860,7 +905,9 @@ static void routed_apdu_handler(
port = dnet_find(dest->net, &remote_dest);
if (port) {
if (port->net == dest->net) {
- debug_printf("Routing to Port %u\n", (unsigned)dest->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "Routing to Port %u\n",
+ (unsigned)dest->net);
/* Case 1: the router is directly
connected to the network referred to by DNET. */
/* In the first case, DNET, DADR, and Hop
@@ -873,15 +920,16 @@ static void routed_apdu_handler(
local_dest.net = 0;
npdu->hop_count--;
routed_src_address(&router_src, snet, src);
- npdu_len =
- npdu_encode_pdu(&Tx_Buffer[0], &local_dest, &router_src, npdu);
- memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
- datalink_send_pdu(
- port->net, &local_dest, npdu, &Tx_Buffer[0],
- npdu_len + apdu_len);
+ pdu_len = routed_npdu_apdu_encode(
+ &Tx_Buffer[0], &local_dest, &router_src, npdu, apdu, apdu_len);
+ if (pdu_len > 0) {
+ datalink_send_pdu(
+ port->net, &local_dest, npdu, &Tx_Buffer[0], pdu_len);
+ }
} else {
- debug_printf(
- "Routing to another Router %u\n", (unsigned)remote_dest.net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "Routing to another Router %u\n",
+ (unsigned)remote_dest.net);
/* Case 2: the message must be
relayed to another router for further transmission */
/* In the second case, if the Hop Count is greater than zero,
@@ -891,30 +939,34 @@ static void routed_apdu_handler(
discarded. */
npdu->hop_count--;
routed_src_address(&router_src, snet, src);
- npdu_len =
- npdu_encode_pdu(&Tx_Buffer[0], &remote_dest, &router_src, npdu);
- memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
- datalink_send_pdu(
- port->net, &remote_dest, npdu, &Tx_Buffer[0],
- npdu_len + apdu_len);
+ pdu_len = routed_npdu_apdu_encode(
+ &Tx_Buffer[0], &remote_dest, &router_src, npdu, apdu, apdu_len);
+ if (pdu_len > 0) {
+ datalink_send_pdu(
+ port->net, &remote_dest, npdu, &Tx_Buffer[0], pdu_len);
+ }
}
} else if (dest->net) {
- debug_printf("Routing to Unknown Route %u\n", (unsigned)dest->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "Routing to Unknown Route %u\n",
+ (unsigned)dest->net);
/* Case 3: a global broadcast is required. */
dest->mac_len = 0;
npdu->hop_count--;
/* encode both source and destination */
routed_src_address(&router_src, snet, src);
- npdu_len = npdu_encode_pdu(&Tx_Buffer[0], dest, &router_src, npdu);
- memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
- /* send to all other ports */
- port = Router_Table_Head;
- while (port != NULL) {
- if (port->net != snet) {
- datalink_send_pdu(
- port->net, dest, npdu, &Tx_Buffer[0], npdu_len + apdu_len);
+ pdu_len = routed_npdu_apdu_encode(
+ &Tx_Buffer[0], dest, &router_src, npdu, apdu, apdu_len);
+ if (pdu_len > 0) {
+ /* send to all other ports */
+ port = Router_Table_Head;
+ while (port != NULL) {
+ if (port->net != snet) {
+ datalink_send_pdu(
+ port->net, dest, npdu, &Tx_Buffer[0], pdu_len);
+ }
+ port = port->next;
}
- port = port->next;
}
/* If the next router is unknown, an attempt shall be made to
identify it using a Who-Is-Router-To-Network message. */
@@ -947,13 +999,18 @@ static void my_routing_npdu_handler(
if (!pdu) {
/* no packet */
- } else {
- protocol_version = pdu[0];
+ return;
+ }
+ if (pdu_len == 0) {
+ /* empty packet */
+ return;
}
+ protocol_version = pdu[0];
if (protocol_version == BACNET_PROTOCOL_VERSION) {
apdu_offset = bacnet_npdu_decode(pdu, pdu_len, &dest, src, &npdu_data);
if (apdu_offset <= 0) {
- fprintf(stderr, "NPDU: Decoding failed; Discarded!\n");
+ debug_log_fprintf(
+ DEBUG_LOG_ERROR, stderr, "NPDU: Decoding failed; Discarded!\n");
} else if (npdu_data.network_layer_message) {
if ((dest.net == 0) || (dest.net == BACNET_BROADCAST_NETWORK)) {
network_control_handler(
@@ -989,13 +1046,15 @@ static void my_routing_npdu_handler(
}
}
} else {
- fprintf(
- stderr, "NPDU: DNET=%u. Discarded!\n", (unsigned)dest.net);
+ debug_log_fprintf(
+ DEBUG_LOG_ERROR, stderr, "NPDU: DNET=%u. Discarded!\n",
+ (unsigned)dest.net);
}
}
} else {
- fprintf(
- stderr, "NPDU: unsupported protocol version %u. Discarded!\n",
+ debug_log_fprintf(
+ DEBUG_LOG_ERROR, stderr,
+ "NPDU: unsupported protocol version %u. Discarded!\n",
protocol_version);
}
@@ -1010,8 +1069,14 @@ static void datalink_init(void)
char *pEnv = NULL;
BACNET_ADDRESS my_address = { 0 };
+ pEnv = getenv("BACNET_ROUTER_DEBUG");
+ if (pEnv) {
+ bip_debug_enable();
+ fprintf(stderr, "Debug=enabled\n");
+ } else {
+ fprintf(stderr, "Debug=disabled\n");
+ }
/* BACnet/IP Initialization */
- bip_debug_enable();
pEnv = getenv("BACNET_IP_PORT");
if (pEnv) {
bip_set_port((uint16_t)strtol(pEnv, NULL, 0));
@@ -1078,7 +1143,7 @@ static void cleanup(void)
{
DNET *port = NULL;
- fprintf(stderr, "Cleaning up...\n");
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "Cleaning up...\n");
/* clean up the remote networks */
port = Router_Table_Head;
while (port != NULL) {
@@ -1130,6 +1195,7 @@ static void control_c_hooks(void)
}
#endif
+#ifndef FUZZING
/**
* Main function of simple router demo.
*
@@ -1169,7 +1235,8 @@ int main(int argc, char *argv[])
bip_receive(&src, &BIP_Rx_Buffer[0], sizeof(BIP_Rx_Buffer), 5);
/* process */
if (pdu_len) {
- debug_printf("BACnet/IP Received packet\n");
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "BACnet/IP Received packet\n");
my_routing_npdu_handler(BIP_Net, &src, &BIP_Rx_Buffer[0], pdu_len);
}
/* returns 0 bytes on timeout */
@@ -1177,7 +1244,8 @@ int main(int argc, char *argv[])
bip6_receive(&src, &BIP6_Rx_Buffer[0], sizeof(BIP6_Rx_Buffer), 5);
/* process */
if (pdu_len) {
- debug_printf("BACnet/IPv6 Received packet\n");
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "BACnet/IPv6 Received packet\n");
my_routing_npdu_handler(
BIP6_Net, &src, &BIP6_Rx_Buffer[0], pdu_len);
}
@@ -1197,3 +1265,4 @@ int main(int argc, char *argv[])
return 0;
}
+#endif
diff --git a/apps/router-mstp/main.c b/apps/router-mstp/main.c
index f8d49c9a2f..68fb4ae8b2 100644
--- a/apps/router-mstp/main.c
+++ b/apps/router-mstp/main.c
@@ -36,6 +36,22 @@
#include "bacnet/datalink/bvlc.h"
#include "bacnet/basic/bbmd/h_bbmd.h"
+#ifndef DEBUG_LOG_DEBUG
+#define DEBUG_LOG_DISABLED 0
+#define DEBUG_LOG_ERROR 1
+#define DEBUG_LOG_INFO 2
+#define DEBUG_LOG_DEBUG 3
+#endif
+
+#ifndef debug_log_fprintf
+#define debug_log_fprintf(level, stream, ...) \
+ debug_fprintf((stream), __VA_ARGS__)
+#endif
+
+#ifndef debug_log_severity_set
+#define debug_log_severity_set(level) ((void)(level))
+#endif
+
/* current version of the BACnet stack */
static const char *BACnet_Version = BACNET_VERSION_TEXT;
@@ -80,29 +96,6 @@ static uint8_t MSTP_Rx_Buffer[DLMSTP_MPDU_MAX];
static uint8_t Tx_Buffer[MAX(DLMSTP_MPDU_MAX, BIP_MPDU_MAX)];
/* main loop exit control */
static bool Exit_Requested;
-/* debugging info */
-static bool Debug_Enabled;
-
-/**
- * @brief print debug info if debug is enabled
- * @param format - printf format string
- * @param ... variable arguments
- * @return number of bytes printed
- */
-static int log_printf(const char *format, ...)
-{
- int length = 0;
- va_list ap;
-
- if (Debug_Enabled) {
- va_start(ap, format);
- length = vfprintf(stdout, format, ap);
- va_end(ap);
- fflush(stdout);
- }
-
- return length;
-}
/**
* Search the router table to find a matching DNET entry
@@ -290,7 +283,8 @@ static void dnet_cleanup(DNET *dnets)
{
DNET *dnet = dnets;
while (dnet != NULL) {
- log_printf("DNET %u removed\n", (unsigned)dnet->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "DNET %u removed\n", (unsigned)dnet->net);
dnet = dnet->next;
free(dnets);
dnets = dnet;
@@ -334,14 +328,20 @@ static int datalink_send_pdu(
int bytes_sent = 0;
if (snet == 0) {
- log_printf("BVLC & MS/TP Send to DNET %u\n", (unsigned)dest->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "BVLC & MS/TP Send to DNET %u\n",
+ (unsigned)dest->net);
bytes_sent = bip_send_pdu(dest, npdu_data, pdu, pdu_len);
bytes_sent = dlmstp_send_pdu(dest, npdu_data, pdu, pdu_len);
} else if (snet == BIP_Net) {
- log_printf("BVLC Send to DNET %u\n", (unsigned)dest->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "BVLC Send to DNET %u\n",
+ (unsigned)dest->net);
bytes_sent = bip_send_pdu(dest, npdu_data, pdu, pdu_len);
} else if (snet == MSTP_Net) {
- log_printf("MS/TP Send to DNET %u\n", (unsigned)dest->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "MS/TP Send to DNET %u\n",
+ (unsigned)dest->net);
bytes_sent = dlmstp_send_pdu(dest, npdu_data, pdu, pdu_len);
}
@@ -379,7 +379,7 @@ static void send_i_am_router_to_network(uint16_t snet, uint16_t net)
len = encode_unsigned16(&Tx_Buffer[pdu_len], net);
pdu_len += len;
} else {
- log_printf("I-Am-Router-To-Network ");
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "I-Am-Router-To-Network ");
/* Each router shall broadcast out each port
an I-Am-Router-To-Network message containing the network
numbers of each accessible network except the networks
@@ -390,12 +390,12 @@ static void send_i_am_router_to_network(uint16_t snet, uint16_t net)
port = Router_Table_Head;
while (port != NULL) {
if (port->net != snet) {
- log_printf("%u,", port->net);
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "%u,", port->net);
len = encode_unsigned16(&Tx_Buffer[pdu_len], port->net);
pdu_len += len;
dnet = port->dnets;
while (dnet != NULL) {
- log_printf("%u,", dnet->net);
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "%u,", dnet->net);
len = encode_unsigned16(&Tx_Buffer[pdu_len], dnet->net);
pdu_len += len;
dnet = dnet->next;
@@ -403,7 +403,7 @@ static void send_i_am_router_to_network(uint16_t snet, uint16_t net)
}
port = port->next;
}
- log_printf("from %u\n", snet);
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "from %u\n", snet);
}
datalink_send_pdu(snet, &dest, &npdu_data, &Tx_Buffer[0], pdu_len);
}
@@ -659,15 +659,11 @@ static void network_control_handler(
uint8_t *npdu,
uint16_t npdu_len)
{
- uint16_t npdu_offset = 0;
uint16_t dnet = 0;
- uint16_t len = 0;
const char *msg_name = NULL;
- (void)src;
- (void)npdu_data;
msg_name = bactext_network_layer_msg_name(npdu_data->network_message_type);
- fprintf(stderr, "Received %s\n", msg_name);
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "Received %s\n", msg_name);
switch (npdu_data->network_message_type) {
case NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK:
who_is_router_to_network_handler(
@@ -675,19 +671,8 @@ static void network_control_handler(
break;
case NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK:
/* add its DNETs to our routing table */
- fprintf(stderr, "for Networks: ");
- len = 2;
- while (npdu_len >= len) {
- len = decode_unsigned16(&npdu[npdu_offset], &dnet);
- fprintf(stderr, "%hu", dnet);
- dnet_add(snet, dnet, src);
- npdu_len -= len;
- npdu_offset += len;
- if (npdu_len) {
- fprintf(stderr, ", ");
- }
- }
- fprintf(stderr, ".\n");
+ npdu_i_am_router_to_network_process(
+ snet, src, npdu, npdu_len, dnet_add);
break;
case NETWORK_MESSAGE_I_COULD_BE_ROUTER_TO_NETWORK:
/* Do nothing, same as previous case. */
@@ -695,32 +680,47 @@ static void network_control_handler(
case NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK:
if (npdu_len >= 3) {
decode_unsigned16(&npdu[1], &dnet);
- fprintf(stderr, "for Network:%hu\n", dnet);
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr, "for Network:%hu\n", dnet);
switch (npdu[0]) {
case 0:
- fprintf(stderr, "Reason: Other Error.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr, "Reason: Other Error.\n");
break;
case 1:
- fprintf(stderr, "Reason: Network unreachable.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Network unreachable.\n");
break;
case 2:
- fprintf(stderr, "Reason: Network is busy.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Network is busy.\n");
break;
case 3:
- fprintf(
- stderr, "Reason: Unknown network message type.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Unknown network message type.\n");
break;
case 4:
- fprintf(stderr, "Reason: Message too long.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Message too long.\n");
break;
case 5:
- fprintf(stderr, "Reason: Security Error.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Security Error.\n");
break;
case 6:
- fprintf(stderr, "Reason: Invalid address length.\n");
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr,
+ "Reason: Invalid address length.\n");
break;
default:
- fprintf(stderr, "Reason: %u\n", (unsigned int)npdu[0]);
+ debug_log_fprintf(
+ DEBUG_LOG_INFO, stderr, "Reason: %u\n",
+ (unsigned int)npdu[0]);
break;
}
}
@@ -812,6 +812,46 @@ static void routed_src_address(
}
}
+/**
+ * @brief Encode a routed APDU into the provided PDU buffer.
+ * @param pdu [out] The buffer to hold the encoded PDU.
+ * @param dest [in] The destination BACNET_ADDRESS.
+ * @param src [in] The source BACNET_ADDRESS.
+ * @param npdu [in] The NPDU data to encode.
+ * @param apdu [in] The APDU data to encode.
+ * @param apdu_len [in] The length of the APDU data.
+ * @return The total length of the encoded PDU, or 0 on error.
+ */
+static int routed_npdu_apdu_encode(
+ uint8_t *pdu,
+ BACNET_ADDRESS *dest,
+ BACNET_ADDRESS *src,
+ const BACNET_NPDU_DATA *npdu,
+ uint8_t *apdu,
+ uint16_t apdu_len)
+{
+ int npdu_len = 0;
+
+ npdu_len = npdu_encode_pdu(pdu, dest, src, npdu);
+ if (npdu_len <= 0) {
+ return 0;
+ }
+ if ((npdu_len + apdu_len) > sizeof(Tx_Buffer)) {
+ /* can't send, message too big */
+ debug_log_fprintf(
+ DEBUG_LOG_ERROR, stderr,
+ "Dropping oversized routed APDU: "
+ "npdu_len=%d apdu_len=%u tx=%lu\n",
+ npdu_len, (unsigned)apdu_len, (unsigned long)sizeof(Tx_Buffer));
+ return 0;
+ }
+ if (apdu && (apdu_len > 0)) {
+ memmove(&pdu[npdu_len], apdu, apdu_len);
+ }
+
+ return npdu_len + apdu_len;
+}
+
/**
* If a BACnet NPDU is received with NPCI indicating that the message
* should be relayed by virtue of the presence of a non-broadcast
@@ -843,7 +883,7 @@ static void routed_apdu_handler(
BACNET_ADDRESS local_dest;
BACNET_ADDRESS remote_dest;
BACNET_ADDRESS router_src;
- int npdu_len = 0;
+ int pdu_len = 0;
/* for broadcast messages no search is needed */
if (dest->net == BACNET_BROADCAST_NETWORK) {
@@ -863,19 +903,21 @@ static void routed_apdu_handler(
npdu->hop_count--;
routed_src_address(&router_src, snet, src);
/* encode both source and destination for broadcast */
- npdu_len =
- npdu_encode_pdu(&Tx_Buffer[0], &local_dest, &router_src, npdu);
- memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
- /* send to my other ports */
- log_printf("Routing a BROADCAST from %u\n", (unsigned)snet);
- port = Router_Table_Head;
- while (port != NULL) {
- if (port->net != snet) {
- datalink_send_pdu(
- port->net, &local_dest, npdu, &Tx_Buffer[0],
- npdu_len + apdu_len);
+ pdu_len = routed_npdu_apdu_encode(
+ &Tx_Buffer[0], &local_dest, &router_src, npdu, apdu, apdu_len);
+ if (pdu_len > 0) {
+ /* send to my other ports */
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "Routing a BROADCAST from %u\n",
+ (unsigned)snet);
+ port = Router_Table_Head;
+ while (port != NULL) {
+ if (port->net != snet) {
+ datalink_send_pdu(
+ port->net, &local_dest, npdu, &Tx_Buffer[0], pdu_len);
+ }
+ port = port->next;
}
- port = port->next;
}
return;
}
@@ -883,7 +925,9 @@ static void routed_apdu_handler(
port = dnet_find(dest->net, &remote_dest);
if (port) {
if (port->net == dest->net) {
- log_printf("Routing to Port %u\n", (unsigned)dest->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "Routing to Port %u\n",
+ (unsigned)dest->net);
/* Case 1: the router is directly
connected to the network referred to by DNET. */
/* In the first case, DNET, DADR, and Hop
@@ -896,15 +940,16 @@ static void routed_apdu_handler(
local_dest.net = 0;
npdu->hop_count--;
routed_src_address(&router_src, snet, src);
- npdu_len =
- npdu_encode_pdu(&Tx_Buffer[0], &local_dest, &router_src, npdu);
- memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
- datalink_send_pdu(
- port->net, &local_dest, npdu, &Tx_Buffer[0],
- npdu_len + apdu_len);
+ pdu_len = routed_npdu_apdu_encode(
+ &Tx_Buffer[0], &local_dest, &router_src, npdu, apdu, apdu_len);
+ if (pdu_len > 0) {
+ datalink_send_pdu(
+ port->net, &local_dest, npdu, &Tx_Buffer[0], pdu_len);
+ }
} else {
- log_printf(
- "Routing to another Router %u\n", (unsigned)remote_dest.net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "Routing to another Router %u\n",
+ (unsigned)remote_dest.net);
/* Case 2: the message must be
relayed to another router for further transmission */
/* In the second case, if the Hop Count is greater than zero,
@@ -914,30 +959,34 @@ static void routed_apdu_handler(
discarded. */
npdu->hop_count--;
routed_src_address(&router_src, snet, src);
- npdu_len =
- npdu_encode_pdu(&Tx_Buffer[0], &remote_dest, &router_src, npdu);
- memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
- datalink_send_pdu(
- port->net, &remote_dest, npdu, &Tx_Buffer[0],
- npdu_len + apdu_len);
+ pdu_len = routed_npdu_apdu_encode(
+ &Tx_Buffer[0], &remote_dest, &router_src, npdu, apdu, apdu_len);
+ if (pdu_len > 0) {
+ datalink_send_pdu(
+ port->net, &remote_dest, npdu, &Tx_Buffer[0], pdu_len);
+ }
}
} else if (dest->net) {
- log_printf("Routing to Unknown Route %u\n", (unsigned)dest->net);
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "Routing to Unknown Route %u\n",
+ (unsigned)dest->net);
/* Case 3: a global broadcast is required. */
dest->mac_len = 0;
npdu->hop_count--;
/* encode both source and destination */
routed_src_address(&router_src, snet, src);
- npdu_len = npdu_encode_pdu(&Tx_Buffer[0], dest, &router_src, npdu);
- memmove(&Tx_Buffer[npdu_len], apdu, apdu_len);
- /* send to all other ports */
- port = Router_Table_Head;
- while (port != NULL) {
- if (port->net != snet) {
- datalink_send_pdu(
- port->net, dest, npdu, &Tx_Buffer[0], npdu_len + apdu_len);
+ pdu_len = routed_npdu_apdu_encode(
+ &Tx_Buffer[0], dest, &router_src, npdu, apdu, apdu_len);
+ if (pdu_len > 0) {
+ /* send to all other ports */
+ port = Router_Table_Head;
+ while (port != NULL) {
+ if (port->net != snet) {
+ datalink_send_pdu(
+ port->net, dest, npdu, &Tx_Buffer[0], pdu_len);
+ }
+ port = port->next;
}
- port = port->next;
}
/* If the next router is unknown, an attempt shall be made to
identify it using a Who-Is-Router-To-Network message. */
@@ -964,15 +1013,24 @@ static void my_routing_npdu_handler(
uint16_t snet, BACNET_ADDRESS *src, uint8_t *pdu, uint16_t pdu_len)
{
int apdu_offset = 0;
+ unsigned protocol_version = 0;
BACNET_ADDRESS dest = { 0 };
BACNET_NPDU_DATA npdu_data = { 0 };
if (!pdu) {
/* no packet */
- } else if (pdu[0] == BACNET_PROTOCOL_VERSION) {
+ return;
+ }
+ if (pdu_len == 0) {
+ /* empty packet */
+ return;
+ }
+ protocol_version = pdu[0];
+ if (protocol_version == BACNET_PROTOCOL_VERSION) {
apdu_offset = bacnet_npdu_decode(pdu, pdu_len, &dest, src, &npdu_data);
if (apdu_offset <= 0) {
- fprintf(stderr, "NPDU: Decoding failed; Discarded!\n");
+ debug_log_fprintf(
+ DEBUG_LOG_ERROR, stderr, "NPDU: Decoding failed; Discarded!\n");
} else if (npdu_data.network_layer_message) {
if ((dest.net == 0) || (dest.net == BACNET_BROADCAST_NETWORK)) {
network_control_handler(
@@ -1008,12 +1066,16 @@ static void my_routing_npdu_handler(
}
}
} else {
- fprintf(
- stderr, "NPDU: DNET=%u. Discarded!\n", (unsigned)dest.net);
+ debug_log_fprintf(
+ DEBUG_LOG_ERROR, stderr, "NPDU: DNET=%u. Discarded!\n",
+ (unsigned)dest.net);
}
}
} else {
- /* unsupported protocol version */
+ debug_log_fprintf(
+ DEBUG_LOG_ERROR, stderr,
+ "NPDU: unsupported protocol version %u. Discarded!\n",
+ protocol_version);
}
return;
@@ -1030,8 +1092,7 @@ static void datalink_init(void)
pEnv = getenv("BACNET_ROUTER_DEBUG");
if (pEnv) {
bip_debug_enable();
- Debug_Enabled = true;
- log_printf("Debug=enabled\n");
+ fprintf(stderr, "Debug=enabled\n");
} else {
fprintf(stderr, "Debug=disabled\n");
}
@@ -1113,7 +1174,7 @@ static void cleanup(void)
{
DNET *port = NULL;
- fprintf(stderr, "Cleaning up...\n");
+ debug_log_fprintf(DEBUG_LOG_INFO, stderr, "Cleaning up...\n");
/* clean up the remote networks */
port = Router_Table_Head;
while (port != NULL) {
@@ -1135,6 +1196,8 @@ static BOOL WINAPI CtrlCHandler(DWORD dwCtrlType)
Sleep(100);
}
exit(0);
+
+ return TRUE;
}
static void control_c_hooks(void)
@@ -1202,7 +1265,8 @@ int main(int argc, char *argv[])
bip_receive(&src, &BIP_Rx_Buffer[0], sizeof(BIP_Rx_Buffer), 5);
/* process */
if (pdu_len) {
- log_printf("BACnet/IP Received packet\n");
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "BACnet/IP Received packet\n");
my_routing_npdu_handler(BIP_Net, &src, &BIP_Rx_Buffer[0], pdu_len);
}
/* returns 0 bytes on timeout */
@@ -1210,7 +1274,8 @@ int main(int argc, char *argv[])
dlmstp_receive(&src, &MSTP_Rx_Buffer[0], sizeof(MSTP_Rx_Buffer), 5);
/* process */
if (pdu_len) {
- log_printf("BACnet MS/TP Received packet\n");
+ debug_log_fprintf(
+ DEBUG_LOG_DEBUG, stderr, "BACnet MS/TP Received packet\n");
my_routing_npdu_handler(
MSTP_Net, &src, &MSTP_Rx_Buffer[0], pdu_len);
}
diff --git a/ports/at91sam7s/device.c b/ports/at91sam7s/device.c
index f511fe5c30..7c04bc2c69 100644
--- a/ports/at91sam7s/device.c
+++ b/ports/at91sam7s/device.c
@@ -205,7 +205,7 @@ static bool Device_Write_Property_Object_Name(
{
bool status = false; /* return value */
int len = 0;
- BACNET_CHARACTER_STRING value;
+ BACNET_CHARACTER_STRING value = { 0 };
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
uint32_t object_instance = 0;
int apdu_size = 0;
@@ -259,6 +259,10 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool status = false;
struct my_object_functions *pObject = NULL;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* initialize the default return values */
pObject = Device_Objects_Find_Functions(wp_data->object_type);
if (pObject) {
@@ -276,7 +280,17 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
status = Device_Write_Property_Object_Name(
wp_data, pObject->Object_Write_Property);
} else {
- status = pObject->Object_Write_Property(wp_data);
+ if ((wp_data->application_data_len == 0) &&
+ !property_list_bacnet_list_member(
+ wp_data->object_type, wp_data->object_property)) {
+ /* only list properties can be written with
+ an empty application payload */
+ wp_data->error_class = ERROR_CLASS_SERVICES;
+ wp_data->error_code = ERROR_CODE_INVALID_TAG;
+ status = false;
+ } else {
+ status = pObject->Object_Write_Property(wp_data);
+ }
}
} else {
if (Device_Objects_Property_List_Member(wp_data->object_type,
diff --git a/ports/bdk-atxx4-mstp/device.c b/ports/bdk-atxx4-mstp/device.c
index e8c8a8d8fb..b012c97f78 100644
--- a/ports/bdk-atxx4-mstp/device.c
+++ b/ports/bdk-atxx4-mstp/device.c
@@ -172,7 +172,7 @@ static bool Device_Write_Property_Object_Name(
{
bool status = false; /* return value */
int len = 0;
- BACNET_CHARACTER_STRING value;
+ BACNET_CHARACTER_STRING value = { 0 };
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
uint32_t object_instance = 0;
int apdu_size = 0;
@@ -226,6 +226,10 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool status = false;
struct my_object_functions *pObject = NULL;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* initialize the default return values */
pObject = Device_Objects_Find_Functions(wp_data->object_type);
if (pObject) {
@@ -243,7 +247,16 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
status = Device_Write_Property_Object_Name(
wp_data, pObject->Object_Write_Property);
} else {
- status = pObject->Object_Write_Property(wp_data);
+ if ((wp_data->application_data_len == 0) && !property_list_bacnet_list_member(
+ wp_data->object_type, wp_data->object_property)) {
+ /* only list properties can be written with
+ an empty application payload */
+ wp_data->error_class = ERROR_CLASS_SERVICES;
+ wp_data->error_code = ERROR_CODE_INVALID_TAG;
+ status = false;
+ } else {
+ status = pObject->Object_Write_Property(wp_data);
+ }
}
} else {
if (Device_Objects_Property_List_Member(wp_data->object_type,
diff --git a/ports/bsd/bip-init.c b/ports/bsd/bip-init.c
index 9a3a5661ae..266cc888a6 100644
--- a/ports/bsd/bip-init.c
+++ b/ports/bsd/bip-init.c
@@ -8,6 +8,13 @@
#include /* for standard integer types uint8_t etc. */
#include /* for the standard bool type. */
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
#include "bacnet/bacdcode.h"
#include "bacnet/bacint.h"
#include "bacnet/datalink/bip.h"
@@ -32,6 +39,8 @@ static struct in_addr BIP_Broadcast_Addr;
/* broadcast binding mechanism */
static bool BIP_Broadcast_Binding_Address_Override;
static struct in_addr BIP_Broadcast_Binding_Address;
+/* IP gateway - stored here in network byte order */
+static struct in_addr BIP_Gateway_Addr;
/* point-to-point interface flag - uses the unicast socket for broadcast */
static bool BIP_Point_To_Point = false;
/* enable debugging */
@@ -213,6 +222,21 @@ bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr)
return true;
}
+/**
+ * @brief Get the BACnet/IP default gateway address
+ * @param addr - network IPv4 address of the gateway
+ * @return true if a gateway address was found
+ */
+bool bip_get_gateway_addr(BACNET_IP_ADDRESS *addr)
+{
+ if (addr) {
+ memcpy(&addr->address[0], &BIP_Gateway_Addr.s_addr, 4);
+ addr->port = 0;
+ }
+
+ return (BIP_Gateway_Addr.s_addr != 0);
+}
+
/**
* @brief Set the BACnet/IP subnet mask CIDR prefix
* @return true if the subnet mask CIDR prefix is set
@@ -576,6 +600,83 @@ int bip_set_broadcast_binding(const char *ip4_broadcast)
return 0;
}
+/**
+ * @brief Get the default gateway address
+ * @param ifname [in] The interface name
+ * @param gateway [out] The gateway address
+ * @return 0 on success, else -1
+ */
+static int bip_get_local_gateway(const char *ifname, struct in_addr *gateway)
+{
+ int mib[] = { CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_DUMP, 0 };
+ size_t l;
+ char *p, *next, *lim;
+ struct rt_msghdr *rtm;
+ struct sockaddr *sa;
+ struct sockaddr_in *sin;
+ bool found = false;
+ unsigned int if_index = 0;
+
+ if (ifname != NULL) {
+ if_index = if_nametoindex(ifname);
+ }
+
+ if (sysctl(mib, 6, NULL, &l, NULL, 0) < 0) {
+ return -1;
+ }
+ if (l == 0) {
+ return -1;
+ }
+ p = malloc(l);
+ if (!p) {
+ return -1;
+ }
+ if (sysctl(mib, 6, p, &l, NULL, 0) < 0) {
+ free(p);
+ return -1;
+ }
+ lim = p + l;
+ for (next = p; next < lim; next += rtm->rtm_msglen) {
+ rtm = (struct rt_msghdr *)next;
+ sa = (struct sockaddr *)(rtm + 1);
+ /* Check if it's the default route (dst is 0.0.0.0) */
+ if (sa->sa_family == AF_INET &&
+ (if_index == 0 || rtm->rtm_index == if_index)) {
+ sin = (struct sockaddr_in *)sa;
+ if (sin->sin_addr.s_addr == 0) {
+ /* The gateway is the second sockaddr after the destination */
+ unsigned int i;
+ char *cp = (char *)(rtm + 1);
+ for (i = 0; i < RTAX_MAX; i++) {
+ if (rtm->rtm_addrs & (1 << i)) {
+ sa = (struct sockaddr *)cp;
+ if (i == RTAX_GATEWAY && sa->sa_family == AF_INET) {
+ gateway->s_addr =
+ ((struct sockaddr_in *)sa)->sin_addr.s_addr;
+ found = true;
+ break;
+ }
+ /* advance pointer to next sockaddr */
+ if (sa->sa_len > 0) {
+ cp +=
+ ((sa->sa_len + sizeof(long) - 1) &
+ ~(sizeof(long) - 1));
+ } else {
+ cp += sizeof(long);
+ }
+ }
+ }
+ }
+ }
+ if (found) {
+ break;
+ }
+ }
+ free(p);
+
+ return found ? 0 : -1;
+}
+
/** Gets the local IP address and local broadcast address from the system,
* and saves it into the BACnet/IP data structures.
*
@@ -647,6 +748,10 @@ void bip_set_interface(const char *ifname)
ntohs(BIP_Port));
fflush(stderr);
}
+ /* setup local gateway address */
+ if (BIP_Gateway_Addr.s_addr == 0) {
+ bip_get_local_gateway(ifname, &BIP_Gateway_Addr);
+ }
}
const char *bip_get_interface(void)
@@ -814,6 +919,7 @@ void bip_cleanup(void)
/* these were set non-zero during interface configuration */
BIP_Address.s_addr = 0;
BIP_Broadcast_Addr.s_addr = 0;
+ BIP_Gateway_Addr.s_addr = 0;
return;
}
diff --git a/ports/linux/bip-init.c b/ports/linux/bip-init.c
index 4c347464f0..9b94ec257c 100644
--- a/ports/linux/bip-init.c
+++ b/ports/linux/bip-init.c
@@ -47,6 +47,8 @@ static struct in_addr BIP_Address;
static struct in_addr BIP_Broadcast_Addr;
/* IP netmask - stored here in network byte order */
static struct in_addr BIP_Netmask;
+/* IP gateway - stored here in network byte order */
+static struct in_addr BIP_Gateway_Addr;
/* broadcast binding mechanism */
static bool BIP_Broadcast_Binding_Address_Override;
static struct in_addr BIP_Broadcast_Binding_Address;
@@ -229,6 +231,25 @@ bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr)
return true;
}
+/**
+ * @brief Get the BACnet/IP default gateway address
+ * @param addr - network IPv4 address of the gateway
+ * @return true if a gateway address was found
+ *
+ * @note The gateway address is captured during ifname_default(),
+ * which is called from bip_init(). If bip_init() has not been
+ * called yet, the address will be zero.
+ */
+bool bip_get_gateway_addr(BACNET_IP_ADDRESS *addr)
+{
+ if (addr) {
+ memcpy(&addr->address[0], &BIP_Gateway_Addr.s_addr, 4);
+ addr->port = 0;
+ }
+
+ return (BIP_Gateway_Addr.s_addr != 0);
+}
+
/**
* @brief Set the BACnet/IP subnet mask CIDR prefix
* @return true if the subnet mask CIDR prefix is set
@@ -725,9 +746,9 @@ static char *ifname_default(void)
memset(rtInfo, 0, sizeof(struct route_info));
parseRoutes(nlMsg, rtInfo);
printRoute(rtInfo);
- if (BIP_Interface_Name[0] == 0) {
- if ((rtInfo->dstAddr == 0) && (rtInfo->ifName[0] != 0)) {
- /* default route */
+ if ((rtInfo->dstAddr == 0) && (rtInfo->ifName[0] != 0)) {
+ /* default route */
+ if (BIP_Interface_Name[0] == 0) {
memcpy(
BIP_Interface_Name, rtInfo->ifName,
sizeof(BIP_Interface_Name));
@@ -771,6 +792,58 @@ int bip_set_broadcast_binding(const char *ip4_broadcast)
return 0;
}
+/**
+ * @brief Find and set the default gateway for the interface via netlink
+ * @param ifname [in] The named interface
+ */
+static void bip_set_gateway(const char *ifname)
+{
+ struct nlmsghdr *nlMsg = NULL;
+ struct route_info *rtInfo = NULL;
+ char msgBuf[8192] = { 0 };
+ int sock, len, msgSeq = 0;
+
+ if (BIP_Gateway_Addr.s_addr != 0) {
+ return;
+ }
+ if ((sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) {
+ perror("Socket Creation: ");
+ return;
+ }
+ nlMsg = (struct nlmsghdr *)msgBuf;
+ nlMsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+ nlMsg->nlmsg_type = RTM_GETROUTE;
+ nlMsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
+ nlMsg->nlmsg_seq = msgSeq++;
+ nlMsg->nlmsg_pid = getpid();
+
+ if (send(sock, nlMsg, nlMsg->nlmsg_len, 0) < 0) {
+ debug_fprintf(stderr, "BIP: Write To Socket Failed...\n");
+ close(sock);
+ return;
+ }
+ if ((len = readNlSock(sock, msgBuf, sizeof(msgBuf), msgSeq, getpid())) <
+ 0) {
+ debug_fprintf(stderr, "BIP: Read From Socket Failed...\n");
+ close(sock);
+ return;
+ }
+
+ rtInfo = (struct route_info *)malloc(sizeof(struct route_info));
+ for (; NLMSG_OK(nlMsg, len); nlMsg = NLMSG_NEXT(nlMsg, len)) {
+ memset(rtInfo, 0, sizeof(struct route_info));
+ parseRoutes(nlMsg, rtInfo);
+ if ((rtInfo->dstAddr == 0) && (rtInfo->gateWay != 0)) {
+ if (ifname == NULL || strcmp(ifname, rtInfo->ifName) == 0) {
+ BIP_Gateway_Addr.s_addr = rtInfo->gateWay;
+ break;
+ }
+ }
+ }
+ free(rtInfo);
+ close(sock);
+}
+
/** Gets the local IP address and local broadcast address from the system,
* and saves it into the BACnet/IP data structures.
*
@@ -841,6 +914,7 @@ void bip_set_interface(const char *ifname)
ntohs(BIP_Port));
fflush(stderr);
}
+ bip_set_gateway(ifname);
}
const char *bip_get_interface(void)
@@ -1007,6 +1081,7 @@ void bip_cleanup(void)
BIP_Address.s_addr = 0;
BIP_Broadcast_Addr.s_addr = 0;
BIP_Netmask.s_addr = 0;
+ BIP_Gateway_Addr.s_addr = 0;
return;
}
diff --git a/ports/linux/dlmstp.c b/ports/linux/dlmstp.c
index 1cf85ad92d..9fe0d2fb92 100644
--- a/ports/linux/dlmstp.c
+++ b/ports/linux/dlmstp.c
@@ -926,7 +926,7 @@ bool dlmstp_init(char *ifname)
RS485_Set_Interface(ifname);
debug_fprintf(stderr, "MS/TP Interface: %s\n", ifname);
} else {
- ifname = (char *)RS485_Interface();
+ ifname = RS485_Interface();
}
pthread_condattr_init(&attr);
if ((rv = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC)) != 0) {
diff --git a/ports/posix/bacfile-posix.c b/ports/posix/bacfile-posix.c
index 5150fc20e9..6a3fbeee18 100644
--- a/ports/posix/bacfile-posix.c
+++ b/ports/posix/bacfile-posix.c
@@ -90,6 +90,8 @@ bool bacfile_posix_file_size_set(const char *pathname, size_t file_size)
* @brief Reads stream data from a file
* @param pathname - name of the file to read from
* @param fileStartPosition - starting position in the file
+ * If the 'File Start Position' parameter is either less than 0
+ * or exceeds the actual file size, then an error is returned.
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
* @return number of bytes read, or 0 if not successful
@@ -103,9 +105,18 @@ size_t bacfile_posix_read_stream_data(
FILE *pFile = NULL;
size_t len = 0;
+ if (fileStartPosition < 0) {
+ /* invalid file start position */
+ return 0;
+ }
if (filename_path_valid(pathname)) {
pFile = fopen(pathname, "rb");
if (pFile) {
+ if (fileStartPosition > fsize(pFile)) {
+ /* invalid file start position */
+ fclose(pFile);
+ return 0;
+ }
(void)fseek(pFile, fileStartPosition, SEEK_SET);
len = fread(fileData, 1, fileDataLen, pFile);
fclose(pFile);
@@ -121,6 +132,13 @@ size_t bacfile_posix_read_stream_data(
* @brief Writes stream data to a file
* @param pathname - name of the file to write to
* @param fileStartPosition - starting position in the file
+ * If the 'File Start Position' parameter exceeds the actual file size,
+ * then the file shall be extended to the size indicated,
+ * but the contents of any intervening octets or records
+ * shall be a local matter.
+ * If this parameter has the special value -1,
+ * then the write operation shall be treated
+ * as an append to the current end of file.
* @param fileData - data buffer to write from
* @param fileDataLen - size of the data buffer
* @return number of bytes written, or 0 if not successful
@@ -143,7 +161,7 @@ size_t bacfile_posix_write_stream_data(
value -1, then the write operation shall be treated
as an append to the current end of file. */
pFile = fopen(pathname, "ab+");
- } else {
+ } else if (fileStartPosition > 0) {
/* open for update */
pFile = fopen(pathname, "rb+");
}
@@ -165,6 +183,10 @@ size_t bacfile_posix_write_stream_data(
* @brief Writes record data to a file
* @param pathname - name of the file to write to
* @param fileStartRecord - starting record in the file
+ * If 'File Start Record' parameter has the special
+ * value -1, then the write operation shall be treated
+ * as an append to the current end of file,
+ * and fileIndexRecord can be ignored.
* @param fileIndexRecord - index of the record to read
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
@@ -195,7 +217,7 @@ bool bacfile_posix_write_record_data(
as an append to the current end of file. */
pFile = fopen(pathname, "ab+");
fileSeekRecord = fileIndexRecord;
- } else {
+ } else if (fileStartRecord > 0) {
/* open for update */
pFile = fopen(pathname, "rb+");
fileSeekRecord = fileStartRecord + fileIndexRecord;
@@ -226,6 +248,8 @@ bool bacfile_posix_write_record_data(
* @brief Reads record data from a file
* @param pathname - name of the file to read from
* @param fileStartRecord - starting record in the file
+ * If the 'File Start Record' parameter is either less than 0
+ * or exceeds the actual file size, then an error is returned.
* @param fileIndexRecord - index of the record to read
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
@@ -245,6 +269,10 @@ bool bacfile_posix_read_record_data(
const char *pData = NULL;
size_t fileSeekRecord = 0;
+ if (fileStartRecord < 0) {
+ /* invalid file start record */
+ return false;
+ }
if (filename_path_valid(pathname)) {
pFile = fopen(pathname, "rb");
if (pFile) {
@@ -257,7 +285,7 @@ bool bacfile_posix_read_record_data(
}
}
if ((i == fileSeekRecord) && (fileDataLen <= sizeof(dummy_data))) {
- /* copy the record data */
+ /* We found the record, so copy it */
memmove(fileData, &dummy_data[0], fileDataLen);
status = true;
}
diff --git a/ports/stm32f10x/device.c b/ports/stm32f10x/device.c
index 9ee76510c9..d2897d9819 100644
--- a/ports/stm32f10x/device.c
+++ b/ports/stm32f10x/device.c
@@ -190,13 +190,26 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool status = false;
struct my_object_functions *pObject = NULL;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* initialize the default return values */
pObject = Device_Objects_Find_Functions(wp_data->object_type);
if (pObject) {
if (pObject->Object_Valid_Instance &&
pObject->Object_Valid_Instance(wp_data->object_instance)) {
if (pObject->Object_Write_Property) {
- status = pObject->Object_Write_Property(wp_data);
+ if ((wp_data->application_data_len == 0) && !property_list_bacnet_list_member(
+ wp_data->object_type, wp_data->object_property)) {
+ /* only list properties can be written with
+ an empty application payload */
+ wp_data->error_class = ERROR_CLASS_SERVICES;
+ wp_data->error_code = ERROR_CODE_INVALID_TAG;
+ status = false;
+ } else {
+ status = pObject->Object_Write_Property(wp_data);
+ }
} else {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
diff --git a/ports/stm32f4xx/device.c b/ports/stm32f4xx/device.c
index 556f94ef70..0186d3da40 100644
--- a/ports/stm32f4xx/device.c
+++ b/ports/stm32f4xx/device.c
@@ -1206,7 +1206,7 @@ static bool Device_Write_Property_Object_Name(
{
bool status = false; /* return value */
int len = 0;
- BACNET_CHARACTER_STRING value;
+ BACNET_CHARACTER_STRING value = { 0 };
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
uint32_t object_instance = 0;
int apdu_size = 0;
@@ -1269,6 +1269,10 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool status = false;
struct my_object_functions *pObject = NULL;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* initialize the default return values */
wp_data->error_class = ERROR_CLASS_OBJECT;
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
@@ -1287,6 +1291,15 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
if (wp_data->object_property == PROP_OBJECT_NAME) {
status = Device_Write_Property_Object_Name(
wp_data, pObject->Object_Write_Property);
+ } else if (
+ (wp_data->application_data_len == 0) &&
+ !property_list_bacnet_list_member(
+ wp_data->object_type, wp_data->object_property)) {
+ /* only list properties can be written with
+ an empty application payload */
+ wp_data->error_class = ERROR_CLASS_SERVICES;
+ wp_data->error_code = ERROR_CODE_INVALID_TAG;
+ status = false;
} else {
status = pObject->Object_Write_Property(wp_data);
}
diff --git a/ports/win32/bip-init.c b/ports/win32/bip-init.c
index 7329cabbec..fcbdd456e9 100644
--- a/ports/win32/bip-init.c
+++ b/ports/win32/bip-init.c
@@ -36,6 +36,8 @@ static struct in_addr BIP_Broadcast_Addr;
/* broadcast binding mechanism */
static bool BIP_Broadcast_Binding_Address_Override;
static struct in_addr BIP_Broadcast_Binding_Address;
+/* IP gateway - stored here in network byte order */
+static struct in_addr BIP_Gateway_Addr;
/* enable debugging */
static bool BIP_Debug;
@@ -369,6 +371,21 @@ bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr)
return true;
}
+/**
+ * @brief Get the BACnet/IP default gateway address
+ * @param addr - network IPv4 address of the gateway
+ * @return true if a gateway address was found
+ */
+bool bip_get_gateway_addr(BACNET_IP_ADDRESS *addr)
+{
+ if (addr) {
+ memcpy(&addr->address[0], &BIP_Gateway_Addr.s_addr, 4);
+ addr->port = 0;
+ }
+
+ return (BIP_Gateway_Addr.s_addr != 0);
+}
+
/**
* @brief Set the BACnet/IP subnet mask CIDR prefix
* @return true if the subnet mask CIDR prefix is set
@@ -673,6 +690,48 @@ static uint32_t getIpMaskForIpAddress(uint32_t ipAddress)
return ipMask;
}
+/* returns the gateway in network byte order */
+static uint32_t getIpGatewayForIpAddress(uint32_t ipAddress)
+{
+ /* Allocate information for up to 16 NICs */
+ IP_ADAPTER_INFO AdapterInfo[16];
+ /* Save memory size of buffer */
+ DWORD dwBufLen = sizeof(AdapterInfo);
+ uint32_t ipGateway = 0;
+ bool found = false;
+
+ PIP_ADAPTER_INFO pAdapterInfo;
+
+ /* GetAdapterInfo:
+ [out] buffer to receive data
+ [in] size of receive data buffer */
+ DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufLen);
+ if (dwStatus == ERROR_SUCCESS) {
+ /* Verify return value is valid, no buffer overflow
+ Contains pointer to current adapter info */
+ pAdapterInfo = AdapterInfo;
+
+ do {
+ IP_ADDR_STRING *pIpAddressInfo = &pAdapterInfo->IpAddressList;
+ do {
+ unsigned long adapterAddress =
+ inet_addr(pIpAddressInfo->IpAddress.String);
+ if (adapterAddress == ipAddress) {
+ ipGateway =
+ inet_addr(pAdapterInfo->GatewayList.IpAddress.String);
+ found = true;
+ }
+ pIpAddressInfo = pIpAddressInfo->Next;
+ } while (pIpAddressInfo && !found);
+ /* Progress through linked list */
+ pAdapterInfo = pAdapterInfo->Next;
+ /* Terminate on last adapter */
+ } while (pAdapterInfo && !found);
+ }
+
+ return ipGateway;
+}
+
/**
* @brief Get the netmask of the BACnet/IP's interface via an ioctl() call.
* @param netmask [out] The netmask, in host order.
@@ -760,6 +819,10 @@ void bip_set_interface(const char *ifname)
if (BIP_Broadcast_Addr.s_addr == 0) {
set_broadcast_address(BIP_Address.s_addr);
}
+ /* setup local gateway address */
+ if (BIP_Gateway_Addr.s_addr == 0) {
+ BIP_Gateway_Addr.s_addr = getIpGatewayForIpAddress(BIP_Address.s_addr);
+ }
}
static SOCKET createSocket(const struct sockaddr_in *sin)
@@ -940,6 +1003,7 @@ void bip_cleanup(void)
/* these were set non-zero during interface configuration */
BIP_Address.s_addr = 0;
BIP_Broadcast_Addr.s_addr = 0;
+ BIP_Gateway_Addr.s_addr = 0;
return;
}
diff --git a/ports/win32/bip6.c b/ports/win32/bip6.c
index 34096a0ad4..19b3410a91 100644
--- a/ports/win32/bip6.c
+++ b/ports/win32/bip6.c
@@ -5,6 +5,14 @@
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*
*********************************************************************/
+/* IPv6 and the API (if_nametoindex) inherently requires
+ Vista or later, so ensure the _WIN32_WINNT is set appropriately */
+#if defined(__MINGW32__) || defined(__MINGW64__) || defined(_WIN32)
+#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600
+#endif
+#endif
#include
#include
#include /* for standard integer types uint8_t etc. */
diff --git a/ports/xplained/device.c b/ports/xplained/device.c
index c50224cb0c..03faad5ea1 100644
--- a/ports/xplained/device.c
+++ b/ports/xplained/device.c
@@ -136,13 +136,26 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool status = false;
struct my_object_functions *pObject = NULL;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* initialize the default return values */
pObject = Device_Objects_Find_Functions(wp_data->object_type);
if (pObject) {
if (pObject->Object_Valid_Instance &&
pObject->Object_Valid_Instance(wp_data->object_instance)) {
if (pObject->Object_Write_Property) {
- status = pObject->Object_Write_Property(wp_data);
+ if ((wp_data->application_data_len == 0) && !property_list_bacnet_list_member(
+ wp_data->object_type, wp_data->object_property)) {
+ /* only list properties can be written with
+ an empty application payload */
+ wp_data->error_class = ERROR_CLASS_SERVICES;
+ wp_data->error_code = ERROR_CODE_INVALID_TAG;
+ status = false;
+ } else {
+ status = pObject->Object_Write_Property(wp_data);
+ }
} else {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
diff --git a/release.sh b/release.sh
index d4ff61db98..4949f452b9 100755
--- a/release.sh
+++ b/release.sh
@@ -6,11 +6,10 @@
#
# Prior to running this script, be sure to:
# a) update CHANGELOG, version.h and CMakeLists.txt with new version number
-# b) commit changes into master branch
+# b) commit those changes into this long term branch bacnet-stack-1.5
# After running this script, be sure to:
-# c) create long term branch as bacnet-stack-x.y if needed
-# d) push tags and branch to github
-# e) mirror github to sourceforge using mirror.sh script
+# c) push tags and branch to github
+# d) mirror github to sourceforge using mirror.sh
USERNAME='skarg'
diff --git a/src/bacnet/arf.c b/src/bacnet/arf.c
index ba8ff003b9..1bfe683325 100644
--- a/src/bacnet/arf.c
+++ b/src/bacnet/arf.c
@@ -334,6 +334,7 @@ int arf_ack_service_encode_apdu(
int apdu_len = 0; /* total length of the apdu, return value */
int len = 0;
uint32_t i = 0;
+ BACNET_UNSIGNED_INTEGER record_count = 0;
/* endOfFile */
len = encode_application_boolean(apdu, data->endOfFile);
@@ -374,13 +375,17 @@ int arf_ack_service_encode_apdu(
if (apdu) {
apdu += len;
}
- len = encode_application_unsigned(
- apdu, data->type.record.RecordCount);
+ /* restrict the record count to the size of the fileData array */
+ record_count = data->type.record.RecordCount;
+ if (record_count > ARRAY_SIZE(data->fileData)) {
+ record_count = ARRAY_SIZE(data->fileData);
+ }
+ len = encode_application_unsigned(apdu, record_count);
apdu_len += len;
if (apdu) {
apdu += len;
}
- for (i = 0; i < data->type.record.RecordCount; i++) {
+ for (i = 0; i < record_count; i++) {
len = encode_application_octet_string(apdu, &data->fileData[i]);
apdu_len += len;
if (apdu) {
@@ -488,6 +493,7 @@ int arf_ack_decode_service_request(
if (data) {
octet_string = &data->fileData[0];
}
+ octetstring_init(octet_string, NULL, 0);
len = bacnet_octet_string_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, octet_string);
if (len <= 0) {
@@ -521,6 +527,9 @@ int arf_ack_decode_service_request(
if (len <= 0) {
return BACNET_STATUS_ERROR;
}
+ if (record_count > BACNET_READ_FILE_RECORD_COUNT) {
+ return BACNET_STATUS_ERROR;
+ }
if (data) {
data->type.record.RecordCount = record_count;
}
@@ -534,6 +543,7 @@ int arf_ack_decode_service_request(
} else {
octet_string = NULL;
}
+ octetstring_init(octet_string, NULL, 0);
len = bacnet_octet_string_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, octet_string);
if (len <= 0) {
diff --git a/src/bacnet/authentication_factor.c b/src/bacnet/authentication_factor.c
index d40d2e086b..1821b279e8 100644
--- a/src/bacnet/authentication_factor.c
+++ b/src/bacnet/authentication_factor.c
@@ -205,3 +205,25 @@ int bacapp_decode_context_authentication_factor(
return bacnet_authentication_factor_context_decode(apdu, MAX_APDU, tag, af);
}
#endif
+
+/**
+ * @brief Compare two BACnetAuthenticationFactor values for equality
+ * @param value Pointer to the first value to compare
+ * @param test_value Pointer to the second value to compare
+ * @return true if the values are the same, false otherwise
+ */
+bool bacnet_authentication_factor_same(
+ const BACNET_AUTHENTICATION_FACTOR *value,
+ const BACNET_AUTHENTICATION_FACTOR *test_value)
+{
+ if (value == NULL || test_value == NULL) {
+ return false;
+ }
+ if (value->format_type != test_value->format_type) {
+ return false;
+ }
+ if (value->format_class != test_value->format_class) {
+ return false;
+ }
+ return octetstring_value_same(&value->value, &test_value->value);
+}
diff --git a/src/bacnet/authentication_factor.h b/src/bacnet/authentication_factor.h
index cf3ac3435e..f19e297008 100644
--- a/src/bacnet/authentication_factor.h
+++ b/src/bacnet/authentication_factor.h
@@ -13,7 +13,7 @@
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
-#include "bacnet/bacapp.h"
+#include "bacnet/bacstr.h"
typedef struct BACnetAuthenticationFactor {
BACNET_AUTHENTICATION_FACTOR_TYPE format_type;
@@ -51,6 +51,10 @@ BACNET_STACK_DEPRECATED(
BACNET_STACK_EXPORT
int bacapp_decode_context_authentication_factor(
const uint8_t *apdu, uint8_t tag, BACNET_AUTHENTICATION_FACTOR *af);
+BACNET_STACK_EXPORT
+bool bacnet_authentication_factor_same(
+ const BACNET_AUTHENTICATION_FACTOR *value,
+ const BACNET_AUTHENTICATION_FACTOR *test_value);
#ifdef __cplusplus
}
diff --git a/src/bacnet/authentication_factor_format.c b/src/bacnet/authentication_factor_format.c
index 3e15b1fdc6..2808f2f81a 100644
--- a/src/bacnet/authentication_factor_format.c
+++ b/src/bacnet/authentication_factor_format.c
@@ -223,3 +223,34 @@ int bacapp_decode_context_authentication_factor_format(
apdu, MAX_APDU, tag, data);
}
#endif
+
+/**
+ * @brief Compare two BACNET_AUTHENTICATION_FACTOR_FORMAT structures
+ * @param data1 Pointer to the first structure to compare
+ * @param data2 Pointer to the second structure to compare
+ * @return true if the structures are the same, false otherwise
+ * @details The structures are considered the same if they have the same format
+ * type, and if the format type is CUSTOM, they must also have the same vendor
+ * ID and vendor format.
+ */
+bool bacnet_authentication_factor_format_same(
+ const BACNET_AUTHENTICATION_FACTOR_FORMAT *data1,
+ const BACNET_AUTHENTICATION_FACTOR_FORMAT *data2)
+{
+ if (!data1 || !data2) {
+ return false;
+ }
+ if (data1->format_type != data2->format_type) {
+ return false;
+ }
+ if (data1->format_type == AUTHENTICATION_FACTOR_CUSTOM) {
+ if (data1->vendor_id != data2->vendor_id) {
+ return false;
+ }
+ if (data1->vendor_format != data2->vendor_format) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/src/bacnet/authentication_factor_format.h b/src/bacnet/authentication_factor_format.h
index 741d473d2b..4d64e9c9b4 100644
--- a/src/bacnet/authentication_factor_format.h
+++ b/src/bacnet/authentication_factor_format.h
@@ -55,6 +55,11 @@ int bacapp_decode_context_authentication_factor_format(
uint8_t tag_number,
BACNET_AUTHENTICATION_FACTOR_FORMAT *aff);
+BACNET_STACK_EXPORT
+bool bacnet_authentication_factor_format_same(
+ const BACNET_AUTHENTICATION_FACTOR_FORMAT *data1,
+ const BACNET_AUTHENTICATION_FACTOR_FORMAT *data2);
+
#ifdef __cplusplus
}
#endif /* __cplusplus */
diff --git a/src/bacnet/awf.c b/src/bacnet/awf.c
index ad822974f3..76fdaf2bf4 100644
--- a/src/bacnet/awf.c
+++ b/src/bacnet/awf.c
@@ -226,6 +226,7 @@ int awf_decode_service_request(
if (data) {
octet_string = &data->fileData[0];
}
+ octetstring_init(octet_string, NULL, 0);
len = bacnet_octet_string_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, octet_string);
if (len <= 0) {
@@ -274,6 +275,7 @@ int awf_decode_service_request(
} else {
octet_string = NULL;
}
+ octetstring_init(octet_string, NULL, 0);
len = bacnet_octet_string_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, octet_string);
if (len <= 0) {
diff --git a/src/bacnet/bacaddr.c b/src/bacnet/bacaddr.c
index a48ca86ce7..1da62889aa 100644
--- a/src/bacnet/bacaddr.c
+++ b/src/bacnet/bacaddr.c
@@ -772,6 +772,32 @@ bool bacnet_vmac_address_set(BACNET_ADDRESS *addr, uint32_t device_id)
return status;
}
+/**
+ * @brief Encode the BACnetAddressBinding entry
+ * @param apdu Pointer to the APDU, or NULL for length calculation.
+ * @param device_id Device ID to encode.
+ * @param address Pointer to the BACnet address to encode.
+ * @return Count of encoded bytes.
+ */
+int bacnet_address_binding_entry_encode(
+ uint8_t *apdu, uint32_t device_id, const BACNET_ADDRESS *address)
+{
+ int len = 0, apdu_len = 0;
+
+ if (!address) {
+ return 0;
+ }
+ len = encode_application_object_id(apdu, OBJECT_DEVICE, device_id);
+ apdu_len += len;
+ if (apdu) {
+ apdu += len;
+ }
+ len = encode_bacnet_address(apdu, address);
+ apdu_len += len;
+
+ return apdu_len;
+}
+
/**
* @brief Encode a given BACnetAddressBinding
* @details
@@ -785,21 +811,11 @@ bool bacnet_vmac_address_set(BACNET_ADDRESS *addr, uint32_t device_id)
int bacnet_address_binding_type_encode(
uint8_t *apdu, const BACNET_ADDRESS_BINDING *value)
{
- int apdu_len = 0, len = 0;
-
if (!value) {
return 0;
}
- len = encode_application_object_id(
- apdu, OBJECT_DEVICE, value->device_identifier);
- apdu_len += len;
- if (apdu) {
- apdu += len;
- }
- len = encode_bacnet_address(apdu, &value->device_address);
- apdu_len += len;
-
- return apdu_len;
+ return bacnet_address_binding_entry_encode(
+ apdu, value->device_identifier, &value->device_address);
}
/**
diff --git a/src/bacnet/bacaddr.h b/src/bacnet/bacaddr.h
index d566f4ff71..ad69dc7a6f 100644
--- a/src/bacnet/bacaddr.h
+++ b/src/bacnet/bacaddr.h
@@ -109,6 +109,9 @@ int bacnet_vmac_entry_decode(
BACNET_STACK_EXPORT
bool bacnet_vmac_address_set(BACNET_ADDRESS *addr, uint32_t device_id);
+BACNET_STACK_EXPORT
+int bacnet_address_binding_entry_encode(
+ uint8_t *apdu, uint32_t device_id, const BACNET_ADDRESS *address);
BACNET_STACK_EXPORT
int bacnet_address_binding_type_encode(
uint8_t *apdu, const BACNET_ADDRESS_BINDING *value);
diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c
index 6dd5885b67..f4fa485935 100644
--- a/src/bacnet/bacapp.c
+++ b/src/bacnet/bacapp.c
@@ -409,6 +409,18 @@ int bacapp_encode_application_data(
apdu_len = bacnet_timer_value_no_value_encode(apdu);
break;
#endif
+#if defined(BACAPP_AUTHENTICATION)
+ case BACNET_APPLICATION_TAG_AUTHENTICATION_FORMAT:
+ /* BACnetAuthenticationFactorFormat */
+ apdu_len = bacapp_encode_authentication_factor_format(
+ apdu, &value->type.Authentication_Format);
+ break;
+ case BACNET_APPLICATION_TAG_AUTHENTICATION_FACTOR:
+ /* BACnetAuthenticationFactor */
+ apdu_len = bacapp_encode_authentication_factor(
+ apdu, &value->type.Authentication_Factor);
+ break;
+#endif
#if defined(BACAPP_LOG_RECORD)
case BACNET_APPLICATION_TAG_LOG_RECORD:
/* BACnetLogRecord */
@@ -1138,6 +1150,9 @@ int bacapp_known_property_tag(
} else if (object_type == OBJECT_CHANNEL) {
/* Properties using BACnetChannelValue */
return BACNET_APPLICATION_TAG_CHANNEL_VALUE;
+ } else if (object_type == OBJECT_CREDENTIAL_DATA_INPUT) {
+ /* Properties using BACnetAuthenticationFactor */
+ return BACNET_APPLICATION_TAG_AUTHENTICATION_FACTOR;
}
/* note: primitive application tagged present-values return '-1' */
return -1;
@@ -1193,7 +1208,9 @@ int bacapp_known_property_tag(
case PROP_SLAVE_ADDRESS_BINDING:
/* BACnetAddressBinding */
return BACNET_APPLICATION_TAG_ADDRESS_BINDING;
-
+ case PROP_SUPPORTED_FORMATS:
+ /* BACnetAuthenticationFactorFormat */
+ return BACNET_APPLICATION_TAG_AUTHENTICATION_FORMAT;
case PROP_LOG_BUFFER:
/* BACnetLogRecord */
return BACNET_APPLICATION_TAG_LOG_RECORD;
@@ -1335,6 +1352,7 @@ int bacapp_decode_application_tag_value(
#endif
#if defined(BACAPP_OCTET_STRING)
case BACNET_APPLICATION_TAG_OCTET_STRING:
+ octetstring_init(&value->type.Octet_String, NULL, 0);
apdu_len = bacnet_octet_string_application_decode(
apdu, apdu_size, &value->type.Octet_String);
if (apdu_len == 0) {
@@ -1346,6 +1364,7 @@ int bacapp_decode_application_tag_value(
#endif
#if defined(BACAPP_CHARACTER_STRING)
case BACNET_APPLICATION_TAG_CHARACTER_STRING:
+ characterstring_init_ansi(&value->type.Character_String, "");
apdu_len = bacnet_character_string_application_decode(
apdu, apdu_size, &value->type.Character_String);
if (apdu_len == 0) {
@@ -1357,6 +1376,7 @@ int bacapp_decode_application_tag_value(
#endif
#if defined(BACAPP_BIT_STRING)
case BACNET_APPLICATION_TAG_BIT_STRING:
+ bitstring_init(&value->type.Bit_String);
apdu_len = bacnet_bitstring_application_decode(
apdu, apdu_size, &value->type.Bit_String);
if (apdu_len == 0) {
@@ -1573,6 +1593,19 @@ int bacapp_decode_application_tag_value(
apdu, apdu_size, &value->type.Address_Binding);
break;
#endif
+#if defined(BACAPP_AUTHENTICATION)
+ case BACNET_APPLICATION_TAG_AUTHENTICATION_FORMAT:
+ /* BACnetAuthenticationFactorFormat */
+ apdu_len = bacnet_authentication_factor_format_decode(
+ apdu, apdu_size, &value->type.Authentication_Format);
+ break;
+ case BACNET_APPLICATION_TAG_AUTHENTICATION_FACTOR:
+ /* BACnetAuthenticationFactor */
+ apdu_len = bacnet_authentication_factor_decode(
+ apdu, apdu_size, &value->type.Authentication_Factor);
+ break;
+
+#endif
#if defined(BACAPP_LOG_RECORD)
case BACNET_APPLICATION_TAG_LOG_RECORD:
/* BACnetLogRecord */
@@ -1629,7 +1662,7 @@ int bacapp_decode_known_array_property(
int apdu_len = 0;
int tag;
- if (bacnet_is_closing_tag(apdu, apdu_size)) {
+ if ((apdu_size == 0) || bacnet_is_closing_tag(apdu, apdu_size)) {
if (value) {
value->tag = BACNET_APPLICATION_TAG_EMPTYLIST;
}
@@ -3755,6 +3788,29 @@ static int bacapp_snprintf_action_command(
}
#endif
+#if defined(BACAPP_AUTHENTICATION)
+int bacapp_snprintf_authentication_factor(
+ char *str, size_t str_len, const BACNET_AUTHENTICATION_FACTOR *value)
+{
+ int slen;
+ int ret_val = 0;
+
+ slen = bacapp_snprintf(str, str_len, "{");
+ ret_val += bacapp_snprintf_shift(slen, &str, &str_len);
+ slen = bacapp_snprintf(
+ str, str_len, "%s,%lu,",
+ bactext_authentication_factor_type_name(value->format_type),
+ (unsigned long)value->format_class);
+ ret_val += bacapp_snprintf_shift(slen, &str, &str_len);
+ slen = bacapp_snprintf_octet_string(str, str_len, &value->value);
+ ret_val += bacapp_snprintf_shift(slen, &str, &str_len);
+ slen = bacapp_snprintf(str, str_len, "}");
+ ret_val += bacapp_snprintf_shift(slen, &str, &str_len);
+
+ return ret_val;
+}
+#endif
+
/**
* @brief Extract the value into a text string
* @param str - the buffer to store the extracted value, or NULL for length
@@ -4053,6 +4109,22 @@ int bacapp_snprintf_value(
ret_val = bacnet_timer_value_no_value_to_ascii(str, str_len);
break;
#endif
+#if defined(BACAPP_AUTHENTICATION)
+ case BACNET_APPLICATION_TAG_AUTHENTICATION_FORMAT:
+ ret_val = bacapp_snprintf(
+ str, str_len, "{%s,%lu,%lu}",
+ bactext_authentication_factor_type_name(
+ value->type.Authentication_Format.format_type),
+ (unsigned long)value->type.Authentication_Format.vendor_id,
+ (unsigned long)
+ value->type.Authentication_Format.vendor_format);
+ break;
+ case BACNET_APPLICATION_TAG_AUTHENTICATION_FACTOR:
+ /* BACnetAuthenticationFactor */
+ ret_val = bacapp_snprintf_authentication_factor(
+ str, str_len, &value->type.Authentication_Factor);
+ break;
+#endif
#if defined(BACAPP_LOG_RECORD)
case BACNET_APPLICATION_TAG_LOG_RECORD:
ret_val = bacapp_snprintf_log_record(
@@ -4798,9 +4870,82 @@ special_event_from_ascii(BACNET_APPLICATION_DATA_VALUE *value, char *str)
}
#endif /* BACAPP_SPECIAL_EVENT */
-/* used to load the app data struct with the proper data
- converted from a command line argument.
- "argv" is not const to allow using strtok internally. It MAY be modified. */
+#if defined(BACAPP_AUTHENTICATION)
+/**
+ * @brief Parse a string into a BACnetAuthenticationFactorFormat value
+ * @param value [out] The BACnetAuthenticationFactorFormat value
+ * @param argv [in] The string to parse
+ * @return True on success, else False
+ */
+static bool bacnet_authentication_format_from_ascii(
+ BACNET_AUTHENTICATION_FACTOR_FORMAT *value, const char *argv)
+{
+ bool status = false;
+ int count;
+ unsigned long format_type, vendor_id, vendor_format;
+
+ if (!status) {
+ count = sscanf(
+ argv, "%lu,%lu,%lu", &format_type, &vendor_id, &vendor_format);
+ if (count == 3) {
+ /* optional fields are required when Format-Type
+ field has a value of CUSTOM. */
+ value->format_type = (BACNET_AUTHENTICATION_FACTOR_TYPE)format_type;
+ value->vendor_id = (uint32_t)vendor_id;
+ value->vendor_format = (uint32_t)vendor_format;
+ status = true;
+ } else if (count == 1) {
+ value->format_type = (BACNET_AUTHENTICATION_FACTOR_TYPE)format_type;
+ value->vendor_id = 0;
+ value->vendor_format = 0;
+ status = true;
+ }
+ }
+
+ return status;
+}
+/**
+ * @brief Parse a string into a BACnetAuthenticationFactor value
+ * @param value [out] The BACnetAuthenticationFactor value
+ * @param argv [in] The string to parse
+ * @return True on success, else False
+ */
+static bool bacnet_authentication_factor_from_ascii(
+ BACNET_AUTHENTICATION_FACTOR *value, const char *argv)
+{
+ bool status = false;
+ int count;
+ unsigned long format_type, format_class;
+ char factor_value[256] = { 0 };
+
+ count = sscanf(
+ argv, "%lu,%lu,%255s", &format_type, &format_class, factor_value);
+ if (count == 3) {
+ value->format_type = (BACNET_AUTHENTICATION_FACTOR_TYPE)format_type;
+ value->format_class = (uint32_t)format_class;
+ octetstring_init_ascii_epics(&value->value, factor_value);
+ status = true;
+ }
+
+ return status;
+}
+#endif
+
+/**
+ * @brief Load the app data struct with the proper data converted from a command
+ * line argument. "argv" is not const to allow using strtok internally. It MAY
+ * be modified.
+ * @param tag_number The expected application tag number of the value to parse.
+ * This is used to determine how to parse the argv string.
+ * @param argv The string to parse into the value struct. This is typically a
+ * command line argument, and may be modified by this function.
+ * @param value [out] The BACNET_APPLICATION_DATA_VALUE struct to load with the
+ * parsed value. The tag field will be set to tag_number, and the type field
+ * will be set based on the tag. The value field will be set to the parsed value
+ * from argv.
+ * @return true if the data was successfully parsed and loaded into the value
+ * struct, else false
+ */
bool bacapp_parse_application_data(
BACNET_APPLICATION_TAG tag_number,
char *argv,
@@ -5108,6 +5253,18 @@ bool bacapp_parse_application_data(
bacnet_timer_value_no_value_from_ascii(&value->tag, argv);
break;
#endif
+#if defined(BACAPP_AUTHENTICATION)
+ case BACNET_APPLICATION_TAG_AUTHENTICATION_FORMAT:
+ /* BACnetAuthenticationFactorFormat */
+ status = bacnet_authentication_format_from_ascii(
+ &value->type.Authentication_Format, argv);
+ break;
+ case BACNET_APPLICATION_TAG_AUTHENTICATION_FACTOR:
+ /* BACnetAuthenticationFactor */
+ status = bacnet_authentication_factor_from_ascii(
+ &value->type.Authentication_Factor, argv);
+ break;
+#endif
#if defined(BACAPP_LOG_RECORD)
case BACNET_APPLICATION_TAG_LOG_RECORD:
status = bacnet_log_record_datum_from_ascii(
@@ -5876,6 +6033,20 @@ bool bacapp_same_value(
}
break;
#endif
+#if defined(BACAPP_AUTHENTICATION)
+ case BACNET_APPLICATION_TAG_AUTHENTICATION_FORMAT:
+ /* BACnetAuthenticationFactorFormat */
+ status = bacnet_authentication_factor_format_same(
+ &value->type.Authentication_Format,
+ &test_value->type.Authentication_Format);
+ break;
+ case BACNET_APPLICATION_TAG_AUTHENTICATION_FACTOR:
+ /* BACnetAuthenticationFactor */
+ status = bacnet_authentication_factor_same(
+ &value->type.Authentication_Factor,
+ &test_value->type.Authentication_Factor);
+ break;
+#endif
#if defined(BACAPP_LOG_RECORD)
case BACNET_APPLICATION_TAG_LOG_RECORD:
status = bacnet_log_record_same(
diff --git a/src/bacnet/bacapp.h b/src/bacnet/bacapp.h
index 7971739c7b..f6066f69e3 100644
--- a/src/bacnet/bacapp.h
+++ b/src/bacnet/bacapp.h
@@ -16,6 +16,8 @@
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/access_rule.h"
+#include "bacnet/authentication_factor.h"
+#include "bacnet/authentication_factor_format.h"
#include "bacnet/bacaction.h"
#include "bacnet/bacaddr.h"
#include "bacnet/bacdest.h"
@@ -185,6 +187,10 @@ typedef struct BACnet_Application_Data_Value {
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS SC_Hub_Function_Status;
BACNET_SC_DIRECT_CONNECTION_STATUS SC_Direct_Status;
BACNET_SC_HUB_CONNECTION_STATUS SC_Hub_Status;
+#endif
+#if defined(BACAPP_AUTHENTICATION)
+ BACNET_AUTHENTICATION_FACTOR_FORMAT Authentication_Format;
+ BACNET_AUTHENTICATION_FACTOR Authentication_Factor;
#endif
} type;
/* simple linked list if needed */
diff --git a/src/bacnet/bacdevobjpropref.c b/src/bacnet/bacdevobjpropref.c
index 76f1062fc4..e228945a42 100644
--- a/src/bacnet/bacdevobjpropref.c
+++ b/src/bacnet/bacdevobjpropref.c
@@ -489,8 +489,10 @@ int bacnet_device_object_reference_decode(
return BACNET_STATUS_ERROR;
} else {
/* OPTIONAL - skip apdu_len increment */
- value->deviceIdentifier.type = BACNET_NO_DEV_TYPE;
- value->deviceIdentifier.instance = BACNET_NO_DEV_ID;
+ if (value) {
+ value->deviceIdentifier.type = BACNET_NO_DEV_TYPE;
+ value->deviceIdentifier.instance = BACNET_NO_DEV_ID;
+ }
}
/* object-identifier [1] BACnetObjectIdentifier */
len = bacnet_object_id_context_decode(
diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h
index dc95f2d1f6..7e0432cadd 100644
--- a/src/bacnet/bacenum.h
+++ b/src/bacnet/bacenum.h
@@ -1705,6 +1705,10 @@ typedef enum {
BACNET_APPLICATION_TAG_ADDRESS_BINDING,
/* no-value - context tagged null */
BACNET_APPLICATION_TAG_NO_VALUE,
+ /* BACnetAuthenticationFactorFormat */
+ BACNET_APPLICATION_TAG_AUTHENTICATION_FORMAT,
+ /* BACnetAuthenticationFactor */
+ BACNET_APPLICATION_TAG_AUTHENTICATION_FACTOR,
/* ABSTRACT-SYNTAX - constructed value */
BACNET_APPLICATION_TAG_ABSTRACT_SYNTAX,
/* == mark the end of this list == */
@@ -2965,10 +2969,14 @@ typedef enum BACnetAuthenticationDisableReason {
AUTHENTICATION_DISABLED_STOLEN = 3,
AUTHENTICATION_DISABLED_DAMAGED = 4,
AUTHENTICATION_DISABLED_DESTROYED = 5,
- AUTHENTICATION_DISABLED_MAX = 6
+ AUTHENTICATION_DISABLED_MAX = 6,
+ AUTHENTICATION_DISABLED_RESERVED_MIN = 6,
+ AUTHENTICATION_DISABLED_RESERVED_MAX = 63,
/* Enumerated values 0-63 are reserved for definition by ASHRAE.
Enumerated values 64-65535 may be used by others subject to
the procedures and constraints described in Clause 23. */
+ AUTHENTICATION_DISABLED_PROPRIETARY_MIN = 64,
+ AUTHENTICATION_DISABLED_PROPRIETARY_MAX = 65535
} BACNET_AUTHENTICATION_DISABLE_REASON;
typedef enum BACnetAuthenticationFactorType {
diff --git a/src/bacnet/bactext.c b/src/bacnet/bactext.c
index 6918abdedc..22c9e04193 100644
--- a/src/bacnet/bactext.c
+++ b/src/bacnet/bactext.c
@@ -191,6 +191,10 @@ INDTEXT_DATA bacnet_application_tag_names[] = {
{ BACNET_APPLICATION_TAG_TIMER_VALUE, "BACnetTimerStateChangeValue" },
{ BACNET_APPLICATION_TAG_ADDRESS_BINDING, "BACnetAddressBinding" },
{ BACNET_APPLICATION_TAG_NO_VALUE, "BACnetNoValue" },
+ { BACNET_APPLICATION_TAG_AUTHENTICATION_FORMAT,
+ "BACnetAuthenticationFormat" },
+ { BACNET_APPLICATION_TAG_AUTHENTICATION_FACTOR,
+ "BACnetAuthenticationFactor" },
{ BACNET_APPLICATION_TAG_ABSTRACT_SYNTAX, "ABSTRACT-SYNTAX" },
{ 0, NULL }
};
@@ -3379,6 +3383,65 @@ const char *bactext_authentication_status_name(uint32_t index)
bactext_authentication_status_names, index, ASHRAE_Reserved_String);
}
+INDTEXT_DATA bactext_authentication_disable_reason_names[] = {
+ /* BACnetAuthenticationDisableReason enumerations */
+ { AUTHENTICATION_NONE, "none" },
+ { AUTHENTICATION_DISABLED, "disabled" },
+ { AUTHENTICATION_DISABLED_LOST, "lost" },
+ { AUTHENTICATION_DISABLED_STOLEN, "stolen" },
+ { AUTHENTICATION_DISABLED_DAMAGED, "damaged" },
+ { AUTHENTICATION_DISABLED_DESTROYED, "destroyed" },
+ { 0, NULL }
+};
+
+const char *bactext_authentication_disable_reason_name(uint32_t index)
+{
+ /* Enumerated values 0-63 are reserved for definition by ASHRAE.
+ Enumerated values 64-65535 may be used by others subject to
+ the procedures and constraints described in Clause 23. */
+ return indtext_by_index_split_default(
+ bactext_authentication_disable_reason_names, index,
+ AUTHENTICATION_DISABLED_PROPRIETARY_MIN, ASHRAE_Reserved_String,
+ Vendor_Proprietary_String);
+}
+
+INDTEXT_DATA bactext_authentication_factor_type_names[] = {
+ /* BACnetAuthenticationFactorType enumerations */
+ { AUTHENTICATION_FACTOR_UNDEFINED, "undefined" },
+ { AUTHENTICATION_FACTOR_ERROR, "error" },
+ { AUTHENTICATION_FACTOR_CUSTOM, "custom" },
+ { AUTHENTICATION_FACTOR_SIMPLE_NUMBER16, "simple-number16" },
+ { AUTHENTICATION_FACTOR_SIMPLE_NUMBER32, "simple-number32" },
+ { AUTHENTICATION_FACTOR_SIMPLE_NUMBER56, "simple-number56" },
+ { AUTHENTICATION_FACTOR_SIMPLE_ALPHA_NUMERIC, "simple-alpha-numeric" },
+ { AUTHENTICATION_FACTOR_ABA_TRACK2, "aba-track2" },
+ { AUTHENTICATION_FACTOR_WIEGAND26, "wiegand26" },
+ { AUTHENTICATION_FACTOR_WIEGAND37, "wiegand37" },
+ { AUTHENTICATION_FACTOR_WIEGAND37_FACILITY, "wiegand37-facility" },
+ { AUTHENTICATION_FACTOR_FACILITY16_CARD32, "facility16-card32" },
+ { AUTHENTICATION_FACTOR_FACILITY32_CARD32, "facility32-card32" },
+ { AUTHENTICATION_FACTOR_FASC_N, "fasc-n" },
+ { AUTHENTICATION_FACTOR_FASC_N_BCD, "fasc-n-bcd" },
+ { AUTHENTICATION_FACTOR_FASC_N_LARGE, "fasc-n-large" },
+ { AUTHENTICATION_FACTOR_FASC_N_LARGE_BCD, "fasc-n-large-bcd" },
+ { AUTHENTICATION_FACTOR_GSA75, "gsa75" },
+ { AUTHENTICATION_FACTOR_CHUID, "chuid" },
+ { AUTHENTICATION_FACTOR_CHUID_FULL, "chuid-full" },
+ { AUTHENTICATION_FACTOR_GUID, "guid" },
+ { AUTHENTICATION_FACTOR_CBEFF_A, "cbeff-a" },
+ { AUTHENTICATION_FACTOR_CBEFF_B, "cbeff-b" },
+ { AUTHENTICATION_FACTOR_CBEFF_C, "cbeff-c" },
+ { AUTHENTICATION_FACTOR_USER_PASSWORD, "user-password" },
+ { 0, NULL }
+};
+
+const char *bactext_authentication_factor_type_name(uint32_t index)
+{
+ return indtext_by_index_default(
+ bactext_authentication_factor_type_names, index,
+ ASHRAE_Reserved_String);
+}
+
INDTEXT_DATA bactext_authorization_mode_names[] = {
/* BACnetAuthorizationMode enumerations */
{ AUTHORIZATION_MODE_AUTHORIZE, "authorize" },
diff --git a/src/bacnet/bactext.h b/src/bacnet/bactext.h
index db042f8e71..b49313fd89 100644
--- a/src/bacnet/bactext.h
+++ b/src/bacnet/bactext.h
@@ -385,6 +385,10 @@ bactext_access_event_name_default(uint32_t index, const char *default_string);
BACNET_STACK_EXPORT
const char *bactext_authentication_status_name(uint32_t index);
+BACNET_STACK_EXPORT
+const char *bactext_authentication_disable_reason_name(uint32_t index);
+BACNET_STACK_EXPORT
+const char *bactext_authentication_factor_type_name(uint32_t index);
BACNET_STACK_EXPORT
const char *bactext_authorization_mode_name(uint32_t index);
diff --git a/src/bacnet/basic/binding/address.c b/src/bacnet/basic/binding/address.c
index 48ba0d3d68..b031d1308b 100644
--- a/src/bacnet/basic/binding/address.c
+++ b/src/bacnet/basic/binding/address.c
@@ -753,59 +753,46 @@ unsigned address_count(void)
* property. Basically encode the address list to be send out.
*
* @param apdu Pointer to the APDU
- * @param apdu_len Remaining buffer size.
+ * @param apdu_size Remaining buffer size.
*
* @return Count of encoded bytes.
*/
-int address_list_encode(uint8_t *apdu, unsigned apdu_len)
+int address_list_encode(uint8_t *apdu, unsigned apdu_size)
{
- int iLen = 0;
+ int len = 0, apdu_len = 0;
struct Address_Cache_Entry *pMatch;
- BACNET_OCTET_STRING MAC_Address;
unsigned index;
- /* Look for matching address. */
+ /* determine the length of the encoded address list */
for (index = 0; index < MAX_ADDRESS_CACHE; index++) {
pMatch = &Address_Cache[index];
if ((pMatch->Flags & (BAC_ADDR_IN_USE | BAC_ADDR_BIND_REQ)) ==
BAC_ADDR_IN_USE) {
- iLen += encode_application_object_id(
- &apdu[iLen], OBJECT_DEVICE, pMatch->device_id);
- iLen +=
- encode_application_unsigned(&apdu[iLen], pMatch->address.net);
- if ((unsigned)iLen >= apdu_len) {
- break;
- }
-
- /* pick the appropriate type of entry from the cache */
-
- if (pMatch->address.len != 0) {
- /* BAC */
- if ((unsigned)(iLen + pMatch->address.len) >= apdu_len) {
- break;
- }
- octetstring_init(
- &MAC_Address, pMatch->address.adr, pMatch->address.len);
- iLen +=
- encode_application_octet_string(&apdu[iLen], &MAC_Address);
- } else {
- /* MAC*/
- if ((unsigned)(iLen + pMatch->address.mac_len) >= apdu_len) {
- break;
+ /* encode matching addresses */
+ len = bacnet_address_binding_entry_encode(
+ NULL, pMatch->device_id, &pMatch->address);
+ apdu_len += len;
+ }
+ }
+ /* encode the address list if there is enough space */
+ if (apdu) {
+ if (apdu_len > (int)apdu_size) {
+ apdu_len = BACNET_STATUS_ABORT;
+ } else {
+ for (index = 0; index < MAX_ADDRESS_CACHE; index++) {
+ pMatch = &Address_Cache[index];
+ if ((pMatch->Flags & (BAC_ADDR_IN_USE | BAC_ADDR_BIND_REQ)) ==
+ BAC_ADDR_IN_USE) {
+ /* encode matching addresses */
+ len = bacnet_address_binding_entry_encode(
+ apdu, pMatch->device_id, &pMatch->address);
+ apdu += len;
}
- octetstring_init(
- &MAC_Address, pMatch->address.mac, pMatch->address.mac_len);
- iLen +=
- encode_application_octet_string(&apdu[iLen], &MAC_Address);
- }
- /* Any space left? */
- if ((unsigned)iLen >= apdu_len) {
- break;
}
}
}
- return (iLen);
+ return apdu_len;
}
/**
@@ -842,7 +829,6 @@ int rr_address_list_encode(uint8_t *apdu, BACNET_READ_RANGE_DATA *pRequest)
int iLen = 0;
int32_t iTemp = 0;
struct Address_Cache_Entry *pMatch = NULL;
- BACNET_OCTET_STRING MAC_Address;
uint32_t uiTotal = 0; /* Number of bound entries in the cache */
uint32_t uiIndex = 0; /* Current entry number */
uint32_t uiFirst = 0; /* Entry number we started encoding from */
@@ -958,23 +944,8 @@ int rr_address_list_encode(uint8_t *apdu, BACNET_READ_RANGE_DATA *pRequest)
&pRequest->ResultFlags, RESULT_FLAG_MORE_ITEMS, true);
break;
}
- iTemp = (int32_t)encode_application_object_id(
- &apdu[iLen], OBJECT_DEVICE, pMatch->device_id);
- iTemp += encode_application_unsigned(
- &apdu[iLen + iTemp], pMatch->address.net);
-
- /* pick the appropriate type of entry from the cache */
- if (pMatch->address.len != 0) {
- octetstring_init(
- &MAC_Address, pMatch->address.adr, pMatch->address.len);
- iTemp += encode_application_octet_string(
- &apdu[iLen + iTemp], &MAC_Address);
- } else {
- octetstring_init(
- &MAC_Address, pMatch->address.mac, pMatch->address.mac_len);
- iTemp += encode_application_octet_string(
- &apdu[iLen + iTemp], &MAC_Address);
- }
+ iTemp = bacnet_address_binding_entry_encode(
+ &apdu[iLen], pMatch->device_id, &pMatch->address);
/* Reduce the remaining space */
uiRemaining -= iTemp;
/* and increase the length consumed */
diff --git a/src/bacnet/basic/object/acc.c b/src/bacnet/basic/object/acc.c
index 1ef6ed24ce..4b257ba2e2 100644
--- a/src/bacnet/basic/object/acc.c
+++ b/src/bacnet/basic/object/acc.c
@@ -579,6 +579,10 @@ bool Accumulator_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool status = false;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_known_array_property(
wp_data->application_data, wp_data->application_data_len, &value,
diff --git a/src/bacnet/basic/object/access_credential.c b/src/bacnet/basic/object/access_credential.c
index 9b563c2367..0b1bed026b 100644
--- a/src/bacnet/basic/object/access_credential.c
+++ b/src/bacnet/basic/object/access_credential.c
@@ -367,6 +367,10 @@ bool Access_Credential_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
BACNET_APPLICATION_DATA_VALUE value = { 0 };
unsigned object_index = 0;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/access_door.c b/src/bacnet/basic/object/access_door.c
index 67e665f0b0..5e23606e8f 100644
--- a/src/bacnet/basic/object/access_door.c
+++ b/src/bacnet/basic/object/access_door.c
@@ -524,6 +524,10 @@ bool Access_Door_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
BACNET_APPLICATION_DATA_VALUE value = { 0 };
unsigned object_index = 0;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/access_point.c b/src/bacnet/basic/object/access_point.c
index 46394b2b02..855c56637a 100644
--- a/src/bacnet/basic/object/access_point.c
+++ b/src/bacnet/basic/object/access_point.c
@@ -358,6 +358,10 @@ bool Access_Point_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/access_rights.c b/src/bacnet/basic/object/access_rights.c
index 69522a7bc3..3a23e0bbea 100644
--- a/src/bacnet/basic/object/access_rights.c
+++ b/src/bacnet/basic/object/access_rights.c
@@ -339,6 +339,10 @@ bool Access_Rights_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
BACNET_APPLICATION_DATA_VALUE value = { 0 };
unsigned object_index = 0;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/access_user.c b/src/bacnet/basic/object/access_user.c
index e01d1f12d4..d09328f8bf 100644
--- a/src/bacnet/basic/object/access_user.c
+++ b/src/bacnet/basic/object/access_user.c
@@ -257,6 +257,10 @@ bool Access_User_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
BACNET_APPLICATION_DATA_VALUE value = { 0 };
unsigned object_index = 0;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/access_zone.c b/src/bacnet/basic/object/access_zone.c
index 762b06f57b..f994e7b941 100644
--- a/src/bacnet/basic/object/access_zone.c
+++ b/src/bacnet/basic/object/access_zone.c
@@ -310,6 +310,10 @@ bool Access_Zone_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
BACNET_APPLICATION_DATA_VALUE value = { 0 };
unsigned object_index = 0;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/ai.c b/src/bacnet/basic/object/ai.c
index c8b6c646a7..205641ae54 100644
--- a/src/bacnet/basic/object/ai.c
+++ b/src/bacnet/basic/object/ai.c
@@ -1195,9 +1195,6 @@ bool Analog_Input_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
if (wp_data == NULL) {
return false;
}
- if (wp_data->application_data_len == 0) {
- return false;
- }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/ao.c b/src/bacnet/basic/object/ao.c
index 2cbd2d1730..b46bd3f885 100644
--- a/src/bacnet/basic/object/ao.c
+++ b/src/bacnet/basic/object/ao.c
@@ -1185,6 +1185,10 @@ bool Analog_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/auditlog.c b/src/bacnet/basic/object/auditlog.c
index 2a8c21b5b1..ec55857f05 100644
--- a/src/bacnet/basic/object/auditlog.c
+++ b/src/bacnet/basic/object/auditlog.c
@@ -781,6 +781,10 @@ bool Audit_Log_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/av.c b/src/bacnet/basic/object/av.c
index 48b3885635..f641b595ec 100644
--- a/src/bacnet/basic/object/av.c
+++ b/src/bacnet/basic/object/av.c
@@ -990,9 +990,6 @@ bool Analog_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
if (wp_data == NULL) {
return false;
}
- if (wp_data->application_data_len == 0) {
- return false;
- }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/bacfile.c b/src/bacnet/basic/object/bacfile.c
index a69b83d177..bae1abab4b 100644
--- a/src/bacnet/basic/object/bacfile.c
+++ b/src/bacnet/basic/object/bacfile.c
@@ -649,9 +649,11 @@ bool bacfile_file_size_set(
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
- if (pObject->File_Access_Stream) {
- status =
- bacfile_file_size_set_callback(pObject->Pathname, file_size);
+ if (!pObject->Read_Only) {
+ if (pObject->File_Access_Stream) {
+ status = bacfile_file_size_set_callback(
+ pObject->Pathname, file_size);
+ }
}
}
@@ -1030,27 +1032,43 @@ uint32_t bacfile_instance_from_tsm(uint8_t invokeID)
}
#endif
+/**
+ * @brief Read stream data from a file
+ * @param data - pointer to the data structure to fill
+ * @return true - if successful
+ * @return false - if failed or file not found
+ */
bool bacfile_read_stream_data(BACNET_ATOMIC_READ_FILE_DATA *data)
{
const char *pathname = NULL;
bool found = false;
- size_t len = 0;
+ size_t len = 0, file_size = 0;
size_t requestedOctetCount = 0;
+ if (!data) {
+ return false;
+ }
pathname = bacfile_pathname(data->object_instance);
if (pathname) {
found = true;
- requestedOctetCount = data->type.stream.requestedOctetCount;
- if (requestedOctetCount > octetstring_capacity(&data->fileData[0])) {
- requestedOctetCount = octetstring_capacity(&data->fileData[0]);
- }
- len = bacfile_read_stream_data_callback(
- pathname, data->type.stream.fileStartPosition,
- octetstring_value(&data->fileData[0]), requestedOctetCount);
- if (len < requestedOctetCount) {
- data->endOfFile = true;
+ file_size = bacfile_file_size_callback(pathname);
+ if ((data->type.stream.fileStartPosition >= 0) &&
+ (data->type.stream.fileStartPosition < file_size)) {
+ requestedOctetCount = data->type.stream.requestedOctetCount;
+ if (requestedOctetCount >
+ octetstring_capacity(&data->fileData[0])) {
+ requestedOctetCount = octetstring_capacity(&data->fileData[0]);
+ }
+ len = bacfile_read_stream_data_callback(
+ pathname, data->type.stream.fileStartPosition,
+ octetstring_value(&data->fileData[0]), requestedOctetCount);
+ if (len < requestedOctetCount) {
+ data->endOfFile = true;
+ } else {
+ data->endOfFile = false;
+ }
} else {
- data->endOfFile = false;
+ data->endOfFile = true;
}
octetstring_truncate(&data->fileData[0], len);
} else {
@@ -1061,28 +1079,55 @@ bool bacfile_read_stream_data(BACNET_ATOMIC_READ_FILE_DATA *data)
return found;
}
+/**
+ * @brief Read record data from a file
+ * @param data - pointer to the data structure to fill
+ * @return true - if successful
+ * @return false - if failed or file not found
+ */
bool bacfile_read_record_data(BACNET_ATOMIC_READ_FILE_DATA *data)
{
const char *pathname = NULL;
bool found = false;
bool status = false;
+ size_t len = 0;
uint32_t i = 0;
+ size_t max_records = 0;
+ if (!data) {
+ return false;
+ }
+ max_records =
+ min(data->type.record.RecordCount, ARRAY_SIZE(data->fileData));
pathname = bacfile_pathname(data->object_instance);
if (pathname) {
found = true;
+ }
+ if (found && (data->type.record.fileStartRecord >= 0) &&
+ (data->type.record.fileStartRecord < ARRAY_SIZE(data->fileData)) &&
+ (max_records > 0)) {
data->endOfFile = false;
- for (i = 0; i < data->type.record.RecordCount; i++) {
+ for (i = 0; i < max_records; i++) {
status = bacfile_read_record_data_callback(
pathname, data->type.record.fileStartRecord, i,
octetstring_value(&data->fileData[i]),
octetstring_capacity(&data->fileData[i]));
- if (!status) {
+ if (status) {
+ /* our records are NULL terminated C strings
+ read with fgets() */
+ len = bacnet_strnlen(
+ (const char *)octetstring_value(&data->fileData[i]),
+ octetstring_capacity(&data->fileData[i]));
+ octetstring_truncate(&data->fileData[i], len);
+ } else {
data->endOfFile = true;
data->type.record.RecordCount = i;
break;
}
}
+ } else {
+ data->endOfFile = true;
+ data->type.record.RecordCount = 0;
}
return found;
@@ -1100,6 +1145,9 @@ bool bacfile_write_stream_data(BACNET_ATOMIC_WRITE_FILE_DATA *data)
bool status = false;
size_t bytes_written = 0;
+ if (!data) {
+ return false;
+ }
if (bacfile_read_only(data->object_instance)) {
/* if the file is read-only, then we cannot write to it */
return false;
@@ -1135,11 +1183,17 @@ bool bacfile_write_record_data(const BACNET_ATOMIC_WRITE_FILE_DATA *data)
const char *pathname = NULL;
bool found = false;
size_t i = 0;
+ size_t max_records = 0;
+ if (!data) {
+ return false;
+ }
if (bacfile_read_only(data->object_instance)) {
/* if the file is read-only, then we cannot write to it */
return false;
}
+ max_records =
+ min(data->type.record.returnedRecordCount, ARRAY_SIZE(data->fileData));
pathname = bacfile_pathname(data->object_instance);
if (pathname) {
found = true;
@@ -1148,7 +1202,7 @@ bool bacfile_write_record_data(const BACNET_ATOMIC_WRITE_FILE_DATA *data)
as an append to the current end of file.
If the 'File Start Record' parameter is 0,
open the file as a clean slate. */
- for (i = 0; i < data->type.record.returnedRecordCount; i++) {
+ for (i = 0; i < max_records; i++) {
bacfile_write_record_data_callback(
pathname, data->type.record.fileStartRecord, i,
octetstring_value((BACNET_OCTET_STRING *)&data->fileData[i]),
@@ -1172,6 +1226,9 @@ bool bacfile_read_ack_stream_data(
bool found = false;
const char *pathname = NULL;
+ if (!data) {
+ return false;
+ }
pathname = bacfile_pathname(instance);
if (pathname) {
found = true;
@@ -1198,6 +1255,12 @@ bool bacfile_read_ack_record_data(
const char *pathname = NULL;
uint32_t i = 0;
+ if (!data) {
+ return false;
+ }
+ if (data->type.record.RecordCount > ARRAY_SIZE(data->fileData)) {
+ return false;
+ }
pathname = bacfile_pathname(instance);
if (pathname) {
found = true;
diff --git a/src/bacnet/basic/object/bi.c b/src/bacnet/basic/object/bi.c
index e4b3251540..7f65081e79 100644
--- a/src/bacnet/basic/object/bi.c
+++ b/src/bacnet/basic/object/bi.c
@@ -1128,6 +1128,10 @@ bool Binary_Input_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
BACNET_APPLICATION_DATA_VALUE value = { 0 };
struct object_data *pObject;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/bitstring_value.c b/src/bacnet/basic/object/bitstring_value.c
index b626ae8355..abee2f9c4e 100644
--- a/src/bacnet/basic/object/bitstring_value.c
+++ b/src/bacnet/basic/object/bitstring_value.c
@@ -714,12 +714,10 @@ bool BitString_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
if (wp_data == NULL) {
return false;
}
- if (wp_data->application_data_len == 0) {
- return false;
- }
/* Decode the some of the request. */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/blo.c b/src/bacnet/basic/object/blo.c
index 1a0a5b7d42..e7d2d5be3b 100644
--- a/src/bacnet/basic/object/blo.c
+++ b/src/bacnet/basic/object/blo.c
@@ -1476,6 +1476,10 @@ bool Binary_Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/bo.c b/src/bacnet/basic/object/bo.c
index 70b0fb9867..96a6ec9a98 100644
--- a/src/bacnet/basic/object/bo.c
+++ b/src/bacnet/basic/object/bo.c
@@ -1195,6 +1195,10 @@ bool Binary_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/calendar.c b/src/bacnet/basic/object/calendar.c
index 3f800fb0c9..44d7c1db16 100644
--- a/src/bacnet/basic/object/calendar.c
+++ b/src/bacnet/basic/object/calendar.c
@@ -598,6 +598,10 @@ bool Calendar_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool pv_old;
bool pv;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/channel.c b/src/bacnet/basic/object/channel.c
index b490b5deae..b04335293a 100644
--- a/src/bacnet/basic/object/channel.c
+++ b/src/bacnet/basic/object/channel.c
@@ -717,72 +717,117 @@ static bool Channel_Write_Members(
unsigned m = 0;
const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL;
- if (pObject && value) {
- pObject->Write_Status = BACNET_WRITE_STATUS_IN_PROGRESS;
- debug_printf(
- "channel[%lu].Channel_Write_Members\n",
- (unsigned long)object_instance);
-
- for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) {
- pMember = &pObject->Members[m];
- /* NOTE: our implementation is for internal objects only */
- /* NOTE: we could check to match our Device ID, but then
- we would need to update all channels when our device ID
- changed. Instead, we'll just screen when members are
- set. */
- if ((pMember->deviceIdentifier.type == OBJECT_DEVICE) &&
- (pMember->deviceIdentifier.instance != BACNET_MAX_INSTANCE) &&
- (pMember->objectIdentifier.instance != BACNET_MAX_INSTANCE)) {
- wp_data.object_type = pMember->objectIdentifier.type;
- wp_data.object_instance = pMember->objectIdentifier.instance;
- wp_data.object_property = pMember->propertyIdentifier;
- wp_data.array_index = pMember->arrayIndex;
- wp_data.error_class = ERROR_CLASS_PROPERTY;
- wp_data.error_code = ERROR_CODE_SUCCESS;
- wp_data.priority = priority;
- wp_data.application_data_len = sizeof(wp_data.application_data);
- status = Channel_Write_Member_Value(&wp_data, value);
- if (status) {
- debug_printf(
- "channel[%lu].Channel_Write_Member[%u] coerced\n",
- (unsigned long)object_instance, m);
- if (Write_Property_Internal_Callback) {
- status = write_property_bacnet_array_valid(&wp_data);
+ if (!pObject) {
+ return false;
+ }
+ if (!value) {
+ return false;
+ }
+ if (pObject->Write_Status == BACNET_WRITE_STATUS_IN_PROGRESS) {
+ return false;
+ }
+ pObject->Write_Status = BACNET_WRITE_STATUS_IN_PROGRESS;
+ debug_printf(
+ "channel[%lu].Channel_Write_Members\n", (unsigned long)object_instance);
+
+ for (m = 0; m < CHANNEL_MEMBERS_MAX; m++) {
+ pMember = &pObject->Members[m];
+ /* NOTE: our implementation is for internal objects only */
+ /* NOTE: we could check to match our Device ID, but then
+ we would need to update all channels when our device ID
+ changed. Instead, we'll just screen when members are
+ set. */
+ if ((pMember->deviceIdentifier.type == OBJECT_DEVICE) &&
+ (pMember->deviceIdentifier.instance != BACNET_MAX_INSTANCE) &&
+ (pMember->objectIdentifier.instance != BACNET_MAX_INSTANCE)) {
+ wp_data.object_type = pMember->objectIdentifier.type;
+ wp_data.object_instance = pMember->objectIdentifier.instance;
+ wp_data.object_property = pMember->propertyIdentifier;
+ wp_data.array_index = pMember->arrayIndex;
+ wp_data.error_class = ERROR_CLASS_PROPERTY;
+ wp_data.error_code = ERROR_CODE_SUCCESS;
+ wp_data.priority = priority;
+ wp_data.application_data_len = sizeof(wp_data.application_data);
+ status = Channel_Write_Member_Value(&wp_data, value);
+ if (status) {
+ debug_printf(
+ "channel[%lu].Channel_Write_Member[%u] coerced\n",
+ (unsigned long)object_instance, m);
+ if (Write_Property_Internal_Callback) {
+ status = write_property_bacnet_array_valid(&wp_data);
+ if (status) {
+ status = Write_Property_Internal_Callback(&wp_data);
if (status) {
- status = Write_Property_Internal_Callback(&wp_data);
- if (status) {
- wp_data.error_code = ERROR_CODE_SUCCESS;
- }
+ wp_data.error_code = ERROR_CODE_SUCCESS;
}
- debug_printf(
- "channel[%lu].Channel_Write_Member[%u] "
- "%s-%u %s %s\n",
- (unsigned long)object_instance, m,
- bactext_object_type_name(wp_data.object_type),
- wp_data.object_instance,
- bactext_property_name(wp_data.object_property),
- bactext_error_code_name(wp_data.error_code));
}
- } else {
- wp_data.error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE;
debug_printf(
"channel[%lu].Channel_Write_Member[%u] "
- "coercion failed!\n",
- (unsigned long)object_instance, m);
- pObject->Write_Status = BACNET_WRITE_STATUS_FAILED;
+ "%s-%u %s %s\n",
+ (unsigned long)object_instance, m,
+ bactext_object_type_name(wp_data.object_type),
+ wp_data.object_instance,
+ bactext_property_name(wp_data.object_property),
+ bactext_error_code_name(wp_data.error_code));
+ if (!status) {
+ if ((bacnet_null_application_decode(
+ wp_data.application_data,
+ wp_data.application_data_len) > 0) &&
+ ((wp_data.error_code ==
+ ERROR_CODE_REJECT_INVALID_PARAMETER_DATA_TYPE) ||
+ (wp_data.error_code ==
+ ERROR_CODE_INVALID_DATA_TYPE))) {
+ /* A special exception shall be the writing of
+ a Null value. If a Null value is written and
+ WriteProperty or WritePropertyMultiple services
+ subsequently receive an ERROR_INVALID_DATATYPE or
+ REJECT_INVALID_PARAMETER_DATA_TYPE,
+ it shall not be treated as a FAILED value.
+ This is specifically to allow Channel objects
+ to point to both commandable and non-commandable
+ properties with the same channel.*/
+ } else {
+ /* The FAILED value indicates that the Channel
+ object has processed a property in and received
+ an error, reject, or abort for at least one
+ of the writes. */
+ pObject->Write_Status = BACNET_WRITE_STATUS_FAILED;
+ }
+ }
+ } else {
+ /* NOTE: internal callback not valid,
+ so ignore the writes and report no error */
}
- Channel_Write_Property_Notify(
- object_instance, status, &wp_data);
} else {
+ /* coercion failed */
+ wp_data.error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE;
debug_printf(
- "channel[%lu].Channel_Write_Member[%u] invalid!\n",
+ "channel[%lu].Channel_Write_Member[%u] "
+ "coercion failed!\n",
(unsigned long)object_instance, m);
+ /* The FAILED value indicates that the Channel object
+ has processed all of the properties in
+ List_Of_Object_Property_References and
+ encountered a coercion failure, or received an error,
+ reject, or abort for at least one of the writes.*/
+ pObject->Write_Status = BACNET_WRITE_STATUS_FAILED;
}
- }
- if (pObject->Write_Status == BACNET_WRITE_STATUS_IN_PROGRESS) {
- pObject->Write_Status = BACNET_WRITE_STATUS_SUCCESSFUL;
+ Channel_Write_Property_Notify(object_instance, status, &wp_data);
+ } else {
+ debug_printf(
+ "channel[%lu].Channel_Write_Member[%u] invalid!\n",
+ (unsigned long)object_instance, m);
}
}
+ if (pObject->Write_Status == BACNET_WRITE_STATUS_IN_PROGRESS) {
+ /* the Write_Status property shall be set to either
+ SUCCESSFUL or FAILED. The SUCCESSFUL value indicates
+ that the Channel object has processed all of the properties
+ in List_Of_Object_Property_References and did not have
+ any coercion errors, and did not receive any errors,
+ rejects, or aborts. */
+ pObject->Write_Status = BACNET_WRITE_STATUS_SUCCESSFUL;
+ }
return status;
}
@@ -1099,20 +1144,39 @@ int Channel_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
static int Channel_List_Of_Object_Property_References_Length(
uint32_t object_instance, uint8_t *apdu, size_t apdu_size)
{
- BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE value = { 0 };
int len = 0;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
- len = bacapp_decode_known_property(
- apdu, apdu_size, &value, OBJECT_CHANNEL,
- PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES);
+ len = bacnet_device_object_property_reference_decode(
+ apdu, apdu_size, &value);
}
return len;
}
+/**
+ * @brief For a given object instance-number and reference, determines if the
+ * reference is for a direct self present-value reference
+ * @param channel_instance [in] BACnet channel object instance number
+ * @param ref [in] BACnet device object property reference
+ * @return true if the reference is for a direct self present-value
+ */
+static bool Channel_Member_Is_Direct_Self_Present_Value(
+ uint32_t channel_instance,
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *ref)
+{
+ if (!ref) {
+ return false;
+ }
+
+ return (ref->objectIdentifier.type == OBJECT_CHANNEL) &&
+ (ref->objectIdentifier.instance == channel_instance) &&
+ (ref->propertyIdentifier == PROP_PRESENT_VALUE);
+}
+
/**
* @brief Write a value to a BACnetARRAY property element value
* @param object_instance [in] BACnet network port object instance number
@@ -1131,7 +1195,7 @@ static BACNET_ERROR_CODE Channel_List_Of_Object_Property_References_Write(
size_t application_data_len)
{
BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT;
- BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE value = { 0 };
int len = 0;
bool status;
struct object_data *pObject;
@@ -1144,23 +1208,24 @@ static BACNET_ERROR_CODE Channel_List_Of_Object_Property_References_Write(
(void)array_size;
error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
} else {
- len = bacapp_decode_known_property(
- application_data, application_data_len, &value, OBJECT_CHANNEL,
- PROP_LIST_OF_OBJECT_PROPERTY_REFERENCES);
+ len = bacnet_device_object_property_reference_decode(
+ application_data, application_data_len, &value);
if (len > 0) {
- if (value.tag ==
- BACNET_APPLICATION_TAG_DEVICE_OBJECT_PROPERTY_REFERENCE) {
+ status = Channel_Member_Is_Direct_Self_Present_Value(
+ object_instance, &value);
+ if (status) {
+ error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ } else {
status = List_Of_Object_Property_References_Set(
- pObject, array_index - 1,
- &value.type.Device_Object_Property_Reference);
+ pObject, array_index - 1, &value);
if (status) {
error_code = ERROR_CODE_SUCCESS;
} else {
error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
}
- } else {
- error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
+ } else if (len == 0) {
+ error_code = ERROR_CODE_INVALID_DATA_TYPE;
} else {
error_code = ERROR_CODE_ABORT_OTHER;
}
@@ -1180,14 +1245,14 @@ static BACNET_ERROR_CODE Channel_List_Of_Object_Property_References_Write(
static int Channel_Control_Groups_Length(
uint32_t object_instance, uint8_t *apdu, size_t apdu_size)
{
- BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ BACNET_UNSIGNED_INTEGER value_unsigned = 0;
int len = 0;
struct object_data *pObject;
pObject = Object_Data(object_instance);
if (pObject) {
- len = bacapp_decode_known_property(
- apdu, apdu_size, &value, OBJECT_CHANNEL, PROP_CONTROL_GROUPS);
+ len = bacnet_unsigned_application_decode(
+ apdu, apdu_size, &value_unsigned);
}
return len;
@@ -1211,7 +1276,7 @@ static BACNET_ERROR_CODE Channel_Control_Groups_Write(
size_t application_data_len)
{
BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT;
- BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ BACNET_UNSIGNED_INTEGER value_unsigned = 0;
uint16_t control_group;
int len = 0;
bool status;
@@ -1225,26 +1290,23 @@ static BACNET_ERROR_CODE Channel_Control_Groups_Write(
(void)array_size;
error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
} else {
- len = bacapp_decode_known_property(
- application_data, application_data_len, &value, OBJECT_CHANNEL,
- PROP_CONTROL_GROUPS);
+ len = bacnet_unsigned_application_decode(
+ application_data, application_data_len, &value_unsigned);
if (len > 0) {
- if (value.tag == BACNET_APPLICATION_TAG_UNSIGNED_INT) {
- if (value.type.Unsigned_Int <= UINT16_MAX) {
- control_group = (uint16_t)value.type.Unsigned_Int;
- status = Control_Groups_Element_Set(
- pObject, array_index, control_group);
- if (status) {
- error_code = ERROR_CODE_SUCCESS;
- } else {
- error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
- }
+ if (value_unsigned <= UINT16_MAX) {
+ control_group = (uint16_t)value_unsigned;
+ status = Control_Groups_Element_Set(
+ pObject, array_index, control_group);
+ if (status) {
+ error_code = ERROR_CODE_SUCCESS;
} else {
error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
}
} else {
- error_code = ERROR_CODE_INVALID_DATA_TYPE;
+ error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
}
+ } else if (len == 0) {
+ error_code = ERROR_CODE_INVALID_DATA_TYPE;
} else {
error_code = ERROR_CODE_ABORT_OTHER;
}
@@ -1269,6 +1331,10 @@ bool Channel_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the first value of the request */
len = bacapp_decode_known_property(
wp_data->application_data, wp_data->application_data_len, &value,
diff --git a/src/bacnet/basic/object/client/device-client.c b/src/bacnet/basic/object/client/device-client.c
index 6f29f6c7af..7f18dd1c54 100644
--- a/src/bacnet/basic/object/client/device-client.c
+++ b/src/bacnet/basic/object/client/device-client.c
@@ -1210,9 +1210,12 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
apdu_len = encode_application_unsigned(&apdu[0], apdu_retries());
break;
case PROP_DEVICE_ADDRESS_BINDING:
- /* FIXME: the real max apdu remaining should be passed into function
- */
- apdu_len = address_list_encode(&apdu[0], MAX_APDU);
+ apdu_len = address_list_encode(&apdu[0], apdu_size);
+ if (apdu_len < 0) {
+ rpdata->error_code =
+ ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
+ apdu_len = BACNET_STATUS_ABORT;
+ }
break;
case PROP_DATABASE_REVISION:
apdu_len = encode_application_unsigned(&apdu[0], Database_Revision);
@@ -1318,7 +1321,7 @@ static bool Device_Write_Property_Object_Name(
{
bool status = false; /* return value */
int len = 0;
- BACNET_CHARACTER_STRING value;
+ BACNET_CHARACTER_STRING value = { 0 };
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
uint32_t object_instance = 0;
int apdu_size = 0;
@@ -1387,6 +1390,10 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool status = false; /* Ever the pessimist! */
struct object_functions *pObject = NULL;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* initialize the default return values */
wp_data->error_class = ERROR_CLASS_OBJECT;
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
@@ -1405,6 +1412,15 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
if (wp_data->object_property == PROP_OBJECT_NAME) {
status = Device_Write_Property_Object_Name(
wp_data, pObject->Object_Write_Property);
+ } else if (
+ (wp_data->application_data_len == 0) &&
+ !property_list_bacnet_list_member(
+ wp_data->object_type, wp_data->object_property)) {
+ /* only list properties can be written with
+ an empty application payload */
+ wp_data->error_class = ERROR_CLASS_SERVICES;
+ wp_data->error_code = ERROR_CODE_INVALID_TAG;
+ status = false;
} else {
status = pObject->Object_Write_Property(wp_data);
}
diff --git a/src/bacnet/basic/object/color_object.c b/src/bacnet/basic/object/color_object.c
index c98c630c82..a2aabeb019 100644
--- a/src/bacnet/basic/object/color_object.c
+++ b/src/bacnet/basic/object/color_object.c
@@ -1026,6 +1026,10 @@ bool Color_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int apdu_size = 0;
const uint8_t *apdu = NULL;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
apdu = wp_data->application_data;
apdu_size = wp_data->application_data_len;
diff --git a/src/bacnet/basic/object/color_temperature.c b/src/bacnet/basic/object/color_temperature.c
index be36f90766..f036996473 100644
--- a/src/bacnet/basic/object/color_temperature.c
+++ b/src/bacnet/basic/object/color_temperature.c
@@ -1464,6 +1464,10 @@ bool Color_Temperature_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int apdu_size = 0;
const uint8_t *apdu = NULL;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
apdu = wp_data->application_data;
apdu_size = wp_data->application_data_len;
diff --git a/src/bacnet/basic/object/command.c b/src/bacnet/basic/object/command.c
index 00c208a508..4694eeb3ba 100644
--- a/src/bacnet/basic/object/command.c
+++ b/src/bacnet/basic/object/command.c
@@ -508,6 +508,11 @@ bool Command_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
unsigned int object_index = 0;
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/credential_data_input.c b/src/bacnet/basic/object/credential_data_input.c
index c2869abe05..38473043e0 100644
--- a/src/bacnet/basic/object/credential_data_input.c
+++ b/src/bacnet/basic/object/credential_data_input.c
@@ -371,6 +371,10 @@ bool Credential_Data_Input_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
BACNET_APPLICATION_DATA_VALUE value = { 0 };
unsigned object_index = 0;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/csv.c b/src/bacnet/basic/object/csv.c
index 857f1773de..dcd50df3e2 100644
--- a/src/bacnet/basic/object/csv.c
+++ b/src/bacnet/basic/object/csv.c
@@ -743,13 +743,10 @@ bool CharacterString_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
struct characterstring_object *pObject = NULL;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
if (wp_data == NULL) {
return false;
}
- if (wp_data->application_data_len == 0) {
- return false;
- }
-
/* Decode the some of the request. */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c
index 2854b34ef9..03205a6f52 100644
--- a/src/bacnet/basic/object/device.c
+++ b/src/bacnet/basic/object/device.c
@@ -2887,6 +2887,11 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
break;
case PROP_DEVICE_ADDRESS_BINDING:
apdu_len = address_list_encode(&apdu[0], apdu_max);
+ if (apdu_len < 0) {
+ rpdata->error_code =
+ ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
+ apdu_len = BACNET_STATUS_ABORT;
+ }
break;
case PROP_DATABASE_REVISION:
apdu_len = encode_application_unsigned(&apdu[0], Database_Revision);
@@ -3389,7 +3394,7 @@ static bool Device_Write_Property_Object_Name(
{
bool status = false; /* return value */
int len = 0;
- BACNET_CHARACTER_STRING value;
+ BACNET_CHARACTER_STRING value = { 0 };
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
uint32_t object_instance = 0;
int apdu_size = 0;
@@ -3471,6 +3476,10 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool status = false; /* Ever the pessimist! */
struct object_functions *pObject = NULL;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
Device_Backup_Failure_Timeout_Restart();
/* initialize the default return values */
wp_data->error_class = ERROR_CLASS_OBJECT;
@@ -3496,6 +3505,15 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
wp_data->object_type, wp_data->object_instance,
wp_data->object_property)) {
status = Write_Property_Proprietary_Callback(wp_data);
+ } else if (
+ (wp_data->application_data_len == 0) &&
+ !property_list_bacnet_list_member(
+ wp_data->object_type, wp_data->object_property)) {
+ /* only list properties can be written with
+ an empty application payload */
+ wp_data->error_class = ERROR_CLASS_SERVICES;
+ wp_data->error_code = ERROR_CODE_INVALID_TAG;
+ status = false;
} else {
status = pObject->Object_Write_Property(wp_data);
}
@@ -3896,8 +3914,6 @@ void Device_End_Restore(void)
datetime_local(&bdateTime.date, &bdateTime.time, NULL, NULL);
bacapp_timestamp_datetime_set(&Last_Restore_Time, &bdateTime);
- /* delete all existing objects before restore */
- Device_Delete_Objects();
/* create objects from the backup file */
file_size = bacfile_file_size(Configuration_Files[0]);
while (offset < file_size) {
@@ -3907,6 +3923,11 @@ void Device_End_Restore(void)
decoded_len = create_object_decode_service_request(
apdu, apdu_len, &create_data);
if (decoded_len > 0) {
+ if (offset == 0) {
+ /* delete all existing objects before restore
+ after the first record is successfully decoded */
+ Device_Delete_Objects();
+ }
offset += decoded_len;
create_data.error_class = ERROR_CLASS_PROPERTY;
create_data.error_code = ERROR_CODE_SUCCESS;
diff --git a/src/bacnet/basic/object/iv.c b/src/bacnet/basic/object/iv.c
index a7466dbc7d..b791f0d481 100644
--- a/src/bacnet/basic/object/iv.c
+++ b/src/bacnet/basic/object/iv.c
@@ -577,6 +577,10 @@ bool Integer_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int32_t old_value = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/lc.c b/src/bacnet/basic/object/lc.c
index 60695801c7..006440e1ce 100644
--- a/src/bacnet/basic/object/lc.c
+++ b/src/bacnet/basic/object/lc.c
@@ -1534,6 +1534,7 @@ bool Load_Control_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0, count = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
if (wp_data == NULL) {
debug_printf("Load_Control_Write_Property(): invalid data\n");
return false;
diff --git a/src/bacnet/basic/object/lo.c b/src/bacnet/basic/object/lo.c
index f22df6223b..488e27256f 100644
--- a/src/bacnet/basic/object/lo.c
+++ b/src/bacnet/basic/object/lo.c
@@ -106,6 +106,8 @@ static const int32_t Properties_Optional[] = {
/* unordered list of optional properties */
PROP_DESCRIPTION,
PROP_TRANSITION,
+ PROP_MIN_ACTUAL_VALUE,
+ PROP_MAX_ACTUAL_VALUE,
#if (BACNET_PROTOCOL_REVISION >= 24)
PROP_COLOR_OVERRIDE,
PROP_COLOR_REFERENCE,
@@ -139,7 +141,8 @@ static const int32_t Writable_Properties[] = {
PROP_TRIM_FADE_TIME, PROP_BLINK_WARN_ENABLE,
PROP_EGRESS_TIME, PROP_LIGHTING_COMMAND_DEFAULT_PRIORITY,
PROP_FEEDBACK_VALUE, PROP_POWER,
- PROP_INSTANTANEOUS_POWER, -1
+ PROP_INSTANTANEOUS_POWER, PROP_MIN_ACTUAL_VALUE,
+ PROP_MAX_ACTUAL_VALUE, -1
};
/**
@@ -507,29 +510,6 @@ unsigned Lighting_Output_Present_Value_Priority(uint32_t object_instance)
return priority;
}
-/**
- * @brief Determine if fade, ramp, or warn command is currently executing
- * @param pObject [in] object to apply the trim values to
- * @param priority [in] priority of the command
- */
-static bool Lighting_Command_In_Progress(struct object_data *pObject)
-{
- bool in_progress = false;
-
- if (!pObject) {
- return in_progress;
- }
- if ((pObject->Lighting_Command.In_Progress ==
- BACNET_LIGHTING_FADE_ACTIVE) ||
- (pObject->Lighting_Command.In_Progress ==
- BACNET_LIGHTING_RAMP_ACTIVE) ||
- (pObject->Lighting_Command.Blink.Duration > 0)) {
- in_progress = true;
- }
-
- return in_progress;
-}
-
/**
* @brief Configure the lighting command to apply low or high trim
* to the tracking value based on the priority of the command
@@ -547,14 +527,12 @@ Lighting_Command_Trim_Apply(struct object_data *pObject, unsigned priority)
the Tracking_Value shall not be clamped. */
if ((priority == 1) || (priority == 2)) {
/* remove any high or low trim */
- pObject->Lighting_Command.High_Trim_Value = 100.0f;
- pObject->Lighting_Command.Low_Trim_Value = 1.0f;
- pObject->Lighting_Command.Trim_Fade_Time = 0;
+ lighting_command_trim_set(&pObject->Lighting_Command, 100.0f, 1.0f, 0);
} else {
/* apply high and low trim */
- pObject->Lighting_Command.High_Trim_Value = pObject->High_End_Trim;
- pObject->Lighting_Command.Low_Trim_Value = pObject->Low_End_Trim;
- pObject->Lighting_Command.Trim_Fade_Time = pObject->Trim_Fade_Time;
+ lighting_command_trim_set(
+ &pObject->Lighting_Command, pObject->High_End_Trim,
+ pObject->Low_End_Trim, pObject->Trim_Fade_Time);
}
}
@@ -653,6 +631,7 @@ static void
Lighting_Command_Warn(struct object_data *pObject, unsigned priority)
{
unsigned current_priority;
+ BACNET_LIGHTING_COMMAND_WARN_DATA blink = { 0 };
if (!pObject) {
return;
@@ -669,9 +648,9 @@ Lighting_Command_Warn(struct object_data *pObject, unsigned priority)
active priority, or
(b) The value at the specified priority is 0.0%, or
(c) Blink_Warn_Enable is FALSE. */
+ lighting_command_blink_copy(&pObject->Lighting_Command, &blink);
lighting_command_blink_warn(
- &pObject->Lighting_Command, BACNET_LIGHTS_WARN,
- &pObject->Lighting_Command.Blink);
+ &pObject->Lighting_Command, BACNET_LIGHTS_WARN, &blink);
}
}
@@ -720,7 +699,7 @@ static void
Lighting_Command_Warn_Off(struct object_data *pObject, unsigned priority)
{
unsigned current_priority;
- BACNET_LIGHTING_COMMAND_WARN_DATA blink;
+ BACNET_LIGHTING_COMMAND_WARN_DATA blink = { 0 };
if (!pObject) {
return;
@@ -740,9 +719,7 @@ Lighting_Command_Warn_Off(struct object_data *pObject, unsigned priority)
active priority, or
(b) The Present_Value is 0.0%, or
(c) Blink_Warn_Enable is FALSE. */
- memmove(
- &blink, &pObject->Lighting_Command.Blink,
- sizeof(BACNET_LIGHTING_COMMAND_WARN_DATA));
+ lighting_command_blink_copy(&pObject->Lighting_Command, &blink);
blink.Duration = pObject->Egress_Time_Seconds * 1000UL;
blink.Priority = priority;
blink.Callback = Lighting_Command_Blink_Stop;
@@ -813,9 +790,7 @@ Lighting_Command_Warn_Relinquish(struct object_data *pObject, unsigned priority)
(b) The Present_Value is 0.0%, or
(c) The Present_Value would not evaluate to 0.0% after
the priority slot is relinquished. */
- memmove(
- &blink, &pObject->Lighting_Command.Blink,
- sizeof(BACNET_LIGHTING_COMMAND_WARN_DATA));
+ lighting_command_blink_copy(&pObject->Lighting_Command, &blink);
blink.Duration = pObject->Egress_Time_Seconds * 1000UL;
blink.Priority = priority;
blink.Callback = Lighting_Command_Blink_Stop;
@@ -860,7 +835,7 @@ static void Lighting_Command_Step_Up_On(
if (!pObject) {
return;
}
- value = pObject->Lighting_Command.Tracking_Value;
+ value = lighting_command_tracking_value_get(&pObject->Lighting_Command);
if (operation == BACNET_LIGHTS_STEP_UP) {
if (is_float_equal(value, 0.0)) {
/* If the starting level of Tracking_Value is 0.0%,
@@ -914,7 +889,7 @@ static void Lighting_Command_Step_Down_Off(
if (!pObject) {
return;
}
- value = pObject->Lighting_Command.Tracking_Value;
+ value = lighting_command_tracking_value_get(&pObject->Lighting_Command);
if (is_float_equal(value, 0.0)) {
/* If the starting level of Tracking_Value is 0.0%,
then this operation is ignored. */
@@ -959,7 +934,7 @@ Lighting_Command_Restore_On(struct object_data *pObject, unsigned priority)
if (!pObject) {
return;
}
- value = pObject->Lighting_Command.Last_On_Value;
+ value = lighting_command_last_on_value_get(&pObject->Lighting_Command);
Lighting_Command_Transition_Default(pObject, priority, value);
}
@@ -978,7 +953,7 @@ Lighting_Command_Default_On(struct object_data *pObject, unsigned priority)
if (!pObject) {
return;
}
- value = pObject->Lighting_Command.Default_On_Value;
+ value = lighting_command_default_on_value_get(&pObject->Lighting_Command);
Lighting_Command_Transition_Default(pObject, priority, value);
}
@@ -1001,7 +976,8 @@ Lighting_Command_Toggle_Restore(struct object_data *pObject, unsigned priority)
/* Prior to the execution of this command, if Present_Value is 0.0%,
write the Last_On_Value to the specified slot in the priority
array. */
- toggle_value = pObject->Lighting_Command.Last_On_Value;
+ toggle_value =
+ lighting_command_last_on_value_get(&pObject->Lighting_Command);
} else {
/* Prior to the execution of this command, if Present_Value is not 0.0%,
write 0.0% to the specified slot in the priority array. */
@@ -1029,7 +1005,8 @@ Lighting_Command_Toggle_Default(struct object_data *pObject, unsigned priority)
/* Prior to the execution of this command, if Present_Value is 0.0%,
write the Default_On_Value to the specified slot in the priority
array. */
- toggle_value = pObject->Lighting_Command.Default_On_Value;
+ toggle_value =
+ lighting_command_default_on_value_get(&pObject->Lighting_Command);
} else {
/* Prior to the execution of this command, if Present_Value is not 0.0%,
write 0.0% to the specified slot in the priority array. */
@@ -1467,10 +1444,11 @@ Lighting_Command_Stop(struct object_data *pObject, unsigned priority)
}
current_priority = Present_Value_Priority(pObject);
if (priority == current_priority) {
- if (Lighting_Command_In_Progress(pObject)) {
+ if (lighting_command_active(&pObject->Lighting_Command)) {
/* fade, ramp, or warn command is currently
executing at the specified priority */
- value = pObject->Lighting_Command.Tracking_Value;
+ value =
+ lighting_command_tracking_value_get(&pObject->Lighting_Command);
Present_Value_Set(pObject, value, priority);
/* configure the Lighting Command */
lighting_command_stop(&pObject->Lighting_Command);
@@ -1762,7 +1740,7 @@ Lighting_Output_In_Progress(uint32_t object_instance)
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
- value = pObject->Lighting_Command.In_Progress;
+ value = lighting_command_in_progress_get(&pObject->Lighting_Command);
}
return value;
@@ -1785,7 +1763,8 @@ bool Lighting_Output_In_Progress_Set(
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
- pObject->Lighting_Command.In_Progress = in_progress;
+ lighting_command_in_progress_set(
+ &pObject->Lighting_Command, in_progress);
}
return status;
@@ -1805,7 +1784,7 @@ float Lighting_Output_Tracking_Value(uint32_t object_instance)
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
- value = pObject->Lighting_Command.Tracking_Value;
+ value = lighting_command_tracking_value_get(&pObject->Lighting_Command);
}
return value;
@@ -1827,7 +1806,7 @@ bool Lighting_Output_Tracking_Value_Set(uint32_t object_instance, float value)
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
- pObject->Lighting_Command.Tracking_Value = value;
+ lighting_command_tracking_value_set(&pObject->Lighting_Command, value);
status = true;
}
@@ -1916,9 +1895,8 @@ bool Lighting_Output_Blink_Warn_Feature_Set(
} else if (isgreater(off_value, 100.0)) {
off_value = 100.0f;
}
- pObject->Lighting_Command.Blink.Off_Value = off_value;
- pObject->Lighting_Command.Blink.Interval = interval;
- pObject->Lighting_Command.Blink.Count = count;
+ lighting_command_blink_warn_feature_set(
+ &pObject->Lighting_Command, off_value, interval, count);
status = true;
}
@@ -2023,9 +2001,8 @@ bool Lighting_Output_Egress_Active(uint32_t object_instance)
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
- if (pObject->Lighting_Command.Blink.Duration > 0) {
- value = true;
- }
+ value =
+ lighting_command_blink_egress_active(&pObject->Lighting_Command);
}
return value;
@@ -2315,6 +2292,176 @@ unsigned Lighting_Output_Default_Priority(uint32_t object_instance)
return value;
}
+/**
+ * @brief For a given object instance-number, gets the minimum-actual-value
+ * property value
+ * @param object_instance - object-instance number of the object
+ * @return the minimum-actual-value property value of this object
+ */
+float Lighting_Output_Min_Actual_Value(uint32_t object_instance)
+{
+ float value = 0.0;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ value =
+ lighting_command_min_actual_value_get(&pObject->Lighting_Command);
+ }
+
+ return value;
+}
+
+/**
+ * @brief For a given object instance-number, sets the minimum-actual-value
+ * property value
+ * @param object_instance - object-instance number of the object
+ * @param value - the minimum-actual-value property value to be set
+ * @return true if the minimum-actual-value property value was set
+ */
+bool Lighting_Output_Min_Actual_Value_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+ float max_actual_value;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (isgreaterequal(value, 1.0f) && islessequal(value, 100.0f)) {
+ /* Min_Actual_Value shall always be a positive number
+ in the range 1.0% to 100.0%.*/
+ max_actual_value = lighting_command_max_actual_value_get(
+ &pObject->Lighting_Command);
+ if (value > max_actual_value) {
+ /* Changing Min_Actual_Value to a value greater than
+ Max_Actual_Value shall force Max_Actual_Value
+ to become equal to Min_Actual_Value. */
+ value = max_actual_value;
+ }
+ lighting_command_min_actual_value_set(
+ &pObject->Lighting_Command, value);
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * Handle a WriteProperty to a specific property.
+ *
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to be written
+ * @param priority - priority-array index value 1..16
+ * @param error_class - the BACnet error class
+ * @param error_code - BACnet Error code
+ *
+ * @return true if values are within range and present-value is set.
+ */
+static bool Lighting_Output_Min_Actual_Value_Write(
+ uint32_t object_instance,
+ float value,
+ uint8_t priority,
+ BACNET_ERROR_CLASS *error_class,
+ BACNET_ERROR_CODE *error_code)
+{
+ bool status = false;
+ (void)priority;
+ status = Lighting_Output_Min_Actual_Value_Set(object_instance, value);
+ if (!status) {
+ *error_class = ERROR_CLASS_PROPERTY;
+ *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+
+ return status;
+}
+
+/**
+ * @brief For a given object instance-number, gets the maximum-actual-value
+ * property value
+ * @param object_instance - object-instance number of the object
+ * @return the maximum-actual-value property value of this object
+ */
+float Lighting_Output_Max_Actual_Value(uint32_t object_instance)
+{
+ float value = 0.0;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ value =
+ lighting_command_max_actual_value_get(&pObject->Lighting_Command);
+ }
+
+ return value;
+}
+
+/**
+ * @brief For a given object instance-number, sets the maximum-actual-value
+ * property value
+ * @param object_instance - object-instance number of the object
+ * @param value - the maximum-actual-value property value to be set
+ * @return true if the maximum-actual-value property value was set
+ */
+bool Lighting_Output_Max_Actual_Value_Set(uint32_t object_instance, float value)
+{
+ bool status = false;
+ struct object_data *pObject;
+ float min_actual_value;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ if (isgreaterequal(value, 1.0f) && islessequal(value, 100.0f)) {
+ /* Max_Actual_Value shall always be a positive number
+ in the range 1.0% to 100.0%.*/
+ min_actual_value = lighting_command_min_actual_value_get(
+ &pObject->Lighting_Command);
+ if (value < min_actual_value) {
+ /* Changing Max_Actual_Value to a value less than
+ Min_Actual_Value shall force Min_Actual_Value
+ to become equal to Max_Actual_Value. */
+ lighting_command_min_actual_value_set(
+ &pObject->Lighting_Command, value);
+ }
+ lighting_command_max_actual_value_set(
+ &pObject->Lighting_Command, value);
+ status = true;
+ }
+ }
+
+ return status;
+}
+
+/**
+ * Handle a WriteProperty to a specific property.
+ *
+ * @param object_instance - object-instance number of the object
+ * @param value - property value to be written
+ * @param priority - priority-array index value 1..16
+ * @param error_class - the BACnet error class
+ * @param error_code - BACnet Error code
+ *
+ * @return true if values are within range and present-value is set.
+ */
+static bool Lighting_Output_Max_Actual_Value_Write(
+ uint32_t object_instance,
+ float value,
+ uint8_t priority,
+ BACNET_ERROR_CLASS *error_class,
+ BACNET_ERROR_CODE *error_code)
+{
+ bool status = false;
+
+ (void)priority;
+ status = Lighting_Output_Max_Actual_Value_Set(object_instance, value);
+ if (!status) {
+ *error_class = ERROR_CLASS_PROPERTY;
+ *error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ }
+
+ return status;
+}
+
/**
* For a given object instance-number, sets the
* lighting-command-default-priority property value of the object.
@@ -2357,7 +2504,7 @@ bool Lighting_Output_Out_Of_Service(uint32_t object_instance)
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
- value = pObject->Lighting_Command.Out_Of_Service;
+ value = lighting_command_out_of_service_get(&pObject->Lighting_Command);
}
return value;
@@ -2377,7 +2524,7 @@ void Lighting_Output_Out_Of_Service_Set(uint32_t object_instance, bool value)
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
- pObject->Lighting_Command.Out_Of_Service = value;
+ lighting_command_out_of_service_set(&pObject->Lighting_Command, value);
}
}
@@ -2477,7 +2624,7 @@ float Lighting_Output_Last_On_Value(uint32_t object_instance)
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
- value = pObject->Lighting_Command.Last_On_Value;
+ value = lighting_command_last_on_value_get(&pObject->Lighting_Command);
}
return value;
@@ -2498,7 +2645,8 @@ bool Lighting_Output_Last_On_Value_Set(uint32_t object_instance, float value)
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
if (isgreaterequal(value, 1.0) && islessequal(value, 100.0)) {
- pObject->Lighting_Command.Last_On_Value = value;
+ lighting_command_last_on_value_set(
+ &pObject->Lighting_Command, value);
status = true;
}
}
@@ -2547,7 +2695,8 @@ float Lighting_Output_Default_On_Value(uint32_t object_instance)
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
- value = pObject->Lighting_Command.Default_On_Value;
+ value =
+ lighting_command_default_on_value_get(&pObject->Lighting_Command);
}
return value;
@@ -2568,7 +2717,8 @@ bool Lighting_Output_Default_On_Value_Set(uint32_t object_instance, float value)
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
if (isgreaterequal(value, 1.0) && islessequal(value, 100.0)) {
- pObject->Lighting_Command.Default_On_Value = value;
+ lighting_command_default_on_value_set(
+ &pObject->Lighting_Command, value);
status = true;
}
}
@@ -2643,9 +2793,7 @@ bool Lighting_Output_High_End_Trim_Set(uint32_t object_instance, float value)
pObject->High_End_Trim = value;
Lighting_Command_Trim_Apply(
pObject, Present_Value_Priority(pObject));
- if (!Lighting_Command_In_Progress(pObject)) {
- lighting_command_refresh(&pObject->Lighting_Command);
- }
+ lighting_command_refresh(&pObject->Lighting_Command);
status = true;
}
}
@@ -2720,9 +2868,7 @@ bool Lighting_Output_Low_End_Trim_Set(uint32_t object_instance, float value)
pObject->Low_End_Trim = value;
Lighting_Command_Trim_Apply(
pObject, Present_Value_Priority(pObject));
- if (!Lighting_Command_In_Progress(pObject)) {
- lighting_command_refresh(&pObject->Lighting_Command);
- }
+ lighting_command_refresh(&pObject->Lighting_Command);
status = true;
}
}
@@ -2798,9 +2944,7 @@ bool Lighting_Output_Trim_Fade_Time_Set(
pObject->Trim_Fade_Time = value;
Lighting_Command_Trim_Apply(
pObject, Present_Value_Priority(pObject));
- if (!Lighting_Command_In_Progress(pObject)) {
- lighting_command_refresh(&pObject->Lighting_Command);
- }
+ lighting_command_refresh(&pObject->Lighting_Command);
status = true;
}
}
@@ -2927,8 +3071,7 @@ bool Lighting_Output_Overridden_Status(uint32_t object_instance)
pObject = Keylist_Data(Object_List, object_instance);
if (pObject) {
- status = pObject->Lighting_Command.Overridden ||
- pObject->Lighting_Command.Overridden_Momentary;
+ status = lighting_command_overridden_status(&pObject->Lighting_Command);
}
return status;
@@ -3417,6 +3560,16 @@ int Lighting_Output_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
apdu_len = encode_application_enumerated(
apdu, Lighting_Output_Transition(rpdata->object_instance));
break;
+ case PROP_MIN_ACTUAL_VALUE:
+ real_value =
+ Lighting_Output_Min_Actual_Value(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
+ case PROP_MAX_ACTUAL_VALUE:
+ real_value =
+ Lighting_Output_Max_Actual_Value(rpdata->object_instance);
+ apdu_len = encode_application_real(&apdu[0], real_value);
+ break;
case PROP_PRIORITY_ARRAY:
apdu_len = bacnet_array_encode(
rpdata->object_instance, rpdata->array_index,
@@ -3533,6 +3686,10 @@ bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_known_property(
wp_data->application_data, wp_data->application_data_len, &value,
@@ -3621,6 +3778,26 @@ bool Lighting_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
&wp_data->error_code);
}
break;
+ case PROP_MIN_ACTUAL_VALUE:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status = Lighting_Output_Min_Actual_Value_Write(
+ wp_data->object_instance, value.type.Real,
+ wp_data->priority, &wp_data->error_class,
+ &wp_data->error_code);
+ }
+ break;
+ case PROP_MAX_ACTUAL_VALUE:
+ status = write_property_type_valid(
+ wp_data, &value, BACNET_APPLICATION_TAG_REAL);
+ if (status) {
+ status = Lighting_Output_Max_Actual_Value_Write(
+ wp_data->object_instance, value.type.Real,
+ wp_data->priority, &wp_data->error_class,
+ &wp_data->error_code);
+ }
+ break;
case PROP_RELINQUISH_DEFAULT:
status = write_property_type_valid(
wp_data, &value, BACNET_APPLICATION_TAG_REAL);
@@ -3823,6 +4000,25 @@ void Lighting_Output_Context_Set(uint32_t object_instance, void *context)
}
}
+/**
+ * @brief Get the lighting command data for a specific object instance
+ * @param object_instance [in] BACnet object instance number
+ * @return pointer to the lighting command data, or NULL if not found
+ */
+BACNET_LIGHTING_COMMAND_DATA *
+Lighting_Output_Lighting_Command_Data(uint32_t object_instance)
+{
+ BACNET_LIGHTING_COMMAND_DATA *data = NULL;
+ struct object_data *pObject;
+
+ pObject = Keylist_Data(Object_List, object_instance);
+ if (pObject) {
+ data = &pObject->Lighting_Command;
+ }
+
+ return data;
+}
+
/**
* @brief Creates a Color object
* @param object_instance - object-instance number of the object
@@ -3857,9 +4053,10 @@ uint32_t Lighting_Output_Create(uint32_t object_instance)
pObject->Description = NULL;
pObject->Present_Value = 0.0f;
lighting_command_init(&pObject->Lighting_Command);
- pObject->Lighting_Command.Key = object_instance;
- pObject->Lighting_Command.Notification_Head.callback =
- Lighting_Output_Tracking_Value_Callback;
+ lighting_command_key_set(&pObject->Lighting_Command, object_instance);
+ lighting_command_tracking_value_callback_set(
+ &pObject->Lighting_Command,
+ Lighting_Output_Tracking_Value_Callback);
pObject->Last_Lighting_Command.operation = BACNET_LIGHTS_NONE;
pObject->Last_Lighting_Command.use_target_level = false;
pObject->Last_Lighting_Command.use_ramp_rate = false;
@@ -3868,9 +4065,10 @@ uint32_t Lighting_Output_Create(uint32_t object_instance)
pObject->Last_Lighting_Command.use_priority = false;
pObject->Blink_Warn_Enable = false;
pObject->High_End_Trim = 100.0f;
- pObject->Lighting_Command.High_Trim_Value = pObject->High_End_Trim;
pObject->Low_End_Trim = 1.0f;
- pObject->Lighting_Command.Low_Trim_Value = pObject->Low_End_Trim;
+ lighting_command_trim_set(
+ &pObject->Lighting_Command, pObject->High_End_Trim,
+ pObject->Low_End_Trim, pObject->Trim_Fade_Time);
pObject->Trim_Fade_Time = 0;
pObject->Egress_Time_Seconds = 0;
pObject->Default_Fade_Time = 100;
diff --git a/src/bacnet/basic/object/lo.h b/src/bacnet/basic/object/lo.h
index 319e597afe..8f849d816d 100644
--- a/src/bacnet/basic/object/lo.h
+++ b/src/bacnet/basic/object/lo.h
@@ -201,6 +201,17 @@ BACNET_STACK_EXPORT
bool Lighting_Output_Default_Priority_Set(
uint32_t object_instance, unsigned priority);
+BACNET_STACK_EXPORT
+float Lighting_Output_Min_Actual_Value(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Lighting_Output_Min_Actual_Value_Set(
+ uint32_t object_instance, float value);
+BACNET_STACK_EXPORT
+float Lighting_Output_Max_Actual_Value(uint32_t object_instance);
+BACNET_STACK_EXPORT
+bool Lighting_Output_Max_Actual_Value_Set(
+ uint32_t object_instance, float value);
+
BACNET_STACK_EXPORT
bool Lighting_Output_Color_Override(uint32_t object_instance);
BACNET_STACK_EXPORT
@@ -248,6 +259,10 @@ void *Lighting_Output_Context_Get(uint32_t object_instance);
BACNET_STACK_EXPORT
void Lighting_Output_Context_Set(uint32_t object_instance, void *context);
+BACNET_STACK_EXPORT
+BACNET_LIGHTING_COMMAND_DATA *
+Lighting_Output_Lighting_Command_Data(uint32_t object_instance);
+
BACNET_STACK_EXPORT
uint32_t Lighting_Output_Create(uint32_t object_instance);
BACNET_STACK_EXPORT
diff --git a/src/bacnet/basic/object/loop.c b/src/bacnet/basic/object/loop.c
index d0ed0150f2..808ddadfff 100644
--- a/src/bacnet/basic/object/loop.c
+++ b/src/bacnet/basic/object/loop.c
@@ -45,6 +45,7 @@ static const BACNET_OBJECT_TYPE Object_Type = OBJECT_LOOP;
/* handling for manipulated and reference properties */
static write_property_function Write_Property_Internal_Callback;
static read_property_function Read_Property_Internal_Callback;
+static uint8_t Read_Property_Buffer[MAX_APDU];
/* Write Property notification callbacks for logging or other purposes */
static struct loop_write_property_notification Write_Property_Notification_Head;
@@ -1620,6 +1621,10 @@ bool Loop_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_known_array_property(
wp_data->application_data, wp_data->application_data_len, &value,
@@ -1986,7 +1991,6 @@ static bool Loop_Read_Variable_Reference_Update(
const BACNET_OBJECT_PROPERTY_REFERENCE *reference, float *value)
{
BACNET_READ_PROPERTY_DATA data = { 0 };
- uint8_t apdu[32] = { 0 };
int apdu_len = 0, len = 0;
bool status = false;
@@ -1995,8 +1999,8 @@ static bool Loop_Read_Variable_Reference_Update(
data.object_instance = reference->object_identifier.instance;
data.object_property = reference->property_identifier;
data.array_index = reference->property_array_index;
- data.application_data = apdu;
- data.application_data_len = sizeof(apdu);
+ data.application_data = Read_Property_Buffer;
+ data.application_data_len = sizeof(Read_Property_Buffer);
data.error_class = ERROR_CLASS_PROPERTY;
data.error_code = ERROR_CODE_UNKNOWN_PROPERTY;
if (Read_Property_Internal_Callback) {
@@ -2004,7 +2008,8 @@ static bool Loop_Read_Variable_Reference_Update(
}
if (apdu_len > 0) {
/* expecting only application tagged REAL values */
- len = bacnet_real_application_decode(apdu, apdu_len, value);
+ len = bacnet_real_application_decode(
+ Read_Property_Buffer, apdu_len, value);
if (len > 0) {
status = true;
}
diff --git a/src/bacnet/basic/object/lsp.c b/src/bacnet/basic/object/lsp.c
index a9a799f45a..47978725be 100644
--- a/src/bacnet/basic/object/lsp.c
+++ b/src/bacnet/basic/object/lsp.c
@@ -502,12 +502,14 @@ int Life_Safety_Point_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
bool state = false;
BACNET_RELIABILITY reliability = RELIABILITY_NO_FAULT_DETECTED;
uint8_t *apdu = NULL;
+ int apdu_size = 0;
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
(rpdata->application_data_len == 0)) {
return 0;
}
apdu = rpdata->application_data;
+ apdu_size = rpdata->application_data_len;
switch (rpdata->object_property) {
case PROP_OBJECT_IDENTIFIER:
apdu_len = encode_application_object_id(
@@ -570,8 +572,14 @@ int Life_Safety_Point_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
apdu_len = encode_application_enumerated(&apdu[0], mode);
break;
case PROP_ACCEPTED_MODES:
- for (mode = 0; mode <= LIFE_SAFETY_MODE_RESERVED_MIN; mode++) {
- len = encode_application_enumerated(&apdu[apdu_len], mode);
+ for (mode = 0; mode < LIFE_SAFETY_MODE_RESERVED_MIN; mode++) {
+ len = bacnet_enumerated_application_encode(
+ &apdu[apdu_len], apdu_size - apdu_len, mode);
+ if (len <= 0) {
+ rpdata->error_code =
+ ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
+ return BACNET_STATUS_ABORT;
+ }
apdu_len += len;
}
break;
@@ -602,6 +610,10 @@ bool Life_Safety_Point_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/lsz.c b/src/bacnet/basic/object/lsz.c
index fe69e3143c..bb3728a051 100644
--- a/src/bacnet/basic/object/lsz.c
+++ b/src/bacnet/basic/object/lsz.c
@@ -551,6 +551,7 @@ bool Life_Safety_Zone_Members_Add(
const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *data)
{
bool status = false;
+ int index = -1;
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *entry;
struct object_data *pObject;
@@ -563,12 +564,29 @@ bool Life_Safety_Zone_Members_Add(
return false;
}
memcpy(entry, data, sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE));
- status = Keylist_Data_Add(
+ index = Keylist_Data_Add(
pObject->Zone_Members, Keylist_Count(pObject->Zone_Members), entry);
+ if (index >= 0) {
+ status = true;
+ } else {
+ free(entry);
+ }
return status;
}
+/**
+ * @brief Free a list and all of its element data
+ * @param list - keylist to free
+ */
+static void Life_Safety_Zone_Members_List_Delete(OS_Keylist list)
+{
+ if (list) {
+ Keylist_Data_Free(list);
+ Keylist_Delete(list);
+ }
+}
+
/**
* @brief Remove all members from the Zone Members list
* @param object_instance - object-instance number of the object
@@ -594,26 +612,72 @@ static bool Life_Safety_Zone_Members_Write(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0, apdu_len = 0, apdu_size = 0;
const uint8_t *apdu = NULL;
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE data = { 0 };
+ struct object_data *pObject;
+ OS_Keylist members = NULL;
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *entry = NULL;
+ int index = -1;
if (wp_data == NULL) {
return false;
}
- /* empty the list */
- Life_Safety_Zone_Members_Clear(wp_data->object_instance);
+ pObject = Keylist_Data(Object_List, wp_data->object_instance);
+ if (!pObject) {
+ wp_data->error_class = ERROR_CLASS_OBJECT;
+ wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ return false;
+ }
+ members = Keylist_Create();
+ if (!members) {
+ wp_data->error_class = ERROR_CLASS_RESOURCES;
+ wp_data->error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY;
+ return false;
+ }
apdu = wp_data->application_data;
apdu_size = wp_data->application_data_len;
- /* decode all packed */
+ /* First pass: validate full payload and enforce forward progress. */
while (apdu_len < apdu_size) {
len = bacnet_device_object_property_reference_decode(
- apdu, apdu_size - apdu_len, &data);
- if (len < 0) {
+ &apdu[apdu_len], apdu_size - apdu_len, NULL);
+ if (len <= 0) {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
+ Life_Safety_Zone_Members_List_Delete(members);
return false;
}
- Life_Safety_Zone_Members_Add(wp_data->object_instance, &data);
apdu_len += len;
}
+ /* Second pass: decode and stage members in temporary list. */
+ apdu_len = 0;
+ while (apdu_len < apdu_size) {
+ len = bacnet_device_object_property_reference_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, &data);
+ if (len <= 0) {
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
+ Life_Safety_Zone_Members_List_Delete(members);
+ return false;
+ }
+ entry = calloc(1, sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE));
+ if (!entry) {
+ wp_data->error_class = ERROR_CLASS_RESOURCES;
+ wp_data->error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY;
+ Life_Safety_Zone_Members_List_Delete(members);
+ return false;
+ }
+ memcpy(entry, &data, sizeof(BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE));
+ index = Keylist_Data_Add(members, Keylist_Count(members), entry);
+ if (index < 0) {
+ free(entry);
+ wp_data->error_class = ERROR_CLASS_RESOURCES;
+ wp_data->error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY;
+ Life_Safety_Zone_Members_List_Delete(members);
+ return false;
+ }
+ apdu_len += len;
+ }
+ /* Commit new list only after full payload validates and stages. */
+ Life_Safety_Zone_Members_List_Delete(pObject->Zone_Members);
+ pObject->Zone_Members = members;
return true;
}
@@ -745,7 +809,13 @@ int Life_Safety_Zone_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
break;
case PROP_ACCEPTED_MODES:
for (mode = 0; mode < LIFE_SAFETY_MODE_RESERVED_MIN; mode++) {
- len = encode_application_enumerated(&apdu[apdu_len], mode);
+ len = bacnet_enumerated_application_encode(
+ &apdu[apdu_len], apdu_size - apdu_len, mode);
+ if (len <= 0) {
+ rpdata->error_code =
+ ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
+ return BACNET_STATUS_ABORT;
+ }
apdu_len += len;
}
break;
@@ -783,16 +853,22 @@ bool Life_Safety_Zone_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
- /* decode the some of the request */
- len = bacapp_decode_application_data(
- wp_data->application_data, wp_data->application_data_len, &value);
- /* FIXME: len < application_data_len: more data? */
- if (len < 0) {
- /* error while decoding - a value larger than we can handle */
- wp_data->error_class = ERROR_CLASS_PROPERTY;
- wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ /* Valid data? */
+ if (wp_data == NULL) {
return false;
}
+ if (wp_data->object_property != PROP_ZONE_MEMBERS) {
+ /* decode the some of the request */
+ len = bacapp_decode_application_data(
+ wp_data->application_data, wp_data->application_data_len, &value);
+ /* FIXME: len < application_data_len: more data? */
+ if (len < 0) {
+ /* error while decoding - a value larger than we can handle */
+ wp_data->error_class = ERROR_CLASS_PROPERTY;
+ wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ return false;
+ }
+ }
switch (wp_data->object_property) {
case PROP_MODE:
status = write_property_type_valid(
diff --git a/src/bacnet/basic/object/ms-input.c b/src/bacnet/basic/object/ms-input.c
index f575c42f0f..ba50fdb19a 100644
--- a/src/bacnet/basic/object/ms-input.c
+++ b/src/bacnet/basic/object/ms-input.c
@@ -883,6 +883,10 @@ bool Multistate_Input_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the first chunk of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/mso.c b/src/bacnet/basic/object/mso.c
index 49f5634e55..a75788dad0 100644
--- a/src/bacnet/basic/object/mso.c
+++ b/src/bacnet/basic/object/mso.c
@@ -1194,6 +1194,10 @@ bool Multistate_Output_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the first chunk of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/msv.c b/src/bacnet/basic/object/msv.c
index d6d9a45e73..73e4f6e339 100644
--- a/src/bacnet/basic/object/msv.c
+++ b/src/bacnet/basic/object/msv.c
@@ -885,6 +885,10 @@ bool Multistate_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/nc.c b/src/bacnet/basic/object/nc.c
index f98d923574..a57ac98914 100644
--- a/src/bacnet/basic/object/nc.c
+++ b/src/bacnet/basic/object/nc.c
@@ -370,6 +370,10 @@ bool Notification_Class_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
uint8_t idx;
int len = 0;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
CurrentNotify = &NC_Info[Notification_Class_Instance_To_Index(
wp_data->object_instance)];
@@ -922,6 +926,13 @@ int Notification_Class_Add_List_Element(BACNET_LIST_ELEMENT_DATA *list_element)
if (len > 0) {
new_element_count++;
application_data_len -= len;
+ if (new_element_count >= NC_MAX_RECIPIENTS) {
+ list_element->first_failed_element_number = new_element_count;
+ list_element->error_class = ERROR_CLASS_RESOURCES;
+ list_element->error_code =
+ ERROR_CODE_NO_SPACE_TO_ADD_LIST_ELEMENT;
+ return BACNET_STATUS_ERROR;
+ }
} else {
list_element->first_failed_element_number = new_element_count;
list_element->error_class = ERROR_CLASS_PROPERTY;
@@ -1085,10 +1096,17 @@ int Notification_Class_Remove_List_Element(
if (len > 0) {
remove_element_count++;
application_data_len -= len;
+ if (remove_element_count >= NC_MAX_RECIPIENTS) {
+ list_element->first_failed_element_number =
+ remove_element_count;
+ list_element->error_class = ERROR_CLASS_SERVICES;
+ list_element->error_code = ERROR_CODE_LIST_ELEMENT_NOT_FOUND;
+ return BACNET_STATUS_ERROR;
+ }
} else {
list_element->first_failed_element_number = remove_element_count;
list_element->error_class = ERROR_CLASS_PROPERTY;
- list_element->error_code = ERROR_CODE_INVALID_DATA_ENCODING;
+ list_element->error_code = ERROR_CODE_INVALID_DATA_TYPE;
return BACNET_STATUS_ERROR;
}
}
diff --git a/src/bacnet/basic/object/netport.c b/src/bacnet/basic/object/netport.c
index 1997261ca1..284f2e3f16 100644
--- a/src/bacnet/basic/object/netport.c
+++ b/src/bacnet/basic/object/netport.c
@@ -4552,6 +4552,10 @@ bool Network_Port_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
uint32_t capacity;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
if (!Network_Port_Valid_Instance(wp_data->object_instance)) {
wp_data->error_class = ERROR_CLASS_OBJECT;
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
diff --git a/src/bacnet/basic/object/osv.c b/src/bacnet/basic/object/osv.c
index b1f9d0eae3..aaca71adac 100644
--- a/src/bacnet/basic/object/osv.c
+++ b/src/bacnet/basic/object/osv.c
@@ -659,12 +659,10 @@ bool OctetString_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
if (wp_data == NULL) {
return false;
}
- if (wp_data->application_data_len == 0) {
- return false;
- }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/piv.c b/src/bacnet/basic/object/piv.c
index 9153044486..45c253fdce 100644
--- a/src/bacnet/basic/object/piv.c
+++ b/src/bacnet/basic/object/piv.c
@@ -470,13 +470,10 @@ bool PositiveInteger_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
BACNET_APPLICATION_DATA_VALUE value = { 0 };
POSITIVEINTEGER_VALUE_DESCR *pObject = NULL;
+ /* Valid data? */
if (wp_data == NULL) {
return false;
}
- if (wp_data->application_data_len == 0) {
- return false;
- }
-
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/program.c b/src/bacnet/basic/object/program.c
index 1fbe69e426..c67aca9c79 100644
--- a/src/bacnet/basic/object/program.c
+++ b/src/bacnet/basic/object/program.c
@@ -937,6 +937,10 @@ bool Program_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/schedule.c b/src/bacnet/basic/object/schedule.c
index 3b74ed16fe..420eda7c82 100644
--- a/src/bacnet/basic/object/schedule.c
+++ b/src/bacnet/basic/object/schedule.c
@@ -940,6 +940,10 @@ bool Schedule_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_known_array_property(
wp_data->application_data, wp_data->application_data_len, &value,
diff --git a/src/bacnet/basic/object/structured_view.c b/src/bacnet/basic/object/structured_view.c
index 45b7d8d810..64d689fba6 100644
--- a/src/bacnet/basic/object/structured_view.c
+++ b/src/bacnet/basic/object/structured_view.c
@@ -1586,6 +1586,10 @@ bool Structured_View_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
BACNET_UNSIGNED_INTEGER array_size = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_known_array_property(
wp_data->application_data, wp_data->application_data_len, &value,
diff --git a/src/bacnet/basic/object/time_value.c b/src/bacnet/basic/object/time_value.c
index f1b7bf3b65..5f40736402 100644
--- a/src/bacnet/basic/object/time_value.c
+++ b/src/bacnet/basic/object/time_value.c
@@ -669,6 +669,10 @@ bool Time_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
BACNET_APPLICATION_DATA_VALUE value = { 0 };
int len = 0;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/object/timer.c b/src/bacnet/basic/object/timer.c
index 7ee187deeb..73e6781ff5 100644
--- a/src/bacnet/basic/object/timer.c
+++ b/src/bacnet/basic/object/timer.c
@@ -69,6 +69,7 @@ struct object_data {
BACNET_RELIABILITY Reliability;
bool Out_Of_Service : 1;
bool Changed : 1;
+ bool Writeback_Active : 1;
void *Context;
};
@@ -254,6 +255,34 @@ static bool Timer_Reference_List_Member_Empty(
return status;
}
+/**
+ * For a given object instance-number, determines if the member is a
+ * self-reference with properties that can re-enter Timer transition logic
+ *
+ * @param object_instance - object-instance number of the object
+ * @param pMember - object property reference element
+ * @return true if the member is self
+ */
+static bool Timer_Reference_List_Member_Self(
+ uint32_t object_instance,
+ const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember)
+{
+ bool status = false;
+
+ if (pMember) {
+ if ((pMember->objectIdentifier.type == OBJECT_TIMER) &&
+ (pMember->objectIdentifier.instance == object_instance) &&
+ (pMember->deviceIdentifier.type == OBJECT_DEVICE) &&
+ (pMember->deviceIdentifier.instance != BACNET_MAX_INSTANCE) &&
+ (((pMember->propertyIdentifier == PROP_PRESENT_VALUE) ||
+ (pMember->propertyIdentifier == PROP_TIMER_RUNNING)))) {
+ status = true;
+ }
+ }
+
+ return status;
+}
+
/**
* For a given object instance-number, returns the list member element
* @param object_instance - object-instance number of the object
@@ -519,6 +548,11 @@ static bool Timer_Write_Members(
const BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *pMember = NULL;
if (pObject && value) {
+ if (pObject->Writeback_Active) {
+ /* Prevent recursive writeback */
+ return false;
+ }
+ pObject->Writeback_Active = true;
for (i = 0; i < BACNET_TIMER_MANIPULATED_PROPERTIES_MAX; i++) {
pMember = &pObject->Manipulated_Properties[i];
if (Timer_Reference_List_Member_Empty(pMember)) {
@@ -549,6 +583,7 @@ static bool Timer_Write_Members(
Timer_Write_Property_Notify(object_instance, status, &wp_data);
}
}
+ pObject->Writeback_Active = false;
}
return status;
@@ -1856,7 +1891,7 @@ static BACNET_ERROR_CODE Timer_List_Of_Object_Property_References_Add(
bool status = false;
if ((!application_data) && (application_data_len == 0)) {
- /* empty the BACnetLIST - remove all before adding */
+ /* empty the BACnetLIST - remove all */
(void)Timer_Reference_List_Member_Element_Remove(object_instance, NULL);
error_code = ERROR_CODE_SUCCESS;
} else {
@@ -1866,6 +1901,11 @@ static BACNET_ERROR_CODE Timer_List_Of_Object_Property_References_Add(
if (Timer_Reference_List_Member_Empty(&new_value)) {
/* The element value is out of range for the property. */
error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
+ } else if (Timer_Reference_List_Member_Self(
+ object_instance, &new_value)) {
+ /* The element value is a self-reference with
+ properties that can re-enter Timer transition logic */
+ error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
} else {
status = Timer_Reference_List_Member_Element_Add(
object_instance, &new_value);
@@ -1941,6 +1981,10 @@ bool Timer_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* decode the some of the request */
len = bacapp_decode_known_array_property(
wp_data->application_data, wp_data->application_data_len, &value,
diff --git a/src/bacnet/basic/object/trendlog.c b/src/bacnet/basic/object/trendlog.c
index 892e885462..00ddfe3c11 100644
--- a/src/bacnet/basic/object/trendlog.c
+++ b/src/bacnet/basic/object/trendlog.c
@@ -539,6 +539,10 @@ bool Trend_Log_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool bEffectiveEnable;
int log_index;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
/* Pin down which log to look at */
log_index = Trend_Log_Instance_To_Index(wp_data->object_instance);
if (log_index >= MAX_TREND_LOGS) {
@@ -547,7 +551,6 @@ bool Trend_Log_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
return false;
}
CurrentLog = &LogInfo[log_index];
-
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
diff --git a/src/bacnet/basic/server/bacnet_device.c b/src/bacnet/basic/server/bacnet_device.c
index ec55c922f3..a6f5e7464d 100644
--- a/src/bacnet/basic/server/bacnet_device.c
+++ b/src/bacnet/basic/server/bacnet_device.c
@@ -1210,6 +1210,7 @@ static const int32_t Device_Properties_Optional[] = {
PROP_LOCAL_DATE,
PROP_DAYLIGHT_SAVINGS_STATUS,
PROP_LOCATION,
+ PROP_DEVICE_UUID,
#if (BACNET_COV_SUBSCRIPTIONS_SIZE > 0)
PROP_ACTIVE_COV_SUBSCRIPTIONS,
#endif
@@ -1350,15 +1351,18 @@ bool Device_Objects_Property_List_Member(
/* local data */
static uint32_t Object_Instance_Number = BACNET_MAX_INSTANCE;
-static BACNET_CHARACTER_STRING My_Object_Name;
+static BACNET_CHARACTER_STRING Object_Name_String;
static BACNET_DEVICE_STATUS System_Status = STATUS_OPERATIONAL;
static const char *Device_Name_Default = BACNET_DEVICE_OBJECT_NAME;
static const char *Device_Vendor_Name_Default = BACNET_VENDOR_NAME;
static uint16_t Vendor_Identifier = BACNET_VENDOR_ID;
-static const char *Model_Name = BACNET_DEVICE_MODEL_NAME;
+static BACNET_CHARACTER_STRING Device_Model_Name_String;
+static const char *Device_Model_Name_Default = BACNET_DEVICE_MODEL_NAME;
static const char *Application_Software_Version = BACNET_DEVICE_VERSION;
static const char *Firmware_Revision = BACNET_VERSION_TEXT;
+static BACNET_CHARACTER_STRING Device_Location_String;
static const char *Device_Location_Default = BACNET_DEVICE_LOCATION_NAME;
+static BACNET_CHARACTER_STRING Device_Description_String;
static const char *Device_Description_Default = BACNET_DEVICE_DESCRIPTION;
static uint32_t Database_Revision;
static BACNET_REINITIALIZED_STATE Reinitialize_State = BACNET_REINIT_IDLE;
@@ -1654,7 +1658,7 @@ bool Device_Object_Name(
bool status = false;
if (object_instance == Object_Instance_Number) {
- status = characterstring_copy(object_name, &My_Object_Name);
+ status = characterstring_copy(object_name, &Object_Name_String);
}
return status;
@@ -1664,9 +1668,9 @@ bool Device_Set_Object_Name(const BACNET_CHARACTER_STRING *object_name)
{
bool status = false; /*return value */
- if (!characterstring_same(&My_Object_Name, object_name)) {
+ if (!characterstring_same(&Object_Name_String, object_name)) {
/* Make the change and update the database revision */
- status = characterstring_copy(&My_Object_Name, object_name);
+ status = characterstring_copy(&Object_Name_String, object_name);
Device_Inc_Database_Revision();
}
@@ -1680,7 +1684,7 @@ bool Device_Set_Object_Name(const BACNET_CHARACTER_STRING *object_name)
*/
bool Device_Object_Name_ANSI_Init(const char *value)
{
- return characterstring_init_ansi(&My_Object_Name, value);
+ return characterstring_init_ansi(&Object_Name_String, value);
}
/**
@@ -1689,7 +1693,7 @@ bool Device_Object_Name_ANSI_Init(const char *value)
*/
char *Device_Object_Name_ANSI(void)
{
- return (char *)characterstring_value(&My_Object_Name);
+ return (char *)characterstring_value(&Object_Name_String);
}
/**
@@ -1848,13 +1852,14 @@ void Device_Set_Vendor_Identifier(uint16_t vendor_id)
const char *Device_Model_Name(void)
{
- return Model_Name;
+ return (char *)characterstring_value(&Device_Model_Name_String);
}
bool Device_Set_Model_Name(const char *name, size_t length)
{
(void)length;
- Model_Name = name ? name : BACNET_DEVICE_MODEL_NAME;
+ characterstring_init_ansi(
+ &Device_Model_Name_String, name ? name : Device_Model_Name_Default);
return true;
}
@@ -1887,26 +1892,28 @@ bool Device_Set_Application_Software_Version(const char *name, size_t length)
const char *Device_Description(void)
{
- return Device_Description_Default;
+ return (char *)characterstring_value(&Device_Description_String);
}
bool Device_Set_Description(const char *name, size_t length)
{
(void)length;
- Device_Description_Default = name ? name : BACNET_DEVICE_DESCRIPTION;
+ characterstring_init_ansi(
+ &Device_Description_String, name ? name : Device_Description_Default);
return true;
}
const char *Device_Location(void)
{
- return Device_Location_Default;
+ return (char *)characterstring_value(&Device_Location_String);
}
bool Device_Set_Location(const char *name, size_t length)
{
(void)length;
- Device_Location_Default = name ? name : BACNET_DEVICE_LOCATION_NAME;
+ characterstring_init_ansi(
+ &Device_Location_String, name ? name : Device_Location_Default);
return true;
}
@@ -2598,6 +2605,7 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
int apdu_len = 0; /* return value */
BACNET_BIT_STRING bit_string = { 0 };
BACNET_CHARACTER_STRING char_string = { 0 };
+ BACNET_OCTET_STRING octet_string = { 0 };
uint32_t i = 0;
uint32_t count = 0;
uint8_t *apdu = NULL;
@@ -2616,8 +2624,8 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
&apdu[0], OBJECT_DEVICE, Object_Instance_Number);
break;
case PROP_OBJECT_NAME:
- apdu_len =
- encode_application_character_string(&apdu[0], &My_Object_Name);
+ apdu_len = encode_application_character_string(
+ &apdu[0], &Object_Name_String);
break;
case PROP_OBJECT_TYPE:
apdu_len = encode_application_enumerated(&apdu[0], OBJECT_DEVICE);
@@ -2640,9 +2648,8 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
apdu_len = encode_application_unsigned(&apdu[0], Vendor_Identifier);
break;
case PROP_MODEL_NAME:
- characterstring_init_ansi(&char_string, Model_Name);
- apdu_len =
- encode_application_character_string(&apdu[0], &char_string);
+ apdu_len = encode_application_character_string(
+ &apdu[0], &Device_Model_Name_String);
break;
case PROP_FIRMWARE_REVISION:
characterstring_init_ansi(&char_string, Firmware_Revision);
@@ -2656,9 +2663,8 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
encode_application_character_string(&apdu[0], &char_string);
break;
case PROP_LOCATION:
- characterstring_init_ansi(&char_string, Device_Location_Default);
- apdu_len =
- encode_application_character_string(&apdu[0], &char_string);
+ apdu_len = encode_application_character_string(
+ &apdu[0], &Device_Location_String);
break;
case PROP_LOCAL_TIME:
Update_Current_Time();
@@ -2748,6 +2754,11 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
break;
case PROP_DEVICE_ADDRESS_BINDING:
apdu_len = address_list_encode(&apdu[0], apdu_max);
+ if (apdu_len < 0) {
+ rpdata->error_code =
+ ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
+ apdu_len = BACNET_STATUS_ABORT;
+ }
break;
case PROP_DATABASE_REVISION:
apdu_len = encode_application_unsigned(
@@ -2839,6 +2850,10 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata)
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
break;
+ case PROP_DEVICE_UUID:
+ octetstring_init(&octet_string, Device_UUID, sizeof(Device_UUID));
+ apdu_len = encode_application_octet_string(&apdu[0], &octet_string);
+ break;
case PROP_TIME_OF_DEVICE_RESTART:
apdu_len =
bacapp_encode_timestamp(&apdu[0], &Time_Of_Device_Restart);
@@ -3031,7 +3046,7 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data)
break;
case PROP_OBJECT_NAME:
status = write_property_string_valid(
- wp_data, &value, characterstring_capacity(&My_Object_Name));
+ wp_data, &value, characterstring_capacity(&Object_Name_String));
if (status) {
/* All the object names in a device must be unique */
if (Device_Valid_Object_Name(
@@ -3055,9 +3070,8 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data)
status = write_property_empty_string_valid(
wp_data, &value, MAX_DEV_LOC_LEN);
if (status) {
- Device_Set_Location(
- characterstring_value(&value.type.Character_String),
- characterstring_length(&value.type.Character_String));
+ characterstring_copy(
+ &Device_Location_String, &value.type.Character_String);
}
break;
@@ -3065,18 +3079,16 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data)
status = write_property_empty_string_valid(
wp_data, &value, MAX_DEV_DESC_LEN);
if (status) {
- Device_Set_Description(
- characterstring_value(&value.type.Character_String),
- characterstring_length(&value.type.Character_String));
+ characterstring_copy(
+ &Device_Description_String, &value.type.Character_String);
}
break;
case PROP_MODEL_NAME:
status = write_property_empty_string_valid(
wp_data, &value, MAX_DEV_MOD_LEN);
if (status) {
- Device_Set_Model_Name(
- characterstring_value(&value.type.Character_String),
- characterstring_length(&value.type.Character_String));
+ characterstring_copy(
+ &Device_Model_Name_String, &value.type.Character_String);
}
break;
#if defined(BACNET_TIME_MASTER)
@@ -3253,7 +3265,7 @@ static bool Device_Write_Property_Object_Name(
{
bool status = false; /* return value */
int len = 0;
- BACNET_CHARACTER_STRING value;
+ BACNET_CHARACTER_STRING value = { 0 };
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
uint32_t object_instance = 0;
int apdu_size = 0;
@@ -3335,6 +3347,10 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool status = false; /* Ever the pessimist! */
struct object_functions *pObject = NULL;
+ /* Valid data? */
+ if (wp_data == NULL) {
+ return false;
+ }
Device_Backup_Failure_Timeout_Restart();
/* initialize the default return values */
wp_data->error_class = ERROR_CLASS_OBJECT;
@@ -3360,6 +3376,15 @@ bool Device_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
wp_data->object_type, wp_data->object_instance,
wp_data->object_property)) {
status = Write_Property_Proprietary_Callback(wp_data);
+ } else if (
+ (wp_data->application_data_len == 0) &&
+ !property_list_bacnet_list_member(
+ wp_data->object_type, wp_data->object_property)) {
+ /* only list properties can be written with
+ an empty application payload */
+ wp_data->error_class = ERROR_CLASS_SERVICES;
+ wp_data->error_code = ERROR_CODE_INVALID_TAG;
+ status = false;
} else {
status = pObject->Object_Write_Property(wp_data);
}
@@ -3760,8 +3785,6 @@ void Device_End_Restore(void)
datetime_local(&bdateTime.date, &bdateTime.time, NULL, NULL);
bacapp_timestamp_datetime_set(&Last_Restore_Time, &bdateTime);
- /* delete all existing objects before restore */
- Device_Delete_Objects();
/* create objects from the backup file */
file_size = bacfile_file_size(Configuration_Files[0]);
while (offset < file_size) {
@@ -3771,6 +3794,11 @@ void Device_End_Restore(void)
decoded_len = create_object_decode_service_request(
apdu, apdu_len, &create_data);
if (decoded_len > 0) {
+ if (offset == 0) {
+ /* delete all existing objects before restore
+ after the first record is successfully decoded */
+ Device_Delete_Objects();
+ }
offset += decoded_len;
create_data.error_class = ERROR_CLASS_PROPERTY;
create_data.error_code = ERROR_CODE_SUCCESS;
@@ -3883,7 +3911,12 @@ void Device_Init(object_functions_t *object_table)
if (Object_Instance_Number > BACNET_MAX_INSTANCE) {
Object_Instance_Number = BACNET_MAX_INSTANCE;
}
- characterstring_init_ansi(&My_Object_Name, Device_Name_Default);
+ characterstring_init_ansi(&Object_Name_String, Device_Name_Default);
+ characterstring_init_ansi(
+ &Device_Model_Name_String, Device_Model_Name_Default);
+ characterstring_init_ansi(
+ &Device_Description_String, Device_Description_Default);
+ characterstring_init_ansi(&Device_Location_String, Device_Location_Default);
#if (BACNET_PROTOCOL_REVISION >= 14)
#ifdef CONFIG_BACNET_BASIC_OBJECT_CHANNEL
/* link WriteProperty to Channel object for references */
diff --git a/src/bacnet/basic/service/h_arf.c b/src/bacnet/basic/service/h_arf.c
index 8a5ba827cc..d8d4c1f402 100644
--- a/src/bacnet/basic/service/h_arf.c
+++ b/src/bacnet/basic/service/h_arf.c
@@ -92,6 +92,7 @@ void handler_atomic_read_file(
BACNET_ADDRESS my_address;
BACNET_ERROR_CLASS error_class = ERROR_CLASS_OBJECT;
BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT;
+ BACNET_UNSIGNED_INTEGER file_size;
#if PRINT_ENABLED
fprintf(stderr, "Received Atomic-Read-File Request!\n");
@@ -116,52 +117,83 @@ void handler_atomic_read_file(
}
len = arf_decode_service_request(service_request, service_len, &data);
/* bad decoding - send an abort */
- if (len < 0) {
- len = abort_encode_apdu(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
- ABORT_REASON_OTHER, true);
- debug_print("ARF: Bad Encoding. Sending Abort!\n");
- goto ARF_ABORT;
- }
- if (data.object_type == OBJECT_FILE) {
- if (!bacfile_valid_instance(data.object_instance)) {
+ if (!error) {
+ if (len < 0) {
+ DEBUG_PRINTF("ARF: Bad Encoding. Sending Abort!\n");
+ error_code = ERROR_CODE_ABORT_OTHER;
error = true;
- } else if (data.access == FILE_STREAM_ACCESS) {
- if (data.type.stream.requestedOctetCount <=
- octetstring_capacity(&data.fileData[0])) {
- bacfile_read_stream_data(&data);
- debug_fprintf(
- stderr, "ARF: Stream offset %d, %d octets.\n",
- (int)data.type.stream.fileStartPosition,
- (int)data.type.stream.requestedOctetCount);
- len = arf_ack_encode_apdu(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
- &data);
- } else {
- len = abort_encode_apdu(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
- ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true);
- debug_fprintf(
- stderr,
- "ARF: Too Big To Send (%d >= %d). "
- "Sending Abort!\n",
- (int)data.type.stream.requestedOctetCount,
- (int)octetstring_capacity(&data.fileData[0]));
- }
- } else if (data.access == FILE_RECORD_ACCESS) {
- if (data.type.record.fileStartRecord >=
- BACNET_READ_FILE_RECORD_COUNT) {
- error_class = ERROR_CLASS_SERVICES;
- error_code = ERROR_CODE_INVALID_FILE_START_POSITION;
+ } else if (data.object_type == OBJECT_FILE) {
+ if (!bacfile_valid_instance(data.object_instance)) {
+ error_code = ERROR_CODE_UNKNOWN_OBJECT;
error = true;
- } else if (bacfile_read_record_data(&data)) {
- debug_fprintf(
- stderr, "ARF: fileStartRecord %d, %u RecordCount.\n",
- (int)data.type.record.fileStartRecord,
- (unsigned)data.type.record.RecordCount);
- len = arf_ack_encode_apdu(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
- &data);
+ } else if (data.access == FILE_STREAM_ACCESS) {
+ file_size = bacfile_file_size(data.object_instance);
+ if (file_size > INT32_MAX) {
+ file_size = INT32_MAX;
+ }
+ if ((data.type.stream.fileStartPosition < 0) ||
+ (data.type.stream.fileStartPosition > file_size)) {
+ /* If the 'File Start Position' parameter is either less
+ than 0 or exceeds the actual file size, then the appropriate
+ error is returned in a 'Result(-)' response.*/
+ error = true;
+ error_code = ERROR_CODE_INVALID_FILE_START_POSITION;
+ } else if (
+ data.type.stream.requestedOctetCount <=
+ octetstring_capacity(&data.fileData[0])) {
+ bacfile_read_stream_data(&data);
+ DEBUG_PRINTF(
+ "ARF: Stream offset %d, %d octets.\n",
+ (int)data.type.stream.fileStartPosition,
+ (int)data.type.stream.requestedOctetCount);
+ len = arf_ack_encode_apdu(
+ &Handler_Transmit_Buffer[pdu_len],
+ service_data->invoke_id, &data);
+ pdu_len += len;
+ } else {
+ error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
+ error = true;
+ DEBUG_PRINTF(
+ "ARF: Too Big To Send (%d >= %d). "
+ "Sending Abort!\n",
+ (int)data.type.stream.requestedOctetCount,
+ (int)octetstring_capacity(&data.fileData[0]));
+ }
+ } else if (data.access == FILE_RECORD_ACCESS) {
+ if (data.type.record.RecordCount > ARRAY_SIZE(data.fileData)) {
+ DEBUG_PRINTF(
+ "ARF: RecordCount %u > %u. Sending Reject!\n",
+ (unsigned)data.type.record.RecordCount,
+ (unsigned)ARRAY_SIZE(data.fileData));
+ error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE;
+ error = true;
+ } else if (
+ (data.type.record.fileStartRecord < 0) ||
+ (data.type.record.fileStartRecord >=
+ ARRAY_SIZE(data.fileData))) {
+ /* If the 'File Start Record' parameter is either less
+ than 0 or exceeds the actual file size, then the appropriate
+ error is returned in a 'Result(-)' response.*/
+ DEBUG_PRINTF(
+ "ARF: fileStartRecord %d >= %u. Sending Error!\n",
+ (int)data.type.record.fileStartRecord,
+ (unsigned)ARRAY_SIZE(data.fileData));
+ error_code = ERROR_CODE_INVALID_FILE_START_POSITION;
+ error = true;
+ } else if (bacfile_read_record_data(&data)) {
+ DEBUG_PRINTF(
+ "ARF: fileStartRecord %d, %u RecordCount.\n",
+ (int)data.type.record.fileStartRecord,
+ (unsigned)data.type.record.RecordCount);
+ len = arf_ack_encode_apdu(
+ &Handler_Transmit_Buffer[pdu_len],
+ service_data->invoke_id, &data);
+ pdu_len += len;
+ } else {
+ DEBUG_PRINTF("ARF: file_access_denied! Sending Error!");
+ error = true;
+ error_code = ERROR_CODE_FILE_ACCESS_DENIED;
+ }
} else {
error = true;
error_class = ERROR_CLASS_OBJECT;
diff --git a/src/bacnet/basic/service/h_arf_a.c b/src/bacnet/basic/service/h_arf_a.c
index 9af412eb00..60186c72d2 100644
--- a/src/bacnet/basic/service/h_arf_a.c
+++ b/src/bacnet/basic/service/h_arf_a.c
@@ -32,7 +32,7 @@ void handler_atomic_read_file_ack(
BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data)
{
int len = 0;
- BACNET_ATOMIC_READ_FILE_DATA data;
+ BACNET_ATOMIC_READ_FILE_DATA data = { 0 };
uint32_t instance = 0;
(void)src;
diff --git a/src/bacnet/basic/service/h_awf.c b/src/bacnet/basic/service/h_awf.c
index d332ce15a3..98faa2ae89 100644
--- a/src/bacnet/basic/service/h_awf.c
+++ b/src/bacnet/basic/service/h_awf.c
@@ -57,18 +57,32 @@ of the BACnet device is a local matter and is not defined by this
standard.
*/
-void handler_atomic_write_file(
+/**
+ * @brief Encode an AtomicWriteFile ACK or Error response into a caller-supplied
+ * buffer. The public handler_atomic_write_file() wrapper calls this
+ * function with Handler_Transmit_Buffer so that the encoding logic is
+ * independently testable.
+ * @param apdu Output buffer for the encoded PDU (NPDU + APDU), or NULL to
+ * calculate the required length only.
+ * @param service_request The raw APDU payload of the received request.
+ * @param service_len Length of service_request in bytes.
+ * @param src Source address of the request (used for NPDU encoding).
+ * @param npdu_data NPDU data structure to populate during encoding.
+ * @param service_data Confirmed-service metadata from the request.
+ * @return Total number of bytes written (NPDU + APDU), or 0 on failure.
+ */
+int handler_atomic_write_file_encode(
+ uint8_t *apdu,
uint8_t *service_request,
uint16_t service_len,
BACNET_ADDRESS *src,
+ BACNET_NPDU_DATA *npdu_data,
BACNET_CONFIRMED_SERVICE_DATA *service_data)
{
BACNET_ATOMIC_WRITE_FILE_DATA data;
int len = 0;
int pdu_len = 0;
bool error = false;
- int bytes_sent = 0;
- BACNET_NPDU_DATA npdu_data;
BACNET_ADDRESS my_address;
BACNET_ERROR_CLASS error_class = ERROR_CLASS_OBJECT;
BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT;
@@ -76,57 +90,68 @@ void handler_atomic_write_file(
debug_print("Received AtomicWriteFile Request!\n");
/* encode the NPDU portion of the packet */
datalink_get_my_address(&my_address);
- npdu_encode_npdu_data(&npdu_data, false, service_data->priority);
- pdu_len = npdu_encode_pdu(
- &Handler_Transmit_Buffer[0], src, &my_address, &npdu_data);
+ npdu_encode_npdu_data(npdu_data, false, service_data->priority);
+ len = npdu_encode_pdu(apdu, src, &my_address, npdu_data);
+ pdu_len += len;
+ if (apdu) {
+ apdu += len;
+ }
if (service_len == 0) {
len = reject_encode_apdu(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
+ apdu, service_data->invoke_id,
REJECT_REASON_MISSING_REQUIRED_PARAMETER);
debug_print("AWF: Missing Required Parameter. Sending Reject!\n");
- goto AWF_ABORT;
+ pdu_len += len;
+ return pdu_len;
} else if (service_data->segmented_message) {
len = abort_encode_apdu(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
+ apdu, service_data->invoke_id,
ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true);
- debug_print("AWF:Segmented Message. Sending Abort!\n");
- goto AWF_ABORT;
+ debug_print("AWF: Segmented Message. Sending Abort!\n");
+ pdu_len += len;
+ return pdu_len;
}
len = awf_decode_service_request(service_request, service_len, &data);
/* bad decoding - send an abort */
if (len < 0) {
len = abort_encode_apdu(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
- ABORT_REASON_OTHER, true);
+ apdu, service_data->invoke_id, ABORT_REASON_OTHER, true);
debug_print("AWF: Bad Encoding. Sending Abort!\n");
- goto AWF_ABORT;
+ pdu_len += len;
+ return pdu_len;
}
if (data.object_type == OBJECT_FILE) {
if (!bacfile_valid_instance(data.object_instance)) {
error = true;
} else if (data.access == FILE_STREAM_ACCESS) {
- if (bacfile_write_stream_data(&data)) {
+ if (data.type.stream.fileStartPosition < -1) {
+ error = true;
+ error_class = ERROR_CLASS_SERVICES;
+ error_code = ERROR_CODE_INVALID_FILE_START_POSITION;
+ } else if (bacfile_write_stream_data(&data)) {
debug_fprintf(
stderr, "AWF: Stream offset %d, %d bytes\n",
data.type.stream.fileStartPosition,
(int)octetstring_length(&data.fileData[0]));
- len = awf_ack_encode_apdu(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
- &data);
+ len = awf_ack_encode_apdu(apdu, service_data->invoke_id, &data);
+ pdu_len += len;
} else {
error = true;
error_class = ERROR_CLASS_OBJECT;
error_code = ERROR_CODE_FILE_ACCESS_DENIED;
}
} else if (data.access == FILE_RECORD_ACCESS) {
- if (bacfile_write_record_data(&data)) {
+ if (data.type.record.fileStartRecord < -1) {
+ error = true;
+ error_class = ERROR_CLASS_SERVICES;
+ error_code = ERROR_CODE_INVALID_FILE_START_POSITION;
+ } else if (bacfile_write_record_data(&data)) {
debug_fprintf(
stderr, "AWF: StartRecord %d, RecordCount %u\n",
data.type.record.fileStartRecord,
data.type.record.returnedRecordCount);
- len = awf_ack_encode_apdu(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
- &data);
+ len = awf_ack_encode_apdu(apdu, service_data->invoke_id, &data);
+ pdu_len += len;
} else {
error = true;
error_class = ERROR_CLASS_OBJECT;
@@ -136,7 +161,7 @@ void handler_atomic_write_file(
error = true;
error_class = ERROR_CLASS_SERVICES;
error_code = ERROR_CODE_INVALID_FILE_ACCESS_METHOD;
- debug_print("AWF: Record Access Requested. Sending Error!\n");
+ debug_print("AWF: Invalid File Access Method. Sending Error!\n");
}
} else {
error = true;
@@ -145,16 +170,38 @@ void handler_atomic_write_file(
}
if (error) {
len = bacerror_encode_apdu(
- &Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
- SERVICE_CONFIRMED_ATOMIC_WRITE_FILE, error_class, error_code);
+ apdu, service_data->invoke_id, SERVICE_CONFIRMED_ATOMIC_WRITE_FILE,
+ error_class, error_code);
+ pdu_len += len;
}
-AWF_ABORT:
- pdu_len += len;
+
+ return pdu_len;
+}
+
+/**
+ * @brief Handler for the AtomicWriteFile service. Encodes and sends an ACK or
+ * Error response based on the provided request and service data.
+ * @param service_request The APDU portion of the request.
+ * @param service_len The length of the service_request buffer.
+ * @param src The source address to send the response to.
+ * @param service_data The confirmed service data from the request.
+ */
+void handler_atomic_write_file(
+ uint8_t *service_request,
+ uint16_t service_len,
+ BACNET_ADDRESS *src,
+ BACNET_CONFIRMED_SERVICE_DATA *service_data)
+{
+ int pdu_len = 0;
+ int bytes_sent = 0;
+ BACNET_NPDU_DATA npdu_data = { 0 };
+
+ pdu_len = handler_atomic_write_file_encode(
+ Handler_Transmit_Buffer, service_request, service_len, src, &npdu_data,
+ service_data);
bytes_sent = datalink_send_pdu(
src, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len);
if (bytes_sent <= 0) {
debug_perror("AWF: Failed to send PDU\n");
}
-
- return;
}
diff --git a/src/bacnet/basic/service/h_awf.h b/src/bacnet/basic/service/h_awf.h
index 142da4f83f..40aeaec47b 100644
--- a/src/bacnet/basic/service/h_awf.h
+++ b/src/bacnet/basic/service/h_awf.h
@@ -15,11 +15,21 @@
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/apdu.h"
+#include "bacnet/npdu.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
+BACNET_STACK_EXPORT
+int handler_atomic_write_file_encode(
+ uint8_t *apdu,
+ uint8_t *service_request,
+ uint16_t service_len,
+ BACNET_ADDRESS *src,
+ BACNET_NPDU_DATA *npdu_data,
+ BACNET_CONFIRMED_SERVICE_DATA *service_data);
+
BACNET_STACK_EXPORT
void handler_atomic_write_file(
uint8_t *service_request,
diff --git a/src/bacnet/basic/service/h_rp_a.c b/src/bacnet/basic/service/h_rp_a.c
index b5c2b1f3be..79f6e14617 100644
--- a/src/bacnet/basic/service/h_rp_a.c
+++ b/src/bacnet/basic/service/h_rp_a.c
@@ -131,6 +131,25 @@ void handler_read_property_ack(
}
}
+/**
+ * @brief Free the memory allocated for a ReadProperty ACK service request.
+ * @param value [in] The head of the linked list of values to free.
+ * @param property [in] The property reference to free.
+ */
+static void rp_ack_service_request_free(
+ BACNET_APPLICATION_DATA_VALUE *value, BACNET_PROPERTY_REFERENCE *property)
+{
+ BACNET_APPLICATION_DATA_VALUE *old_value;
+
+ while (value) {
+ /* free the linked list of values */
+ old_value = value;
+ value = value->next;
+ free(old_value);
+ }
+ free(property);
+}
+
/** Decode the received RP data into a linked list of the results, with the
* same data structure used by RPM ACK replies.
* This function is provided to provide common handling for RP and RPM data,
@@ -149,7 +168,7 @@ int rp_ack_fully_decode_service_request(
uint8_t *apdu, int apdu_len, BACNET_READ_ACCESS_DATA *read_access_data)
{
int decoded_len = 0; /* return value */
- BACNET_READ_PROPERTY_DATA rp1data;
+ BACNET_READ_PROPERTY_DATA rp1data = { 0 };
BACNET_PROPERTY_REFERENCE *rp1_property; /* single property */
BACNET_APPLICATION_DATA_VALUE *value, *old_value;
uint8_t *vdata;
@@ -170,33 +189,31 @@ int rp_ack_fully_decode_service_request(
}
rp1_property->propertyIdentifier = rp1data.object_property;
rp1_property->propertyArrayIndex = rp1data.array_index;
- /* Is there no Error case possible here, as there is when decoding RPM?
- */
- /* rp1_property->error.error_class = ?? */
/* rp_ack_decode_service_request() processing already removed the
- * Opening and Closing '3' Tags.
- * note: if this is an array, there will be
- more than one element to decode */
+ * Opening and Closing '3' Tags. */
vdata = rp1data.application_data;
vlen = rp1data.application_data_len;
value = calloc(1, sizeof(BACNET_APPLICATION_DATA_VALUE));
+ if (value == NULL) {
+ /* can't proceed if calloc failed. */
+ rp_ack_service_request_free(NULL, rp1_property);
+ read_access_data->listOfProperties = NULL;
+ return BACNET_STATUS_ERROR;
+ }
rp1_property->value = value;
+ /* check for empty list */
+ if (rp1data.application_data_len == 0) {
+ bacapp_value_list_init(value, 1);
+ value->tag = BACNET_APPLICATION_TAG_EMPTYLIST;
+ return 0;
+ }
while (value && vdata && (vlen > 0)) {
- if (IS_CONTEXT_SPECIFIC(*vdata)) {
- len = bacapp_decode_context_data(
- vdata, vlen, value, rp1_property->propertyIdentifier);
- } else {
- len = bacapp_decode_application_data(vdata, vlen, value);
- }
+ len = bacapp_decode_known_array_property(
+ vdata, (unsigned)vlen, value, rp1data.object_type,
+ rp1data.object_property, rp1data.array_index);
if (len < 0) {
/* unable to decode the data */
- while (value) {
- /* free the linked list of values */
- old_value = value;
- value = value->next;
- free(old_value);
- }
- free(rp1_property);
+ rp_ack_service_request_free(value, rp1_property);
read_access_data->listOfProperties = NULL;
return len;
}
@@ -211,13 +228,7 @@ int rp_ack_fully_decode_service_request(
} else {
if (len == 0) {
/* nothing decoded and no closing tag, so malformed */
- while (value) {
- /* free the linked list of values */
- old_value = value;
- value = value->next;
- free(old_value);
- }
- free(rp1_property);
+ rp_ack_service_request_free(value, rp1_property);
read_access_data->listOfProperties = NULL;
return BACNET_STATUS_ERROR;
}
diff --git a/src/bacnet/basic/service/h_rpm_a.c b/src/bacnet/basic/service/h_rpm_a.c
index 1b39e3ad2f..f90a4eeca5 100644
--- a/src/bacnet/basic/service/h_rpm_a.c
+++ b/src/bacnet/basic/service/h_rpm_a.c
@@ -46,6 +46,7 @@ int rpm_ack_decode_service_request(
int len = 0; /* number of bytes returned from decoding */
uint8_t tag_number = 0; /* decoded tag number */
int data_len = 0; /* data blob length */
+ int data_remaining = 0; /* bytes left in the current data blob */
int tag_len = 0; /* length of the tag portion of the data */
BACNET_READ_ACCESS_DATA *rpm_object;
BACNET_READ_ACCESS_DATA *old_rpm_object;
@@ -98,10 +99,15 @@ int rpm_ack_decode_service_request(
if (apdu_len &&
bacnet_is_opening_tag_number(apdu, apdu_len, 4, &tag_len)) {
data_len = bacnet_enclosed_data_length(apdu, apdu_len);
+ if (data_len < 0) {
+ PERROR("RPM Ack: invalid enclosed property value length\n");
+ return BACNET_STATUS_ERROR;
+ }
/* propertyValue */
decoded_len += tag_len;
apdu_len -= tag_len;
apdu += tag_len;
+ data_remaining = data_len;
value = calloc(1, sizeof(BACNET_APPLICATION_DATA_VALUE));
rpm_property->value = value;
if (apdu_len &&
@@ -126,27 +132,26 @@ int rpm_ack_decode_service_request(
* OK. */
if (len < 0) {
/* problem decoding */
- if (data_len >= 0) {
- /* valid data that we'll skip over */
- len = data_len;
- bacapp_value_list_init(value, 1);
- } else {
- PERROR(
- "RPM Ack: unable to decode! %s:%s\n",
- bactext_object_type_name(
- rpm_object->object_type),
- bactext_property_name(
- rpm_property->propertyIdentifier));
- /* note: caller will free the memory */
- return BACNET_STATUS_ERROR;
- }
+ len = data_remaining;
+ bacapp_value_list_init(value, 1);
+ }
+ if (len > data_remaining) {
+ PERROR("RPM Ack: decoded length exceeds property "
+ "value length\n");
+ return BACNET_STATUS_ERROR;
}
decoded_len += len;
apdu_len -= len;
apdu += len;
- if (apdu_len &&
+ data_remaining -= len;
+ if ((apdu_len < 0) || (data_remaining < 0)) {
+ PERROR("RPM Ack: invalid remaining length while "
+ "decoding property value\n");
+ return BACNET_STATUS_ERROR;
+ }
+ if (apdu_len > 0 &&
bacnet_is_closing_tag_number(
- apdu, apdu_len, 4, &tag_len)) {
+ apdu, (unsigned)apdu_len, 4, &tag_len)) {
decoded_len += tag_len;
apdu_len -= tag_len;
apdu += tag_len;
diff --git a/src/bacnet/basic/service/s_arfs.c b/src/bacnet/basic/service/s_arfs.c
index 5f4c36f757..2c206771e2 100644
--- a/src/bacnet/basic/service/s_arfs.c
+++ b/src/bacnet/basic/service/s_arfs.c
@@ -30,16 +30,16 @@ uint8_t Send_Atomic_Read_File_Stream(
int fileStartPosition,
unsigned requestedOctetCount)
{
- BACNET_ADDRESS dest;
- BACNET_ADDRESS my_address;
- BACNET_NPDU_DATA npdu_data;
+ BACNET_ADDRESS dest = { 0 };
+ BACNET_ADDRESS my_address = { 0 };
+ BACNET_NPDU_DATA npdu_data = { 0 };
unsigned max_apdu = 0;
uint8_t invoke_id = 0;
bool status = false;
int len = 0;
int pdu_len = 0;
int bytes_sent = 0;
- BACNET_ATOMIC_READ_FILE_DATA data;
+ BACNET_ATOMIC_READ_FILE_DATA data = { 0 };
/* if we are forbidden to send, don't send! */
if (!dcc_communication_enabled()) {
diff --git a/src/bacnet/basic/sys/bramfs.c b/src/bacnet/basic/sys/bramfs.c
index 81efb1eac3..5a3865e88c 100644
--- a/src/bacnet/basic/sys/bramfs.c
+++ b/src/bacnet/basic/sys/bramfs.c
@@ -130,6 +130,10 @@ bool bacfile_ramfs_file_size_set(const char *pathname, size_t new_size)
if (new_size > 0) {
new_data = realloc(pFile->data, new_size);
if (new_data) {
+ /* zero the new memory to avoid leaking sensitive data */
+ if (new_size > pFile->size) {
+ memset(new_data + pFile->size, 0, new_size - pFile->size);
+ }
pFile->data = new_data;
pFile->size = new_size;
status = true;
@@ -150,6 +154,8 @@ bool bacfile_ramfs_file_size_set(const char *pathname, size_t new_size)
* @brief Reads stream data from a file
* @param pathname - name of the file to read from
* @param fileStartPosition - starting position in the file
+ * If the 'File Start Position' parameter less than 0
+ * or exceeds the actual file size, then an error is returned.
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
* @return number of bytes read, or 0 if not successful
@@ -163,8 +169,16 @@ size_t bacfile_ramfs_read_stream_data(
struct file_data *pFile;
size_t len = 0;
+ if (fileStartPosition < 0) {
+ /* invalid file start position */
+ return 0;
+ }
pFile = bacfile_ramfs_open(pathname);
if (pFile) {
+ if (fileStartPosition > pFile->size) {
+ /* invalid file start position */
+ return 0;
+ }
if (fileStartPosition + fileDataLen > pFile->size) {
/* read only up to the end of the file */
len = pFile->size - fileStartPosition;
@@ -180,7 +194,14 @@ size_t bacfile_ramfs_read_stream_data(
/**
* @brief Writes stream data to a file
* @param pathname - name of the file to write to
- * @param fileStartPosition - starting position in the file
+ * @param fileStartPosition - starting position in the file.
+ * If the 'File Start Position' parameter exceeds the actual file size,
+ * then the file shall be extended to the size indicated,
+ * but the contents of any intervening octets or records
+ * shall be a local matter.
+ * If this parameter has the special value -1,
+ * then the write operation shall be treated
+ * as an append to the current end of file.
* @param fileData - data buffer to write from
* @param fileDataLen - size of the data buffer
* @return number of bytes written, or 0 if not successful
@@ -219,7 +240,7 @@ size_t bacfile_ramfs_write_stream_data(
memcpy(pFile->data + old_size, fileData, fileDataLen);
bytes_written = fileDataLen;
}
- } else {
+ } else if (fileStartPosition > 0) {
/* open for update */
if (fileStartPosition + fileDataLen > pFile->size) {
/* extend the file size */
@@ -245,22 +266,30 @@ size_t bacfile_ramfs_write_stream_data(
/**
* @brief Count the number of records in a file
* @param records - string of null-terminated records
+ * @param size - total buffer size in bytes
* @return number of records
*/
-static size_t record_count(const char *records)
+static size_t record_count(const char *records, size_t size)
{
size_t count = 0;
int len = 0;
+ const char *end;
- if (records) {
- do {
- len = bacnet_strnlen(records, MAX_OCTET_STRING_BYTES);
- if (len > 0) {
- count++;
- records = records + len + 1;
- }
- } while (len > 0);
+ if (!records) {
+ return 0;
}
+ end = records + size;
+
+ do {
+ if (records >= end) {
+ break;
+ }
+ len = bacnet_strnlen(records, MAX_OCTET_STRING_BYTES);
+ if (len > 0) {
+ count++;
+ records = records + len + 1;
+ }
+ } while (len > 0);
return count;
}
@@ -269,25 +298,33 @@ static size_t record_count(const char *records)
* @brief Get the specific record at index 0..N
* @param records - string of null-terminated records
* @param record_index - record index number 0..N of the records
+ * @param size - total buffer size in bytes
* @return record, or NULL
*/
-static char *record_by_index(char *records, size_t index)
+static char *record_by_index(char *records, size_t index, size_t size)
{
size_t count = 0;
int len = 0;
+ const char *end;
- if (records) {
- do {
- len = bacnet_strnlen(records, MAX_OCTET_STRING_BYTES);
- if (len > 0) {
- if (index == count) {
- return records;
- }
- count++;
- records = records + len + 1;
- }
- } while (len > 0);
+ if (!records) {
+ return NULL;
}
+ end = records + size;
+
+ do {
+ if (records >= end) {
+ break;
+ }
+ len = bacnet_strnlen(records, MAX_OCTET_STRING_BYTES);
+ if (len > 0) {
+ if (index == count) {
+ return records;
+ }
+ count++;
+ records = records + len + 1;
+ }
+ } while (len > 0);
return NULL;
}
@@ -296,6 +333,10 @@ static char *record_by_index(char *records, size_t index)
* @brief Writes record data to a file
* @param pathname - name of the file to write to
* @param fileStartRecord - starting record in the file
+ * If 'File Start Record' parameter has the special
+ * value -1, then the write operation shall be treated
+ * as an append to the current end of file,
+ * and fileIndexRecord can be ignored.
* @param fileIndexRecord - index of the record to read
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
@@ -315,6 +356,9 @@ bool bacfile_ramfs_write_record_data(
char *record;
size_t record_len;
size_t tail_record_len;
+ char *tail_data = NULL;
+ size_t tail_data_len = 0;
+ size_t new_size = 0;
char fileDataStr[MAX_OCTET_STRING_BYTES + 1] = {
0
}; /* +1 for null terminator */
@@ -322,19 +366,22 @@ bool bacfile_ramfs_write_record_data(
pFile = bacfile_ramfs_open(pathname);
if (pFile) {
- fileRecordCount = record_count(pFile->data);
+ fileRecordCount = record_count(pFile->data, pFile->size);
if (fileStartRecord == -1) {
/* If 'File Start Record' parameter has the special
value -1, then the write operation shall be treated
as an append to the current end of file,
and fileIndexRecord can be ignored. */
fileSeekRecord = fileRecordCount;
- } else {
+ } else if (fileStartRecord >= 0) {
fileSeekRecord = fileStartRecord + fileIndexRecord;
if (fileSeekRecord > fileRecordCount) {
/* cannot write more than 1 record beyond the end of the file */
return false;
}
+ } else {
+ /* invalid file start record */
+ return false;
}
/* sanitize the incoming record; assume from an octetstring */
fileDataStrLen = min(fileDataLen, MAX_OCTET_STRING_BYTES);
@@ -345,23 +392,38 @@ bool bacfile_ramfs_write_record_data(
}
if (fileSeekRecord < fileRecordCount) {
/* find the old record length */
- record = record_by_index(pFile->data, fileSeekRecord);
+ record = record_by_index(pFile->data, fileSeekRecord, pFile->size);
record_len = bacnet_strnlen(record, MAX_OCTET_STRING_BYTES);
tail_record_len = pFile->size - (record - pFile->data) - record_len;
+ /* save tail data (excluding old record's null terminator) before
+ realloc (may be lost if buffer shrinks) */
+ tail_data = NULL;
+ tail_data_len = 0;
+ if (tail_record_len > 1) {
+ tail_data_len = tail_record_len - 1;
+ tail_data = malloc(tail_data_len);
+ if (tail_data) {
+ memcpy(tail_data, record + record_len + 1, tail_data_len);
+ } else {
+ return false; /* out of memory */
+ }
+ }
/* reallocate file to make room for new record */
- record = realloc(
- pFile->data, pFile->size - record_len + fileDataStrLen + 1);
+ new_size = pFile->size - record_len + fileDataStrLen + 1;
+ record = realloc(pFile->data, new_size);
if (!record) {
+ free(tail_data);
return false; /* out of memory */
}
pFile->data = record;
+ pFile->size = new_size;
/* find the old record position after a realloc */
- record = record_by_index(pFile->data, fileSeekRecord);
- /* move all existing records after the inserted record */
- if (tail_record_len > 0) {
- memmove(
- record + fileDataStrLen, record + record_len,
- tail_record_len);
+ record = record_by_index(pFile->data, fileSeekRecord, pFile->size);
+ /* restore tail data to new position (after new record + null
+ terminator) */
+ if (tail_data && tail_data_len > 0) {
+ memmove(record + fileDataStrLen + 1, tail_data, tail_data_len);
+ free(tail_data);
}
} else {
/* extend the file by this one record */
@@ -386,6 +448,8 @@ bool bacfile_ramfs_write_record_data(
* @brief Reads record data from a file
* @param pathname - name of the file to read from
* @param fileStartRecord - starting record in the file
+ * If the 'File Start Record' parameter is either less than 0
+ * or exceeds the actual file size, then an error is returned.
* @param fileIndexRecord - index of the record to read
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
@@ -404,11 +468,14 @@ bool bacfile_ramfs_read_record_data(
char *record;
size_t record_len;
+ if (fileStartRecord < 0) {
+ return false; /* invalid file start record */
+ }
pFile = bacfile_ramfs_open(pathname);
if (pFile) {
fileSeekRecord = fileStartRecord + fileIndexRecord;
/* seek to the start record */
- record = record_by_index(pFile->data, fileSeekRecord);
+ record = record_by_index(pFile->data, fileSeekRecord, pFile->size);
if (record) {
record_len = bacnet_strnlen(record, MAX_OCTET_STRING_BYTES);
if ((record_len > 0) && (record_len <= fileDataLen)) {
diff --git a/src/bacnet/basic/sys/bsramfs.c b/src/bacnet/basic/sys/bsramfs.c
index 5750c4cec2..6b3a5b3c3a 100644
--- a/src/bacnet/basic/sys/bsramfs.c
+++ b/src/bacnet/basic/sys/bsramfs.c
@@ -104,6 +104,8 @@ size_t bacfile_sramfs_file_size(const char *pathname)
* @brief Reads stream data from a file
* @param pathname - name of the file to read from
* @param fileStartPosition - starting position in the file
+ * If the 'File Start Position' parameter less than 0
+ * or exceeds the actual file size, then an error is returned.
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
* @return number of bytes read, or 0 if not successful
@@ -117,8 +119,16 @@ size_t bacfile_sramfs_read_stream_data(
struct bacnet_file_sramfs_data *pFile;
size_t len = 0;
+ if (fileStartPosition < 0) {
+ /* invalid file start position */
+ return 0;
+ }
pFile = bacfile_sramfs_open(pathname);
if (pFile) {
+ if (fileStartPosition > pFile->size) {
+ /* invalid file start position */
+ return 0;
+ }
if (fileStartPosition + fileDataLen > pFile->size) {
/* read only up to the end of the file */
len = pFile->size - fileStartPosition;
@@ -162,6 +172,8 @@ static char *record_by_index(char *records, size_t index)
* @brief Reads record data from a file
* @param pathname - name of the file to read from
* @param fileStartRecord - starting record in the file
+ * If the 'File Start Record' parameter is either less than 0
+ * or exceeds the actual file size, then an error is returned.
* @param fileIndexRecord - index of the record to read
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
@@ -180,6 +192,9 @@ bool bacfile_sramfs_read_record_data(
char *record;
size_t record_len;
+ if (fileStartRecord < 0) {
+ return false; /* invalid file start record */
+ }
pFile = bacfile_sramfs_open(pathname);
if (pFile) {
fileSeekRecord = fileStartRecord + fileIndexRecord;
diff --git a/src/bacnet/basic/sys/keylist.c b/src/bacnet/basic/sys/keylist.c
index 96aba8f6a6..3dc602e5f1 100644
--- a/src/bacnet/basic/sys/keylist.c
+++ b/src/bacnet/basic/sys/keylist.c
@@ -69,7 +69,7 @@ static bool CheckArraySize(OS_Keylist list)
/* See if we got the memory we wanted */
if (!new_array) {
- return true;
+ return false;
}
/* copy the nodes from the old array to the new array */
@@ -171,6 +171,11 @@ int Keylist_Data_Add(OS_Keylist list, KEY key, void *data)
int i; /* counts through the array */
if (list && CheckArraySize(list)) {
+ node = NodeCreate();
+ if (!node) {
+ return -1;
+ }
+
/* figure out where to put the new node */
if (list->count) {
(void)FindIndex(list, key, &index);
@@ -190,14 +195,11 @@ int Keylist_Data_Add(OS_Keylist list, KEY key, void *data)
index = 0;
}
- /* create and add the node */
- node = NodeCreate();
- if (node) {
- list->count++;
- node->key = key;
- node->data = data;
- list->array[index] = node;
- }
+ /* add the node */
+ list->count++;
+ node->key = key;
+ node->data = data;
+ list->array[index] = node;
}
return index;
}
diff --git a/src/bacnet/basic/sys/lighting_command.c b/src/bacnet/basic/sys/lighting_command.c
index 050154bd53..4809067785 100644
--- a/src/bacnet/basic/sys/lighting_command.c
+++ b/src/bacnet/basic/sys/lighting_command.c
@@ -16,12 +16,142 @@
#include "lighting_command.h"
/**
- * @brief call the lighting command tracking value callbacks
+ * @brief compare two floating point values to 3 decimal places
+ *
+ * @param x1 - first comparison value
+ * @param x2 - second comparison value
+ * @return true if the value is the same to 3 decimal points
+ */
+static bool is_float_equal(float x1, float x2)
+{
+ return fabs(x1 - x2) < 0.001;
+}
+
+/**
+ * @brief Clamp the value within the normalized range
+ * @details The normalized output level is specified as the
+ * linearized percentage (0..100%) of the possible light output range
+ * with 0.0% being off, 1.0% being dimmest, and 100.0% being brightest.
+ * @param value [in] value to clamp within the normalized range
+ * @return value clamped within the normalized range of 0.0% to 100.0%
+ */
+float lighting_command_normalized_range_clamp(float value)
+{
+ float physical_value;
+
+ /* clamp value within physical values, if non-zero */
+ if (isless(value, 1.0f)) {
+ /* jump target to OFF */
+ physical_value = 0.0f;
+ } else if (isgreater(value, 100.0f)) {
+ physical_value = 100.0f;
+ } else {
+ physical_value = value;
+ }
+
+ return physical_value;
+}
+
+/**
+ * @brief Clamp the value within the normal ON range
+ * @details The normal ON output level is specified as the linearized
+ * percentage (1..100%) of the possible light output range with 1.0% being
+ * dimmest, and 100.0% being brightest.
+ * @param value [in] value to clamp within the normalized ON range
+ * @return value clamped within the normalized ON range of 1.0% to 100.0%
+ */
+float lighting_command_normalized_on_range_clamp(float value)
+{
+ float normalized_on_value;
+
+ /* clamp value within 1.0 and 100.0 values */
+ if (isless(value, 1.0f)) {
+ normalized_on_value = 1.0f;
+ } else if (isgreater(value, 100.0f)) {
+ normalized_on_value = 100.0f;
+ } else {
+ normalized_on_value = value;
+ }
+
+ return normalized_on_value;
+}
+
+/**
+ * @brief Adjust Min/Max Actual Value Range
+ * @details Min_Actual_Value property, of type Real, shall specify
+ * the physical output level that corresponds to a Present_Value of 1.0%.
+ * Changing Min_Actual_Value to a value greater than Max_Actual_Value
+ * shall force Max_Actual_Value to become equal to Min_Actual_Value.
+ * Min_Actual_Value shall always be a positive number in the
+ * range 1.0% to 100.0%.
+ *
+ * Max_Actual_Value property, of type Real, shall specify the physical
+ * output level that corresponds to a Present_Value of 100.0%.
+ * Changing Max_Actual_Value to a value less than Min_Actual_Value
+ * shall force Min_Actual_Value to become equal to Max_Actual_Value.
+ * Max_Actual_Value shall always be a positive number in the range
+ * 1.0% to 100.0%.
+ */
+static void lighting_command_min_max_value_range_adjust(
+ struct bacnet_lighting_command_data *data)
+{
+ float swap_value, min_value, max_value;
+
+ min_value =
+ lighting_command_normalized_on_range_clamp(data->Min_Actual_Value);
+ max_value =
+ lighting_command_normalized_on_range_clamp(data->Max_Actual_Value);
+ if (isgreater(min_value, max_value)) {
+ /* swap the configured high and low actual values if they are inverse */
+ swap_value = min_value;
+ min_value = max_value;
+ max_value = swap_value;
+ }
+ data->Min_Actual_Value = min_value;
+ data->Max_Actual_Value = max_value;
+}
+
+/**
+ * @brief Calculate the Feedback_Value property value
+ *
+ * This property, of type Real, shall indicate the actual value
+ * of the physical lighting output within the normalized range.
+ * If the actual value of the physical lighting output in the
+ * non-normalized range is not off but is less than the
+ * Min_Actual_Value, then Feedback_Value shall be set to 1.0%.
+ * If the actual value in the non-normalized range is greater than
+ * Max_Actual_Value, then Feedback_Value shall be set to 100.0%.
+ * @param data - dimmer data structure
+ * @return calculated feedback-value
+ */
+float lighting_command_normalized_to_physical_value(
+ float min_value, float max_value, float normalized_value)
+{
+ float physical_value, x1, x2, x3, y1, y3;
+
+ if (isless(normalized_value, 1.0f)) {
+ physical_value = 0.0f;
+ } else if (isgreater(normalized_value, 100.0f)) {
+ physical_value = max_value;
+ } else {
+ x1 = 1.0f;
+ x2 = normalized_value;
+ x3 = 100.0f;
+ y1 = min_value;
+ y3 = max_value;
+ physical_value = linear_interpolate(x1, x2, x3, y1, y3);
+ }
+
+ return physical_value;
+}
+
+/**
+ * @brief call the lighting command notification callbacks
* @param data - dimmer data structure
- * @param old_value - value prior to write
- * @param value - value of the write
+ * @param old_value - physical value prior to write
+ * @param value - physical value of the write
*/
-static void lighting_command_tracking_value_notify(
+static void lighting_command_notify(
struct bacnet_lighting_command_data *data, float old_value, float value)
{
struct lighting_command_notification *head;
@@ -46,6 +176,11 @@ void lighting_command_notification_add(
{
struct lighting_command_notification *head;
+ if (!data || !notification) {
+ return;
+ }
+ lighting_command_lock(data);
+
head = &data->Notification_Head;
do {
if (head->next == notification) {
@@ -58,6 +193,7 @@ void lighting_command_notification_add(
}
head = head->next;
} while (head);
+ lighting_command_unlock(data);
}
/**
@@ -90,6 +226,11 @@ void lighting_command_timer_notfication_add(
{
struct lighting_command_timer_notification *head;
+ if (!data || !notification) {
+ return;
+ }
+ lighting_command_lock(data);
+
head = &data->Timer_Notification_Head;
do {
if (head->next == notification) {
@@ -102,6 +243,7 @@ void lighting_command_timer_notfication_add(
}
head = head->next;
} while (head);
+ lighting_command_unlock(data);
}
/**
@@ -109,8 +251,8 @@ void lighting_command_timer_notfication_add(
* for WARN_OFF/WARN_RELINQUISH operations
* @param data - dimmer data structure
*/
-static void
-lighting_command_blink_stop_notify(struct bacnet_lighting_command_data *data)
+static void lighting_command_blink_stop_notify_nolock(
+ struct bacnet_lighting_command_data *data)
{
if (data->Blink.Callback) {
/* do some checking to avoid extra callbacks */
@@ -163,32 +305,6 @@ float lighting_command_step_increment_clamp(float step_increment)
return step_increment;
}
-/**
- * @brief Clamp the value within the physical min/max range
- * @details The physical output level, or non-normalized range,
- * is specified as the linearized percentage (0..100%)
- * of the possible light output range with 0.0% being off,
- * 1.0% being dimmest, and 100.0% being brightest.
- * @param value [in] value to clamp within the physical min/max range
- * @return value clamped within the physical min/max range of 0.0% to 100.0%
- */
-float lighting_command_physical_range_clamp(float value)
-{
- float physical_value;
-
- /* clamp value within physical values, if non-zero */
- if (isless(value, 1.0f)) {
- /* jump target to OFF */
- physical_value = 0.0f;
- } else if (isgreater(value, 100.0f)) {
- physical_value = 100.0f;
- } else {
- physical_value = value;
- }
-
- return physical_value;
-}
-
/**
* @brief Calculate the target value for a step down command
* @param tracking_value [in] current tracking value
@@ -288,152 +404,49 @@ static float lighting_command_trim_fade(
* @return value clamped within the operating range defined by the High_End_Trim
* and Low_End_Trim property values
*/
-float lighting_command_operating_range_clamp_fade(
+static float lighting_command_operating_range_clamp_fade_nolock(
struct bacnet_lighting_command_data *data,
float value,
uint16_t milliseconds)
{
- float high_trim, low_trim, swap_value;
-
if (data) {
- /* clamp range within physical limits */
- high_trim =
- lighting_command_physical_range_clamp(data->High_Trim_Value);
- low_trim = lighting_command_physical_range_clamp(data->Low_Trim_Value);
- /* valid range check for high and low trim values */
- if (isgreater(low_trim, high_trim)) {
- /* swap the trims if they are inverse */
- swap_value = low_trim;
- low_trim = high_trim;
- high_trim = swap_value;
- }
/* clamp value within trim values, if non-zero */
if (isless(value, 1.0f)) {
/* jump target to OFF if below normalized min */
value = 0.0f;
- } else if (isgreater(value, high_trim)) {
+ } else if (isgreater(value, data->High_Trim_Value)) {
value = lighting_command_trim_fade(
- data, value, high_trim, milliseconds);
+ data, value, data->High_Trim_Value, milliseconds);
data->In_Progress = BACNET_LIGHTING_TRIM_ACTIVE;
- } else if (isless(value, low_trim)) {
- value =
- lighting_command_trim_fade(data, value, low_trim, milliseconds);
+ } else if (isless(value, data->Low_Trim_Value)) {
+ value = lighting_command_trim_fade(
+ data, value, data->Low_Trim_Value, milliseconds);
data->In_Progress = BACNET_LIGHTING_TRIM_ACTIVE;
}
} else {
/* no data, so just clamp value within physical limits */
- value = lighting_command_physical_range_clamp(value);
+ value = lighting_command_normalized_range_clamp(value);
}
return value;
}
-/**
- * @brief Clamp the value within the operating range between low and high
- * end trim values immediately.
- * @details The Operating Range is a subset of the Normalized Range,
- * that represents the range of acceptable values for control of the object.
- * The Operating Range is defined by the High_End_Trim and Low_End_Trim
- * property values. When values are written outside of the Operating Range,
- * the Tracking_Value will reflect the actual, clamped normalized light
- * output while the Present_Value will reflect the original target value.
- * @param data - dimmer data structure
- * @param value the value that will be subject to clamping
- * @return value clamped within the operating range defined by the High_End_Trim
- * and Low_End_Trim property values
- */
-float lighting_command_operating_range_clamp(
- struct bacnet_lighting_command_data *data, float value)
-{
- return lighting_command_operating_range_clamp_fade(data, value, 0);
-}
-
-/**
- * @brief Clamp the value within the normalized ON range 1% to 100%.
- * @details The physical output level, or non-normalized range,
- * is specified as the linearized percentage (0..100%)
- * of the possible light output range with 0.0% being off,
- * 1.0% being dimmest, and 100.0% being brightest.
- * The actual range represents the subset of physical output levels
- * defined by Min_Actual_Value and Max_Actual_Value
- * (or 1.0 to 100.0% if these properties are not present).
- * The normalized range is always 0.0 to 100.0% where
- * 1.0% = bottom of the actual range and 100.0% = top of the actual range.
- * @param data - dimmer data structure
- * @param value [in] value to normalize
- * @return normalized value within the range defined by Min_Actual_Value
- * and Max_Actual_Value
- */
-float lighting_command_normalized_on_range_clamp(
- struct bacnet_lighting_command_data *data, float value)
-{
- float min_value, max_value, swap_value;
-
- /* clamp range within physical limits */
- max_value = lighting_command_physical_range_clamp(data->Max_Actual_Value);
- min_value = lighting_command_physical_range_clamp(data->Min_Actual_Value);
- /* valid range check for high and low trim values */
- if (isgreater(min_value, max_value)) {
- /* swap the trims if they are inverse */
- swap_value = min_value;
- min_value = max_value;
- max_value = swap_value;
- }
- /* clamp value within trim values, if non-zero */
- if (isgreater(value, max_value)) {
- value = max_value;
- } else if (isless(value, min_value)) {
- value = min_value;
- }
-
- return value;
-}
-
-/**
- * @brief Normalize the value to the min/max range
- * @details The physical output level, or non-normalized range,
- * is specified as the linearized percentage (0..100%)
- * of the possible light output range with 0.0% being off,
- * 1.0% being dimmest, and 100.0% being brightest.
- * The actual range represents the subset of physical output levels
- * defined by Min_Actual_Value and Max_Actual_Value
- * (or 1.0 to 100.0% if these properties are not present).
- * The normalized range is always 0.0 to 100.0% where
- * 1.0% = bottom of the actual range and 100.0% = top of the actual range.
- * @param data - dimmer data structure
- * @param value [in] value to normalize
- * @return normalized value within the range defined by
- * 0.0%, Min_Actual_Value, and Max_Actual_Value
- */
-float lighting_command_normalized_range_clamp(
- struct bacnet_lighting_command_data *data, float value)
+float lighting_command_operating_range_clamp_fade(
+ struct bacnet_lighting_command_data *data,
+ float value,
+ uint16_t milliseconds)
{
- float normalized_value;
- float min_value, max_value, swap_value;
+ float clamped_value;
- /* clamp range within physical limits */
- max_value = lighting_command_physical_range_clamp(data->Max_Actual_Value);
- min_value = lighting_command_physical_range_clamp(data->Min_Actual_Value);
- /* valid range check for high and low trim values */
- if (isgreater(min_value, max_value)) {
- /* swap the trims if they are inverse */
- swap_value = min_value;
- min_value = max_value;
- max_value = swap_value;
- }
- /* clamp value within normalized values, if non-zero */
- if (isless(value, 1.0f)) {
- /* jump target to OFF if below normalized min */
- normalized_value = 0.0f;
- } else if (isgreater(value, max_value)) {
- normalized_value = max_value;
- } else if (isless(value, min_value)) {
- normalized_value = min_value;
- } else {
- normalized_value = value;
+ if (!data) {
+ return value;
}
+ lighting_command_lock(data);
+ clamped_value = lighting_command_operating_range_clamp_fade_nolock(
+ data, value, milliseconds);
+ lighting_command_unlock(data);
- return normalized_value;
+ return clamped_value;
}
/**
@@ -444,16 +457,25 @@ float lighting_command_normalized_range_clamp(
* @param milliseconds - number of milliseconds elapsed
*/
static void lighting_command_tracking_value_event(
- struct bacnet_lighting_command_data *data, float old_value, float value)
+ struct bacnet_lighting_command_data *data,
+ float old_value,
+ float tracking_value)
{
+ float physical_value, old_physical_value;
+
+ lighting_command_min_max_value_range_adjust(data);
+ physical_value = lighting_command_normalized_to_physical_value(
+ data->Min_Actual_Value, data->Max_Actual_Value, tracking_value);
+ old_physical_value = lighting_command_normalized_to_physical_value(
+ data->Min_Actual_Value, data->Max_Actual_Value, old_value);
if (data->Overridden) {
- lighting_command_tracking_value_notify(data, old_value, value);
+ lighting_command_notify(data, old_physical_value, physical_value);
if (data->Overridden_Momentary) {
data->Overridden = false;
}
} else if (!data->Out_Of_Service) {
data->Overridden_Momentary = false;
- lighting_command_tracking_value_notify(data, old_value, value);
+ lighting_command_notify(data, old_physical_value, physical_value);
} else {
debug_printf(
"Lighting-Command[%lu]-Out-of-Service\n", (unsigned long)data->Key);
@@ -475,39 +497,47 @@ static void lighting_command_fade_handler(
float target_value;
old_value = data->Tracking_Value;
- /* clamp Tracking value within the Normalized ON Range */
- target_value =
- lighting_command_normalized_on_range_clamp(data, data->Target_Level);
- if ((milliseconds >= data->Fade_Time) ||
- (!islessgreater(data->Tracking_Value, target_value))) {
- /* stop fading */
- if (isless(data->Target_Level, 1.0f)) {
- /* jump target to OFF if below normalized min */
- data->Tracking_Value = 0.0f;
- } else {
- data->Tracking_Value = target_value;
- }
+ if (isless(old_value, 1.0f) && isless(data->Target_Level, 1.0f)) {
+ /* check for OFF to OFF transition */
+ data->Tracking_Value = 0.0f;
data->In_Progress = BACNET_LIGHTING_IDLE;
data->Lighting_Operation = BACNET_LIGHTS_STOP;
- data->Fade_Time = 0;
} else {
- /* fading */
- x1 = 0.0f;
- x2 = (float)milliseconds;
- x3 = (float)data->Fade_Time;
- if (isless(old_value, data->Min_Actual_Value)) {
- y1 = data->Min_Actual_Value;
+ /* clamp Target value within the Normalized ON Range */
+ target_value =
+ lighting_command_normalized_on_range_clamp(data->Target_Level);
+ if ((milliseconds >= data->Fade_Time) ||
+ (is_float_equal(data->Tracking_Value, target_value))) {
+ /* stop fading */
+ if (isless(data->Target_Level, 1.0f)) {
+ /* jump target to OFF if below normalized min */
+ data->Tracking_Value = 0.0f;
+ } else {
+ data->Tracking_Value = target_value;
+ }
+ data->In_Progress = BACNET_LIGHTING_IDLE;
+ data->Lighting_Operation = BACNET_LIGHTS_STOP;
+ data->Fade_Time = 0;
} else {
- y1 = old_value;
+ /* fading in the normalized ON range */
+ x1 = 0.0f;
+ x2 = (float)milliseconds;
+ x3 = (float)data->Fade_Time;
+ if (isless(old_value, 1.0f)) {
+ y1 = 1.0f;
+ } else {
+ y1 = old_value;
+ }
+ y3 = target_value;
+ data->Tracking_Value = linear_interpolate(x1, x2, x3, y1, y3);
+ data->Fade_Time -= milliseconds;
+ data->In_Progress = BACNET_LIGHTING_FADE_ACTIVE;
}
- y3 = target_value;
- data->Tracking_Value = linear_interpolate(x1, x2, x3, y1, y3);
- data->Fade_Time -= milliseconds;
- data->In_Progress = BACNET_LIGHTING_FADE_ACTIVE;
- }
- /* clamp Tracking Value inclusively within the Operating Range */
- data->Tracking_Value = lighting_command_operating_range_clamp_fade(
- data, data->Tracking_Value, milliseconds);
+ /* clamp Tracking Value inclusively within the Operating Range */
+ data->Tracking_Value =
+ lighting_command_operating_range_clamp_fade_nolock(
+ data, data->Tracking_Value, milliseconds);
+ }
/* notify */
lighting_command_tracking_value_event(
data, old_value, data->Tracking_Value);
@@ -522,8 +552,7 @@ static void lighting_command_fade_handler(
* at a particular percent per second defined by ramp-rate.
* While the ramp operation is executing, In_Progress shall be set
* to RAMP_ACTIVE, and Tracking_Value shall be updated to reflect the current
- * progress of the ramp. shall be clamped to
- * Min_Actual_Value and Max_Actual_Value.
+ * progress of the ramp.
*
* @param data - dimmer data structure
* @param milliseconds - number of milliseconds elapsed
@@ -535,70 +564,75 @@ static void lighting_command_ramp_handler(
operating_value;
old_value = data->Tracking_Value;
- /* clamp Tracking value within the Normalized ON Range */
- target_value =
- lighting_command_normalized_on_range_clamp(data, data->Target_Level);
- if (!islessgreater(data->Tracking_Value, target_value)) {
- /* stop ramping */
- if (isless(data->Target_Level, 1.0f)) {
- /* jump target to OFF if below normalized min */
- data->Tracking_Value = 0.0f;
- } else {
- data->Tracking_Value = target_value;
- }
+ if (isless(old_value, 1.0f) && isless(data->Target_Level, 1.0f)) {
+ /* check for OFF to OFF transition */
+ data->Tracking_Value = 0.0f;
data->In_Progress = BACNET_LIGHTING_IDLE;
data->Lighting_Operation = BACNET_LIGHTS_STOP;
} else {
- ramp_rate = lighting_command_ramp_rate_clamp(data->Ramp_Rate);
- /* determine the number of steps */
- if (milliseconds <= 1000) {
- /* percent per second */
- steps = linear_interpolate(
- 0.0f, (float)milliseconds, 1000.0f, 0.0f, ramp_rate);
- } else {
- steps = ((float)milliseconds * ramp_rate) / 1000.0f;
- }
- if (isless(old_value, target_value)) {
- step_value = old_value + steps;
- if (isgreater(step_value, target_value)) {
- /* stop ramping */
- data->Lighting_Operation = BACNET_LIGHTS_STOP;
+ /* clamp Target value within the Normalized ON Range */
+ target_value =
+ lighting_command_normalized_on_range_clamp(data->Target_Level);
+ if (is_float_equal(data->Tracking_Value, target_value)) {
+ /* stop ramping */
+ if (isless(data->Target_Level, 1.0f)) {
+ /* jump target to OFF if below normalized min */
+ data->Tracking_Value = 0.0f;
+ } else {
+ data->Tracking_Value = target_value;
}
- } else if (isgreater(old_value, target_value)) {
- if (isgreater(old_value, steps)) {
- step_value = old_value - steps;
+ data->In_Progress = BACNET_LIGHTING_IDLE;
+ data->Lighting_Operation = BACNET_LIGHTS_STOP;
+ } else {
+ ramp_rate = lighting_command_ramp_rate_clamp(data->Ramp_Rate);
+ /* determine the number of steps */
+ if (milliseconds <= 1000) {
+ /* percent per second */
+ steps = linear_interpolate(
+ 0.0f, (float)milliseconds, 1000.0f, 0.0f, ramp_rate);
} else {
- step_value = target_value;
+ steps = ((float)milliseconds * ramp_rate) / 1000.0f;
}
- if (isless(step_value, target_value)) {
+ if (isless(old_value, target_value)) {
+ step_value = old_value + steps;
+ if (isgreater(step_value, target_value)) {
+ /* stop ramping */
+ data->Lighting_Operation = BACNET_LIGHTS_STOP;
+ }
+ } else if (isgreater(old_value, target_value)) {
+ if (isgreater(old_value, steps)) {
+ step_value = old_value - steps;
+ } else {
+ step_value = target_value;
+ }
+ if (isless(step_value, target_value)) {
+ /* stop ramping */
+ data->Lighting_Operation = BACNET_LIGHTS_STOP;
+ }
+ } else {
/* stop ramping */
+ step_value = target_value;
data->Lighting_Operation = BACNET_LIGHTS_STOP;
}
- } else {
- /* stop ramping */
- step_value = target_value;
- data->Lighting_Operation = BACNET_LIGHTS_STOP;
- }
- /* clamp target within min/max, if needed */
- step_value =
- lighting_command_normalized_on_range_clamp(data, step_value);
- if (data->Lighting_Operation == BACNET_LIGHTS_STOP) {
- if (isless(data->Target_Level, 1.0f)) {
- /* jump target to OFF if below normalized min */
- data->Tracking_Value = 0.0f;
+ step_value = lighting_command_normalized_on_range_clamp(step_value);
+ if (data->Lighting_Operation == BACNET_LIGHTS_STOP) {
+ if (isless(data->Target_Level, 1.0f)) {
+ /* jump target to OFF if below normalized min */
+ data->Tracking_Value = 0.0f;
+ } else {
+ data->Tracking_Value = step_value;
+ }
+ data->In_Progress = BACNET_LIGHTING_IDLE;
} else {
data->Tracking_Value = step_value;
+ data->In_Progress = BACNET_LIGHTING_RAMP_ACTIVE;
}
- data->In_Progress = BACNET_LIGHTING_IDLE;
- } else {
- data->Tracking_Value = step_value;
- data->In_Progress = BACNET_LIGHTING_RAMP_ACTIVE;
}
+ /* clamp Tracking_Value inclusively within the Operating Range */
+ operating_value = lighting_command_operating_range_clamp_fade_nolock(
+ data, data->Tracking_Value, milliseconds);
+ data->Tracking_Value = operating_value;
}
- /* clamp Tracking_Value inclusively within the Operating Range */
- operating_value = lighting_command_operating_range_clamp_fade(
- data, data->Tracking_Value, milliseconds);
- data->Tracking_Value = operating_value;
/* notify */
lighting_command_tracking_value_event(
data, old_value, data->Tracking_Value);
@@ -608,30 +642,26 @@ static void lighting_command_ramp_handler(
* Updates the object tracking value while stepping
*
* Commands the dimmer to a value equal to the Tracking_Value
- * plus the step-increment. The resulting sum shall be clamped to
- * Min_Actual_Value and Max_Actual_Value
+ * plus the step-increment. If the result of the addition is
+ * greater than 100.0%, the value shall be set to 100.0%.
*
* @param data [in] dimmer data
*/
static void
lighting_command_step_up_handler(struct bacnet_lighting_command_data *data)
{
- float old_value, target_value, operating_value;
+ float old_value, target_value;
old_value = data->Tracking_Value;
- if (isgreaterequal(old_value, data->Min_Actual_Value)) {
- /* inhibit ON if the value is already OFF */
+ if (isgreaterequal(old_value, 1.0f)) {
+ /* inhibit ON if the value is currently OFF */
target_value = lighting_command_step_up_target_value(
data->Tracking_Value, data->Step_Increment);
- /* clamp Tracking value within the Normalized ON Range */
+ /* clamp Tracking value inclusively within the Operating Range */
data->Tracking_Value =
- lighting_command_normalized_on_range_clamp(data, target_value);
+ lighting_command_normalized_on_range_clamp(target_value);
data->In_Progress = BACNET_LIGHTING_IDLE;
data->Lighting_Operation = BACNET_LIGHTS_STOP;
- /* clamp Tracking value inclusively within the Operating Range */
- operating_value =
- lighting_command_operating_range_clamp(data, data->Tracking_Value);
- data->Tracking_Value = operating_value;
/* notify */
lighting_command_tracking_value_event(
data, old_value, data->Tracking_Value);
@@ -642,8 +672,7 @@ lighting_command_step_up_handler(struct bacnet_lighting_command_data *data)
* Updates the object tracking value while stepping
*
* Commands the dimmer to a value equal to the Tracking_Value
- * plus the step-increment. The resulting sum shall be clamped to
- * Min_Actual_Value and Max_Actual_Value
+ * plus the step-increment.
*
* @param data [in] dimmer data
*/
@@ -655,14 +684,13 @@ lighting_command_step_down_handler(struct bacnet_lighting_command_data *data)
old_value = data->Tracking_Value;
target_value = lighting_command_step_down_target_value(
data->Tracking_Value, data->Step_Increment);
- /* clamp Tracking value within the Normalized ON Range */
data->Tracking_Value =
- lighting_command_normalized_on_range_clamp(data, target_value);
+ lighting_command_normalized_on_range_clamp(target_value);
data->In_Progress = BACNET_LIGHTING_IDLE;
data->Lighting_Operation = BACNET_LIGHTS_STOP;
/* clamp Tracking value inclusively within the Operating Range */
operating_value =
- lighting_command_operating_range_clamp(data, data->Tracking_Value);
+ lighting_command_normalized_range_clamp(data->Tracking_Value);
data->Tracking_Value = operating_value;
/* notify */
lighting_command_tracking_value_event(
@@ -673,27 +701,32 @@ lighting_command_step_down_handler(struct bacnet_lighting_command_data *data)
* Updates the object tracking value while stepping
*
* Commands the dimmer to a value equal to the Tracking_Value
- * plus the step-increment. The resulting sum shall be clamped to
- * Min_Actual_Value and Max_Actual_Value
+ * plus the step-increment.
+ * If the result of the addition is greater than 100.0%,
+ * the value shall be set to 100.0%.
+ * When the Tracking_Value is 0.0%, 1.0% is written
+ * to the specified slot in the priority array.
*
* @param data [in] dimmer data
*/
static void
lighting_command_step_on_handler(struct bacnet_lighting_command_data *data)
{
- float old_value, target_value, operating_value;
+ float old_value, target_value;
old_value = data->Tracking_Value;
- target_value = lighting_command_step_up_target_value(
- data->Tracking_Value, data->Step_Increment);
- data->Tracking_Value =
- lighting_command_normalized_range_clamp(data, target_value);
+ if (isless(data->Tracking_Value, 1.0f)) {
+ /* step is ignored when starting at OFF */
+ data->Tracking_Value = 1.0f;
+ } else {
+ target_value = lighting_command_step_up_target_value(
+ data->Tracking_Value, data->Step_Increment);
+ /* clamp Tracking value inclusively within the Normalized Range */
+ data->Tracking_Value =
+ lighting_command_normalized_range_clamp(target_value);
+ }
data->In_Progress = BACNET_LIGHTING_IDLE;
data->Lighting_Operation = BACNET_LIGHTS_STOP;
- /* clamp Tracking value inclusively within the Operating Range */
- operating_value =
- lighting_command_operating_range_clamp(data, data->Tracking_Value);
- data->Tracking_Value = operating_value;
/* notify */
lighting_command_tracking_value_event(
data, old_value, data->Tracking_Value);
@@ -702,28 +735,28 @@ lighting_command_step_on_handler(struct bacnet_lighting_command_data *data)
/**
* Updates the object tracking value while stepping
*
- * Commands the dimmer to a value equal to the Tracking_Value
- * plus the step-increment. The resulting sum shall be clamped to
- * Min_Actual_Value and Max_Actual_Value
+ * Commands Present_Value to a value equal to the Tracking_Value
+ * minus the step-increment at the specified priority.
+ * The step-down operation is implemented by writing
+ * the Tracking_Value minus step-increment to the specified
+ * slot in the priority array.
+ * If the result of the subtraction is less than 1.0%,
+ * 0.0% is written to the specified slot in the priority array.
*
* @param data [in] dimmer data
*/
static void
lighting_command_step_off_handler(struct bacnet_lighting_command_data *data)
{
- float old_value, target_value, operating_value;
+ float old_value, target_value;
old_value = data->Tracking_Value;
target_value = lighting_command_step_down_target_value(
data->Tracking_Value, data->Step_Increment);
data->Tracking_Value =
- lighting_command_normalized_range_clamp(data, target_value);
+ lighting_command_normalized_range_clamp(target_value);
data->In_Progress = BACNET_LIGHTING_IDLE;
data->Lighting_Operation = BACNET_LIGHTS_STOP;
- /* clamp Tracking value inclusively within the Operating Range */
- operating_value =
- lighting_command_operating_range_clamp(data, data->Tracking_Value);
- data->Tracking_Value = operating_value;
/* notify */
lighting_command_tracking_value_event(
data, old_value, data->Tracking_Value);
@@ -767,7 +800,7 @@ static void lighting_command_blink_handler(
}
if (data->Blink.Duration == 0) {
/* 'end' operation */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
data->In_Progress = BACNET_LIGHTING_IDLE;
data->Lighting_Operation = BACNET_LIGHTS_STOP;
target_value = data->Blink.End_Value;
@@ -799,7 +832,7 @@ static void lighting_command_blink_handler(
}
if (data->Blink.Count == 0) {
/* 'end' operation */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
data->In_Progress = BACNET_LIGHTING_IDLE;
data->Lighting_Operation = BACNET_LIGHTS_STOP;
target_value = data->Blink.End_Value;
@@ -807,10 +840,8 @@ static void lighting_command_blink_handler(
}
}
}
- target_value = lighting_command_normalized_range_clamp(data, target_value);
/* clamp Tracking value inclusively within the Operating Range */
- operating_value =
- lighting_command_operating_range_clamp(data, target_value);
+ operating_value = lighting_command_normalized_range_clamp(target_value);
/* note: The blink-warn notifications shall not be reflected
in the tracking value. */
if (data->In_Progress == BACNET_LIGHTING_IDLE) {
@@ -823,20 +854,32 @@ static void lighting_command_blink_handler(
* @brief Overrides the current lighting command with the provided value
* @param data [in] dimmer data
*/
-void lighting_command_override(
+static void lighting_command_override_nolock(
struct bacnet_lighting_command_data *data, float value)
{
float old_value;
- if (!data) {
- return;
- }
old_value = data->Tracking_Value;
- data->Tracking_Value = lighting_command_physical_range_clamp(value);
+ data->Tracking_Value = lighting_command_normalized_range_clamp(value);
lighting_command_tracking_value_event(
data, old_value, data->Tracking_Value);
}
+/**
+ * @brief Overrides the current lighting command with the provided value
+ * @param data [in] dimmer data
+ */
+void lighting_command_override(
+ struct bacnet_lighting_command_data *data, float value)
+{
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ lighting_command_override_nolock(data, value);
+ lighting_command_unlock(data);
+}
+
/**
* @brief Overrides the current lighting command with the provided value
* @param data [in] dimmer data
@@ -847,9 +890,11 @@ void lighting_command_override_set(
if (!data) {
return;
}
+ lighting_command_lock(data);
data->Overridden = true;
data->Overridden_Momentary = false;
- lighting_command_override(data, value);
+ lighting_command_override_nolock(data, value);
+ lighting_command_unlock(data);
}
/**
@@ -859,21 +904,20 @@ void lighting_command_override_set(
void lighting_command_override_clear(
struct bacnet_lighting_command_data *data, float value)
{
- float old_value, normalized_value, operating_value;
+ float old_value;
if (!data) {
return;
}
+ lighting_command_lock(data);
data->Overridden = false;
data->Overridden_Momentary = false;
old_value = data->Tracking_Value;
/* clamp Tracking value within the Normalized Range */
- normalized_value = lighting_command_normalized_range_clamp(data, value);
- /* clamp Tracking value inclusively within the Operating Range */
- operating_value =
- lighting_command_operating_range_clamp(data, normalized_value);
- data->Tracking_Value = operating_value;
- lighting_command_tracking_value_event(data, old_value, operating_value);
+ data->Tracking_Value = lighting_command_normalized_range_clamp(value);
+ lighting_command_tracking_value_event(
+ data, old_value, data->Tracking_Value);
+ lighting_command_unlock(data);
}
/**
@@ -886,9 +930,11 @@ void lighting_command_override_momentary(
if (!data) {
return;
}
+ lighting_command_lock(data);
data->Overridden = true;
data->Overridden_Momentary = true;
- lighting_command_override(data, value);
+ lighting_command_override_nolock(data, value);
+ lighting_command_unlock(data);
}
/**
@@ -902,8 +948,10 @@ void lighting_command_refresh(struct bacnet_lighting_command_data *data)
if (!data) {
return;
}
+ lighting_command_lock(data);
value = data->Tracking_Value;
lighting_command_tracking_value_event(data, value, value);
+ lighting_command_unlock(data);
}
/**
@@ -918,6 +966,7 @@ void lighting_command_timer(
if (!data) {
return;
}
+ lighting_command_lock(data);
if (data->Overridden) {
data->Lighting_Operation = BACNET_LIGHTS_NONE;
}
@@ -961,6 +1010,7 @@ void lighting_command_timer(
break;
}
lighting_command_timer_notify(data, milliseconds);
+ lighting_command_unlock(data);
}
/**
@@ -975,8 +1025,9 @@ void lighting_command_fade_to(
if (!data) {
return;
}
+ lighting_command_lock(data);
/* possibly interrupting a blink warn, so notify */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
/* configure the lighting operation */
data->Fade_Time = fade_time;
data->Lighting_Operation = BACNET_LIGHTS_FADE_TO;
@@ -985,6 +1036,7 @@ void lighting_command_fade_to(
/* the last value that was greater than or equal to 1.0%.*/
data->Last_On_Value = value;
}
+ lighting_command_unlock(data);
}
/**
@@ -999,8 +1051,9 @@ void lighting_command_ramp_to(
if (!data) {
return;
}
+ lighting_command_lock(data);
/* possibly interrupting a blink warn, so notify */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
/* configure the lighting operation */
data->Ramp_Rate = lighting_command_ramp_rate_clamp(ramp_rate);
data->Lighting_Operation = BACNET_LIGHTS_RAMP_TO;
@@ -1009,6 +1062,7 @@ void lighting_command_ramp_to(
/* the last value that was greater than or equal to 1.0%.*/
data->Last_On_Value = value;
}
+ lighting_command_unlock(data);
}
/**
@@ -1027,15 +1081,16 @@ void lighting_command_step(
if (!data) {
return;
}
+ lighting_command_lock(data);
/* possibly interrupting a blink warn, so notify */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
/* configure the lighting operation */
if (((operation == BACNET_LIGHTS_STEP_UP) ||
(operation == BACNET_LIGHTS_STEP_DOWN)) &&
- (!islessgreater(data->Tracking_Value, 0.0))) {
+ (is_float_equal(data->Tracking_Value, 0.0))) {
/* If the starting level of Tracking_Value is 0.0%,
then this operation is ignored. */
- return;
+ goto done;
}
data->Lighting_Operation = operation;
data->Fade_Time = 0;
@@ -1044,30 +1099,29 @@ void lighting_command_step(
if (operation == BACNET_LIGHTS_STEP_UP) {
target_value = lighting_command_step_up_target_value(
data->Tracking_Value, data->Step_Increment);
- target_value =
- lighting_command_normalized_on_range_clamp(data, target_value);
+ target_value = lighting_command_normalized_on_range_clamp(target_value);
} else if (operation == BACNET_LIGHTS_STEP_DOWN) {
target_value = lighting_command_step_down_target_value(
data->Tracking_Value, data->Step_Increment);
- target_value =
- lighting_command_normalized_on_range_clamp(data, target_value);
+ target_value = lighting_command_normalized_on_range_clamp(target_value);
} else if (operation == BACNET_LIGHTS_STEP_ON) {
target_value = lighting_command_step_up_target_value(
data->Tracking_Value, data->Step_Increment);
- target_value =
- lighting_command_normalized_range_clamp(data, target_value);
+ target_value = lighting_command_normalized_range_clamp(target_value);
} else if (operation == BACNET_LIGHTS_STEP_OFF) {
target_value = lighting_command_step_down_target_value(
data->Tracking_Value, data->Step_Increment);
- target_value =
- lighting_command_normalized_range_clamp(data, target_value);
+ target_value = lighting_command_normalized_range_clamp(target_value);
} else {
- return;
+ goto done;
}
if (isgreaterequal(target_value, 1.0)) {
/* the last value that was greater than or equal to 1.0%.*/
data->Last_On_Value = target_value;
}
+
+done:
+ lighting_command_unlock(data);
}
/**
@@ -1081,11 +1135,12 @@ void lighting_command_blink_warn(
BACNET_LIGHTING_OPERATION operation,
struct bacnet_lighting_command_warn_data *blink)
{
- if (!data) {
+ if (!data || !blink) {
return;
}
+ lighting_command_lock(data);
/* possibly interrupting a blink warn, so notify */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
/* configure the new warning */
data->Lighting_Operation = operation;
data->Blink.Target_Interval = blink->Interval;
@@ -1101,6 +1156,33 @@ void lighting_command_blink_warn(
/* configure next interval */
data->Blink.State = false;
data->Blink.Interval = blink->Interval;
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Copy the current blink data from the lighting command
+ * @param data [in] dimmer object instance
+ * @param blink [out] BACnet blink data to copy into
+ */
+void lighting_command_blink_copy(
+ struct bacnet_lighting_command_data *data,
+ struct bacnet_lighting_command_warn_data *blink)
+{
+ if (!data || !blink) {
+ return;
+ }
+ lighting_command_lock(data);
+ blink->On_Value = data->Blink.On_Value;
+ blink->Off_Value = data->Blink.Off_Value;
+ blink->End_Value = data->Blink.End_Value;
+ blink->Priority = data->Blink.Priority;
+ blink->Callback = data->Blink.Callback;
+ blink->Target_Interval = data->Blink.Target_Interval;
+ blink->Interval = data->Blink.Interval;
+ blink->Duration = data->Blink.Duration;
+ blink->Count = data->Blink.Count;
+ blink->State = data->Blink.State;
+ lighting_command_unlock(data);
}
/**
@@ -1113,14 +1195,16 @@ void lighting_command_stop(struct bacnet_lighting_command_data *data)
if (!data) {
return;
}
+ lighting_command_lock(data);
/* possibly interrupting a blink warn, so notify */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
/* configure the lighting operation */
data->Lighting_Operation = BACNET_LIGHTS_STOP;
if (isgreaterequal(data->Tracking_Value, 1.0)) {
/* the last value that was greater than or equal to 1.0%.*/
data->Last_On_Value = data->Tracking_Value;
}
+ lighting_command_unlock(data);
}
/**
@@ -1133,10 +1217,12 @@ void lighting_command_none(struct bacnet_lighting_command_data *data)
if (!data) {
return;
}
+ lighting_command_lock(data);
/* possibly interrupting a blink warn, so notify */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
/* configure the lighting operation */
data->Lighting_Operation = BACNET_LIGHTS_NONE;
+ lighting_command_unlock(data);
}
/**
@@ -1150,12 +1236,14 @@ void lighting_command_restore_on(
if (!data) {
return;
}
+ lighting_command_lock(data);
/* possibly interrupting a blink warn, so notify */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
/* configure the lighting operation */
data->Fade_Time = fade_time;
data->Lighting_Operation = BACNET_LIGHTS_RESTORE_ON;
data->Target_Level = data->Last_On_Value;
+ lighting_command_unlock(data);
}
/**
@@ -1169,12 +1257,14 @@ void lighting_command_default_on(
if (!data) {
return;
}
+ lighting_command_lock(data);
/* possibly interrupting a blink warn, so notify */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
/* configure the lighting operation */
data->Fade_Time = fade_time;
data->Lighting_Operation = BACNET_LIGHTS_DEFAULT_ON;
data->Target_Level = data->Default_On_Value;
+ lighting_command_unlock(data);
}
/**
@@ -1188,8 +1278,9 @@ void lighting_command_toggle_restore(
if (!data) {
return;
}
+ lighting_command_lock(data);
/* possibly interrupting a blink warn, so notify */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
/* configure the lighting operation */
data->Fade_Time = fade_time;
data->Lighting_Operation = BACNET_LIGHTS_TOGGLE_RESTORE;
@@ -1200,6 +1291,7 @@ void lighting_command_toggle_restore(
/* not OFF, write 0.0% */
data->Target_Level = 0.0f;
}
+ lighting_command_unlock(data);
}
/**
@@ -1213,8 +1305,9 @@ void lighting_command_toggle_default(
if (!data) {
return;
}
+ lighting_command_lock(data);
/* possibly interrupting a blink warn, so notify */
- lighting_command_blink_stop_notify(data);
+ lighting_command_blink_stop_notify_nolock(data);
/* configure the lighting operation */
data->Fade_Time = fade_time;
data->Lighting_Operation = BACNET_LIGHTS_TOGGLE_DEFAULT;
@@ -1225,13 +1318,467 @@ void lighting_command_toggle_default(
/* not OFF, write 0.0% */
data->Target_Level = 0.0f;
}
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Configure the lighting command to apply low or high trim
+ * @param data [in] dimmer data
+ * @param high_end_trim [in] BACnet lighting high end trim
+ * @param low_end_trim [in] BACnet lighting low end trim
+ * @param trim_fade_time [in] BACnet lighting trim fade time
+ */
+void lighting_command_trim_set(
+ struct bacnet_lighting_command_data *data,
+ float high_end_trim,
+ float low_end_trim,
+ uint32_t trim_fade_time)
+{
+ float swap_value, high_trim, low_trim;
+
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ /* clamp range within normalized limits */
+ high_trim = lighting_command_normalized_on_range_clamp(high_end_trim);
+ low_trim = lighting_command_normalized_on_range_clamp(low_end_trim);
+ /* valid range check for high and low trim values */
+ if (isgreater(low_trim, high_trim)) {
+ /* swap the trims if they are inverse */
+ swap_value = low_trim;
+ low_trim = high_trim;
+ high_trim = swap_value;
+ }
+ data->High_Trim_Value = high_trim;
+ data->Low_Trim_Value = low_trim;
+ data->Trim_Fade_Time = trim_fade_time;
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Set the lighting command key
+ * @param data [in] dimmer data
+ * @param key [in] BACnet lighting key
+ */
+void lighting_command_key_set(
+ struct bacnet_lighting_command_data *data, uint32_t key)
+{
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ data->Key = key;
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Set the lighting command tracking value callback
+ * @param data [in] dimmer data
+ * @param cb [in] BACnet lighting tracking value callback
+ */
+void lighting_command_tracking_value_callback_set(
+ struct bacnet_lighting_command_data *data,
+ lighting_command_tracking_value_callback cb)
+{
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ data->Notification_Head.callback = cb;
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Get the lighting command in progress
+ * @param data [in] dimmer data
+ * @return BACNET_LIGHTING_IN_PROGRESS - lighting command in progress
+ */
+BACNET_LIGHTING_IN_PROGRESS
+lighting_command_in_progress_get(struct bacnet_lighting_command_data *data)
+{
+ BACNET_LIGHTING_IN_PROGRESS in_progress = BACNET_LIGHTING_IDLE;
+
+ if (!data) {
+ return in_progress;
+ }
+ lighting_command_lock(data);
+ in_progress = data->In_Progress;
+ lighting_command_unlock(data);
+
+ return in_progress;
+}
+
+/**
+ * @brief Set the lighting command in progress
+ * @param data [in] dimmer data
+ * @param in_progress [in] BACnet lighting in progress
+ */
+void lighting_command_in_progress_set(
+ struct bacnet_lighting_command_data *data,
+ BACNET_LIGHTING_IN_PROGRESS in_progress)
+{
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ data->In_Progress = in_progress;
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Get the lighting command tracking value
+ * @param data [in] dimmer data
+ * @return float - lighting command tracking value
+ */
+float lighting_command_tracking_value_get(
+ struct bacnet_lighting_command_data *data)
+{
+ float value = 0.0f;
+
+ if (!data) {
+ return value;
+ }
+ lighting_command_lock(data);
+ value = data->Tracking_Value;
+ lighting_command_unlock(data);
+
+ return value;
+}
+
+/**
+ * @brief Set the lighting command tracking value
+ * @param data [in] dimmer data
+ * @param value [in] BACnet lighting tracking value
+ */
+void lighting_command_tracking_value_set(
+ struct bacnet_lighting_command_data *data, float value)
+{
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ data->Tracking_Value = value;
+ lighting_command_unlock(data);
+}
+
+void lighting_command_blink_warn_feature_set(
+ struct bacnet_lighting_command_data *data,
+ float off_value,
+ uint16_t interval,
+ uint16_t count)
+{
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ data->Blink.Off_Value = off_value;
+ data->Blink.Interval = interval;
+ data->Blink.Count = count;
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Check if the lighting command blink egress is active
+ * @param data [in] dimmer data
+ * @return bool - true if the lighting command blink egress is active
+ */
+bool lighting_command_blink_egress_active(
+ struct bacnet_lighting_command_data *data)
+{
+ bool active = false;
+
+ if (!data) {
+ return active;
+ }
+ lighting_command_lock(data);
+ active = data->Blink.Duration > 0;
+ lighting_command_unlock(data);
+
+ return active;
+}
+
+/**
+ * @brief Get the lighting command out of service
+ * @param data [in] dimmer data
+ * @return bool - true if the lighting command is out of service
+ */
+bool lighting_command_out_of_service_get(
+ struct bacnet_lighting_command_data *data)
+{
+ bool value = false;
+
+ if (!data) {
+ return value;
+ }
+ lighting_command_lock(data);
+ value = data->Out_Of_Service;
+ lighting_command_unlock(data);
+
+ return value;
+}
+
+/**
+ * @brief Set the lighting command out of service
+ * @param data [in] dimmer data
+ * @param value [in] BACnet lighting out of service
+ */
+void lighting_command_out_of_service_set(
+ struct bacnet_lighting_command_data *data, bool value)
+{
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ data->Out_Of_Service = value;
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Get the lighting command last on value
+ * @param data [in] dimmer data
+ * @return float - lighting command last on value
+ */
+float lighting_command_last_on_value_get(
+ struct bacnet_lighting_command_data *data)
+{
+ float value = 0.0f;
+
+ if (!data) {
+ return value;
+ }
+ lighting_command_lock(data);
+ value = data->Last_On_Value;
+ lighting_command_unlock(data);
+
+ return value;
+}
+
+/**
+ * @brief Set the lighting command last on value
+ * @param data [in] dimmer data
+ * @param value [in] BACnet lighting last on value
+ */
+void lighting_command_last_on_value_set(
+ struct bacnet_lighting_command_data *data, float value)
+{
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ data->Last_On_Value = value;
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Get the lighting command default on value
+ * @param data [in] dimmer data
+ * @return float - lighting command default on value
+ */
+float lighting_command_default_on_value_get(
+ struct bacnet_lighting_command_data *data)
+{
+ float value = 0.0f;
+
+ if (!data) {
+ return value;
+ }
+ lighting_command_lock(data);
+ value = data->Default_On_Value;
+ lighting_command_unlock(data);
+
+ return value;
+}
+
+/**
+ * @brief Get the lighting command Min_Actual_Value property value
+ * @param data [in] dimmer data
+ * @return float - lighting command Min_Actual_Value
+ */
+float lighting_command_min_actual_value_get(
+ struct bacnet_lighting_command_data *data)
+{
+ float value = 0.0f;
+
+ if (!data) {
+ return value;
+ }
+ lighting_command_lock(data);
+ value = data->Min_Actual_Value;
+ lighting_command_unlock(data);
+
+ return value;
+}
+
+/**
+ * @brief Set the lighting command Min_Actual_Value property value
+ * @param data [in] dimmer data
+ * @param value [in] BACnet lighting Min_Actual_Value
+ */
+void lighting_command_min_actual_value_set(
+ struct bacnet_lighting_command_data *data, float value)
+{
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ data->Min_Actual_Value = value;
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Get the lighting command Max_Actual_Value property value
+ * @param data [in] dimmer data
+ * @return float - lighting command Max_Actual_Value
+ */
+float lighting_command_max_actual_value_get(
+ struct bacnet_lighting_command_data *data)
+{
+ float value = 0.0f;
+
+ if (!data) {
+ return value;
+ }
+ lighting_command_lock(data);
+ value = data->Max_Actual_Value;
+ lighting_command_unlock(data);
+
+ return value;
}
+/**
+ * @brief Set the lighting command Max_Actual_Value property value
+ * @param data [in] dimmer data
+ * @param value [in] BACnet lighting Max_Actual_Value
+ */
+void lighting_command_max_actual_value_set(
+ struct bacnet_lighting_command_data *data, float value)
+{
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ data->Max_Actual_Value = value;
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Set the lighting command default on value
+ * @param data [in] dimmer data
+ * @param value [in] BACnet lighting default on value
+ */
+void lighting_command_default_on_value_set(
+ struct bacnet_lighting_command_data *data, float value)
+{
+ if (!data) {
+ return;
+ }
+ lighting_command_lock(data);
+ data->Default_On_Value = value;
+ lighting_command_unlock(data);
+}
+
+/**
+ * @brief Get the lighting command overridden status
+ * @param data [in] dimmer data
+ * @return bool - true if the lighting command is overridden
+ */
+bool lighting_command_overridden_status(
+ struct bacnet_lighting_command_data *data)
+{
+ bool status = false;
+
+ if (!data) {
+ return status;
+ }
+ lighting_command_lock(data);
+ status = data->Overridden || data->Overridden_Momentary;
+ lighting_command_unlock(data);
+
+ return status;
+}
+
+/**
+ * @brief Get the lighting command feedback value
+ * @param data [in] dimmer data
+ * @return float - lighting command feedback value
+ */
+float lighting_command_feedback_value(struct bacnet_lighting_command_data *data)
+{
+ float feedback_value;
+
+ if (!data) {
+ return 0.0f;
+ }
+ lighting_command_lock(data);
+ lighting_command_min_max_value_range_adjust(data);
+ feedback_value = lighting_command_normalized_to_physical_value(
+ data->Min_Actual_Value, data->Max_Actual_Value, data->Tracking_Value);
+ lighting_command_unlock(data);
+
+ return feedback_value;
+}
+
+/**
+ * @brief Determine if fade, ramp, or warn command is currently executing
+ * @param pObject [in] object to apply the trim values to
+ * @param priority [in] priority of the command
+ */
+bool lighting_command_active(struct bacnet_lighting_command_data *data)
+{
+ bool in_progress = false;
+
+ if (!data) {
+ return in_progress;
+ }
+ lighting_command_lock(data);
+ if ((data->In_Progress == BACNET_LIGHTING_FADE_ACTIVE) ||
+ (data->In_Progress == BACNET_LIGHTING_RAMP_ACTIVE) ||
+ (data->Blink.Duration > 0)) {
+ in_progress = true;
+ }
+ lighting_command_unlock(data);
+
+ return in_progress;
+}
+
+/**
+ * @brief Locks the lighting command for exclusive access
+ * @param data [in] dimmer data
+ */
+void lighting_command_lock(struct bacnet_lighting_command_data *data)
+{
+ if (!data) {
+ return;
+ }
+ if (data->Lock) {
+ data->Lock(data);
+ }
+}
+
+/**
+ * @brief Unlocks the lighting command for exclusive access
+ * @param data [in] dimmer data
+ */
+void lighting_command_unlock(struct bacnet_lighting_command_data *data)
+{
+ if (!data) {
+ return;
+ }
+ if (data->Unlock) {
+ data->Unlock(data);
+ }
+}
+
+/**
+ * @brief Initializes the lighting command data structure to default values
+ */
void lighting_command_init(struct bacnet_lighting_command_data *data)
{
if (!data) {
return;
}
+ lighting_command_lock(data);
data->Tracking_Value = 0.0f;
data->Lighting_Operation = BACNET_LIGHTS_NONE;
data->In_Progress = BACNET_LIGHTING_NOT_CONTROLLED;
@@ -1256,4 +1803,7 @@ void lighting_command_init(struct bacnet_lighting_command_data *data)
data->Blink.State = false;
data->Notification_Head.next = NULL;
data->Notification_Head.callback = NULL;
+ data->Timer_Notification_Head.next = NULL;
+ data->Timer_Notification_Head.callback = NULL;
+ lighting_command_unlock(data);
}
diff --git a/src/bacnet/basic/sys/lighting_command.h b/src/bacnet/basic/sys/lighting_command.h
index d8babfd39a..9c3980ea7f 100644
--- a/src/bacnet/basic/sys/lighting_command.h
+++ b/src/bacnet/basic/sys/lighting_command.h
@@ -13,10 +13,10 @@
#include "bacnet/bacdef.h"
/**
- * @brief Callback for tracking value updates
+ * @brief Callback for lighting command notifications
* @param key - key used to link to specific light
- * @param old_value - value prior to write
- * @param value - value of the write
+ * @param old_value - physical value prior to write
+ * @param value - physical value of the write
*/
typedef void (*lighting_command_tracking_value_callback)(
uint32_t key, float old_value, float value);
@@ -41,6 +41,13 @@ struct lighting_command_timer_notification {
lighting_command_timer_callback callback;
};
+/**
+ * @brief Callback for locking shared state during lighting command processing
+ * @param data - dimmer data structure
+ */
+typedef void (*lighting_command_lock_callback)(
+ struct bacnet_lighting_command_data *);
+
/**
* @brief Callback that manipulates the value at the specified priority slot
after a delay of Egress_Time seconds.
@@ -91,6 +98,10 @@ typedef struct bacnet_lighting_command_data {
uint32_t Key;
struct lighting_command_notification Notification_Head;
struct lighting_command_timer_notification Timer_Notification_Head;
+ /* lock for accessing shared state */
+ lighting_command_lock_callback Lock;
+ lighting_command_lock_callback Unlock;
+ void *Context;
} BACNET_LIGHTING_COMMAND_DATA;
#ifdef __cplusplus
@@ -117,6 +128,10 @@ void lighting_command_blink_warn(
BACNET_LIGHTING_OPERATION operation,
struct bacnet_lighting_command_warn_data *blink);
BACNET_STACK_EXPORT
+void lighting_command_blink_copy(
+ struct bacnet_lighting_command_data *data,
+ struct bacnet_lighting_command_warn_data *blink);
+BACNET_STACK_EXPORT
void lighting_command_stop(struct bacnet_lighting_command_data *data);
BACNET_STACK_EXPORT
void lighting_command_none(struct bacnet_lighting_command_data *data);
@@ -150,22 +165,101 @@ BACNET_STACK_EXPORT
float lighting_command_ramp_rate_clamp(float ramp_rate);
BACNET_STACK_EXPORT
float lighting_command_step_increment_clamp(float step_increment);
-BACNET_STACK_EXPORT
-float lighting_command_operating_range_clamp(
- struct bacnet_lighting_command_data *data, float value);
+
BACNET_STACK_EXPORT
float lighting_command_operating_range_clamp_fade(
struct bacnet_lighting_command_data *data,
float value,
uint16_t milliseconds);
BACNET_STACK_EXPORT
-float lighting_command_normalized_range_clamp(
+float lighting_command_normalized_range_clamp(float value);
+
+BACNET_STACK_EXPORT
+float lighting_command_normalized_on_range_clamp(float value);
+BACNET_STACK_EXPORT
+float lighting_command_normalized_to_physical_value(
+ float min_value, float max_value, float normalized_value);
+BACNET_STACK_EXPORT
+float lighting_command_feedback_value(
+ struct bacnet_lighting_command_data *data);
+
+BACNET_STACK_EXPORT
+void lighting_command_trim_set(
+ struct bacnet_lighting_command_data *data,
+ float High_End_Trim,
+ float Low_End_Trim,
+ uint32_t Trim_Fade_Time);
+BACNET_STACK_EXPORT
+void lighting_command_key_set(
+ struct bacnet_lighting_command_data *data, uint32_t key);
+BACNET_STACK_EXPORT
+void lighting_command_tracking_value_callback_set(
+ struct bacnet_lighting_command_data *data,
+ lighting_command_tracking_value_callback cb);
+BACNET_STACK_EXPORT
+BACNET_LIGHTING_IN_PROGRESS
+lighting_command_in_progress_get(struct bacnet_lighting_command_data *data);
+BACNET_STACK_EXPORT
+void lighting_command_in_progress_set(
+ struct bacnet_lighting_command_data *data,
+ BACNET_LIGHTING_IN_PROGRESS in_progress);
+BACNET_STACK_EXPORT
+float lighting_command_tracking_value_get(
+ struct bacnet_lighting_command_data *data);
+BACNET_STACK_EXPORT
+void lighting_command_tracking_value_set(
+ struct bacnet_lighting_command_data *data, float value);
+BACNET_STACK_EXPORT
+void lighting_command_blink_warn_feature_set(
+ struct bacnet_lighting_command_data *data,
+ float off_value,
+ uint16_t interval,
+ uint16_t count);
+BACNET_STACK_EXPORT
+bool lighting_command_blink_egress_active(
+ struct bacnet_lighting_command_data *data);
+BACNET_STACK_EXPORT
+bool lighting_command_out_of_service_get(
+ struct bacnet_lighting_command_data *data);
+BACNET_STACK_EXPORT
+void lighting_command_out_of_service_set(
+ struct bacnet_lighting_command_data *data, bool value);
+BACNET_STACK_EXPORT
+float lighting_command_last_on_value_get(
+ struct bacnet_lighting_command_data *data);
+BACNET_STACK_EXPORT
+void lighting_command_last_on_value_set(
+ struct bacnet_lighting_command_data *data, float value);
+BACNET_STACK_EXPORT
+float lighting_command_default_on_value_get(
+ struct bacnet_lighting_command_data *data);
+BACNET_STACK_EXPORT
+void lighting_command_default_on_value_set(
struct bacnet_lighting_command_data *data, float value);
BACNET_STACK_EXPORT
-float lighting_command_normalized_on_range_clamp(
+bool lighting_command_overridden_status(
+ struct bacnet_lighting_command_data *data);
+
+BACNET_STACK_EXPORT
+float lighting_command_min_actual_value_get(
+ struct bacnet_lighting_command_data *data);
+BACNET_STACK_EXPORT
+void lighting_command_min_actual_value_set(
struct bacnet_lighting_command_data *data, float value);
BACNET_STACK_EXPORT
-float lighting_command_physical_range_clamp(float value);
+float lighting_command_max_actual_value_get(
+ struct bacnet_lighting_command_data *data);
+BACNET_STACK_EXPORT
+void lighting_command_max_actual_value_set(
+ struct bacnet_lighting_command_data *data, float value);
+
+BACNET_STACK_EXPORT
+bool lighting_command_active(struct bacnet_lighting_command_data *data);
+
+BACNET_STACK_EXPORT
+void lighting_command_lock(struct bacnet_lighting_command_data *data);
+BACNET_STACK_EXPORT
+void lighting_command_unlock(struct bacnet_lighting_command_data *data);
BACNET_STACK_EXPORT
void lighting_command_refresh(struct bacnet_lighting_command_data *data);
diff --git a/src/bacnet/channel_value.c b/src/bacnet/channel_value.c
index dcf17f25a0..2b337552c7 100644
--- a/src/bacnet/channel_value.c
+++ b/src/bacnet/channel_value.c
@@ -1290,18 +1290,21 @@ int bacnet_channel_value_no_coerce_decode(
#endif
#if defined(CHANNEL_OCTET_STRING)
case BACNET_APPLICATION_TAG_OCTET_STRING:
+ octetstring_init(&value->type.Octet_String, NULL, 0);
len = bacnet_octet_string_application_decode(
apdu, apdu_size, &value->type.Octet_String);
break;
#endif
#if defined(CHANNEL_CHARACTER_STRING)
case BACNET_APPLICATION_TAG_CHARACTER_STRING:
+ characterstring_init_ansi(&value->type.Character_String, "");
len = bacnet_character_string_application_decode(
apdu, apdu_size, &value->type.Character_String);
break;
#endif
#if defined(CHANNEL_BIT_STRING)
case BACNET_APPLICATION_TAG_BIT_STRING:
+ bitstring_init(&value->type.Bit_String);
len = bacnet_bitstring_application_decode(
apdu, apdu_size, &value->type.Bit_String);
break;
diff --git a/src/bacnet/config.h b/src/bacnet/config.h
index 68c96e419f..b9f30b2408 100644
--- a/src/bacnet/config.h
+++ b/src/bacnet/config.h
@@ -241,6 +241,7 @@
defined(BACAPP_RECIPIENT) || \
defined(BACAPP_ADDRESS_BINDING) || \
defined(BACAPP_NO_VALUE) || \
+ defined(BACAPP_AUTHENTICATION) || \
defined(BACAPP_LOG_RECORD) || \
defined(BACAPP_SECURE_CONNECT) || \
defined(BACAPP_TYPES_EXTRA))
@@ -335,6 +336,8 @@
#define BACAPP_ADDRESS_BINDING
#undef BACAPP_NO_VALUE
#define BACAPP_NO_VALUE
+#undef BACAPP_AUTHENTICATION
+#define BACAPP_AUTHENTICATION
#undef BACAPP_LOG_RECORD
#define BACAPP_LOG_RECORD
#undef BACAPP_SECURE_CONNECT
@@ -368,6 +371,7 @@
defined(BACAPP_RECIPIENT) || \
defined(BACAPP_ADDRESS_BINDING) || \
defined(BACAPP_NO_VALUE) || \
+ defined(BACAPP_AUTHENTICATION) || \
defined(BACAPP_LOG_RECORD)
#undef BACAPP_COMPLEX_TYPES
#define BACAPP_COMPLEX_TYPES
diff --git a/src/bacnet/datalink/bip.h b/src/bacnet/datalink/bip.h
index 0c454cdeb7..0428393f44 100644
--- a/src/bacnet/datalink/bip.h
+++ b/src/bacnet/datalink/bip.h
@@ -93,6 +93,9 @@ bool bip_set_broadcast_addr(const BACNET_IP_ADDRESS *addr);
BACNET_STACK_EXPORT
bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr);
+BACNET_STACK_EXPORT
+bool bip_get_gateway_addr(BACNET_IP_ADDRESS *addr);
+
BACNET_STACK_EXPORT
bool bip_set_subnet_prefix(uint8_t prefix);
diff --git a/src/bacnet/datalink/bsc/bsc-node.c b/src/bacnet/datalink/bsc/bsc-node.c
index 35ee74fce5..86d344f084 100644
--- a/src/bacnet/datalink/bsc/bsc-node.c
+++ b/src/bacnet/datalink/bsc/bsc-node.c
@@ -405,22 +405,27 @@ static void bsc_node_parse_urls(
.utf8_websocket_uri_string_len;
i++) {
if (url[i] == 0x20) {
- if (i > BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK ||
+ if ((i - start) >
+ BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK ||
(i - start) == 0) {
start = i + 1;
continue;
- } else {
+ } else if (
+ j < BSC_CONF_NODE_MAX_URIS_NUM_IN_ADDRESS_RESOLUTION_ACK) {
memcpy(&r->utf8_urls[j][0], &url[start], i - start);
- r->utf8_urls[j][i] = 0;
+ r->utf8_urls[j][i - start] = 0;
j++;
start = i + 1;
+ } else {
+ break;
}
}
}
if (i - start > 0 &&
- i <= BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK) {
+ (i - start) <= BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK &&
+ j < BSC_CONF_NODE_MAX_URIS_NUM_IN_ADDRESS_RESOLUTION_ACK) {
memcpy(&r->utf8_urls[j][0], &url[start], i - start);
- r->utf8_urls[j][i] = 0;
+ r->utf8_urls[j][i - start] = 0;
j++;
}
r->urls_num = j;
diff --git a/src/bacnet/datalink/dlenv.c b/src/bacnet/datalink/dlenv.c
index a49e04f336..c33ae95974 100644
--- a/src/bacnet/datalink/dlenv.c
+++ b/src/bacnet/datalink/dlenv.c
@@ -45,7 +45,12 @@ static uint16_t BBMD_TTL_Seconds = 60000;
/* BBMD variables */
static BACNET_IP_ADDRESS BBMD_Address;
static bool BBMD_Address_Valid;
-static uint16_t BBMD_Result = 0;
+/** BBMD Result:
+ * Positive number (of bytes sent) if registration was successful,
+ * 0 if no registration request was made, or
+ * -1 if registration attempt failed.
+ */
+static int BBMD_Result;
#if defined(BACDL_BIP) && BBMD_ENABLED
static BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY BBMD_Table_Entry;
#endif
@@ -130,7 +135,7 @@ int dlenv_bbmd_result(void)
*/
static int bbmd_register_as_foreign_device(void)
{
- int retval = -1;
+ int registration = 0;
#if defined(BACDL_BIP) && BBMD_CLIENT_ENABLED
char *pEnv = NULL;
long long_value = 0;
@@ -176,8 +181,8 @@ static int bbmd_register_as_foreign_device(void)
(unsigned)BBMD_Address.address[3], (unsigned)BBMD_Address.port,
(unsigned)BBMD_TTL_Seconds);
}
- retval = bvlc_register_with_bbmd(&BBMD_Address, BBMD_TTL_Seconds);
- if (retval < 0) {
+ registration = bvlc_register_with_bbmd(&BBMD_Address, BBMD_TTL_Seconds);
+ if (registration < 0) {
fprintf(
stderr, "FAILED to Register with BBMD at %u.%u.%u.%u:%u\n",
(unsigned)BBMD_Address.address[0],
@@ -266,9 +271,9 @@ static int bbmd_register_as_foreign_device(void)
}
#endif
#endif
- BBMD_Result = retval;
+ BBMD_Result = registration;
- return retval;
+ return registration;
}
/** Register as a Foreign Device with the designated BBMD.
@@ -287,7 +292,7 @@ static int bbmd_register_as_foreign_device(void)
*/
static int bbmd6_register_as_foreign_device(void)
{
- int retval = -1;
+ int registration = 0;
#if defined(BACDL_BIP6) && BBMD6_ENABLED
char *pEnv = NULL;
long long_value = 0;
@@ -315,8 +320,8 @@ static int bbmd6_register_as_foreign_device(void)
stderr, "Registering with BBMD6 at %s:0x%04x for %u seconds\n",
pEnv, (unsigned)bip6_port, (unsigned)BBMD_TTL_Seconds);
}
- retval = bvlc6_register_with_bbmd(&bip6_addr, BBMD_TTL_Seconds);
- if (retval < 0) {
+ registration = bvlc6_register_with_bbmd(&bip6_addr, BBMD_TTL_Seconds);
+ if (registration < 0) {
fprintf(
stderr, "FAILED to Register with BBMD6 at %s:%u\n", pEnv,
(unsigned)BBMD_Address.port);
@@ -324,9 +329,9 @@ static int bbmd6_register_as_foreign_device(void)
BBMD_Timer_Seconds = BBMD_TTL_Seconds;
}
#endif
- BBMD_Result = retval;
+ BBMD_Result = registration;
- return retval;
+ return registration;
}
/**
@@ -414,6 +419,7 @@ static void dlenv_network_port_bip_init(uint32_t instance)
bvlc_set_global_address_for_nat(&addr);
}
}
+ /* IP Address */
bip_get_addr(&addr);
prefix = bip_get_subnet_prefix();
if (Datalink_Debug) {
@@ -429,6 +435,14 @@ static void dlenv_network_port_bip_init(uint32_t instance)
addr.address[3]);
Network_Port_IP_Subnet_Prefix_Set(instance, prefix);
Network_Port_Link_Speed_Set(instance, 0.0);
+ /* IP Gateway */
+ if (bip_get_gateway_addr(&addr)) {
+ Network_Port_IP_Gateway_Set(
+ instance, addr.address[0], addr.address[1], addr.address[2],
+ addr.address[3]);
+ } else {
+ Network_Port_IP_Gateway_Set(instance, 0, 0, 0, 0);
+ }
#if BBMD_ENABLED
bdt_table = bvlc_bdt_list();
fdt_table = bvlc_fdt_list();
@@ -902,10 +916,10 @@ void dlenv_maintenance_timer(uint16_t elapsed_seconds)
}
if (BBMD_Timer_Seconds == 0) {
if (Network_Port_Type(Network_Port_Instance) == PORT_TYPE_BIP) {
- bbmd_register_as_foreign_device();
+ (void)bbmd_register_as_foreign_device();
} else if (
Network_Port_Type(Network_Port_Instance) == PORT_TYPE_BIP6) {
- bbmd6_register_as_foreign_device();
+ (void)bbmd6_register_as_foreign_device();
}
/* If that failed (negative), maybe just a network issue.
* If nothing happened (0), may be un/misconfigured.
diff --git a/src/bacnet/datalink/mstp.c b/src/bacnet/datalink/mstp.c
index b83cda2215..51b705883d 100644
--- a/src/bacnet/datalink/mstp.c
+++ b/src/bacnet/datalink/mstp.c
@@ -561,8 +561,8 @@ void MSTP_Receive_Frame_FSM(struct mstp_port_struct_t *mstp_port)
(mstp_port->FrameType <= Nmax_COBS_type)) {
mstp_port->DataLength = cobs_frame_decode(
&mstp_port->InputBuffer[mstp_port->Index + 1],
- mstp_port->InputBufferSize, mstp_port->InputBuffer,
- mstp_port->Index + 1);
+ mstp_port->InputBufferSize - (mstp_port->Index + 1),
+ mstp_port->InputBuffer, mstp_port->Index + 1);
if (mstp_port->DataLength > 0) {
/* GoodCRC */
if (mstp_port->receive_state ==
diff --git a/src/bacnet/ihave.c b/src/bacnet/ihave.c
index 6202031f3f..10c55bf2e4 100644
--- a/src/bacnet/ihave.c
+++ b/src/bacnet/ihave.c
@@ -115,6 +115,7 @@ int ihave_decode_service_request(
if (data) {
decoded_string = &data->object_name;
}
+ characterstring_init_ansi(decoded_string, "");
len = bacnet_character_string_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, decoded_string);
if (len <= 0) {
diff --git a/src/bacnet/lighting.c b/src/bacnet/lighting.c
index 382730f802..1a0b7cde2f 100644
--- a/src/bacnet/lighting.c
+++ b/src/bacnet/lighting.c
@@ -192,129 +192,139 @@ int lighting_command_decode(
case BACNET_LIGHTS_NONE:
break;
case BACNET_LIGHTS_FADE_TO:
- if ((apdu_size - apdu_len) == 0) {
- return BACNET_STATUS_REJECT;
- }
- /* target-level [1] REAL (0.0..100.0) OPTIONAL */
+ /* target-level [1] REAL (0.0..100.0) */
len = bacnet_real_context_decode(
- &apdu[apdu_len], apdu_size, 1, &real_value);
+ &apdu[apdu_len], apdu_size - apdu_len, 1, &real_value);
if (len > 0) {
apdu_len += len;
if (data) {
data->target_level = real_value;
data->use_target_level = true;
}
+ } else {
+ return BACNET_STATUS_ERROR;
}
- if ((apdu_size - apdu_len) > 0) {
- /* Tag 4: fade-time - OPTIONAL */
- len = bacnet_unsigned_context_decode(
- &apdu[apdu_len], apdu_size, 4, &unsigned_value);
- if (len > 0) {
- apdu_len += len;
- if (data) {
- data->fade_time = (uint32_t)unsigned_value;
- data->use_fade_time = true;
- }
- } else {
- return BACNET_STATUS_ERROR;
+ /* Tag 4: fade-time - OPTIONAL */
+ len = bacnet_unsigned_context_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, 4, &unsigned_value);
+ if (len > 0) {
+ apdu_len += len;
+ if (data) {
+ data->fade_time = (uint32_t)unsigned_value;
+ data->use_fade_time = true;
}
+ } else if (len == 0) {
+ /* fade-time is optional, so do nothing */
+ } else {
+ return BACNET_STATUS_ERROR;
}
- if ((apdu_size - apdu_len) > 0) {
- /* priority [5] Unsigned (1..16) OPTIONAL */
- len = bacnet_unsigned_context_decode(
- &apdu[apdu_len], apdu_size, 5, &unsigned_value);
- if (len > 0) {
- apdu_len += len;
- if (data) {
- data->priority = (uint8_t)unsigned_value;
- data->use_priority = true;
- }
+ /* priority [5] Unsigned (1..16) OPTIONAL */
+ len = bacnet_unsigned_context_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, 5, &unsigned_value);
+ if (len > 0) {
+ apdu_len += len;
+ if (data) {
+ data->priority = (uint8_t)unsigned_value;
+ data->use_priority = true;
}
+ } else if (len == 0) {
+ /* priority is optional, so do nothing */
+ } else {
+ return BACNET_STATUS_ERROR;
}
break;
case BACNET_LIGHTS_RAMP_TO:
- if ((apdu_size - apdu_len) == 0) {
- return BACNET_STATUS_REJECT;
- }
- /* target-level [1] REAL (0.0..100.0) OPTIONAL */
+ /* target-level [1] REAL (0.0..100.0) */
len = bacnet_real_context_decode(
- &apdu[apdu_len], apdu_size, 1, &real_value);
+ &apdu[apdu_len], apdu_size - apdu_len, 1, &real_value);
if (len > 0) {
apdu_len += len;
if (data) {
data->target_level = real_value;
data->use_target_level = true;
}
+ } else {
+ return BACNET_STATUS_ERROR;
}
- if ((apdu_size - apdu_len) > 0) {
- /* ramp-rate [2] REAL (0.1..100.0) OPTIONAL */
- len = bacnet_real_context_decode(
- &apdu[apdu_len], apdu_size, 2, &real_value);
- if (len > 0) {
- apdu_len += len;
- if (data) {
- data->ramp_rate = real_value;
- data->use_ramp_rate = true;
- }
+ /* ramp-rate [2] REAL (0.1..100.0) OPTIONAL */
+ len = bacnet_real_context_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, 2, &real_value);
+ if (len > 0) {
+ apdu_len += len;
+ if (data) {
+ data->ramp_rate = real_value;
+ data->use_ramp_rate = true;
}
+ } else if (len == 0) {
+ /* ramp-rate is optional, so do nothing */
+ } else {
+ return BACNET_STATUS_ERROR;
}
- if ((apdu_size - apdu_len) > 0) {
- /* priority [5] Unsigned (1..16) OPTIONAL */
- len = bacnet_unsigned_context_decode(
- &apdu[apdu_len], apdu_size, 5, &unsigned_value);
- if (len > 0) {
- apdu_len += len;
- if (data) {
- data->priority = (uint8_t)unsigned_value;
- data->use_priority = true;
- }
+ /* priority [5] Unsigned (1..16) OPTIONAL */
+ len = bacnet_unsigned_context_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, 5, &unsigned_value);
+ if (len > 0) {
+ apdu_len += len;
+ if (data) {
+ data->priority = (uint8_t)unsigned_value;
+ data->use_priority = true;
}
+ } else if (len == 0) {
+ /* priority is optional, so do nothing */
+ } else {
+ return BACNET_STATUS_ERROR;
}
break;
case BACNET_LIGHTS_STEP_UP:
case BACNET_LIGHTS_STEP_DOWN:
case BACNET_LIGHTS_STEP_ON:
case BACNET_LIGHTS_STEP_OFF:
- if ((apdu_size - apdu_len) > 0) {
- /* step-increment [3] REAL (0.1..100.0) OPTIONAL */
- len = bacnet_real_context_decode(
- &apdu[apdu_len], apdu_size, 3, &real_value);
- if (len > 0) {
- apdu_len += len;
- if (data) {
- data->step_increment = real_value;
- data->use_step_increment = true;
- }
+ /* step-increment [3] REAL (0.1..100.0) OPTIONAL */
+ len = bacnet_real_context_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, 3, &real_value);
+ if (len > 0) {
+ apdu_len += len;
+ if (data) {
+ data->step_increment = real_value;
+ data->use_step_increment = true;
}
+ } else if (len == 0) {
+ /* step-increment is optional, so do nothing */
+ } else {
+ return BACNET_STATUS_ERROR;
}
- if ((apdu_size - apdu_len) > 0) {
- /* priority [5] Unsigned (1..16) OPTIONAL */
- len = bacnet_unsigned_context_decode(
- &apdu[apdu_len], apdu_size, 5, &unsigned_value);
- if (len > 0) {
- apdu_len += len;
- if (data) {
- data->priority = (uint8_t)unsigned_value;
- data->use_priority = true;
- }
+ /* priority [5] Unsigned (1..16) OPTIONAL */
+ len = bacnet_unsigned_context_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, 5, &unsigned_value);
+ if (len > 0) {
+ apdu_len += len;
+ if (data) {
+ data->priority = (uint8_t)unsigned_value;
+ data->use_priority = true;
}
+ } else if (len == 0) {
+ /* priority is optional, so do nothing */
+ } else {
+ return BACNET_STATUS_ERROR;
}
break;
case BACNET_LIGHTS_WARN:
case BACNET_LIGHTS_WARN_OFF:
case BACNET_LIGHTS_WARN_RELINQUISH:
case BACNET_LIGHTS_STOP:
- if ((apdu_size - apdu_len) > 0) {
- /* priority [5] Unsigned (1..16) OPTIONAL */
- len = bacnet_unsigned_context_decode(
- &apdu[apdu_len], apdu_size, 5, &unsigned_value);
- if (len > 0) {
- apdu_len += len;
- if (data) {
- data->priority = (uint8_t)unsigned_value;
- data->use_priority = true;
- }
+ /* priority [5] Unsigned (1..16) OPTIONAL */
+ len = bacnet_unsigned_context_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, 5, &unsigned_value);
+ if (len > 0) {
+ apdu_len += len;
+ if (data) {
+ data->priority = (uint8_t)unsigned_value;
+ data->use_priority = true;
}
+ } else if (len == 0) {
+ /* priority is optional, so do nothing */
+ } else {
+ return BACNET_STATUS_ERROR;
}
break;
default:
diff --git a/src/bacnet/npdu.c b/src/bacnet/npdu.c
index 45fc2e42e2..af27bc6af6 100644
--- a/src/bacnet/npdu.c
+++ b/src/bacnet/npdu.c
@@ -891,3 +891,108 @@ bool npdu_is_data_expecting_reply(
request_pdu, request_pdu_len, &request_address, reply_pdu,
reply_pdu_len, &reply_address);
}
+
+/**
+ * @brief Process the NPDU portion of an I-Am-Router-To-Network message, which
+ * contains a list of BACnet network numbers that the router is connected to.
+ * @param snet [in] The source network number of the I-Am-Router-To
+ * Network message, which is the network number of the router sending the
+ * message.
+ * @param src [in] The source address of the I-Am-Router-To-Network message,
+ * which is the address of the router sending the message.
+ * @param npdu [in] The buffer containing the NPDU portion of the
+ * I-Am-Router-To-Network message, which contains the list of BACnet network
+ * numbers that the router is connected to.
+ * @param npdu_size [in] The size of the npdu buffer in bytes.
+ * @param dnet_add [in] A callback function that will be called for each BACnet
+ * network number (DNET)
+ */
+void npdu_i_am_router_to_network_process(
+ uint16_t snet,
+ const BACNET_ADDRESS *src,
+ const uint8_t *npdu,
+ uint16_t npdu_size,
+ npdu_dnet_add_callback_t dnet_add)
+{
+ int len = 2;
+ uint16_t dnet = 0;
+ uint16_t npdu_offset = 0;
+ uint16_t npdu_len = npdu_size;
+
+ while (npdu_len >= len) {
+ len = decode_unsigned16(&npdu[npdu_offset], &dnet);
+ if (dnet_add) {
+ dnet_add(snet, dnet, src);
+ }
+ npdu_len -= len;
+ npdu_offset += len;
+ }
+}
+
+/**
+ * @brief Process the NPDU portion of an Initialize-Routing-Table message, which
+ * contains a list of BACnet network numbers (DNETs) and per-port information.
+ * @param snet [in] The source network number of the Initialize-Routing-Table
+ * message, which is the network number of the router sending the message.
+ * @param src [in] The source address of the I-Have-Router-To-Network message,
+ * which is the address of the router sending the message.
+ * @param npdu [in] The buffer containing the NPDU portion of the
+ * I-Have-Router-To-Network message, which contains the list of BACnet network
+ * numbers that the router is connected to, along with port information.
+ * @param npdu_size [in] The size of the npdu buffer in bytes.
+ * @param dnet_add [in] Optional callback invoked for each decoded DNET value.
+ * The callback receives the source network, decoded DNET, and source address.
+ */
+void npdu_init_routing_table_process(
+ uint16_t snet,
+ const BACNET_ADDRESS *src,
+ const uint8_t *npdu,
+ uint16_t npdu_size,
+ npdu_dnet_add_callback_t dnet_add)
+{
+ int len = 2;
+ uint16_t dnet = 0;
+ uint16_t npdu_offset = 0;
+ uint8_t port_id = 0;
+ uint8_t port_info_len = 0;
+ uint8_t net_count;
+ uint16_t npdu_len = npdu_size;
+
+ if (npdu_len <= 1) {
+ /* malformed message */
+ return;
+ }
+ net_count = npdu[npdu_offset];
+ npdu_offset += 1;
+ npdu_len -= 1;
+ if (net_count == 0) {
+ /* no networks, nothing to do */
+ return;
+ }
+ /* DNET(2) + PortID(1) + PortInfoLen(1) = 4 bytes */
+ while ((npdu_len >= 4) && (net_count--)) {
+ /* DNET */
+ len = decode_unsigned16(&npdu[npdu_offset], &dnet);
+ npdu_offset += len;
+ npdu_len -= len;
+ /* update routing table */
+ if (dnet_add) {
+ dnet_add(snet, dnet, src);
+ }
+ /* skip port_id & port_info */
+ port_id = npdu[npdu_offset];
+ npdu_offset += 1;
+ npdu_len -= 1;
+ port_info_len = npdu[npdu_offset];
+ npdu_offset += 1;
+ npdu_len -= 1;
+ if (npdu_len >= port_info_len) {
+ npdu_offset += port_info_len;
+ npdu_len -= port_info_len;
+ } else {
+ /* malformed message */
+ break;
+ }
+ (void)port_id;
+ }
+}
diff --git a/src/bacnet/npdu.h b/src/bacnet/npdu.h
index e8d2327a4b..fe63a316e6 100644
--- a/src/bacnet/npdu.h
+++ b/src/bacnet/npdu.h
@@ -54,6 +54,9 @@ typedef struct router_port_t {
struct router_port_t *next; /**< Point to next in linked list */
} BACNET_ROUTER_PORT;
+typedef void (*npdu_dnet_add_callback_t)(
+ uint16_t snet, uint16_t net, const BACNET_ADDRESS *addr);
+
#define NETWORK_NUMBER_LEARNED 0
#define NETWORK_NUMBER_CONFIGURED 1
@@ -123,6 +126,7 @@ bool npdu_is_expected_reply(
const uint8_t *reply_pdu,
uint16_t reply_pdu_len,
BACNET_ADDRESS *reply_address);
+BACNET_STACK_EXPORT
bool npdu_is_data_expecting_reply(
const uint8_t *request_pdu,
uint16_t request_pdu_len,
@@ -130,6 +134,20 @@ bool npdu_is_data_expecting_reply(
const uint8_t *reply_pdu,
uint16_t reply_pdu_len,
uint8_t reply_mac);
+BACNET_STACK_EXPORT
+void npdu_i_am_router_to_network_process(
+ uint16_t snet,
+ const BACNET_ADDRESS *src,
+ const uint8_t *npdu,
+ uint16_t npdu_size,
+ npdu_dnet_add_callback_t dnet_add);
+BACNET_STACK_EXPORT
+void npdu_init_routing_table_process(
+ uint16_t snet,
+ const BACNET_ADDRESS *src,
+ const uint8_t *npdu,
+ uint16_t npdu_size,
+ npdu_dnet_add_callback_t dnet_add);
#ifdef __cplusplus
}
diff --git a/src/bacnet/proplist.c b/src/bacnet/proplist.c
index 6cd272e288..92ebe1d436 100644
--- a/src/bacnet/proplist.c
+++ b/src/bacnet/proplist.c
@@ -385,6 +385,7 @@ static const int32_t Properties_BACnetARRAY[] = {
PROP_ISSUER_CERTIFICATE_FILES,
PROP_NEGATIVE_ACCESS_RULES,
PROP_POSITIVE_ACCESS_RULES,
+ PROP_ACCESS_DOORS,
#if (INT_MAX > 0xFFFF)
PROP_SC_HUB_FUNCTION_ACCEPT_URIS,
#endif
diff --git a/src/bacnet/rpm.c b/src/bacnet/rpm.c
index 47048a32cf..bd1ccdc6a8 100644
--- a/src/bacnet/rpm.c
+++ b/src/bacnet/rpm.c
@@ -341,39 +341,43 @@ int rpm_decode_object_property(
BACNET_UNSIGNED_INTEGER unsigned_value = 0; /* for decoding */
/* check for valid pointer and minimum size */
- if (apdu && apdu_size) {
- /* propertyIdentifier [0] BACnetPropertyIdentifier */
- len = bacnet_enumerated_context_decode(
- &apdu[apdu_len], apdu_size - apdu_len, 0, &property);
- if (len <= 0) {
- if (rpmdata) {
- rpmdata->error_code = ERROR_CODE_REJECT_INVALID_TAG;
- }
- return BACNET_STATUS_REJECT;
+ if (!apdu || !apdu_size) {
+ if (rpmdata) {
+ rpmdata->error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER;
}
+ return BACNET_STATUS_REJECT;
+ }
+ /* propertyIdentifier [0] BACnetPropertyIdentifier */
+ len = bacnet_enumerated_context_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, 0, &property);
+ if (len <= 0) {
if (rpmdata) {
- rpmdata->object_property = (BACNET_PROPERTY_ID)property;
+ rpmdata->error_code = ERROR_CODE_REJECT_INVALID_TAG;
}
+ return BACNET_STATUS_REJECT;
+ }
+ if (rpmdata) {
+ rpmdata->object_property = (BACNET_PROPERTY_ID)property;
+ }
+ apdu_len += len;
+ len = bacnet_unsigned_context_decode(
+ &apdu[apdu_len], apdu_size - apdu_len, 1, &unsigned_value);
+ if (len > 0) {
+ /* propertyArrayIndex [1] Unsigned OPTIONAL */
apdu_len += len;
- len = bacnet_unsigned_context_decode(
- &apdu[apdu_len], apdu_size - apdu_len, 1, &unsigned_value);
- if (len > 0) {
- /* propertyArrayIndex [1] Unsigned OPTIONAL */
- apdu_len += len;
- if (rpmdata) {
- rpmdata->array_index = unsigned_value;
- }
- } else if (len == 0) {
- /* optional - assume ALL array elements */
- if (rpmdata) {
- rpmdata->array_index = BACNET_ARRAY_ALL;
- }
- } else {
- if (rpmdata) {
- rpmdata->error_code = ERROR_CODE_REJECT_INVALID_TAG;
- }
- return BACNET_STATUS_REJECT;
+ if (rpmdata) {
+ rpmdata->array_index = unsigned_value;
+ }
+ } else if (len == 0) {
+ /* optional - assume ALL array elements */
+ if (rpmdata) {
+ rpmdata->array_index = BACNET_ARRAY_ALL;
+ }
+ } else {
+ if (rpmdata) {
+ rpmdata->error_code = ERROR_CODE_REJECT_INVALID_TAG;
}
+ return BACNET_STATUS_REJECT;
}
return apdu_len;
diff --git a/src/bacnet/version.h b/src/bacnet/version.h
index 0f5a01e78e..734103d3d8 100644
--- a/src/bacnet/version.h
+++ b/src/bacnet/version.h
@@ -15,8 +15,8 @@
#define BACNET_VERSION(x, y, z) (((x) << 16) + ((y) << 8) + (z))
#endif
-#define BACNET_VERSION_TEXT "1.5.0"
-#define BACNET_VERSION_CODE BACNET_VERSION(1, 5, 0)
+#define BACNET_VERSION_TEXT "1.5.2-rc1"
+#define BACNET_VERSION_CODE BACNET_VERSION(1, 5, 2)
#define BACNET_VERSION_MAJOR ((BACNET_VERSION_CODE >> 16) & 0xFF)
#define BACNET_VERSION_MINOR ((BACNET_VERSION_CODE >> 8) & 0xFF)
#define BACNET_VERSION_MAINTENANCE (BACNET_VERSION_CODE & 0xFF)
diff --git a/src/bacnet/whoami.c b/src/bacnet/whoami.c
index 92bf35adb7..7174f5150d 100644
--- a/src/bacnet/whoami.c
+++ b/src/bacnet/whoami.c
@@ -134,6 +134,7 @@ int who_am_i_request_decode(
return BACNET_STATUS_ERROR;
}
apdu_len += len;
+ characterstring_init_ansi(model_name, "");
len = bacnet_character_string_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, model_name);
if (len > 0) {
@@ -141,6 +142,7 @@ int who_am_i_request_decode(
} else {
return BACNET_STATUS_ERROR;
}
+ characterstring_init_ansi(serial_number, "");
len = bacnet_character_string_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, serial_number);
if (len > 0) {
diff --git a/src/bacnet/youare.c b/src/bacnet/youare.c
index b81c9905d3..c46b2a7f6f 100644
--- a/src/bacnet/youare.c
+++ b/src/bacnet/youare.c
@@ -171,6 +171,7 @@ int you_are_request_decode(
return BACNET_STATUS_ERROR;
}
apdu_len += len;
+ characterstring_init_ansi(model_name, "");
len = bacnet_character_string_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, model_name);
if (len > 0) {
@@ -178,6 +179,7 @@ int you_are_request_decode(
} else {
return BACNET_STATUS_ERROR;
}
+ characterstring_init_ansi(serial_number, "");
len = bacnet_character_string_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, serial_number);
if (len > 0) {
@@ -203,6 +205,7 @@ int you_are_request_decode(
} else {
return BACNET_STATUS_ERROR;
}
+ octetstring_init(mac_address, NULL, 0);
len = bacnet_octet_string_application_decode(
&apdu[apdu_len], apdu_size - apdu_len, mac_address);
if (len > 0) {
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 4cae33a85a..30f0b5ccfa 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -193,6 +193,8 @@ list(APPEND testdirs
bacnet/basic/object/trendlog
# basic/program
bacnet/basic/program/ubasic
+ # basic/service
+ bacnet/basic/service/h_rpm_a
# basic/server
bacnet/basic/server/bacnet_device
# basic/sys
diff --git a/test/Makefile b/test/Makefile
index ea9c362b71..6e8bb6179a 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -64,6 +64,12 @@ retest:
[ -d $(BUILD_DIR) ] && cd $(BUILD_DIR) && ctest $(CTEST_OPTIONS)
[ -d $(BUILD_DIR) ] && $(MAKE) -C $(BUILD_DIR) lcov
+.PHONY: unit
+unit:
+ [ -d $(BUILD_DIR) ] || mkdir -p $(BUILD_DIR)
+ [ -d $(BUILD_DIR) ] && cd $(BUILD_DIR) && cmake .. && cd ..
+ [ -d $(BUILD_DIR) ] && cd $(BUILD_DIR) && ctest && cd ..
+
.PHONY: report
report:
[ -d $(BUILD_DIR) ] && cat $(BUILD_DIR)/Testing/Temporary/LastTest*.log
diff --git a/test/bacnet/access_rule/CMakeLists.txt b/test/bacnet/access_rule/CMakeLists.txt
index 08b3a23ed7..eaed888fd3 100644
--- a/test/bacnet/access_rule/CMakeLists.txt
+++ b/test/bacnet/access_rule/CMakeLists.txt
@@ -39,6 +39,8 @@ add_executable(${PROJECT_NAME}
# File(s) under test
${SRC_DIR}/bacnet/access_rule.c
# Support files and stubs (pathname alphabetical)
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/arf/src/main.c b/test/bacnet/arf/src/main.c
index d60d0bfba5..a6371cc8a0 100644
--- a/test/bacnet/arf/src/main.c
+++ b/test/bacnet/arf/src/main.c
@@ -6,6 +6,7 @@
*/
#include
#include
+#include
/**
* @addtogroup bacnet_tests
@@ -154,6 +155,19 @@ static void testAtomicReadFileAccess(const BACNET_ATOMIC_READ_FILE_DATA *data)
}
}
+static int
+encode_application_octet_string_with_raw_length(uint8_t *apdu, uint32_t length)
+{
+ int apdu_len = 0;
+
+ apdu_len =
+ encode_tag(apdu, BACNET_APPLICATION_TAG_OCTET_STRING, false, length);
+ memset(&apdu[apdu_len], 0xA5, length);
+ apdu_len += (int)length;
+
+ return apdu_len;
+}
+
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(arf_tests, testAtomicReadFile)
#else
@@ -179,6 +193,61 @@ static void testAtomicReadFile(void)
return;
}
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(arf_tests, testAtomicReadFileAckOversizedOctetString)
+#else
+static void testAtomicReadFileAckOversizedOctetString(void)
+#endif
+{
+ BACNET_ATOMIC_READ_FILE_DATA data;
+ uint8_t apdu[2048] = { 0 };
+ uint32_t oversized_length = MAX_OCTET_STRING_BYTES + 1;
+ int apdu_len = 0;
+ int len = 0;
+
+ zassert_true((oversized_length + 16U) < sizeof(apdu), NULL);
+
+ memset(&data, 0xA5, sizeof(data));
+ apdu_len = 0;
+ len = encode_application_boolean(&apdu[apdu_len], true);
+ apdu_len += len;
+ len = encode_opening_tag(&apdu[apdu_len], 0);
+ apdu_len += len;
+ len = encode_application_signed(&apdu[apdu_len], 1234);
+ apdu_len += len;
+ len = encode_application_octet_string_with_raw_length(
+ &apdu[apdu_len], oversized_length);
+ apdu_len += len;
+ len = encode_closing_tag(&apdu[apdu_len], 0);
+ apdu_len += len;
+
+ len = arf_ack_decode_service_request(apdu, apdu_len, &data);
+ zassert_equal(len, apdu_len, NULL);
+ zassert_true(data.endOfFile, NULL);
+ zassert_equal(data.access, FILE_STREAM_ACCESS, NULL);
+ zassert_equal(data.type.stream.fileStartPosition, 1234, NULL);
+ zassert_equal(octetstring_length(&data.fileData[0]), 0, NULL);
+
+ apdu_len = 0;
+ len = encode_application_boolean(&apdu[apdu_len], false);
+ apdu_len += len;
+ len = encode_opening_tag(&apdu[apdu_len], 0);
+ apdu_len += len;
+ len = encode_application_signed(&apdu[apdu_len], -17);
+ apdu_len += len;
+ len = encode_application_octet_string_with_raw_length(&apdu[apdu_len], 3);
+ apdu_len += len;
+ len = encode_closing_tag(&apdu[apdu_len], 0);
+ apdu_len += len;
+
+ len = arf_ack_decode_service_request(apdu, apdu_len, &data);
+ zassert_equal(len, apdu_len, NULL);
+ zassert_false(data.endOfFile, NULL);
+ zassert_equal(data.access, FILE_STREAM_ACCESS, NULL);
+ zassert_equal(data.type.stream.fileStartPosition, -17, NULL);
+ zassert_equal(octetstring_length(&data.fileData[0]), 3, NULL);
+}
+
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(arf_tests, testAtomicReadFileMalformed)
#else
@@ -226,6 +295,7 @@ void test_main(void)
ztest_test_suite(
arf_tests, ztest_unit_test(testAtomicReadFile),
ztest_unit_test(testAtomicReadFileAck),
+ ztest_unit_test(testAtomicReadFileAckOversizedOctetString),
ztest_unit_test(testAtomicReadFileMalformed));
ztest_run_test_suite(arf_tests);
diff --git a/test/bacnet/authentication_factor_format/CMakeLists.txt b/test/bacnet/authentication_factor_format/CMakeLists.txt
index dcac5885b7..9cec216fc8 100644
--- a/test/bacnet/authentication_factor_format/CMakeLists.txt
+++ b/test/bacnet/authentication_factor_format/CMakeLists.txt
@@ -34,6 +34,7 @@ include_directories(
add_executable(${PROJECT_NAME}
# File(s) under test
+ ${SRC_DIR}/bacnet/authentication_factor.c
${SRC_DIR}/bacnet/authentication_factor_format.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/bacaction.c
diff --git a/test/bacnet/awf/src/main.c b/test/bacnet/awf/src/main.c
index 39d0c0a24c..daa0837535 100644
--- a/test/bacnet/awf/src/main.c
+++ b/test/bacnet/awf/src/main.c
@@ -7,6 +7,7 @@
*/
#include
#include
+#include
/**
* @addtogroup bacnet_tests
@@ -143,6 +144,19 @@ testAtomicWriteFileAckAccess(const BACNET_ATOMIC_WRITE_FILE_DATA *data)
}
}
+static int
+encode_application_octet_string_with_raw_length(uint8_t *apdu, uint32_t length)
+{
+ int apdu_len = 0;
+
+ apdu_len =
+ encode_tag(apdu, BACNET_APPLICATION_TAG_OCTET_STRING, false, length);
+ memset(&apdu[apdu_len], 0xA5, length);
+ apdu_len += (int)length;
+
+ return apdu_len;
+}
+
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(awf_tests, testAtomicWriteFileAck)
#else
@@ -162,6 +176,63 @@ static void testAtomicWriteFileAck(void)
return;
}
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(awf_tests, testAtomicWriteFileOversizedOctetString)
+#else
+static void testAtomicWriteFileOversizedOctetString(void)
+#endif
+{
+ BACNET_ATOMIC_WRITE_FILE_DATA data;
+ uint8_t apdu[2048] = { 0 };
+ uint32_t oversized_length = MAX_OCTET_STRING_BYTES + 1;
+ int apdu_len = 0;
+ int len = 0;
+
+ zassert_true((oversized_length + 24U) < sizeof(apdu), NULL);
+
+ memset(&data, 0xA5, sizeof(data));
+ apdu_len = 0;
+ len = encode_application_object_id(&apdu[apdu_len], OBJECT_FILE, 77);
+ apdu_len += len;
+ len = encode_opening_tag(&apdu[apdu_len], 0);
+ apdu_len += len;
+ len = encode_application_signed(&apdu[apdu_len], 321);
+ apdu_len += len;
+ len = encode_application_octet_string_with_raw_length(
+ &apdu[apdu_len], oversized_length);
+ apdu_len += len;
+ len = encode_closing_tag(&apdu[apdu_len], 0);
+ apdu_len += len;
+
+ len = awf_decode_service_request(apdu, apdu_len, &data);
+ zassert_equal(len, apdu_len, NULL);
+ zassert_equal(data.object_type, OBJECT_FILE, NULL);
+ zassert_equal(data.object_instance, 77, NULL);
+ zassert_equal(data.access, FILE_STREAM_ACCESS, NULL);
+ zassert_equal(data.type.stream.fileStartPosition, 321, NULL);
+ zassert_equal(octetstring_length(&data.fileData[0]), 0, NULL);
+
+ apdu_len = 0;
+ len = encode_application_object_id(&apdu[apdu_len], OBJECT_FILE, 99);
+ apdu_len += len;
+ len = encode_opening_tag(&apdu[apdu_len], 0);
+ apdu_len += len;
+ len = encode_application_signed(&apdu[apdu_len], -42);
+ apdu_len += len;
+ len = encode_application_octet_string_with_raw_length(&apdu[apdu_len], 3);
+ apdu_len += len;
+ len = encode_closing_tag(&apdu[apdu_len], 0);
+ apdu_len += len;
+
+ len = awf_decode_service_request(apdu, apdu_len, &data);
+ zassert_equal(len, apdu_len, NULL);
+ zassert_equal(data.object_type, OBJECT_FILE, NULL);
+ zassert_equal(data.object_instance, 99, NULL);
+ zassert_equal(data.access, FILE_STREAM_ACCESS, NULL);
+ zassert_equal(data.type.stream.fileStartPosition, -42, NULL);
+ zassert_equal(octetstring_length(&data.fileData[0]), 3, NULL);
+}
+
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(awf_tests, testAtomicWriteFileMalformed)
#else
@@ -212,6 +283,7 @@ void test_main(void)
ztest_test_suite(
awf_tests, ztest_unit_test(testAtomicWriteFile),
ztest_unit_test(testAtomicWriteFileAck),
+ ztest_unit_test(testAtomicWriteFileOversizedOctetString),
ztest_unit_test(testAtomicWriteFileMalformed));
ztest_run_test_suite(awf_tests);
diff --git a/test/bacnet/bacapp/CMakeLists.txt b/test/bacnet/bacapp/CMakeLists.txt
index 06c6a32b75..f2294dfe8e 100644
--- a/test/bacnet/bacapp/CMakeLists.txt
+++ b/test/bacnet/bacapp/CMakeLists.txt
@@ -37,6 +37,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/bacapp.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacdest.c
diff --git a/test/bacnet/bacaudit/CMakeLists.txt b/test/bacnet/bacaudit/CMakeLists.txt
index 1de8dd1476..b06f6fad1d 100644
--- a/test/bacnet/bacaudit/CMakeLists.txt
+++ b/test/bacnet/bacaudit/CMakeLists.txt
@@ -37,6 +37,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/bacaudit.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/bacdcode/src/main.c b/test/bacnet/bacdcode/src/main.c
index ac71fdc2dd..48a94c1da9 100644
--- a/test/bacnet/bacdcode/src/main.c
+++ b/test/bacnet/bacdcode/src/main.c
@@ -1190,8 +1190,8 @@ static void testBACDCodeCharacterString(void)
{
uint8_t apdu[MAX_APDU] = { 0 };
uint8_t encoded_apdu[MAX_APDU] = { 0 };
- BACNET_CHARACTER_STRING value;
- BACNET_CHARACTER_STRING test_value;
+ BACNET_CHARACTER_STRING value = { 0 };
+ BACNET_CHARACTER_STRING test_value = { 0 };
char test_name[MAX_APDU] = { "" };
int i; /* for loop counter */
int apdu_len = 0, len = 0, null_len = 0, tag_len, test_len;
diff --git a/test/bacnet/bacdest/CMakeLists.txt b/test/bacnet/bacdest/CMakeLists.txt
index 49dd81e2fb..1c242bfa65 100644
--- a/test/bacnet/bacdest/CMakeLists.txt
+++ b/test/bacnet/bacdest/CMakeLists.txt
@@ -37,6 +37,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/bacdest.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/bacdevobjpropref/CMakeLists.txt b/test/bacnet/bacdevobjpropref/CMakeLists.txt
index 970e61fa5a..a211b51f44 100644
--- a/test/bacnet/bacdevobjpropref/CMakeLists.txt
+++ b/test/bacnet/bacdevobjpropref/CMakeLists.txt
@@ -40,6 +40,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/bacdevobjpropref.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/bacdevobjpropref/src/main.c b/test/bacnet/bacdevobjpropref/src/main.c
index 9a3ab60a1c..bf5bb719da 100644
--- a/test/bacnet/bacdevobjpropref/src/main.c
+++ b/test/bacnet/bacdevobjpropref/src/main.c
@@ -172,6 +172,14 @@ static void testDevIdRef(void)
test_len =
bacnet_device_object_reference_decode(NULL, sizeof(apdu), &test_data);
zassert_true(test_len <= 0, NULL);
+ /* verify that NULL value pointer does not crash when the optional
+ device-identifier field is absent (regression test for the fix
+ that adds a null check before writing to value->deviceIdentifier) */
+ data.deviceIdentifier.instance = 0;
+ data.deviceIdentifier.type = BACNET_NO_DEV_TYPE;
+ len = bacapp_encode_device_obj_ref(apdu, &data);
+ null_len = bacnet_device_object_reference_decode(apdu, len, NULL);
+ zassert_equal(null_len, len, "null_len=%d len=%d", null_len, len);
}
#if defined(CONFIG_ZTEST_NEW_API)
diff --git a/test/bacnet/baclog/CMakeLists.txt b/test/bacnet/baclog/CMakeLists.txt
index 9ed3188e96..2876d3236f 100644
--- a/test/bacnet/baclog/CMakeLists.txt
+++ b/test/bacnet/baclog/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/baclog.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/bactimevalue/CMakeLists.txt b/test/bacnet/bactimevalue/CMakeLists.txt
index a587053aba..caa90f9bea 100644
--- a/test/bacnet/bactimevalue/CMakeLists.txt
+++ b/test/bacnet/bactimevalue/CMakeLists.txt
@@ -37,6 +37,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/bactimevalue.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/binding/address/CMakeLists.txt b/test/bacnet/basic/binding/address/CMakeLists.txt
index b5ead17b6a..a0ab460861 100644
--- a/test/bacnet/basic/binding/address/CMakeLists.txt
+++ b/test/bacnet/basic/binding/address/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/binding/address.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/binding/address/src/main.c b/test/bacnet/basic/binding/address/src/main.c
index 6971f728e4..c89605a965 100644
--- a/test/bacnet/basic/binding/address/src/main.c
+++ b/test/bacnet/basic/binding/address/src/main.c
@@ -5,6 +5,7 @@
* @date 2004
* @copyright SPDX-License-Identifier: MIT
*/
+#include
#include
#include
#include
@@ -262,6 +263,163 @@ static void testAddress(void)
zassert_equal(count, (MAX_ADDRESS_CACHE - i - 1), NULL);
}
}
+/**
+ * @brief Test address_list_encode for correct return value and no buffer
+ * overrun.
+ *
+ * The function has two phases:
+ * 1. A length-calculation pass (apdu == NULL) to find the total bytes needed.
+ * 2. An encoding pass that must write exactly those bytes into the caller's
+ * buffer without exceeding it.
+ *
+ * Bugs guarded against:
+ * - Writing past the end of a buffer whose size was obtained from the
+ * NULL-apdu length-calculation call (original overrun).
+ * - Double-counting encoded bytes in the return value when apdu != NULL
+ * (second-loop len accumulation bug).
+ * - Advancing the apdu pointer by the accumulated total rather than by the
+ * per-entry size, which shifts subsequent entries to wrong offsets and
+ * can push the write pointer past the buffer end.
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(address_tests, test_address_list_encode)
+#else
+static void test_address_list_encode(void)
+#endif
+{
+ /* Large backing array; guard region sits immediately after apdu_len bytes
+ */
+ uint8_t apdu[1024];
+ BACNET_ADDRESS src = { 0 };
+ BACNET_ADDRESS addr = { 0 };
+ int len_null = 0;
+ int len_encoded = 0;
+ unsigned apdu_len = 0;
+ unsigned i = 0;
+ uint32_t device_id = 0;
+ unsigned max_apdu_size = 0;
+
+ /* Reset the cache. In BACNET_ADDRESS_CACHE_FILE builds address_init()
+ * re-reads a file left by an earlier test, so drain any loaded entries
+ * explicitly so the "empty cache" assertions below are reliable. */
+ address_init();
+ for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
+ if (address_get_by_index(i, &device_id, &max_apdu_size, &addr)) {
+ address_remove_device(device_id);
+ }
+ }
+ zassert_equal(address_count(), 0, "Cache must be empty at test start");
+
+ /* ------------------------------------------------------------------ */
+ /* empty cache */
+ /* ------------------------------------------------------------------ */
+ len_null = address_list_encode(NULL, 0);
+ zassert_equal(len_null, 0, "Empty cache: length-only call must return 0");
+
+ len_encoded = address_list_encode(apdu, sizeof(apdu));
+ zassert_equal(len_encoded, 0, "Empty cache: encode call must return 0");
+
+ /* ------------------------------------------------------------------ */
+ /* single MAC-layer entry (no routing, net == 0) */
+ /* ------------------------------------------------------------------ */
+ src.mac_len = 1;
+ src.mac[0] = 0x05;
+ src.net = 0;
+ src.len = 0;
+ address_add(1001, 480, &src);
+ zassert_equal(address_count(), 1, NULL);
+
+ /* length-only call must be positive */
+ len_null = address_list_encode(NULL, 0);
+ zassert_true(len_null > 0, "Single entry: length-only call must be > 0");
+
+ /* encode into a large buffer: return value must equal length-only result */
+ memset(apdu, 0, sizeof(apdu));
+ len_encoded = address_list_encode(apdu, sizeof(apdu));
+ zassert_equal(
+ len_encoded, len_null,
+ "Single entry: encoded length must match length-only call");
+
+ /* too-small buffer: must fail without writing */
+ apdu_len = (unsigned)len_null - 1;
+ memset(apdu, 0xAA, sizeof(apdu));
+ len_encoded = address_list_encode(apdu, apdu_len);
+ zassert_equal(
+ len_encoded, BACNET_STATUS_ABORT,
+ "Single entry: too-small buffer must return BACNET_STATUS_ABORT");
+ zassert_equal(
+ apdu[0], 0xAA, "Single entry: too-small buffer must not be written");
+
+ /* exactly-right-sized buffer: guard bytes after it must survive */
+ apdu_len = (unsigned)len_null;
+ memset(apdu, 0, sizeof(apdu));
+ memset(apdu + apdu_len, 0xBB, 8);
+ len_encoded = address_list_encode(apdu, apdu_len);
+ zassert_equal(
+ len_encoded, len_null,
+ "Single entry: exact-sized buffer must return correct length");
+ for (i = 0; i < 8; i++) {
+ zassert_equal(
+ apdu[apdu_len + i], 0xBB,
+ "Single entry: guard bytes must not be overwritten");
+ }
+
+ /* ------------------------------------------------------------------ */
+ /* two entries: this is the primary regression case for the overrun. */
+ /* With the second-loop bug, `apdu += len` advances the write pointer */
+ /* by the ever-growing accumulated total instead of by the per-entry */
+ /* size. When the buffer is exactly the right size the second entry */
+ /* is written past the end of the buffer. */
+ /* ------------------------------------------------------------------ */
+ src.mac_len = 6;
+ src.mac[0] = 0xC0;
+ src.mac[1] = 0xA8;
+ src.mac[2] = 0x00;
+ src.mac[3] = 0x18;
+ src.mac[4] = 0xBA;
+ src.mac[5] = 0xC0;
+ src.net = 26001;
+ src.len = 1;
+ src.adr[0] = 0x19;
+ address_add(2002, 480, &src);
+ zassert_equal(address_count(), 2, NULL);
+
+ /* length-only call with 2 entries */
+ len_null = address_list_encode(NULL, 0);
+ zassert_true(len_null > 0, "Two entries: length-only call must be > 0");
+
+ /* encode into a large buffer: return value must equal length-only result */
+ memset(apdu, 0, sizeof(apdu));
+ len_encoded = address_list_encode(apdu, sizeof(apdu));
+ zassert_equal(
+ len_encoded, len_null,
+ "Two entries: encoded length must match length-only call");
+
+ /* exactly-right-sized buffer: guard bytes must survive */
+ apdu_len = (unsigned)len_null;
+ memset(apdu, 0, sizeof(apdu));
+ memset(apdu + apdu_len, 0xBB, 8);
+ len_encoded = address_list_encode(apdu, apdu_len);
+ zassert_equal(
+ len_encoded, len_null,
+ "Two entries: exact-sized buffer must return correct length");
+ for (i = 0; i < 8; i++) {
+ zassert_equal(
+ apdu[apdu_len + i], 0xBB,
+ "Two entries: guard bytes must not be overwritten");
+ }
+
+ /* too-small buffer with 2 entries: must fail without writing */
+ apdu_len = (unsigned)len_null - 1;
+ memset(apdu, 0xAA, sizeof(apdu));
+ len_encoded = address_list_encode(apdu, apdu_len);
+ zassert_equal(
+ len_encoded, BACNET_STATUS_ABORT,
+ "Two entries: too-small buffer must return BACNET_STATUS_ABORT");
+ zassert_equal(
+ apdu[0], 0xAA, "Two entries: too-small buffer must not be written");
+}
+
/**
* @}
*/
@@ -274,13 +432,15 @@ void test_main(void)
#ifdef BACNET_ADDRESS_CACHE_FILE
ztest_test_suite(
address_tests, ztest_unit_test(testAddressFile),
- ztest_unit_test(testAddress), ztest_unit_test(test_rr_address));
+ ztest_unit_test(testAddress), ztest_unit_test(test_rr_address),
+ ztest_unit_test(test_address_list_encode));
ztest_run_test_suite(address_tests);
#else
ztest_test_suite(
address_tests, ztest_unit_test(testAddress),
- ztest_unit_test(test_rr_address));
+ ztest_unit_test(test_rr_address),
+ ztest_unit_test(test_address_list_encode));
ztest_run_test_suite(address_tests);
#endif
diff --git a/test/bacnet/basic/object/acc/CMakeLists.txt b/test/bacnet/basic/object/acc/CMakeLists.txt
index b525a3a564..7b0c15cb61 100644
--- a/test/bacnet/basic/object/acc/CMakeLists.txt
+++ b/test/bacnet/basic/object/acc/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/acc.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/access_credential/CMakeLists.txt b/test/bacnet/basic/object/access_credential/CMakeLists.txt
index 2d98d50b91..a291457e36 100644
--- a/test/bacnet/basic/object/access_credential/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_credential/CMakeLists.txt
@@ -37,6 +37,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/access_rule.c
${SRC_DIR}/bacnet/assigned_access_rights.c
${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/access_door/CMakeLists.txt b/test/bacnet/basic/object/access_door/CMakeLists.txt
index fcdc5895ec..bba0e6e628 100644
--- a/test/bacnet/basic/object/access_door/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_door/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/access_door.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/access_point/CMakeLists.txt b/test/bacnet/basic/object/access_point/CMakeLists.txt
index e3ede1f8cf..e4aa3d4bab 100644
--- a/test/bacnet/basic/object/access_point/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_point/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/access_point.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/access_rights/CMakeLists.txt b/test/bacnet/basic/object/access_rights/CMakeLists.txt
index 3f8f47d143..0b67ceecc0 100644
--- a/test/bacnet/basic/object/access_rights/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_rights/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/access_rights.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/access_user/CMakeLists.txt b/test/bacnet/basic/object/access_user/CMakeLists.txt
index d17bd469c3..cfb9672915 100644
--- a/test/bacnet/basic/object/access_user/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_user/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/access_user.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/access_zone/CMakeLists.txt b/test/bacnet/basic/object/access_zone/CMakeLists.txt
index e7b34f8a51..2045565468 100644
--- a/test/bacnet/basic/object/access_zone/CMakeLists.txt
+++ b/test/bacnet/basic/object/access_zone/CMakeLists.txt
@@ -36,6 +36,7 @@ add_executable(${PROJECT_NAME}
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/assigned_access_rights.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
diff --git a/test/bacnet/basic/object/ai/CMakeLists.txt b/test/bacnet/basic/object/ai/CMakeLists.txt
index e35d369fb0..c493dbb839 100644
--- a/test/bacnet/basic/object/ai/CMakeLists.txt
+++ b/test/bacnet/basic/object/ai/CMakeLists.txt
@@ -37,6 +37,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/ai.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacdcode.c
diff --git a/test/bacnet/basic/object/ao/CMakeLists.txt b/test/bacnet/basic/object/ao/CMakeLists.txt
index 2ca1ca805d..65cd5688d3 100644
--- a/test/bacnet/basic/object/ao/CMakeLists.txt
+++ b/test/bacnet/basic/object/ao/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/ao.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/auditlog/CMakeLists.txt b/test/bacnet/basic/object/auditlog/CMakeLists.txt
index ad4d416b65..5c322c0c72 100644
--- a/test/bacnet/basic/object/auditlog/CMakeLists.txt
+++ b/test/bacnet/basic/object/auditlog/CMakeLists.txt
@@ -37,6 +37,8 @@ add_executable(${PROJECT_NAME}
# File(s) under test
${SRC_DIR}/bacnet/basic/object/auditlog.c
# Support files and stubs (pathname alphabetical)
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacaudit.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/av/CMakeLists.txt b/test/bacnet/basic/object/av/CMakeLists.txt
index 7381025685..b40d592c80 100644
--- a/test/bacnet/basic/object/av/CMakeLists.txt
+++ b/test/bacnet/basic/object/av/CMakeLists.txt
@@ -37,6 +37,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/av.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/bacfile/CMakeLists.txt b/test/bacnet/basic/object/bacfile/CMakeLists.txt
index a0cb46fa8b..5756676333 100644
--- a/test/bacnet/basic/object/bacfile/CMakeLists.txt
+++ b/test/bacnet/basic/object/bacfile/CMakeLists.txt
@@ -37,6 +37,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/bacfile.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/arf.c
${SRC_DIR}/bacnet/awf.c
${SRC_DIR}/bacnet/bacaction.c
diff --git a/test/bacnet/basic/object/bi/CMakeLists.txt b/test/bacnet/basic/object/bi/CMakeLists.txt
index 8c68de541d..f3df5c1fe1 100644
--- a/test/bacnet/basic/object/bi/CMakeLists.txt
+++ b/test/bacnet/basic/object/bi/CMakeLists.txt
@@ -38,6 +38,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/bi.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/bitstring_value/CMakeLists.txt b/test/bacnet/basic/object/bitstring_value/CMakeLists.txt
index b9ee0c6741..70d5ee5c35 100644
--- a/test/bacnet/basic/object/bitstring_value/CMakeLists.txt
+++ b/test/bacnet/basic/object/bitstring_value/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/bitstring_value.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/blo/CMakeLists.txt b/test/bacnet/basic/object/blo/CMakeLists.txt
index b61d5c60c6..9b94757a54 100644
--- a/test/bacnet/basic/object/blo/CMakeLists.txt
+++ b/test/bacnet/basic/object/blo/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/blo.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/bo/CMakeLists.txt b/test/bacnet/basic/object/bo/CMakeLists.txt
index aaef3f3d8a..e5766ace14 100644
--- a/test/bacnet/basic/object/bo/CMakeLists.txt
+++ b/test/bacnet/basic/object/bo/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/bo.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/bv/CMakeLists.txt b/test/bacnet/basic/object/bv/CMakeLists.txt
index d56237f292..92cce711f0 100644
--- a/test/bacnet/basic/object/bv/CMakeLists.txt
+++ b/test/bacnet/basic/object/bv/CMakeLists.txt
@@ -38,6 +38,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/bv.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/calendar/CMakeLists.txt b/test/bacnet/basic/object/calendar/CMakeLists.txt
index 61bafdc2df..815ef1ebad 100644
--- a/test/bacnet/basic/object/calendar/CMakeLists.txt
+++ b/test/bacnet/basic/object/calendar/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/calendar.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/channel/CMakeLists.txt b/test/bacnet/basic/object/channel/CMakeLists.txt
index aab763414e..85d96d3ed2 100644
--- a/test/bacnet/basic/object/channel/CMakeLists.txt
+++ b/test/bacnet/basic/object/channel/CMakeLists.txt
@@ -37,6 +37,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/channel.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/color_object/CMakeLists.txt b/test/bacnet/basic/object/color_object/CMakeLists.txt
index f193fbda4f..c6c002e3a5 100644
--- a/test/bacnet/basic/object/color_object/CMakeLists.txt
+++ b/test/bacnet/basic/object/color_object/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/color_object.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/color_temperature/CMakeLists.txt b/test/bacnet/basic/object/color_temperature/CMakeLists.txt
index 814ef64f34..3048f44095 100644
--- a/test/bacnet/basic/object/color_temperature/CMakeLists.txt
+++ b/test/bacnet/basic/object/color_temperature/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/color_temperature.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/command/CMakeLists.txt b/test/bacnet/basic/object/command/CMakeLists.txt
index 452f3b4f41..812d01217c 100644
--- a/test/bacnet/basic/object/command/CMakeLists.txt
+++ b/test/bacnet/basic/object/command/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/command.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/csv/CMakeLists.txt b/test/bacnet/basic/object/csv/CMakeLists.txt
index d846b85ec8..a8ffc65112 100644
--- a/test/bacnet/basic/object/csv/CMakeLists.txt
+++ b/test/bacnet/basic/object/csv/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/csv.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/iv/CMakeLists.txt b/test/bacnet/basic/object/iv/CMakeLists.txt
index 496dc26b71..721c748025 100644
--- a/test/bacnet/basic/object/iv/CMakeLists.txt
+++ b/test/bacnet/basic/object/iv/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/iv.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/lc/CMakeLists.txt b/test/bacnet/basic/object/lc/CMakeLists.txt
index 9ed34ebc3e..a3f71c3349 100644
--- a/test/bacnet/basic/object/lc/CMakeLists.txt
+++ b/test/bacnet/basic/object/lc/CMakeLists.txt
@@ -39,6 +39,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/lc.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/lo/CMakeLists.txt b/test/bacnet/basic/object/lo/CMakeLists.txt
index 7eb0d44915..f24832682a 100644
--- a/test/bacnet/basic/object/lo/CMakeLists.txt
+++ b/test/bacnet/basic/object/lo/CMakeLists.txt
@@ -37,6 +37,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/lo.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/lo/src/main.c b/test/bacnet/basic/object/lo/src/main.c
index e484c714cb..0640890d64 100644
--- a/test/bacnet/basic/object/lo/src/main.c
+++ b/test/bacnet/basic/object/lo/src/main.c
@@ -683,6 +683,32 @@ static void testLightingOutput(void)
/* context get/set */
Lighting_Output_Context_Set(
instance, Lighting_Output_Context_Get(instance));
+ /* min-actual-value get/set */
+ real_value = 5.0f;
+ status = Lighting_Output_Min_Actual_Value_Set(instance, real_value);
+ zassert_true(status, NULL);
+ test_real = Lighting_Output_Min_Actual_Value(instance);
+ zassert_true(
+ is_float_equal(test_real, real_value), "value=%f test=%f", real_value,
+ test_real);
+ real_value = 1.0f;
+ status = Lighting_Output_Min_Actual_Value_Set(instance, real_value);
+ zassert_true(status, NULL);
+ test_real = Lighting_Output_Min_Actual_Value(instance);
+ zassert_true(is_float_equal(test_real, real_value), NULL);
+ /* max-actual-value get/set */
+ real_value = 95.0f;
+ status = Lighting_Output_Max_Actual_Value_Set(instance, real_value);
+ zassert_true(status, NULL);
+ test_real = Lighting_Output_Max_Actual_Value(instance);
+ zassert_true(
+ is_float_equal(test_real, real_value), "value=%f test=%f", real_value,
+ test_real);
+ real_value = 100.0f;
+ status = Lighting_Output_Max_Actual_Value_Set(instance, real_value);
+ zassert_true(status, NULL);
+ test_real = Lighting_Output_Max_Actual_Value(instance);
+ zassert_true(is_float_equal(test_real, real_value), NULL);
/* out-of-bounds */
test_instance = Lighting_Output_Create(BACNET_MAX_INSTANCE + 1);
zassert_equal(test_instance, BACNET_MAX_INSTANCE, NULL);
@@ -695,6 +721,112 @@ static void testLightingOutput(void)
return;
}
+/**
+ * @brief Test boundary and relationship behavior for Min/Max Actual Value
+ *
+ * Verifies:
+ * - values outside 1.0..100.0 are rejected
+ * - exact boundary values 1.0 and 100.0 are accepted
+ * - setting Min above Max clamps Min down to the current Max value
+ * - setting Max below Min forces Min down to the new Max value
+ * - calls on a non-existent instance fail gracefully
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(lo_tests, testLightingOutputMinMaxActualValue)
+#else
+static void testLightingOutputMinMaxActualValue(void)
+#endif
+{
+ const uint32_t instance = 400;
+ bool status;
+ float test_real;
+
+ Lighting_Output_Init();
+ Lighting_Output_Create(instance);
+
+ /* --- out-of-range rejection for Min_Actual_Value --- */
+ status = Lighting_Output_Min_Actual_Value_Set(instance, 0.0f);
+ zassert_false(status, "Min=0.0 should be rejected (below 1.0)");
+ status = Lighting_Output_Min_Actual_Value_Set(instance, -1.0f);
+ zassert_false(status, "Min=-1.0 should be rejected");
+ status = Lighting_Output_Min_Actual_Value_Set(instance, 100.1f);
+ zassert_false(status, "Min=100.1 should be rejected (above 100.0)");
+ status = Lighting_Output_Min_Actual_Value_Set(instance, 200.0f);
+ zassert_false(status, "Min=200.0 should be rejected");
+
+ /* --- out-of-range rejection for Max_Actual_Value --- */
+ status = Lighting_Output_Max_Actual_Value_Set(instance, 0.0f);
+ zassert_false(status, "Max=0.0 should be rejected (below 1.0)");
+ status = Lighting_Output_Max_Actual_Value_Set(instance, -1.0f);
+ zassert_false(status, "Max=-1.0 should be rejected");
+ status = Lighting_Output_Max_Actual_Value_Set(instance, 100.1f);
+ zassert_false(status, "Max=100.1 should be rejected (above 100.0)");
+ status = Lighting_Output_Max_Actual_Value_Set(instance, 200.0f);
+ zassert_false(status, "Max=200.0 should be rejected");
+
+ /* --- exact boundary values must be accepted --- */
+ status = Lighting_Output_Min_Actual_Value_Set(instance, 1.0f);
+ zassert_true(status, "Min=1.0 (lower bound) should be accepted");
+ status = Lighting_Output_Max_Actual_Value_Set(instance, 100.0f);
+ zassert_true(status, "Max=100.0 (upper bound) should be accepted");
+
+ /* --- relationship invariant: Min > Max clamps Min down to Max ---
+ * Per the implementation: when the requested Min exceeds the current Max,
+ * Min is clamped to the current Max value (Max is unchanged). */
+ status = Lighting_Output_Max_Actual_Value_Set(instance, 50.0f);
+ zassert_true(status, NULL);
+ status = Lighting_Output_Min_Actual_Value_Set(instance, 1.0f);
+ zassert_true(status, NULL);
+ /* request Min=80 with Max=50: call succeeds, Min stored as 50 */
+ status = Lighting_Output_Min_Actual_Value_Set(instance, 80.0f);
+ zassert_true(status, "Min=80 > Max=50 should succeed (clamped)");
+ test_real = Lighting_Output_Min_Actual_Value(instance);
+ zassert_true(
+ is_float_equal(test_real, 50.0f),
+ "Min should be clamped to Max(50.0), got %f", test_real);
+ test_real = Lighting_Output_Max_Actual_Value(instance);
+ zassert_true(
+ is_float_equal(test_real, 50.0f), "Max should remain 50.0, got %f",
+ test_real);
+
+ /* --- relationship invariant: Max < Min forces Min down to new Max ---
+ * Per the implementation: when the requested Max is below the current Min,
+ * Min is set to the new Max and then Max is set to the new Max. */
+ status = Lighting_Output_Max_Actual_Value_Set(instance, 80.0f);
+ zassert_true(status, NULL);
+ status = Lighting_Output_Min_Actual_Value_Set(instance, 60.0f);
+ zassert_true(status, NULL);
+ /* request Max=30 with Min=60: call succeeds, both become 30 */
+ status = Lighting_Output_Max_Actual_Value_Set(instance, 30.0f);
+ zassert_true(status, "Max=30 < Min=60 should succeed (forces Min down)");
+ test_real = Lighting_Output_Max_Actual_Value(instance);
+ zassert_true(
+ is_float_equal(test_real, 30.0f), "Max should be 30.0, got %f",
+ test_real);
+ test_real = Lighting_Output_Min_Actual_Value(instance);
+ zassert_true(
+ is_float_equal(test_real, 30.0f),
+ "Min should be forced to new Max(30.0), got %f", test_real);
+
+ /* --- non-existent instance fails gracefully --- */
+ status = Lighting_Output_Min_Actual_Value_Set(instance + 1, 50.0f);
+ zassert_false(status, "Min set on non-existent instance should fail");
+ status = Lighting_Output_Max_Actual_Value_Set(instance + 1, 50.0f);
+ zassert_false(status, "Max set on non-existent instance should fail");
+ test_real = Lighting_Output_Min_Actual_Value(instance + 1);
+ zassert_true(
+ is_float_equal(test_real, 0.0f),
+ "Min get on non-existent instance should return 0.0");
+ test_real = Lighting_Output_Max_Actual_Value(instance + 1);
+ zassert_true(
+ is_float_equal(test_real, 0.0f),
+ "Max get on non-existent instance should return 0.0");
+
+ status = Lighting_Output_Delete(instance);
+ zassert_true(status, NULL);
+ Lighting_Output_Cleanup();
+}
+
/**
* @}
*/
@@ -708,7 +840,8 @@ void test_main(void)
lo_tests, ztest_unit_test(testLightingOutput),
ztest_unit_test(testLightingOutputWritablePropertyList),
ztest_unit_test(testLightingOutputBlinkStop),
- ztest_unit_test(testLightingOutputWarnRelinquishEgress));
+ ztest_unit_test(testLightingOutputWarnRelinquishEgress),
+ ztest_unit_test(testLightingOutputMinMaxActualValue));
ztest_run_test_suite(lo_tests);
}
diff --git a/test/bacnet/basic/object/loop/CMakeLists.txt b/test/bacnet/basic/object/loop/CMakeLists.txt
index 1f1a5ebb90..0559c7dbf1 100644
--- a/test/bacnet/basic/object/loop/CMakeLists.txt
+++ b/test/bacnet/basic/object/loop/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/loop.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/lsp/CMakeLists.txt b/test/bacnet/basic/object/lsp/CMakeLists.txt
index a73ed32cc4..4aff54475e 100644
--- a/test/bacnet/basic/object/lsp/CMakeLists.txt
+++ b/test/bacnet/basic/object/lsp/CMakeLists.txt
@@ -34,6 +34,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/lsp.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/lsz/CMakeLists.txt b/test/bacnet/basic/object/lsz/CMakeLists.txt
index c5f6be9ce1..6a4e530ef7 100644
--- a/test/bacnet/basic/object/lsz/CMakeLists.txt
+++ b/test/bacnet/basic/object/lsz/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/lsz.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/lsz/src/main.c b/test/bacnet/basic/object/lsz/src/main.c
index ba78ee0971..812c70f57e 100644
--- a/test/bacnet/basic/object/lsz/src/main.c
+++ b/test/bacnet/basic/object/lsz/src/main.c
@@ -5,11 +5,84 @@
* @date 2024
* @copyright SPDX-License-Identifier: MIT
*/
+#include
#include
#include
+#include
#include
#include
+static int lsz_zone_members_count(
+ uint32_t object_instance,
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *first,
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *second)
+{
+ BACNET_READ_PROPERTY_DATA rpdata = { 0 };
+ uint8_t apdu[MAX_APDU] = { 0 };
+ int apdu_len = 0;
+ int len = 0;
+ int count = 0;
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE member = { 0 };
+
+ rpdata.object_type = OBJECT_LIFE_SAFETY_ZONE;
+ rpdata.object_instance = object_instance;
+ rpdata.object_property = PROP_ZONE_MEMBERS;
+ rpdata.array_index = BACNET_ARRAY_ALL;
+ rpdata.application_data = &apdu[0];
+ rpdata.application_data_len = sizeof(apdu);
+
+ apdu_len = Life_Safety_Zone_Read_Property(&rpdata);
+ zassert_true(apdu_len >= 0, NULL);
+ while (len < apdu_len) {
+ int decoded_len = bacnet_device_object_property_reference_decode(
+ &apdu[len], apdu_len - len, &member);
+ zassert_true(decoded_len > 0, NULL);
+ len += decoded_len;
+ if ((count == 0) && first) {
+ *first = member;
+ } else if ((count == 1) && second) {
+ *second = member;
+ }
+ count++;
+ }
+ zassert_equal(len, apdu_len, NULL);
+
+ return count;
+}
+
+static bool lsz_zone_members_write(
+ uint32_t object_instance,
+ const uint8_t *payload,
+ int payload_len,
+ BACNET_ERROR_CLASS *error_class,
+ BACNET_ERROR_CODE *error_code)
+{
+ BACNET_WRITE_PROPERTY_DATA wp_data = { 0 };
+ bool status = false;
+
+ wp_data.object_type = OBJECT_LIFE_SAFETY_ZONE;
+ wp_data.object_instance = object_instance;
+ wp_data.object_property = PROP_ZONE_MEMBERS;
+ wp_data.array_index = BACNET_ARRAY_ALL;
+ wp_data.priority = BACNET_NO_PRIORITY;
+ wp_data.application_data_len = payload_len;
+ if (payload && (payload_len > 0) &&
+ (payload_len <= sizeof(wp_data.application_data))) {
+ memcpy(wp_data.application_data, payload, payload_len);
+ } else {
+ return false;
+ }
+ status = Life_Safety_Zone_Write_Property(&wp_data);
+ if (error_class) {
+ *error_class = wp_data.error_class;
+ }
+ if (error_code) {
+ *error_code = wp_data.error_code;
+ }
+
+ return status;
+}
+
/**
* @addtogroup bacnet_tests
* @{
@@ -53,6 +126,135 @@ static void testLifeSafetyZone(void)
/* cleanup */
status = Life_Safety_Zone_Delete(object_instance);
}
+
+/**
+ * @brief Regression tests for zone-members WriteProperty decode handling
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(testsLifeSafetyZone, testLifeSafetyZoneZoneMembersWriteProperty)
+#else
+static void testLifeSafetyZoneZoneMembersWriteProperty(void)
+#endif
+{
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE member = { 0 };
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE member2 = { 0 };
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE test_member = { 0 };
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE test_member2 = { 0 };
+ BACNET_ERROR_CLASS error_class = ERROR_CLASS_DEVICE;
+ BACNET_ERROR_CODE error_code = ERROR_CODE_OTHER;
+ uint8_t apdu[MAX_APDU] = { 0 };
+ int len = 0;
+ int len2 = 0;
+ uint32_t object_instance;
+ bool status;
+
+ Life_Safety_Zone_Init();
+ object_instance = Life_Safety_Zone_Create(BACNET_MAX_INSTANCE);
+ zassert_not_equal(object_instance, BACNET_MAX_INSTANCE, NULL);
+
+ member.objectIdentifier.type = OBJECT_ANALOG_INPUT;
+ member.objectIdentifier.instance = 1;
+ member.propertyIdentifier = PROP_PRESENT_VALUE;
+ member.arrayIndex = BACNET_ARRAY_ALL;
+ member.deviceIdentifier.type = BACNET_NO_DEV_TYPE;
+ member.deviceIdentifier.instance = BACNET_NO_DEV_ID;
+
+ member2.objectIdentifier.type = OBJECT_BINARY_INPUT;
+ member2.objectIdentifier.instance = 2;
+ member2.propertyIdentifier = PROP_PRESENT_VALUE;
+ member2.arrayIndex = BACNET_ARRAY_ALL;
+ member2.deviceIdentifier.type = BACNET_NO_DEV_TYPE;
+ member2.deviceIdentifier.instance = BACNET_NO_DEV_ID;
+
+ len = bacapp_encode_device_obj_property_ref(&apdu[0], &member);
+ zassert_true(len > 0, NULL);
+ status = lsz_zone_members_write(
+ object_instance, &apdu[0], len, &error_class, &error_code);
+ zassert_true(status, NULL);
+ zassert_equal(
+ lsz_zone_members_count(object_instance, &test_member, NULL), 1, NULL);
+ zassert_true(
+ bacnet_device_object_property_reference_same(&test_member, &member),
+ NULL);
+
+ /* zero-length decode (first tag mismatch): opening[3], null, closing[3] */
+ apdu[0] = 0x3E;
+ apdu[1] = 0x00;
+ apdu[2] = 0x3F;
+ status = lsz_zone_members_write(
+ object_instance, &apdu[0], 3, &error_class, &error_code);
+ zassert_false(status, NULL);
+ zassert_equal(error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(error_code, ERROR_CODE_INVALID_DATA_TYPE, NULL);
+ zassert_equal(
+ lsz_zone_members_count(object_instance, &test_member, NULL), 1, NULL);
+ zassert_true(
+ bacnet_device_object_property_reference_same(&test_member, &member),
+ NULL);
+
+ /* zero-length decode with non-empty payload and wrong first element tag */
+ apdu[0] = 0x00;
+ status = lsz_zone_members_write(
+ object_instance, &apdu[0], 1, &error_class, &error_code);
+ zassert_false(status, NULL);
+ zassert_equal(error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(error_code, ERROR_CODE_INVALID_DATA_TYPE, NULL);
+ zassert_equal(
+ lsz_zone_members_count(object_instance, &test_member, NULL), 1, NULL);
+ zassert_true(
+ bacnet_device_object_property_reference_same(&test_member, &member),
+ NULL);
+
+ /* negative decode: payload starts like ref, but truncated */
+ len = bacapp_encode_device_obj_property_ref(&apdu[0], &member);
+ zassert_true(len > 1, NULL);
+ status = lsz_zone_members_write(
+ object_instance, &apdu[0], 1, &error_class, &error_code);
+ zassert_false(status, NULL);
+ zassert_equal(error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(error_code, ERROR_CODE_INVALID_DATA_TYPE, NULL);
+ zassert_equal(
+ lsz_zone_members_count(object_instance, &test_member, NULL), 1, NULL);
+ zassert_true(
+ bacnet_device_object_property_reference_same(&test_member, &member),
+ NULL);
+
+ /* malformed tail after valid first element must not partially commit */
+ len = bacapp_encode_device_obj_property_ref(&apdu[0], &member2);
+ zassert_true(len > 0, NULL);
+ apdu[len] = 0x00;
+ status = lsz_zone_members_write(
+ object_instance, &apdu[0], len + 1, &error_class, &error_code);
+ zassert_false(status, NULL);
+ zassert_equal(error_class, ERROR_CLASS_PROPERTY, NULL);
+ zassert_equal(error_code, ERROR_CODE_INVALID_DATA_TYPE, NULL);
+ zassert_equal(
+ lsz_zone_members_count(object_instance, &test_member, NULL), 1, NULL);
+ zassert_true(
+ bacnet_device_object_property_reference_same(&test_member, &member),
+ NULL);
+
+ /* positive decode + multi-element list + cursor advancement */
+ len = bacapp_encode_device_obj_property_ref(&apdu[0], &member);
+ zassert_true(len > 0, NULL);
+ len2 = bacapp_encode_device_obj_property_ref(&apdu[len], &member2);
+ zassert_true(len2 > 0, NULL);
+ status = lsz_zone_members_write(
+ object_instance, &apdu[0], len + len2, &error_class, &error_code);
+ zassert_true(status, NULL);
+ zassert_equal(
+ lsz_zone_members_count(object_instance, &test_member, &test_member2), 2,
+ NULL);
+ zassert_true(
+ bacnet_device_object_property_reference_same(&test_member, &member),
+ NULL);
+ zassert_true(
+ bacnet_device_object_property_reference_same(&test_member2, &member2),
+ NULL);
+
+ status = Life_Safety_Zone_Delete(object_instance);
+ zassert_true(status, NULL);
+}
/**
* @}
*/
@@ -62,7 +264,9 @@ ZTEST_SUITE(testsLifeSafetyZone, NULL, NULL, NULL, NULL, NULL);
#else
void test_main(void)
{
- ztest_test_suite(testsLifeSafetyZone, ztest_unit_test(testLifeSafetyZone));
+ ztest_test_suite(
+ testsLifeSafetyZone, ztest_unit_test(testLifeSafetyZone),
+ ztest_unit_test(testLifeSafetyZoneZoneMembersWriteProperty));
ztest_run_test_suite(testsLifeSafetyZone);
}
diff --git a/test/bacnet/basic/object/ms-input/CMakeLists.txt b/test/bacnet/basic/object/ms-input/CMakeLists.txt
index e7f1c9b13d..04ab1f702b 100644
--- a/test/bacnet/basic/object/ms-input/CMakeLists.txt
+++ b/test/bacnet/basic/object/ms-input/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/ms-input.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/mso/CMakeLists.txt b/test/bacnet/basic/object/mso/CMakeLists.txt
index 7f3c4f1123..1e220b2d4f 100644
--- a/test/bacnet/basic/object/mso/CMakeLists.txt
+++ b/test/bacnet/basic/object/mso/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/mso.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/msv/CMakeLists.txt b/test/bacnet/basic/object/msv/CMakeLists.txt
index 2376b145ad..533f7421f8 100644
--- a/test/bacnet/basic/object/msv/CMakeLists.txt
+++ b/test/bacnet/basic/object/msv/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/msv.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/nc/CMakeLists.txt b/test/bacnet/basic/object/nc/CMakeLists.txt
index b23614b203..0a8a6c9381 100644
--- a/test/bacnet/basic/object/nc/CMakeLists.txt
+++ b/test/bacnet/basic/object/nc/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/nc.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/nc/src/main.c b/test/bacnet/basic/object/nc/src/main.c
index e5c760853e..88df61f337 100644
--- a/test/bacnet/basic/object/nc/src/main.c
+++ b/test/bacnet/basic/object/nc/src/main.c
@@ -402,6 +402,258 @@ static void test_Notification_Class_Common_Reporting(void)
Notification_Class_common_reporting_function(&event_data);
}
+/**
+ * @brief Test security fix for excessive recipient list elements
+ *
+ * This test verifies the fix for the vulnerability where a remote BACnet
+ * client can send a crafted AddListElement request containing more than
+ * NC_MAX_RECIPIENTS recipient-list elements. The vulnerability caused a
+ * stack-based out-of-bounds write as the server decoded elements into a
+ * fixed-size stack array before enforcing the capacity limit.
+ *
+ * This test verifies that:
+ * 1. Multiple recipients can be added (up to NC_MAX_RECIPIENTS-1)
+ * 2. Attempting to add more than NC_MAX_RECIPIENTS elements is rejected
+ * 3. The error codes are set correctly for overflow conditions
+ * 4. The stack buffer is protected from overflow
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(
+ notification_class_tests, test_Notification_Class_Add_List_Element_Overflow)
+#else
+static void test_Notification_Class_Add_List_Element_Overflow(void)
+#endif
+{
+ const uint32_t instance = 1;
+ int err = 0;
+ BACNET_LIST_ELEMENT_DATA list_element = { 0 };
+ BACNET_DESTINATION destination = { 0 };
+ BACNET_DESTINATION recipient_list[NC_MAX_RECIPIENTS] = { 0 };
+ uint8_t apdu[MAX_APDU] = { 0 };
+ uint8_t apdu_large[MAX_APDU] = { 0 };
+ int len = 0;
+ int total_len = 0;
+ unsigned i = 0, count = 0, encoded_count = 0;
+ bool status = false;
+
+ Notification_Class_Init();
+ zassert_true(Notification_Class_Valid_Instance(instance), NULL);
+
+ /* Setup common destination parameters */
+ for (unsigned j = 0; j < MAX_BACNET_DAYS_OF_WEEK; j++) {
+ bitstring_set_bit(&destination.ValidDays, j, true);
+ }
+ datetime_set_time(&destination.FromTime, 0, 0, 0, 0);
+ datetime_set_time(&destination.ToTime, 23, 59, 59, 99);
+ destination.ConfirmedNotify = true;
+ bitstring_set_bit(&destination.Transitions, TRANSITION_TO_OFFNORMAL, true);
+ bitstring_set_bit(&destination.Transitions, TRANSITION_TO_FAULT, true);
+ bitstring_set_bit(&destination.Transitions, TRANSITION_TO_NORMAL, true);
+
+ /* Test 1: Successfully add one recipient */
+ total_len = 0;
+ bacnet_recipient_device_set(&destination.Recipient, OBJECT_DEVICE, 100);
+ destination.ProcessIdentifier = 0;
+ len = bacnet_destination_encode(&apdu[total_len], &destination);
+ zassert(len > 0, "encode failed", NULL);
+ total_len += len;
+
+ /* Add a single recipient */
+ list_element.object_type = OBJECT_NOTIFICATION_CLASS;
+ list_element.object_instance = instance;
+ list_element.object_property = PROP_RECIPIENT_LIST;
+ list_element.array_index = BACNET_ARRAY_ALL;
+ list_element.application_data = apdu;
+ list_element.application_data_len = total_len;
+ list_element.first_failed_element_number = 0;
+ err = Notification_Class_Add_List_Element(&list_element);
+ zassert_equal(err, BACNET_STATUS_OK, "Failed to add a single recipient");
+
+ /* Test 2: Attempt to add exactly NC_MAX_RECIPIENTS in a single request
+ This should be rejected because the security fix prevents decoding more
+ than NC_MAX_RECIPIENTS-1 elements to avoid buffer overflow */
+ total_len = 0;
+ for (i = 0; i < NC_MAX_RECIPIENTS && total_len < (int)sizeof(apdu_large);
+ i++) {
+ bacnet_recipient_device_set(
+ &destination.Recipient, OBJECT_DEVICE, 200 + i);
+ destination.ProcessIdentifier = 100 + i;
+ len = bacnet_destination_encode(&apdu_large[total_len], &destination);
+ if (len > 0) {
+ total_len += len;
+ } else {
+ break;
+ }
+ }
+ encoded_count = i;
+ /* Try to add all NC_MAX_RECIPIENTS in one request - should be rejected */
+ list_element.application_data = apdu_large;
+ list_element.application_data_len = total_len;
+ list_element.first_failed_element_number = 0;
+ list_element.error_class = 0;
+ list_element.error_code = 0;
+ err = Notification_Class_Add_List_Element(&list_element);
+ /* The security fix should reject this because it exceeds the buffer size */
+ if (encoded_count >= NC_MAX_RECIPIENTS) {
+ zassert_not_equal(
+ err, BACNET_STATUS_OK,
+ "Should reject request to add NC_MAX_RECIPIENTS elements");
+ zassert_equal(
+ list_element.error_class, ERROR_CLASS_RESOURCES,
+ "Error class should be RESOURCES for overflow");
+ zassert_equal(
+ list_element.error_code, ERROR_CODE_NO_SPACE_TO_ADD_LIST_ELEMENT,
+ "Error code should be NO_SPACE_TO_ADD_LIST_ELEMENT");
+ }
+
+ /* Test 3: Verify existing recipients are not corrupted */
+ status =
+ Notification_Class_Get_Recipient_List(instance, &recipient_list[0]);
+ zassert_true(status, NULL);
+ /* Verify we have at least one recipient (stack was not corrupted) */
+ count = 0;
+ for (i = 0; i < NC_MAX_RECIPIENTS; i++) {
+ if (!bacnet_recipient_device_wildcard(&recipient_list[i].Recipient)) {
+ count++;
+ }
+ }
+ zassert(count > 0, "Recipient list should not be corrupted", NULL);
+
+ return;
+}
+
+/**
+ * @brief Test Remove_List_Element function
+ *
+ * This test verifies the security and functionality of the Remove function:
+ * 1. Tests successful removal of existing recipients
+ * 2. Tests the decode buffer overflow protection
+ * 3. Tests error handling for invalid inputs
+ * 4. Tests that removed elements are properly cleared
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(
+ notification_class_tests,
+ test_Notification_Class_Remove_List_Element_Overflow)
+#else
+static void test_Notification_Class_Remove_List_Element_Overflow(void)
+#endif
+{
+ const uint32_t instance = 1;
+ int err = 0;
+ BACNET_LIST_ELEMENT_DATA list_element = { 0 };
+ BACNET_DESTINATION destination = { 0 };
+ uint8_t apdu_add[MAX_APDU] = { 0 };
+ uint8_t apdu_remove[MAX_APDU] = { 0 };
+ int len = 0;
+ int total_len = 0;
+ unsigned i = 0;
+
+ Notification_Class_Init();
+ zassert_true(Notification_Class_Valid_Instance(instance), NULL);
+
+ /* Setup common destination parameters */
+ for (unsigned j = 0; j < MAX_BACNET_DAYS_OF_WEEK; j++) {
+ bitstring_set_bit(&destination.ValidDays, j, true);
+ }
+ datetime_set_time(&destination.FromTime, 0, 0, 0, 0);
+ datetime_set_time(&destination.ToTime, 23, 59, 59, 99);
+ destination.ConfirmedNotify = true;
+ bitstring_set_bit(&destination.Transitions, TRANSITION_TO_OFFNORMAL, true);
+ bitstring_set_bit(&destination.Transitions, TRANSITION_TO_FAULT, true);
+ bitstring_set_bit(&destination.Transitions, TRANSITION_TO_NORMAL, true);
+
+ /* Test 1: Add two recipients for removal testing */
+ total_len = 0;
+ for (i = 0; i < 2 && total_len < (int)sizeof(apdu_add); i++) {
+ bacnet_recipient_device_set(
+ &destination.Recipient, OBJECT_DEVICE, 100 + i);
+ destination.ProcessIdentifier = i;
+ len = bacnet_destination_encode(&apdu_add[total_len], &destination);
+ zassert(len > 0, "encode failed for add", NULL);
+ total_len += len;
+ }
+
+ list_element.object_type = OBJECT_NOTIFICATION_CLASS;
+ list_element.object_instance = instance;
+ list_element.object_property = PROP_RECIPIENT_LIST;
+ list_element.array_index = BACNET_ARRAY_ALL;
+ list_element.application_data = apdu_add;
+ list_element.application_data_len = total_len;
+ list_element.first_failed_element_number = 0;
+ err = Notification_Class_Add_List_Element(&list_element);
+ zassert_equal(err, BACNET_STATUS_OK, "Failed to add recipients");
+
+ /* Add first recipient */
+ total_len = 0;
+ destination.ProcessIdentifier = 0;
+ len = bacnet_destination_encode(&apdu_add[0], &destination);
+ zassert(len > 0, "encode failed for first recipient", NULL);
+ list_element.object_type = OBJECT_NOTIFICATION_CLASS;
+ list_element.object_instance = instance;
+ list_element.object_property = PROP_RECIPIENT_LIST;
+ list_element.array_index = BACNET_ARRAY_ALL;
+ list_element.application_data = apdu_add;
+ list_element.application_data_len = len;
+ list_element.first_failed_element_number = 0;
+ err = Notification_Class_Add_List_Element(&list_element);
+ zassert_equal(err, BACNET_STATUS_OK, "Failed to add first recipient");
+
+ /* Test 2: Successfully remove the added recipient */
+ total_len = 0;
+ bacnet_recipient_device_set(&destination.Recipient, OBJECT_DEVICE, 100);
+ len = bacnet_destination_encode(&apdu_remove[total_len], &destination);
+ zassert(len > 0, "encode failed for remove", NULL);
+ total_len = len;
+ list_element.application_data = apdu_remove;
+ list_element.application_data_len = total_len;
+ list_element.first_failed_element_number = 0;
+ err = Notification_Class_Remove_List_Element(&list_element);
+ zassert_equal(err, BACNET_STATUS_OK, "Failed to remove existing recipient");
+
+ /* Test 3: Test overflow protection on decode buffer - attempt excessive
+ * elements */
+ total_len = 0;
+ unsigned extra_count = 3;
+ for (i = 0; i < (NC_MAX_RECIPIENTS + extra_count) &&
+ total_len < (int)sizeof(apdu_remove);
+ i++) {
+ bacnet_recipient_device_set(
+ &destination.Recipient, OBJECT_DEVICE, 200 + i);
+ destination.ProcessIdentifier = 200 + i;
+ len = bacnet_destination_encode(&apdu_remove[total_len], &destination);
+ if (len > 0) {
+ total_len += len;
+ } else {
+ break;
+ }
+ }
+ list_element.application_data = apdu_remove;
+ list_element.application_data_len = total_len;
+ list_element.first_failed_element_number = 0;
+ list_element.error_class = 0;
+ list_element.error_code = 0;
+ err = Notification_Class_Remove_List_Element(&list_element);
+ /* Should be rejected - these don't exist in the list or overflow */
+ zassert(
+ err != BACNET_STATUS_ABORT, "Should not abort (overflow protected)",
+ NULL);
+
+ /* Test 4: Verify function properly handles null input */
+ err = Notification_Class_Remove_List_Element(NULL);
+ zassert_equal(err, BACNET_STATUS_ABORT, "Should abort on null input");
+
+ /* Test 5: Verify invalid property fails */
+ list_element.object_property = PROP_PRIORITY;
+ list_element.application_data = apdu_remove;
+ list_element.application_data_len = 1;
+ err = Notification_Class_Remove_List_Element(&list_element);
+ zassert_equal(err, BACNET_STATUS_ERROR, "Should error on invalid property");
+ zassert_equal(list_element.error_class, ERROR_CLASS_PROPERTY, NULL);
+
+ return;
+}
+
/**
* @}
*/
@@ -417,7 +669,9 @@ void test_main(void)
ztest_unit_test(test_Notification_Class_Priority),
ztest_unit_test(test_Notification_Class_Ack_Required),
ztest_unit_test(test_Notification_Class_Recipient_List),
- ztest_unit_test(test_Notification_Class_Common_Reporting));
+ ztest_unit_test(test_Notification_Class_Common_Reporting),
+ ztest_unit_test(test_Notification_Class_Add_List_Element_Overflow),
+ ztest_unit_test(test_Notification_Class_Remove_List_Element_Overflow));
ztest_run_test_suite(notification_class_tests);
}
diff --git a/test/bacnet/basic/object/netport/CMakeLists.txt b/test/bacnet/basic/object/netport/CMakeLists.txt
index 966523cbda..46a8981588 100644
--- a/test/bacnet/basic/object/netport/CMakeLists.txt
+++ b/test/bacnet/basic/object/netport/CMakeLists.txt
@@ -52,6 +52,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/sc_netport.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/arf.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
diff --git a/test/bacnet/basic/object/osv/CMakeLists.txt b/test/bacnet/basic/object/osv/CMakeLists.txt
index 0bf8750242..4c52d410f2 100644
--- a/test/bacnet/basic/object/osv/CMakeLists.txt
+++ b/test/bacnet/basic/object/osv/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/osv.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/piv/CMakeLists.txt b/test/bacnet/basic/object/piv/CMakeLists.txt
index 8883b236b6..5621cf810f 100644
--- a/test/bacnet/basic/object/piv/CMakeLists.txt
+++ b/test/bacnet/basic/object/piv/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/piv.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/program/CMakeLists.txt b/test/bacnet/basic/object/program/CMakeLists.txt
index 4a369f0981..a90ff65196 100644
--- a/test/bacnet/basic/object/program/CMakeLists.txt
+++ b/test/bacnet/basic/object/program/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/program.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/schedule/CMakeLists.txt b/test/bacnet/basic/object/schedule/CMakeLists.txt
index 55220daa93..10d0c04efc 100644
--- a/test/bacnet/basic/object/schedule/CMakeLists.txt
+++ b/test/bacnet/basic/object/schedule/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/schedule.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/structured_view/CMakeLists.txt b/test/bacnet/basic/object/structured_view/CMakeLists.txt
index 4475ab5b67..f220d4b449 100644
--- a/test/bacnet/basic/object/structured_view/CMakeLists.txt
+++ b/test/bacnet/basic/object/structured_view/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/structured_view.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/time_value/CMakeLists.txt b/test/bacnet/basic/object/time_value/CMakeLists.txt
index 5d85128a0a..05871ca7d8 100644
--- a/test/bacnet/basic/object/time_value/CMakeLists.txt
+++ b/test/bacnet/basic/object/time_value/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/time_value.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/timer/CMakeLists.txt b/test/bacnet/basic/object/timer/CMakeLists.txt
index 7b1a88e9b3..8a2161759a 100644
--- a/test/bacnet/basic/object/timer/CMakeLists.txt
+++ b/test/bacnet/basic/object/timer/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/timer.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/object/timer/src/main.c b/test/bacnet/basic/object/timer/src/main.c
index 8fae6ff2c4..0bc365ebe1 100644
--- a/test/bacnet/basic/object/timer/src/main.c
+++ b/test/bacnet/basic/object/timer/src/main.c
@@ -26,6 +26,18 @@ static bool Write_Property_Internal(BACNET_WRITE_PROPERTY_DATA *wp_data)
return true;
}
+static unsigned Reentrant_Write_Count;
+static bool
+Reentrant_Write_Property_Internal(BACNET_WRITE_PROPERTY_DATA *wp_data)
+{
+ Reentrant_Write_Count++;
+
+ /* Exercise re-entrant self-write path through Timer_Write_Property(). */
+ (void)Timer_Write_Property(wp_data);
+
+ return true;
+}
+
static struct timer_write_property_notification Write_Property_Notification;
static BACNET_WRITE_PROPERTY_DATA Write_Property_Notification_Data;
static uint32_t Write_Property_Notification_Instance;
@@ -759,6 +771,60 @@ static void test_Timer_Operation(void)
/* cleanup all */
Timer_Cleanup();
}
+
+/**
+ * @brief Regression test for self-reference writeback recursion guard
+ */
+static void test_Timer_Self_Reference_Reentrant_Write(void)
+{
+ const uint32_t instance = 124;
+ bool status = false;
+ BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE member = { 0 };
+ BACNET_TIMER_STATE_CHANGE_VALUE *value = NULL;
+
+ Timer_Init();
+ Timer_Create(instance);
+ status = Timer_Valid_Instance(instance);
+ zassert_true(status, NULL);
+
+ Timer_Write_Property_Internal_Callback_Set(
+ Reentrant_Write_Property_Internal);
+
+ member.deviceIdentifier.type = OBJECT_DEVICE;
+ member.deviceIdentifier.instance = 0;
+ member.objectIdentifier.type = OBJECT_TIMER;
+ member.objectIdentifier.instance = instance;
+ member.propertyIdentifier = PROP_PRESENT_VALUE;
+ member.arrayIndex = BACNET_ARRAY_ALL;
+ status = Timer_Reference_List_Member_Element_Set(instance, 0, &member);
+ zassert_true(status, NULL);
+
+ value =
+ Timer_State_Change_Value(instance, TIMER_TRANSITION_IDLE_TO_RUNNING);
+ zassert_not_null(value, NULL);
+ value->tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value->type.Unsigned_Int = 10;
+ value =
+ Timer_State_Change_Value(instance, TIMER_TRANSITION_RUNNING_TO_RUNNING);
+ zassert_not_null(value, NULL);
+ value->tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
+ value->type.Unsigned_Int = 10;
+
+ Reentrant_Write_Count = 0;
+ status = Timer_Running_Set(instance, true);
+ zassert_true(status, NULL);
+ zassert_equal(Reentrant_Write_Count, 1, NULL);
+
+ /* A second outer write proves the recursion guard was cleared. */
+ status = Timer_Running_Set(instance, true);
+ zassert_true(status, NULL);
+ zassert_equal(Reentrant_Write_Count, 2, NULL);
+
+ status = Timer_Delete(instance);
+ zassert_true(status, NULL);
+ Timer_Cleanup();
+ Timer_Write_Property_Internal_Callback_Set(NULL);
+}
/**
* @}
*/
@@ -767,7 +833,8 @@ void test_main(void)
{
ztest_test_suite(
timer_tests, ztest_unit_test(test_Timer_Read_Write),
- ztest_unit_test(test_Timer_Operation));
+ ztest_unit_test(test_Timer_Operation),
+ ztest_unit_test(test_Timer_Self_Reference_Reentrant_Write));
ztest_run_test_suite(timer_tests);
}
diff --git a/test/bacnet/basic/object/trendlog/CMakeLists.txt b/test/bacnet/basic/object/trendlog/CMakeLists.txt
index b194fee6d7..1ac6fd8798 100644
--- a/test/bacnet/basic/object/trendlog/CMakeLists.txt
+++ b/test/bacnet/basic/object/trendlog/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/object/trendlog.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/basic/server/bacnet_device/CMakeLists.txt b/test/bacnet/basic/server/bacnet_device/CMakeLists.txt
index 097ad1c66d..9222515b3e 100644
--- a/test/bacnet/basic/server/bacnet_device/CMakeLists.txt
+++ b/test/bacnet/basic/server/bacnet_device/CMakeLists.txt
@@ -37,6 +37,8 @@ add_executable(${PROJECT_NAME}
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/abort.c
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/arf.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
diff --git a/test/bacnet/basic/server/bacnet_device/src/main.c b/test/bacnet/basic/server/bacnet_device/src/main.c
index 2c2796dcea..8e341cb7c9 100644
--- a/test/bacnet/basic/server/bacnet_device/src/main.c
+++ b/test/bacnet/basic/server/bacnet_device/src/main.c
@@ -464,6 +464,33 @@ static void testDevice(void)
zassert_true(property_list.Required.count > 0, NULL);
}
}
+
+/**
+ * @brief Test Device_Write_Property with empty application payload
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(device_tests, test_Device_Write_Property_Empty_Payload)
+#else
+static void test_Device_Write_Property_Empty_Payload(void)
+#endif
+{
+ BACNET_WRITE_PROPERTY_DATA wp_data = { 0 };
+ bool status;
+
+ Device_Init(NULL);
+ wp_data.object_type = OBJECT_DEVICE;
+ wp_data.object_instance = Device_Object_Instance_Number();
+ /* Choose a property that is NOT a list property */
+ wp_data.object_property = PROP_DESCRIPTION;
+ wp_data.application_data_len = 0;
+
+ status = Device_Write_Property(&wp_data);
+
+ zassert_false(status, NULL);
+ zassert_equal(wp_data.error_class, ERROR_CLASS_SERVICES, NULL);
+ zassert_equal(wp_data.error_code, ERROR_CODE_INVALID_TAG, NULL);
+}
+
/**
* @}
*/
@@ -475,7 +502,8 @@ void test_main(void)
{
ztest_test_suite(
device_tests, ztest_unit_test(testDevice),
- ztest_unit_test(test_Device_Data_Sharing));
+ ztest_unit_test(test_Device_Data_Sharing),
+ ztest_unit_test(test_Device_Write_Property_Empty_Payload));
ztest_run_test_suite(device_tests);
}
diff --git a/test/bacnet/basic/service/h_rpm_a/CMakeLists.txt b/test/bacnet/basic/service/h_rpm_a/CMakeLists.txt
new file mode 100644
index 0000000000..f2f86c4655
--- /dev/null
+++ b/test/bacnet/basic/service/h_rpm_a/CMakeLists.txt
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: MIT
+
+cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
+
+get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME)
+project(test_${basename}
+ VERSION 1.0.0
+ LANGUAGES C)
+
+string(REGEX REPLACE
+ "/test/bacnet/[a-zA-Z0-9_/-]*$"
+ "/src"
+ SRC_DIR
+ ${CMAKE_CURRENT_SOURCE_DIR})
+string(REGEX REPLACE
+ "/test/bacnet/[a-zA-Z0-9_/-]*$"
+ "/test"
+ TST_DIR
+ ${CMAKE_CURRENT_SOURCE_DIR})
+
+set(ZTST_DIR "${TST_DIR}/ztest/src")
+
+add_compile_definitions(
+ BACNET_BIG_ENDIAN=0
+ CONFIG_ZTEST=1
+ BACDL_NONE=1
+ BACAPP_ALL
+)
+
+include_directories(
+ ${SRC_DIR}
+ ${TST_DIR}/ztest/include
+)
+
+add_executable(${PROJECT_NAME}
+ # File(s) under test
+ ${SRC_DIR}/bacnet/basic/service/h_rpm_a.c
+ # Support files and stubs (pathname alphabetical)
+ ${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
+ ${SRC_DIR}/bacnet/bacaction.c
+ ${SRC_DIR}/bacnet/bacaddr.c
+ ${SRC_DIR}/bacnet/bacapp.c
+ ${SRC_DIR}/bacnet/bacdcode.c
+ ${SRC_DIR}/bacnet/bacdest.c
+ ${SRC_DIR}/bacnet/bacdevobjpropref.c
+ ${SRC_DIR}/bacnet/abort.c
+ ${SRC_DIR}/bacnet/bacerror.c
+ ${SRC_DIR}/bacnet/reject.c
+ ${SRC_DIR}/bacnet/bacint.c
+ ${SRC_DIR}/bacnet/baclog.c
+ ${SRC_DIR}/bacnet/bacreal.c
+ ${SRC_DIR}/bacnet/bacstr.c
+ ${SRC_DIR}/bacnet/bactext.c
+ ${SRC_DIR}/bacnet/basic/sys/bigend.c
+ ${SRC_DIR}/bacnet/basic/sys/debug.c
+ ${SRC_DIR}/bacnet/datetime.c
+ ${SRC_DIR}/bacnet/basic/sys/days.c
+ ${SRC_DIR}/bacnet/indtext.c
+ ${SRC_DIR}/bacnet/hostnport.c
+ ${SRC_DIR}/bacnet/lighting.c
+ ${SRC_DIR}/bacnet/shed_level.c
+ ${SRC_DIR}/bacnet/timer_value.c
+ ${SRC_DIR}/bacnet/timestamp.c
+ ${SRC_DIR}/bacnet/memcopy.c
+ ${SRC_DIR}/bacnet/weeklyschedule.c
+ ${SRC_DIR}/bacnet/bactimevalue.c
+ ${SRC_DIR}/bacnet/dailyschedule.c
+ ${SRC_DIR}/bacnet/calendar_entry.c
+ ${SRC_DIR}/bacnet/special_event.c
+ ${SRC_DIR}/bacnet/channel_value.c
+ ${SRC_DIR}/bacnet/secure_connect.c
+ ${SRC_DIR}/bacnet/rpm.c
+ # Test and test library files
+ ./src/main.c
+ ${ZTST_DIR}/ztest_mock.c
+ ${ZTST_DIR}/ztest.c
+)
diff --git a/test/bacnet/basic/service/h_rpm_a/src/main.c b/test/bacnet/basic/service/h_rpm_a/src/main.c
new file mode 100644
index 0000000000..660f08533c
--- /dev/null
+++ b/test/bacnet/basic/service/h_rpm_a/src/main.c
@@ -0,0 +1,74 @@
+/**
+ * @file
+ * @brief Unit tests for handler_read_property_multiple_ack decoder paths
+ * @copyright SPDX-License-Identifier: MIT
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(h_rpm_a_tests, testMalformedTag4PayloadReturnsError)
+#else
+static void testMalformedTag4PayloadReturnsError(void)
+#endif
+{
+ uint8_t service_request[480] = { 0 };
+ uint8_t value_buffer[16] = { 0 };
+ BACNET_RPM_DATA rpmdata = { 0 };
+ BACNET_READ_ACCESS_DATA *read_access_data = NULL;
+ int len = 0;
+ int value_len = 0;
+ int test_len = 0;
+
+ rpmdata.object_type = OBJECT_DEVICE;
+ rpmdata.object_instance = 123;
+ len += rpm_ack_encode_apdu_object_begin(&service_request[len], &rpmdata);
+ len += rpm_ack_encode_apdu_object_property(
+ &service_request[len], PROP_OBJECT_LIST, BACNET_ARRAY_ALL);
+
+ len += encode_opening_tag(&service_request[len], 4);
+
+ value_len = encode_application_object_id(
+ &value_buffer[0], OBJECT_DEVICE, rpmdata.object_instance);
+ zassert_true(value_len > 0, NULL);
+ memcpy(&service_request[len], &value_buffer[0], (size_t)value_len);
+ len += value_len;
+
+ /* Truncated second value in same tag-4 payload to force partial decode. */
+ value_len = encode_application_real(&value_buffer[0], 1.0f);
+ zassert_true(value_len >= 5, NULL);
+ memcpy(&service_request[len], &value_buffer[0], 2);
+ len += 2;
+
+ len += encode_closing_tag(&service_request[len], 4);
+ len += rpm_ack_encode_apdu_object_end(&service_request[len]);
+
+ read_access_data = calloc(1, sizeof(BACNET_READ_ACCESS_DATA));
+ zassert_not_null(read_access_data, NULL);
+
+ test_len =
+ rpm_ack_decode_service_request(service_request, len, read_access_data);
+ zassert_equal(
+ test_len, BACNET_STATUS_ERROR,
+ "rpm_ack_decode_service_request returned %d, expected %d", test_len,
+ BACNET_STATUS_ERROR);
+
+ while (read_access_data) {
+ read_access_data = rpm_data_free(read_access_data);
+ }
+}
+
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST_SUITE(h_rpm_a_tests, NULL, NULL, NULL, NULL, NULL);
+#else
+void test_main(void)
+{
+ ztest_test_suite(
+ h_rpm_a_tests, ztest_unit_test(testMalformedTag4PayloadReturnsError));
+ ztest_run_test_suite(h_rpm_a_tests);
+}
+#endif
diff --git a/test/bacnet/basic/sys/bramfs/src/main.c b/test/bacnet/basic/sys/bramfs/src/main.c
index 204ce0e657..a3c2e8e8d0 100644
--- a/test/bacnet/basic/sys/bramfs/src/main.c
+++ b/test/bacnet/basic/sys/bramfs/src/main.c
@@ -207,6 +207,221 @@ static void test_BRAMFS_records(void)
bacfile_ramfs_deinit();
}
+/**
+ * @brief Unit Test for invalid file start positions in stream access
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(bramfs_tests, test_BRAMFS_invalid_stream_positions)
+#else
+static void test_BRAMFS_invalid_stream_positions(void)
+#endif
+{
+ const char *pathname = "testfile_invalid.txt";
+ uint8_t file_data[] = { "ABCDEFGHIJ" };
+ uint8_t read_buf[32] = { 0 };
+ size_t len = 0;
+
+ bacfile_ramfs_init();
+
+ /* write initial data starting at position 0 */
+ len = bacfile_ramfs_write_stream_data(
+ pathname, 0, file_data, sizeof(file_data));
+ zassert_equal(
+ len, sizeof(file_data), "Initial write should succeed, len=%zu", len);
+
+ /* read with negative file start position must return 0 */
+ len = bacfile_ramfs_read_stream_data(
+ pathname, -1, read_buf, sizeof(read_buf));
+ zassert_equal(
+ len, 0, "Read with fileStartPosition=-1 must return 0, got %zu", len);
+
+ len = bacfile_ramfs_read_stream_data(
+ pathname, -100, read_buf, sizeof(read_buf));
+ zassert_equal(
+ len, 0, "Read with fileStartPosition=-100 must return 0, got %zu", len);
+
+ /* read with start position exceeding file size must return 0 */
+ len = bacfile_ramfs_read_stream_data(
+ pathname, (int32_t)sizeof(file_data) + 1, read_buf, sizeof(read_buf));
+ zassert_equal(
+ len, 0,
+ "Read with fileStartPosition beyond file size must return 0, got %zu",
+ len);
+
+ /* write with an invalid position (not -1, 0, or >0) must return 0 */
+ len = bacfile_ramfs_write_stream_data(
+ pathname, -2, file_data, sizeof(file_data));
+ zassert_equal(
+ len, 0, "Write with fileStartPosition=-2 must return 0, got %zu", len);
+
+ bacfile_ramfs_deinit();
+}
+
+/**
+ * @brief Unit Test for invalid file start records in record access
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(bramfs_tests, test_BRAMFS_invalid_record_positions)
+#else
+static void test_BRAMFS_invalid_record_positions(void)
+#endif
+{
+ const char *pathname = "testfile_rec_invalid.txt";
+ char record_1[] = { "First record." };
+ uint8_t read_buf[MAX_OCTET_STRING_BYTES] = { 0 };
+ size_t record_len = 0;
+ bool status = false;
+
+ bacfile_ramfs_init();
+
+ /* write first record so file is non-empty */
+ record_len = bacnet_strnlen(record_1, sizeof(record_1));
+ status = bacfile_ramfs_write_record_data(
+ pathname, 0, 0, (const uint8_t *)record_1, record_len);
+ zassert_true(status, "Initial write_record should succeed");
+
+ /* write_record with fileStartRecord < -1 must return false */
+ status = bacfile_ramfs_write_record_data(
+ pathname, -2, 0, (const uint8_t *)record_1, record_len);
+ zassert_false(
+ status, "write_record_data with fileStartRecord=-2 must return false");
+
+ /* read_record with negative fileStartRecord must return false */
+ status = bacfile_ramfs_read_record_data(
+ pathname, -1, 0, read_buf, sizeof(read_buf));
+ zassert_false(
+ status, "read_record_data with fileStartRecord=-1 must return false");
+
+ status = bacfile_ramfs_read_record_data(
+ pathname, -2, 0, read_buf, sizeof(read_buf));
+ zassert_false(
+ status, "read_record_data with fileStartRecord=-2 must return false");
+
+ bacfile_ramfs_deinit();
+}
+
+/**
+ * @brief Unit Test for consecutive record appends (regression test for buffer
+ * overrun)
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(bramfs_tests, test_BRAMFS_consecutive_appends)
+#else
+static void test_BRAMFS_consecutive_appends(void)
+#endif
+{
+ const char *pathname = "testfile_consecutive.txt";
+ bool status = false;
+ char record_1[] = { "First appended record." };
+ char record_2[] = { "Second appended record." };
+ char read_buf[MAX_OCTET_STRING_BYTES] = { 0 };
+ size_t record_len = 0;
+
+ bacfile_ramfs_init();
+
+ /* Append first record with fileStartRecord = -1 */
+ record_len = bacnet_strnlen(record_1, sizeof(record_1));
+ status = bacfile_ramfs_write_record_data(
+ pathname, -1, 0, (const uint8_t *)record_1, record_len);
+ zassert_true(status, "First append should succeed");
+
+ /* Append second record consecutively with fileStartRecord = -1 */
+ record_len = bacnet_strnlen(record_2, sizeof(record_2));
+ status = bacfile_ramfs_write_record_data(
+ pathname, -1, 0, (const uint8_t *)record_2, record_len);
+ zassert_true(
+ status,
+ "Second consecutive append should succeed (buffer overrun bug)");
+
+ /* Verify both records readable */
+ record_len = bacnet_strnlen(record_1, sizeof(record_1));
+ status = bacfile_ramfs_read_record_data(
+ pathname, 0, 0, (uint8_t *)read_buf, record_len);
+ zassert_true(status, "Read first record should succeed");
+ zassert_true(
+ memcmp(read_buf, record_1, record_len) == 0, "First record data match");
+
+ record_len = bacnet_strnlen(record_2, sizeof(record_2));
+ status = bacfile_ramfs_read_record_data(
+ pathname, 0, 1, (uint8_t *)read_buf, record_len);
+ zassert_true(status, "Read second record should succeed");
+ zassert_true(
+ memcmp(read_buf, record_2, record_len) == 0,
+ "Second record data match");
+
+ bacfile_ramfs_deinit();
+}
+
+/**
+ * @brief Unit Test for record replacement with shorter data (regression test
+ * for stale memmove length causing heap OOB read)
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(bramfs_tests, test_BRAMFS_record_replace_shorter)
+#else
+static void test_BRAMFS_record_replace_shorter(void)
+#endif
+{
+ const char *pathname = "testfile_replace.txt";
+ bool status = false;
+ char record_1[] = { "Original long record that is quite lengthy." };
+ char record_2[] = { "Keep." };
+ char record_3[] = { "Third record after replacement." };
+ char read_buf[MAX_OCTET_STRING_BYTES] = { 0 };
+ size_t record_len = 0;
+
+ bacfile_ramfs_init();
+
+ /* Write three records */
+ record_len = bacnet_strnlen(record_1, sizeof(record_1));
+ status = bacfile_ramfs_write_record_data(
+ pathname, 0, 0, (const uint8_t *)record_1, record_len);
+ zassert_true(status, "Write record 1 should succeed");
+
+ record_len = bacnet_strnlen(record_2, sizeof(record_2));
+ status = bacfile_ramfs_write_record_data(
+ pathname, -1, 0, (const uint8_t *)record_2, record_len);
+ zassert_true(status, "Write record 2 should succeed");
+
+ record_len = bacnet_strnlen(record_3, sizeof(record_3));
+ status = bacfile_ramfs_write_record_data(
+ pathname, -1, 0, (const uint8_t *)record_3, record_len);
+ zassert_true(status, "Write record 3 should succeed");
+
+ /* Replace first record with much shorter data; tests stale length in
+ * memmove */
+ char short_record[] = { "X" };
+ record_len = bacnet_strnlen(short_record, sizeof(short_record));
+ status = bacfile_ramfs_write_record_data(
+ pathname, 0, 0, (const uint8_t *)short_record, record_len);
+ zassert_true(status, "Replace record 1 with shorter data should succeed");
+
+ /* Verify all records still readable and correct */
+ record_len = bacnet_strnlen(short_record, sizeof(short_record));
+ status = bacfile_ramfs_read_record_data(
+ pathname, 0, 0, (uint8_t *)read_buf, record_len);
+ zassert_true(status, "Read replaced record should succeed");
+ zassert_true(
+ memcmp(read_buf, short_record, record_len) == 0,
+ "Replaced record data match");
+
+ record_len = bacnet_strnlen(record_2, sizeof(record_2));
+ status = bacfile_ramfs_read_record_data(
+ pathname, 0, 1, (uint8_t *)read_buf, record_len);
+ zassert_true(status, "Read record 2 should succeed after replace");
+ zassert_true(
+ memcmp(read_buf, record_2, record_len) == 0, "Record 2 data match");
+
+ record_len = bacnet_strnlen(record_3, sizeof(record_3));
+ status = bacfile_ramfs_read_record_data(
+ pathname, 0, 2, (uint8_t *)read_buf, record_len);
+ zassert_true(status, "Read record 3 should succeed after replace");
+ zassert_true(
+ memcmp(read_buf, record_3, record_len) == 0, "Record 3 data match");
+
+ bacfile_ramfs_deinit();
+}
+
/**
* @}
*/
@@ -218,7 +433,11 @@ void test_main(void)
{
ztest_test_suite(
bramfs_tests, ztest_unit_test(test_BRAMFS_stream),
- ztest_unit_test(test_BRAMFS_records));
+ ztest_unit_test(test_BRAMFS_records),
+ ztest_unit_test(test_BRAMFS_invalid_stream_positions),
+ ztest_unit_test(test_BRAMFS_invalid_record_positions),
+ ztest_unit_test(test_BRAMFS_consecutive_appends),
+ ztest_unit_test(test_BRAMFS_record_replace_shorter));
ztest_run_test_suite(bramfs_tests);
}
diff --git a/test/bacnet/basic/sys/bsramfs/src/main.c b/test/bacnet/basic/sys/bsramfs/src/main.c
index 556ffeb4c6..645f187062 100644
--- a/test/bacnet/basic/sys/bsramfs/src/main.c
+++ b/test/bacnet/basic/sys/bsramfs/src/main.c
@@ -125,6 +125,65 @@ static void test_BSRAMFS_records(void)
zassert_true(status, "Read record 3 should succeed");
}
+/**
+ * @brief Unit Test for invalid file start positions in BSRAMFS stream/record
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(bramfs_tests, test_BSRAMFS_invalid_positions)
+#else
+static void test_BSRAMFS_invalid_positions(void)
+#endif
+{
+ uint8_t raw_data[] = { "HELLO" };
+ /* records: two null-terminated strings */
+ char rec_data[] = "First record.\0Second record.";
+ struct bacnet_file_sramfs_data stream_file = { sizeof(raw_data),
+ (char *)raw_data,
+ "invalid_stream.txt", NULL };
+ struct bacnet_file_sramfs_data record_file = { sizeof(rec_data), rec_data,
+ "invalid_record.txt", NULL };
+ uint8_t read_buf[64] = { 0 };
+ size_t len = 0;
+ bool status = false;
+
+ bacfile_sramfs_init();
+ zassert_true(bacfile_sramfs_add(&stream_file), "Failed to add stream_file");
+ zassert_true(bacfile_sramfs_add(&record_file), "Failed to add record_file");
+
+ /* read_stream with negative start position must return 0 */
+ len = bacfile_sramfs_read_stream_data(
+ stream_file.pathname, -1, read_buf, sizeof(read_buf));
+ zassert_equal(
+ len, 0, "read_stream with fileStartPosition=-1 must return 0, got %zu",
+ len);
+
+ len = bacfile_sramfs_read_stream_data(
+ stream_file.pathname, -100, read_buf, sizeof(read_buf));
+ zassert_equal(
+ len, 0,
+ "read_stream with fileStartPosition=-100 must return 0, got %zu", len);
+
+ /* read_stream with start position exceeding file size must return 0 */
+ len = bacfile_sramfs_read_stream_data(
+ stream_file.pathname, (int32_t)stream_file.size + 1, read_buf,
+ sizeof(read_buf));
+ zassert_equal(
+ len, 0,
+ "read_stream with position beyond file size must return 0, got %zu",
+ len);
+
+ /* read_record with negative fileStartRecord must return false */
+ status = bacfile_sramfs_read_record_data(
+ record_file.pathname, -1, 0, read_buf, sizeof(read_buf));
+ zassert_false(
+ status, "read_record with fileStartRecord=-1 must return false");
+
+ status = bacfile_sramfs_read_record_data(
+ record_file.pathname, -2, 0, read_buf, sizeof(read_buf));
+ zassert_false(
+ status, "read_record with fileStartRecord=-2 must return false");
+}
+
/**
* @}
*/
@@ -136,7 +195,8 @@ void test_main(void)
{
ztest_test_suite(
bramfs_tests, ztest_unit_test(test_BSRAMFS_stream),
- ztest_unit_test(test_BSRAMFS_records));
+ ztest_unit_test(test_BSRAMFS_records),
+ ztest_unit_test(test_BSRAMFS_invalid_positions));
ztest_run_test_suite(bramfs_tests);
}
diff --git a/test/bacnet/basic/sys/lighting_command/src/main.c b/test/bacnet/basic/sys/lighting_command/src/main.c
index 600ee19300..791dcd329d 100644
--- a/test/bacnet/basic/sys/lighting_command/src/main.c
+++ b/test/bacnet/basic/sys/lighting_command/src/main.c
@@ -235,15 +235,23 @@ static void test_lighting_command_unit(void)
lighting_command_timer(&data, milliseconds);
zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL);
- /* normalized range clamp testing */
+ /* normalized range scaling to physical values */
data.Max_Actual_Value = 95.0f;
data.Min_Actual_Value = 5.0f;
- target_level = lighting_command_normalized_range_clamp(&data, 0.1f);
+ target_level = lighting_command_normalized_to_physical_value(
+ data.Min_Actual_Value, data.Max_Actual_Value, 0.1f);
zassert_true(is_float_equal(target_level, 0.0f), NULL);
- target_level = lighting_command_normalized_range_clamp(&data, 100.0f);
+ target_level = lighting_command_normalized_to_physical_value(
+ data.Min_Actual_Value, data.Max_Actual_Value, 100.1f);
zassert_true(is_float_equal(target_level, data.Max_Actual_Value), NULL);
- target_level = lighting_command_normalized_range_clamp(&data, 1.0f);
- zassert_true(is_float_equal(target_level, data.Min_Actual_Value), NULL);
+ target_level = lighting_command_normalized_to_physical_value(
+ data.Min_Actual_Value, data.Max_Actual_Value, 50.0f);
+ zassert_true(
+ islessequal(target_level, data.Max_Actual_Value), "physical-value=%.2f",
+ target_level);
+ zassert_true(
+ isgreaterequal(target_level, data.Min_Actual_Value),
+ "physical-value=%.2f", target_level);
data.Max_Actual_Value = 100.0f;
data.Min_Actual_Value = 1.0f;
@@ -277,6 +285,18 @@ static void test_lighting_command_unit(void)
zassert_true(
is_float_equal(data.Last_On_Value, 100.0f), "last-on-value=%f",
data.Last_On_Value);
+ /* off to off should not clamp to the minimum ON level */
+ data.Tracking_Value = 0.0f;
+ Tracking_Value = data.Tracking_Value;
+ target_level = 0.0f;
+ lighting_command_fade_to(&data, target_level, fade_time);
+ milliseconds = fade_time / 2;
+ lighting_command_timer(&data, milliseconds);
+ zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL);
+ zassert_true(is_float_equal(Tracking_Value, 0.0f), NULL);
+ zassert_true(is_float_equal(data.Tracking_Value, 0.0f), NULL);
+ zassert_true(data.Lighting_Operation == BACNET_LIGHTS_STOP, NULL);
+ zassert_true(is_float_equal(data.Last_On_Value, 100.0f), NULL);
/* low trim */
data.Low_Trim_Value = 10.0f;
target_level = 1.0f;
@@ -294,8 +314,8 @@ static void test_lighting_command_unit(void)
lighting_command_timer(&data, milliseconds);
zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL);
zassert_true(is_float_equal(Tracking_Value, target_level), NULL);
- data.Low_Trim_Value = data.Min_Actual_Value;
zassert_true(is_float_equal(data.Last_On_Value, 1.0f), NULL);
+ data.Low_Trim_Value = 1.0f;
/* high trim */
data.High_Trim_Value = 90.0f;
target_level = 100.0f;
@@ -305,8 +325,8 @@ static void test_lighting_command_unit(void)
lighting_command_timer(&data, milliseconds);
zassert_true(data.In_Progress == BACNET_LIGHTING_TRIM_ACTIVE, NULL);
zassert_true(is_float_equal(Tracking_Value, data.High_Trim_Value), NULL);
- data.High_Trim_Value = data.Max_Actual_Value;
zassert_true(is_float_equal(data.Last_On_Value, target_level), NULL);
+ data.High_Trim_Value = 100.0f;
/* trim fade time */
target_level = 80.0f;
milliseconds = 10;
@@ -396,21 +416,21 @@ static void test_lighting_command_unit(void)
zassert_true(is_float_equal(target_step, 100.0f), NULL);
/* physical range clamping */
- target_level = lighting_command_physical_range_clamp(0.0f);
+ target_level = lighting_command_normalized_range_clamp(0.0f);
zassert_true(is_float_equal(target_level, 0.0f), NULL);
- target_level = lighting_command_physical_range_clamp(0.5f);
+ target_level = lighting_command_normalized_range_clamp(0.5f);
zassert_true(is_float_equal(target_level, 0.0f), NULL);
- target_level = lighting_command_physical_range_clamp(0.9f);
+ target_level = lighting_command_normalized_range_clamp(0.9f);
zassert_true(is_float_equal(target_level, 0.0f), NULL);
- target_level = lighting_command_physical_range_clamp(1.0f);
+ target_level = lighting_command_normalized_range_clamp(1.0f);
zassert_true(is_float_equal(target_level, 1.0f), NULL);
- target_level = lighting_command_physical_range_clamp(50.0f);
+ target_level = lighting_command_normalized_range_clamp(50.0f);
zassert_true(is_float_equal(target_level, 50.0f), NULL);
- target_level = lighting_command_physical_range_clamp(100.0f);
+ target_level = lighting_command_normalized_range_clamp(100.0f);
zassert_true(is_float_equal(target_level, 100.0f), NULL);
- target_level = lighting_command_physical_range_clamp(100.1f);
+ target_level = lighting_command_normalized_range_clamp(100.1f);
zassert_true(is_float_equal(target_level, 100.0f), NULL);
- target_level = lighting_command_physical_range_clamp(150.0f);
+ target_level = lighting_command_normalized_range_clamp(150.0f);
zassert_true(is_float_equal(target_level, 100.0f), NULL);
/* step UP - inhibit ON */
@@ -471,7 +491,7 @@ static void test_lighting_command_unit(void)
is_float_equal(data.Last_On_Value, data.Max_Actual_Value), NULL);
/* step DOWN, not off */
target_step = 1.0f;
- target_level = data.Min_Actual_Value + target_step;
+ target_level = 1.0f + target_step;
milliseconds = 10;
lighting_command_fade_to(&data, target_level, 0);
lighting_command_timer(&data, milliseconds);
@@ -480,18 +500,17 @@ static void test_lighting_command_unit(void)
lighting_command_step(&data, BACNET_LIGHTS_STEP_DOWN, target_step);
lighting_command_timer(&data, milliseconds);
zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL);
- zassert_true(is_float_equal(Tracking_Value, data.Min_Actual_Value), NULL);
- zassert_true(
- is_float_equal(data.Last_On_Value, data.Min_Actual_Value), NULL);
- /* clamp to min */
+ zassert_true(is_float_equal(Tracking_Value, 1.0f), NULL);
+ zassert_true(is_float_equal(data.Last_On_Value, 1.0f), NULL);
+ /* clamp to min normal */
target_step = 100.0f;
lighting_command_step(&data, BACNET_LIGHTS_STEP_DOWN, target_step);
lighting_command_timer(&data, milliseconds);
zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL);
- zassert_true(is_float_equal(Tracking_Value, data.Min_Actual_Value), NULL);
+ zassert_true(is_float_equal(Tracking_Value, 1.0f), NULL);
/* step DOWN and off */
target_step = 100.0f;
- target_level = data.Min_Actual_Value;
+ target_level = 1.0f;
milliseconds = 10;
lighting_command_fade_to(&data, target_level, 0);
lighting_command_timer(&data, milliseconds);
@@ -501,8 +520,7 @@ static void test_lighting_command_unit(void)
lighting_command_timer(&data, milliseconds);
zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL);
zassert_true(is_float_equal(Tracking_Value, 0.0f), NULL);
- zassert_true(
- is_float_equal(data.Last_On_Value, data.Min_Actual_Value), NULL);
+ zassert_true(is_float_equal(data.Last_On_Value, 1.0f), NULL);
/* blink warn - immediate off */
data.Blink.Interval = 0;
data.Blink.Duration = 0;
@@ -562,8 +580,8 @@ static void test_lighting_command_unit(void)
data.In_Progress == BACNET_LIGHTING_RAMP_ACTIVE,
"In_Progress=%d", data.In_Progress);
zassert_true(
- isgreater(data.Tracking_Value, data.Min_Actual_Value),
- "Tracking_Value=%f", Tracking_Value);
+ isgreater(data.Tracking_Value, 1.0f), "Tracking_Value=%f",
+ Tracking_Value);
zassert_true(
isless(data.Tracking_Value, data.Max_Actual_Value),
"Tracking_Value=%f", Tracking_Value);
@@ -571,7 +589,7 @@ static void test_lighting_command_unit(void)
} while (data.Lighting_Operation != BACNET_LIGHTS_STOP);
/* slower ramp down */
- target_level = data.Min_Actual_Value;
+ target_level = 1.0f;
milliseconds = 33;
ramp_rate = 0.1f;
do {
@@ -582,15 +600,14 @@ static void test_lighting_command_unit(void)
data.In_Progress == BACNET_LIGHTING_RAMP_ACTIVE,
"In_Progress=%d", data.In_Progress);
zassert_true(
- isgreater(data.Tracking_Value, data.Min_Actual_Value),
- "Tracking_Value=%f", Tracking_Value);
+ isgreater(data.Tracking_Value, 1.0f), "Tracking_Value=%f",
+ Tracking_Value);
zassert_true(
isless(data.Tracking_Value, data.Max_Actual_Value),
"Tracking_Value=%f", Tracking_Value);
}
} while (data.Lighting_Operation != BACNET_LIGHTS_STOP);
- zassert_true(
- is_float_equal(data.Last_On_Value, data.Min_Actual_Value), NULL);
+ zassert_true(is_float_equal(data.Last_On_Value, 1.0f), NULL);
/* large elapsed timer - ramp up */
target_level = data.Max_Actual_Value;
milliseconds = 2000;
@@ -603,13 +620,27 @@ static void test_lighting_command_unit(void)
data.In_Progress == BACNET_LIGHTING_RAMP_ACTIVE,
"In_Progress=%d", data.In_Progress);
zassert_true(
- isgreater(data.Tracking_Value, data.Min_Actual_Value),
- "Tracking_Value=%f", Tracking_Value);
+ isgreater(data.Tracking_Value, 1.0f), "Tracking_Value=%f",
+ Tracking_Value);
zassert_true(
isless(data.Tracking_Value, data.Max_Actual_Value),
"Tracking_Value=%f", Tracking_Value);
}
} while (data.Lighting_Operation != BACNET_LIGHTS_STOP);
+ zassert_true(
+ is_float_equal(data.Last_On_Value, data.Max_Actual_Value), NULL);
+ /* off to off should not clamp to the minimum ON level */
+ data.Tracking_Value = 0.0f;
+ Tracking_Value = data.Tracking_Value;
+ target_level = 0.0f;
+ milliseconds = 100;
+ ramp_rate = 1.0f;
+ lighting_command_ramp_to(&data, target_level, ramp_rate);
+ lighting_command_timer(&data, milliseconds);
+ zassert_true(data.In_Progress == BACNET_LIGHTING_IDLE, NULL);
+ zassert_true(is_float_equal(Tracking_Value, 0.0f), NULL);
+ zassert_true(is_float_equal(data.Tracking_Value, 0.0f), NULL);
+ zassert_true(data.Lighting_Operation == BACNET_LIGHTS_STOP, NULL);
zassert_true(
is_float_equal(data.Last_On_Value, data.Max_Actual_Value), NULL);
@@ -683,7 +714,27 @@ static void test_lighting_command_unit(void)
lighting_command_timer(&data, milliseconds);
zassert_equal(data.Lighting_Operation, BACNET_LIGHTS_PROPRIETARY_MAX, NULL);
+ /* min-actual-value get/set */
+ lighting_command_min_actual_value_set(&data, 5.0f);
+ target_level = lighting_command_min_actual_value_get(&data);
+ zassert_true(is_float_equal(target_level, 5.0f), NULL);
+ lighting_command_min_actual_value_set(&data, 1.0f);
+ target_level = lighting_command_min_actual_value_get(&data);
+ zassert_true(is_float_equal(target_level, 1.0f), NULL);
+ /* max-actual-value get/set */
+ lighting_command_max_actual_value_set(&data, 95.0f);
+ target_level = lighting_command_max_actual_value_get(&data);
+ zassert_true(is_float_equal(target_level, 95.0f), NULL);
+ lighting_command_max_actual_value_set(&data, 100.0f);
+ target_level = lighting_command_max_actual_value_get(&data);
+ zassert_true(is_float_equal(target_level, 100.0f), NULL);
/* null check code coverage */
+ lighting_command_min_actual_value_set(NULL, 1.0f);
+ target_level = lighting_command_min_actual_value_get(NULL);
+ zassert_true(is_float_equal(target_level, 0.0f), NULL);
+ lighting_command_max_actual_value_set(NULL, 100.0f);
+ target_level = lighting_command_max_actual_value_get(NULL);
+ zassert_true(is_float_equal(target_level, 0.0f), NULL);
lighting_command_override_set(NULL, override_level);
lighting_command_override_clear(NULL, override_level);
lighting_command_override_momentary(NULL, override_level);
diff --git a/test/bacnet/cov/CMakeLists.txt b/test/bacnet/cov/CMakeLists.txt
index 9502f3c3fc..717d2c888c 100644
--- a/test/bacnet/cov/CMakeLists.txt
+++ b/test/bacnet/cov/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/cov.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/create_object/CMakeLists.txt b/test/bacnet/create_object/CMakeLists.txt
index 74db0a3661..53eae23481 100644
--- a/test/bacnet/create_object/CMakeLists.txt
+++ b/test/bacnet/create_object/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/create_object.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/datalink/bsc-datalink/CMakeLists.txt b/test/bacnet/datalink/bsc-datalink/CMakeLists.txt
index 20734357f0..b0e8a7dcd6 100644
--- a/test/bacnet/datalink/bsc-datalink/CMakeLists.txt
+++ b/test/bacnet/datalink/bsc-datalink/CMakeLists.txt
@@ -171,6 +171,8 @@ target_sources(${PROJECT_NAME} PRIVATE
${SRC_DIR}/bacnet/basic/sys/keylist.c
${SRC_DIR}/bacnet/basic/sys/mstimer.c
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/arf.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/datalink/bsc-node/CMakeLists.txt b/test/bacnet/datalink/bsc-node/CMakeLists.txt
index 7828dcc910..5d2d40de5f 100644
--- a/test/bacnet/datalink/bsc-node/CMakeLists.txt
+++ b/test/bacnet/datalink/bsc-node/CMakeLists.txt
@@ -160,6 +160,8 @@ target_sources(${PROJECT_NAME} PRIVATE
${SRC_DIR}/bacnet/basic/sys/keylist.c
${SRC_DIR}/bacnet/basic/sys/mstimer.c
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/arf.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/datalink/bsc-socket/CMakeLists.txt b/test/bacnet/datalink/bsc-socket/CMakeLists.txt
index b14acf2c10..f9f7c3a521 100644
--- a/test/bacnet/datalink/bsc-socket/CMakeLists.txt
+++ b/test/bacnet/datalink/bsc-socket/CMakeLists.txt
@@ -158,6 +158,8 @@ target_sources(${PROJECT_NAME} PRIVATE
${SRC_DIR}/bacnet/basic/sys/keylist.c
${SRC_DIR}/bacnet/basic/sys/mstimer.c
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/arf.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/datalink/hub-sc/CMakeLists.txt b/test/bacnet/datalink/hub-sc/CMakeLists.txt
index 162cd41168..c20ccd1e4a 100644
--- a/test/bacnet/datalink/hub-sc/CMakeLists.txt
+++ b/test/bacnet/datalink/hub-sc/CMakeLists.txt
@@ -163,6 +163,8 @@ target_sources(${PROJECT_NAME} PRIVATE
${SRC_DIR}/bacnet/basic/sys/keylist.c
${SRC_DIR}/bacnet/basic/sys/mstimer.c
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/arf.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/datalink/mstp/src/main.c b/test/bacnet/datalink/mstp/src/main.c
index 362fa00123..a61cb5267b 100644
--- a/test/bacnet/datalink/mstp/src/main.c
+++ b/test/bacnet/datalink/mstp/src/main.c
@@ -646,6 +646,66 @@ static void testMasterNodeFSM(void)
/* FIXME: write a unit test for the Master Node State Machine */
}
+static void testReceiveNodeFSM_COBS_Decode_TightBuffer(void)
+{
+ struct mstp_port_struct_t mstp_port = { 0 }; /* port data */
+ uint8_t my_mac = 0x05; /* local MAC address */
+ uint8_t frame[MAX_MPDU] = { 0 };
+ uint8_t rx_tight[MAX_MPDU] = { 0 };
+ uint8_t payload[64] = { 0 };
+ unsigned len;
+ unsigned cobs_len;
+ unsigned tight_size;
+ unsigned guard_start;
+ unsigned i;
+
+ for (i = 0; i < sizeof(payload); i++) {
+ payload[i] = (uint8_t)(i + 1);
+ }
+ /* Include zeros to force multiple COBS blocks and real decode writes. */
+ payload[3] = 0;
+ payload[17] = 0;
+
+ len = MSTP_Create_Frame(
+ frame, sizeof(frame), FRAME_TYPE_BACNET_EXTENDED_DATA_EXPECTING_REPLY,
+ my_mac, my_mac, payload, sizeof(payload));
+ zassert_true(len > 0, NULL);
+
+ cobs_len = (((unsigned)frame[5]) << 8) | frame[6];
+ cobs_len += 2;
+ tight_size = cobs_len + 1;
+ zassert_true(tight_size < sizeof(rx_tight), NULL);
+
+ mstp_port.InputBuffer = &rx_tight[0];
+ mstp_port.InputBufferSize = tight_size;
+ mstp_port.OutputBuffer = &TxBuffer[0];
+ mstp_port.OutputBufferSize = sizeof(TxBuffer);
+ mstp_port.SilenceTimer = Timer_Silence;
+ mstp_port.SilenceTimerReset = Timer_Silence_Reset;
+ mstp_port.This_Station = my_mac;
+ mstp_port.Nmax_info_frames = 1;
+ mstp_port.Nmax_master = 127;
+ MSTP_Init(&mstp_port);
+
+ guard_start = tight_size;
+ for (i = guard_start; i < (guard_start + 8); i++) {
+ rx_tight[i] = 0xA5;
+ }
+
+ Load_Input_Buffer(frame, len);
+ for (i = 0; i < len; i++) {
+ RS485_Check_UART_Data(&mstp_port);
+ MSTP_Receive_Frame_FSM(&mstp_port);
+ }
+
+ zassert_true(mstp_port.ReceivedInvalidFrame == true, NULL);
+ zassert_true(mstp_port.ReceivedValidFrame == false, NULL);
+ zassert_true(mstp_port.receive_state == MSTP_RECEIVE_STATE_IDLE, NULL);
+ for (i = guard_start; i < (guard_start + 8); i++) {
+ zassert_true(rx_tight[i] == 0xA5, NULL);
+ }
+}
+
static void testSlaveNodeFSM(void)
{
struct mstp_port_struct_t MSTP_Port = { 0 }; /* port data */
@@ -1367,6 +1427,7 @@ void test_main(void)
{
ztest_test_suite(
crc_tests, ztest_unit_test(testReceiveNodeFSM),
+ ztest_unit_test(testReceiveNodeFSM_COBS_Decode_TightBuffer),
ztest_unit_test(testMasterNodeFSM), ztest_unit_test(testSlaveNodeFSM),
ztest_unit_test(testZeroConfigNodeFSM),
ztest_unit_test(testAutoBaudNodeFSM));
diff --git a/test/bacnet/delete_object/CMakeLists.txt b/test/bacnet/delete_object/CMakeLists.txt
index 6748043fad..7e298f3ec3 100644
--- a/test/bacnet/delete_object/CMakeLists.txt
+++ b/test/bacnet/delete_object/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/delete_object.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/event/CMakeLists.txt b/test/bacnet/event/CMakeLists.txt
index da50fac21a..1b402d2f65 100644
--- a/test/bacnet/event/CMakeLists.txt
+++ b/test/bacnet/event/CMakeLists.txt
@@ -60,6 +60,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/basic/sys/days.c
# Dependencies of bacapp.c
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bactext.c
${SRC_DIR}/bacnet/indtext.c
diff --git a/test/bacnet/getalarm/CMakeLists.txt b/test/bacnet/getalarm/CMakeLists.txt
index 45fe4a84e5..666ce6b67f 100644
--- a/test/bacnet/getalarm/CMakeLists.txt
+++ b/test/bacnet/getalarm/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/get_alarm_sum.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/getevent/CMakeLists.txt b/test/bacnet/getevent/CMakeLists.txt
index 820eb21e13..6992c40192 100644
--- a/test/bacnet/getevent/CMakeLists.txt
+++ b/test/bacnet/getevent/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/getevent.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/hostnport/CMakeLists.txt b/test/bacnet/hostnport/CMakeLists.txt
index 3b8ad1a044..e9dd887a51 100644
--- a/test/bacnet/hostnport/CMakeLists.txt
+++ b/test/bacnet/hostnport/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/hostnport.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/lighting/src/main.c b/test/bacnet/lighting/src/main.c
index 515b68c459..8b1cc9d829 100644
--- a/test/bacnet/lighting/src/main.c
+++ b/test/bacnet/lighting/src/main.c
@@ -8,6 +8,7 @@
* @brief test BACnet integer encode/decode APIs
*/
+#include
#include
#include
#include
@@ -264,6 +265,68 @@ static void testBACnetXYColor(void)
}
}
+/**
+ * @brief Test bounds checking for lighting_command_decode
+ * Validates fix for CWE-125 out-of-bounds read.
+ * Old code passed apdu_size instead of (apdu_size - apdu_len) to nested
+ * decoders, causing them to read past buffer when operation field advanced the
+ * pointer.
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(lighting_tests, testBACnetLightingCommandBoundsCheck)
+#else
+static void testBACnetLightingCommandBoundsCheck(void)
+#endif
+{
+ BACNET_LIGHTING_COMMAND test_cases[] = {
+ /* FADE_TO with optional fade_time and priority */
+ { BACNET_LIGHTS_FADE_TO, true, false, false, true, true, 50.0, 0.0, 0.0,
+ 5000, 8 },
+ /* RAMP_TO with optional ramp_rate and priority */
+ { BACNET_LIGHTS_RAMP_TO, true, true, false, false, true, 25.0, 10.0,
+ 0.0, 0, 5 },
+ /* STEP_UP with optional step_increment and priority */
+ { BACNET_LIGHTS_STEP_UP, false, false, true, false, true, 0.0, 0.0, 5.0,
+ 0, 12 },
+ /* WARN with optional priority */
+ { BACNET_LIGHTS_WARN, false, false, false, false, true, 0.0, 0.0, 0.0,
+ 0, 1 },
+ };
+ unsigned i, trunc_pos;
+ int enc_len;
+ uint8_t apdu_full[MAX_APDU] = { 0 };
+ uint8_t apdu_trunc[MAX_APDU] = { 0 };
+ int decode_result;
+ BACNET_LIGHTING_COMMAND decoded = { 0 };
+
+ for (i = 0; i < ARRAY_SIZE(test_cases); i++) {
+ /* Encode full valid message */
+ enc_len = lighting_command_encode(apdu_full, &test_cases[i]);
+ zassert_true(
+ enc_len > 0, "Failed encode operation %u", test_cases[i].operation);
+
+ /* Copy full buffer */
+ memcpy(apdu_trunc, apdu_full, (size_t)enc_len);
+
+ /* Test: Progressively truncate buffer, should return error */
+ for (trunc_pos = 1; trunc_pos < enc_len; trunc_pos++) {
+ decode_result =
+ lighting_command_decode(apdu_trunc, trunc_pos, &decoded);
+ /* Decoder must not report consuming more bytes than provided */
+ zassert_true(
+ decode_result <= 0 || decode_result <= (int)trunc_pos,
+ "Op %u: truncated to %u bytes returned decode_result %d",
+ test_cases[i].operation, trunc_pos, decode_result);
+ }
+
+ /* Valid decode with full buffer */
+ decode_result = lighting_command_decode(apdu_full, enc_len, &decoded);
+ zassert_equal(
+ decode_result, enc_len, "Op %u: full buffer decode failed",
+ test_cases[i].operation);
+ }
+}
+
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST_SUITE(lighting_tests, NULL, NULL, NULL, NULL, NULL);
#else
@@ -272,7 +335,8 @@ void test_main(void)
ztest_test_suite(
lighting_tests, ztest_unit_test(testBACnetLightingCommandAll),
ztest_unit_test(testBACnetColorCommandAll),
- ztest_unit_test(testBACnetXYColor));
+ ztest_unit_test(testBACnetXYColor),
+ ztest_unit_test(testBACnetLightingCommandBoundsCheck));
ztest_run_test_suite(lighting_tests);
}
diff --git a/test/bacnet/list_element/CMakeLists.txt b/test/bacnet/list_element/CMakeLists.txt
index 7cb9d445a2..b8450a4497 100644
--- a/test/bacnet/list_element/CMakeLists.txt
+++ b/test/bacnet/list_element/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/list_element.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/lso/CMakeLists.txt b/test/bacnet/lso/CMakeLists.txt
index a189709c5c..bc26bd66fa 100644
--- a/test/bacnet/lso/CMakeLists.txt
+++ b/test/bacnet/lso/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/lso.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/npdu/src/main.c b/test/bacnet/npdu/src/main.c
index 43cc4a57d8..f53bead348 100644
--- a/test/bacnet/npdu/src/main.c
+++ b/test/bacnet/npdu/src/main.c
@@ -5,6 +5,7 @@
* @date 2012
* @copyright SPDX-License-Identifier: MIT
*/
+#include
#include
#include
#include
@@ -538,6 +539,152 @@ static void test_NPDU_Data_Expecting_Reply(void)
reply_pdu, reply_pdu_len, &test_address, npdu_len + 3);
}
+/**
+ * @brief Test npdu_i_am_router_to_network_process
+ */
+struct test_dnet_add_data {
+ uint16_t snet;
+ uint16_t net[16];
+ unsigned count;
+};
+
+static void
+test_dnet_add_callback(uint16_t snet, uint16_t net, const BACNET_ADDRESS *addr)
+{
+ struct test_dnet_add_data *data =
+ (struct test_dnet_add_data *)(uintptr_t)(const void *)addr;
+ data->snet = snet;
+ if (data->count < 16) {
+ data->net[data->count++] = net;
+ }
+}
+
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(npdu_tests, test_NPDU_I_Am_Router_To_Network_Process)
+#else
+static void test_NPDU_I_Am_Router_To_Network_Process(void)
+#endif
+{
+ struct test_dnet_add_data cb_data = { 0 };
+ uint8_t npdu[16] = { 0 };
+ uint16_t npdu_size = 0;
+ uint16_t snet = 42;
+
+ /* encode two network numbers: 100 and 200 */
+ npdu[0] = 0x00;
+ npdu[1] = 100;
+ npdu[2] = 0x00;
+ npdu[3] = 200;
+ npdu_size = 4;
+
+ npdu_i_am_router_to_network_process(
+ snet, (BACNET_ADDRESS *)&cb_data, npdu, npdu_size,
+ test_dnet_add_callback);
+
+ zassert_equal(cb_data.count, 2, NULL);
+ zassert_equal(cb_data.snet, snet, NULL);
+ zassert_equal(cb_data.net[0], 100, NULL);
+ zassert_equal(cb_data.net[1], 200, NULL);
+
+ /* empty buffer - nothing should be added */
+ memset(&cb_data, 0, sizeof(cb_data));
+ npdu_i_am_router_to_network_process(
+ snet, (BACNET_ADDRESS *)&cb_data, npdu, 1, test_dnet_add_callback);
+ zassert_equal(cb_data.count, 0, NULL);
+
+ /* NULL callback - no crash */
+ npdu_i_am_router_to_network_process(
+ snet, (BACNET_ADDRESS *)&cb_data, npdu, npdu_size, NULL);
+
+ /* single network number */
+ memset(&cb_data, 0, sizeof(cb_data));
+ npdu[0] = 0x00;
+ npdu[1] = 0xFF;
+ npdu_size = 2;
+ npdu_i_am_router_to_network_process(
+ snet, (BACNET_ADDRESS *)&cb_data, npdu, npdu_size,
+ test_dnet_add_callback);
+ zassert_equal(cb_data.count, 1, NULL);
+ zassert_equal(cb_data.net[0], 255, NULL);
+}
+
+/**
+ * @brief Test npdu_init_routing_table_process
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(npdu_tests, test_NPDU_Init_Routing_Table_Process)
+#else
+static void test_NPDU_Init_Routing_Table_Process(void)
+#endif
+{
+ struct test_dnet_add_data cb_data = { 0 };
+ uint8_t npdu[64] = { 0 };
+ uint16_t npdu_size = 0;
+ uint16_t snet = 10;
+ uint8_t *p = npdu;
+
+ /* encode 2 entries:
+ * net_count = 2
+ * Entry 1: DNET=300, PortID=1, PortInfoLen=0
+ * Entry 2: DNET=400, PortID=2, PortInfoLen=3, PortInfo={0xAA,0xBB,0xCC} */
+ *p++ = 2; /* net_count */
+ /* entry 1 */
+ *p++ = 0x01; /* DNET high */
+ *p++ = 0x2C; /* DNET low: 300 */
+ *p++ = 1; /* PortID */
+ *p++ = 0; /* PortInfoLen */
+ /* entry 2 */
+ *p++ = 0x01; /* DNET high */
+ *p++ = 0x90; /* DNET low: 400 */
+ *p++ = 2; /* PortID */
+ *p++ = 3; /* PortInfoLen */
+ *p++ = 0xAA;
+ *p++ = 0xBB;
+ *p++ = 0xCC;
+ npdu_size = (uint16_t)(p - npdu);
+
+ npdu_init_routing_table_process(
+ snet, (BACNET_ADDRESS *)&cb_data, npdu, npdu_size,
+ test_dnet_add_callback);
+
+ zassert_equal(cb_data.count, 2, NULL);
+ zassert_equal(cb_data.snet, snet, NULL);
+ zassert_equal(cb_data.net[0], 300, NULL);
+ zassert_equal(cb_data.net[1], 400, NULL);
+
+ /* malformed: too short (<=1 byte) */
+ memset(&cb_data, 0, sizeof(cb_data));
+ npdu_init_routing_table_process(
+ snet, (BACNET_ADDRESS *)&cb_data, npdu, 1, test_dnet_add_callback);
+ zassert_equal(cb_data.count, 0, NULL);
+
+ /* net_count == 0 */
+ memset(&cb_data, 0, sizeof(cb_data));
+ npdu[0] = 0;
+ npdu_init_routing_table_process(
+ snet, (BACNET_ADDRESS *)&cb_data, npdu, npdu_size,
+ test_dnet_add_callback);
+ zassert_equal(cb_data.count, 0, NULL);
+
+ /* NULL callback - no crash */
+ npdu[0] = 2;
+ npdu_init_routing_table_process(
+ snet, (BACNET_ADDRESS *)&cb_data, npdu, npdu_size, NULL);
+
+ /* truncated buffer - only enough bytes for first entry's
+ * DNET+PortID+PortInfoLen but not enough for second entry's 4-byte minimum;
+ * first entry should be added */
+ memset(&cb_data, 0, sizeof(cb_data));
+ npdu[0] = 2;
+ /* npdu_size=7: net_count(1)+DNET(2)+PortID(1)+PortInfoLen(1)+2bytes of
+ * entry2 After processing entry 1: npdu_len=3, which is < 4 so loop exits
+ */
+ npdu_init_routing_table_process(
+ snet, (BACNET_ADDRESS *)&cb_data, npdu, 7, test_dnet_add_callback);
+ zassert_equal(cb_data.count, 1, NULL);
+ zassert_equal(cb_data.net[0], 300, NULL);
+}
+
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST_SUITE(npdu_tests, NULL, NULL, NULL, NULL, NULL);
#else
@@ -548,7 +695,9 @@ void test_main(void)
ztest_unit_test(test_NPDU_Network), ztest_unit_test(test_NPDU_Copy),
ztest_unit_test(test_NPDU_Confirmed_Service),
ztest_unit_test(test_NPDU_Segmented_Complex_Ack_Reply),
- ztest_unit_test(test_NPDU_Data_Expecting_Reply));
+ ztest_unit_test(test_NPDU_Data_Expecting_Reply),
+ ztest_unit_test(test_NPDU_I_Am_Router_To_Network_Process),
+ ztest_unit_test(test_NPDU_Init_Routing_Table_Process));
ztest_run_test_suite(npdu_tests);
}
diff --git a/test/bacnet/ptransfer/CMakeLists.txt b/test/bacnet/ptransfer/CMakeLists.txt
index 754d772727..901016ca83 100644
--- a/test/bacnet/ptransfer/CMakeLists.txt
+++ b/test/bacnet/ptransfer/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/ptransfer.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/rpm/CMakeLists.txt b/test/bacnet/rpm/CMakeLists.txt
index 67d5dbde95..ce8786cf6f 100644
--- a/test/bacnet/rpm/CMakeLists.txt
+++ b/test/bacnet/rpm/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/rpm.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/rpm/src/main.c b/test/bacnet/rpm/src/main.c
index c1706ead96..f7af0e64d1 100644
--- a/test/bacnet/rpm/src/main.c
+++ b/test/bacnet/rpm/src/main.c
@@ -628,6 +628,77 @@ static void testReadPropertyMultipleAckProcess(void)
Read_Property_Ack_Data[4].error_code, ERROR_CODE_INVALID_TAG, NULL);
}
+/**
+ * @brief Regression test for DoS vulnerability: handler_read_property_multiple
+ * infinite loop on malformed RPM request with missing closing tag.
+ *
+ * When rpm_decode_object_property is called with zero remaining bytes
+ * (exhausted buffer) it must return a negative error code so that the
+ * handler breaks out of its inner for(;;) loop. Before the fix the
+ * function returned 0, decode_len never advanced, and the server thread
+ * spun at 100% CPU indefinitely.
+ */
+#if defined(CONFIG_ZTEST_NEW_API)
+ZTEST(rpm_tests, testReadPropertyMultipleMissingClosingTag)
+#else
+static void testReadPropertyMultipleMissingClosingTag(void)
+#endif
+{
+ uint8_t apdu[480] = { 0 };
+ int len = 0;
+ int test_len = 0;
+ int apdu_len = 0;
+ uint8_t invoke_id = 12;
+ uint8_t test_invoke_id = 0;
+ uint8_t *service_request = NULL;
+ unsigned service_request_len = 0;
+ BACNET_RPM_DATA rpmdata;
+
+ /* Build a valid RPM request that intentionally omits the closing tag
+ (rpm_encode_apdu_object_end is not called). This reproduces the
+ malformed/truncated packet described in the DoS report. */
+ apdu_len = rpm_encode_apdu_init(&apdu[0], invoke_id);
+ apdu_len +=
+ rpm_encode_apdu_object_begin(&apdu[apdu_len], OBJECT_DEVICE, 123);
+ apdu_len += rpm_encode_apdu_object_property(
+ &apdu[apdu_len], PROP_OBJECT_IDENTIFIER, BACNET_ARRAY_ALL);
+ /* Closing tag deliberately omitted:
+ apdu_len += rpm_encode_apdu_object_end(&apdu[apdu_len]); */
+
+ /* Decode the APDU wrapper to obtain the service-request slice */
+ test_len = rpm_decode_apdu(
+ &apdu[0], apdu_len, &test_invoke_id, &service_request,
+ &service_request_len);
+ zassert_true(test_len >= 0, NULL);
+ zassert_equal(test_invoke_id, invoke_id, NULL);
+
+ /* Decode the object identifier */
+ test_len =
+ rpm_decode_object_id(service_request, service_request_len, &rpmdata);
+ zassert_true(test_len > 0, NULL);
+ len += test_len;
+
+ /* Decode the one encoded property - this must succeed */
+ test_len = rpm_decode_object_property(
+ &service_request[len], service_request_len - len, &rpmdata);
+ zassert_true(test_len > 0, NULL);
+ zassert_equal(rpmdata.object_property, PROP_OBJECT_IDENTIFIER, NULL);
+ len += test_len;
+
+ /* The buffer is now exhausted: service_request_len - len == 0.
+ rpm_decode_object_property must return a negative error code here.
+ Returning 0 would cause handler_read_property_multiple to spin in
+ an infinite loop (DoS). */
+ zassert_equal((unsigned)len, service_request_len, NULL);
+ test_len = rpm_decode_object_property(
+ &service_request[len], service_request_len - len, &rpmdata);
+ zassert_true(
+ test_len < 0,
+ "rpm_decode_object_property returned %d on empty buffer; "
+ "expected negative error (fix DoS infinite-loop)",
+ test_len);
+}
+
/**
* @}
*/
@@ -641,7 +712,8 @@ void test_main(void)
rpm_tests, ztest_unit_test(testReadPropertyMultiple),
ztest_unit_test(testReadPropertyMultipleRequest),
ztest_unit_test(testReadPropertyMultipleAck),
- ztest_unit_test(testReadPropertyMultipleAckProcess));
+ ztest_unit_test(testReadPropertyMultipleAckProcess),
+ ztest_unit_test(testReadPropertyMultipleMissingClosingTag));
ztest_run_test_suite(rpm_tests);
}
diff --git a/test/bacnet/secure_connect/CMakeLists.txt b/test/bacnet/secure_connect/CMakeLists.txt
index e90b4acc74..af055d7f74 100644
--- a/test/bacnet/secure_connect/CMakeLists.txt
+++ b/test/bacnet/secure_connect/CMakeLists.txt
@@ -34,6 +34,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/secure_connect.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/specialevent/CMakeLists.txt b/test/bacnet/specialevent/CMakeLists.txt
index 8197178164..e8106ceba8 100644
--- a/test/bacnet/specialevent/CMakeLists.txt
+++ b/test/bacnet/specialevent/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/special_event.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/timesync/CMakeLists.txt b/test/bacnet/timesync/CMakeLists.txt
index 7d937c6094..3ca09bb5b1 100644
--- a/test/bacnet/timesync/CMakeLists.txt
+++ b/test/bacnet/timesync/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/timesync.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/weeklyschedule/CMakeLists.txt b/test/bacnet/weeklyschedule/CMakeLists.txt
index 46a62567e6..aa36fbf1bd 100644
--- a/test/bacnet/weeklyschedule/CMakeLists.txt
+++ b/test/bacnet/weeklyschedule/CMakeLists.txt
@@ -38,6 +38,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/dailyschedule.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/wp/CMakeLists.txt b/test/bacnet/wp/CMakeLists.txt
index 753edfb61f..76421ef081 100644
--- a/test/bacnet/wp/CMakeLists.txt
+++ b/test/bacnet/wp/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/wp.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/wpm/CMakeLists.txt b/test/bacnet/wpm/CMakeLists.txt
index 8e8afa9908..6f4de920a6 100644
--- a/test/bacnet/wpm/CMakeLists.txt
+++ b/test/bacnet/wpm/CMakeLists.txt
@@ -36,6 +36,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/wpm.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c
diff --git a/test/bacnet/write_group/CMakeLists.txt b/test/bacnet/write_group/CMakeLists.txt
index adf94ba8f2..7447b8357e 100644
--- a/test/bacnet/write_group/CMakeLists.txt
+++ b/test/bacnet/write_group/CMakeLists.txt
@@ -35,6 +35,8 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/write_group.c
# Support files and stubs (pathname alphabetical)
${SRC_DIR}/bacnet/access_rule.c
+ ${SRC_DIR}/bacnet/authentication_factor.c
+ ${SRC_DIR}/bacnet/authentication_factor_format.c
${SRC_DIR}/bacnet/bacaction.c
${SRC_DIR}/bacnet/bacaddr.c
${SRC_DIR}/bacnet/bacapp.c