diff --git a/.mailmap b/.mailmap index 1b488ed7..a8de933b 100644 --- a/.mailmap +++ b/.mailmap @@ -23,6 +23,7 @@ Frédéric Chaxel Boris Weitsch Vasyl Tkhir +Julien Bennet # github mappings Greg Shue <32416235+shuegr-personal@users.noreply.github.com> diff --git a/CHANGELOG.md b/CHANGELOG.md index 3626cc58..d47a3a69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ The git repositories are hosted at the following sites: ### Added +* Added segmentation support functions and example changes, but + no support for segmentation in the TSM or APDU handlers. (#1218) * Added channel and timer object write-property observers in blinkt app to monitor internal writes. Added vacancy timer command line argument for testing initial timer object vacancy time for lights channel. (#1212) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b4bd91b..a21c1435 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,11 @@ option( "compile with secure-connect support" OFF) +option( + BACNET_SEGMENTATION_ENABLED + "enable segmentation" + ON) + if(NOT (BACDL_ETHERNET OR BACDL_MSTP OR BACDL_ARCNET OR @@ -683,6 +688,8 @@ add_library(${PROJECT_NAME} src/bacnet/rp.h src/bacnet/rpm.c src/bacnet/rpm.h + $<$:src/bacnet/segmentack.c> + $<$:src/bacnet/segmentack.h> src/bacnet/shed_level.c src/bacnet/shed_level.h src/bacnet/timer_value.c @@ -736,6 +743,7 @@ target_compile_definitions( $<$:BACDL_NONE> $<$:BACNET_PROPERTY_LISTS=1> $<$:BAC_ROUTING> + $<$:BACNET_SEGMENTATION_ENABLED=1> $<$>:BACNET_STACK_STATIC_DEFINE> PRIVATE PRINT_ENABLED=1) @@ -1302,3 +1310,4 @@ message(STATUS "BACNET: BACDL_ARCNET:...................\"${BACDL_ARCNET}\"") message(STATUS "BACNET: BACDL_MSTP:.....................\"${BACDL_MSTP}\"") message(STATUS "BACNET: BACDL_ZIGBEE:...................\"${BACDL_ZIGBEE}\"") message(STATUS "BACNET: BACDL_ETHERNET:.................\"${BACDL_ETHERNET}\"") +message(STATUS "BACNET: BACNET_SEGMENTATION_ENABLED:....\"${BACNET_SEGMENTATION_ENABLED}\"") diff --git a/Makefile b/Makefile index 442c5728..8e8e6d23 100644 --- a/Makefile +++ b/Makefile @@ -224,6 +224,10 @@ server-discover: server-mini: $(MAKE) LEGACY=true NOTIFY=false -s -C apps $@ +.PHONY: server-segmentation +server-segmentation: + $(MAKE) LEGACY=true SEGMENT=true -s -C apps server + .PHONY: sc-hub sc-hub: $(MAKE) LEGACY=true BACDL=bsc -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index d0333507..6529b1a8 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -223,6 +223,11 @@ ifeq (${LEGACY},true) BACNET_DEFINES += -DBACNET_STACK_DEPRECATED_DISABLE endif +ifeq (${SEGMENT},true) +# enable segmentation support +BACNET_DEFINES += -DBACNET_SEGMENTATION_ENABLED=1 +endif + ifeq (${NOTIFY},false) # disable intrinsic reporting else diff --git a/ports/atmega328/device.c b/ports/atmega328/device.c index dca50848..ad01a4a2 100644 --- a/ports/atmega328/device.c +++ b/ports/atmega328/device.c @@ -58,6 +58,11 @@ bool Device_Set_Object_Instance_Number(uint32_t object_id) return status; } +BACNET_SEGMENTATION Device_Segmentation_Supported(void) +{ + return SEGMENTATION_NONE; +} + bool Device_Valid_Object_Instance_Number(uint32_t object_id) { /* BACnet allows for a wildcard instance number */ diff --git a/src/bacnet/bacdef.h b/src/bacnet/bacdef.h index c2143619..c140f161 100644 --- a/src/bacnet/bacdef.h +++ b/src/bacnet/bacdef.h @@ -212,8 +212,24 @@ typedef struct BACnet_Object_Id { uint32_t instance; } BACNET_OBJECT_ID; +#if !defined(BACNET_MAX_SEGMENTS_ACCEPTED) +#if BACNET_SEGMENTATION_ENABLED +/* note: BACNET_MAX_SEGMENTS_ACCEPTED can be 1..255. + ASDU in this library is usually sized for 16-bit at 65535 max. + Therefore, the default here is limited to avoid overflow warnings. */ +#define BACNET_MAX_SEGMENTS_ACCEPTED 32 +#else +#define BACNET_MAX_SEGMENTS_ACCEPTED 1 +#endif +#endif +#if !defined(MAX_APDU) +#define MAX_APDU 1476 +#endif #define MAX_NPDU (1 + 1 + 2 + 1 + MAX_MAC_LEN + 2 + 1 + MAX_MAC_LEN + 1 + 1 + 2) #define MAX_PDU (MAX_APDU + MAX_NPDU) +/* Application Service Data Unit (ASDU) that has not yet been segmented + into a protocol data unit (PDU) by the lower layer. */ +#define MAX_ASDU ((MAX_APDU * BACNET_MAX_SEGMENTS_ACCEPTED) + MAX_NPDU) #define BACNET_ID_VALUE(bacnet_object_instance, bacnet_object_type) \ ((((bacnet_object_type) & BACNET_MAX_OBJECT) << BACNET_INSTANCE_BITS) | \ diff --git a/src/bacnet/basic/binding/address.c b/src/bacnet/basic/binding/address.c index 25669f03..0dea3ead 100644 --- a/src/bacnet/basic/binding/address.c +++ b/src/bacnet/basic/binding/address.c @@ -45,6 +45,10 @@ static struct Address_Cache_Entry { uint8_t Flags; uint32_t device_id; unsigned max_apdu; +#if BACNET_SEGMENTATION_ENABLED + uint8_t segmentation; + uint16_t maxsegments; +#endif BACNET_ADDRESS address; uint32_t TimeToLive; } Address_Cache[MAX_ADDRESS_CACHE]; @@ -346,9 +350,16 @@ void address_set_device_TTL( * @param device_id Device-Id * @param max_apdu Pointer to a variable, taking the maximum APDU size. * @param src Pointer to address structure for return. + * @param segmentation Pointer to a variable, taking the BACNET_SEGMENTATION + * flag. + * @param maxsegments Pointer to a variable, taking the maximum segments. */ -bool address_get_by_device( - uint32_t device_id, unsigned *max_apdu, BACNET_ADDRESS *src) +bool address_segment_get_by_device( + uint32_t device_id, + unsigned *max_apdu, + BACNET_ADDRESS *src, + uint8_t *segmentation, + uint16_t *maxsegments) { struct Address_Cache_Entry *pMatch; bool found = false; /* return value */ @@ -364,6 +375,20 @@ bool address_get_by_device( if (max_apdu) { *max_apdu = pMatch->max_apdu; } + if (segmentation) { +#if BACNET_SEGMENTATION_ENABLED + *segmentation = pMatch->segmentation; +#else + *segmentation = SEGMENTATION_NONE; +#endif + } + if (maxsegments) { +#if BACNET_SEGMENTATION_ENABLED + *maxsegments = pMatch->maxsegments; +#else + *maxsegments = 1; +#endif + } /* Prove we found it */ found = true; } @@ -375,6 +400,18 @@ bool address_get_by_device( return found; } +/** + * @brief Return the cached address for the given device-id + * @param device_id Device-Id + * @param max_apdu Pointer to a variable, taking the maximum APDU size. + * @param src Pointer to address structure for return. + */ +bool address_get_by_device( + uint32_t device_id, unsigned *max_apdu, BACNET_ADDRESS *src) +{ + return address_segment_get_by_device(device_id, max_apdu, src, NULL, NULL); +} + /** * Find a device id from a given MAC address. * diff --git a/src/bacnet/basic/binding/address.h b/src/bacnet/basic/binding/address.h index 059dc1c4..692a1a9b 100644 --- a/src/bacnet/basic/binding/address.h +++ b/src/bacnet/basic/binding/address.h @@ -43,6 +43,14 @@ BACNET_STACK_EXPORT bool address_get_by_device( uint32_t device_id, unsigned *max_apdu, BACNET_ADDRESS *src); +BACNET_STACK_EXPORT +bool address_segment_get_by_device( + uint32_t device_id, + unsigned *max_apdu, + BACNET_ADDRESS *src, + uint8_t *segmentation, + uint16_t *maxsegments); + BACNET_STACK_EXPORT bool address_get_by_index( unsigned index, diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index ca191f41..e9f098f2 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -1333,7 +1333,11 @@ uint8_t Device_Protocol_Revision(void) BACNET_SEGMENTATION Device_Segmentation_Supported(void) { +#if BACNET_SEGMENTATION_ENABLED + return SEGMENTATION_BOTH; +#else return SEGMENTATION_NONE; +#endif } /** @@ -1804,8 +1808,12 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) rpdata->object_instance, rpdata->array_index, Device_Object_List_Element_Encode, count, apdu, apdu_max); if (apdu_len == BACNET_STATUS_ABORT) { +#if BACNET_SEGMENTATION_ENABLED + rpdata->error_code = ERROR_CODE_ABORT_BUFFER_OVERFLOW; +#else rpdata->error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; +#endif } else if (apdu_len == BACNET_STATUS_ERROR) { rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; diff --git a/src/bacnet/basic/service/s_iam.c b/src/bacnet/basic/service/s_iam.c index 6b7399b8..76a7cb6b 100644 --- a/src/bacnet/basic/service/s_iam.c +++ b/src/bacnet/basic/service/s_iam.c @@ -85,7 +85,7 @@ int iam_encode_pdu( /* encode the APDU portion of the packet */ len = iam_encode_apdu( &buffer[pdu_len], Device_Object_Instance_Number(), MAX_APDU, - SEGMENTATION_NONE, Device_Vendor_Identifier()); + Device_Segmentation_Supported(), Device_Vendor_Identifier()); pdu_len += len; return pdu_len; @@ -158,7 +158,7 @@ int iam_unicast_encode_pdu( /* encode the APDU portion of the packet */ apdu_len = iam_encode_apdu( &buffer[npdu_len], Device_Object_Instance_Number(), MAX_APDU, - SEGMENTATION_NONE, Device_Vendor_Identifier()); + Device_Segmentation_Supported(), Device_Vendor_Identifier()); pdu_len = npdu_len + apdu_len; return pdu_len; diff --git a/src/bacnet/config.h b/src/bacnet/config.h index 8fbc82da..68c96e41 100644 --- a/src/bacnet/config.h +++ b/src/bacnet/config.h @@ -171,6 +171,11 @@ #endif #endif +/* Enable or disable segmentation support in the library */ +#ifndef BACNET_SEGMENTATION_ENABLED +#define BACNET_SEGMENTATION_ENABLED 0 +#endif + /* for confirmed messages, this is the number of transactions */ /* that we hold in a queue waiting for timeout. */ /* Configure to zero if you don't want any confirmed messages */ diff --git a/src/bacnet/segmentack.c b/src/bacnet/segmentack.c new file mode 100644 index 00000000..6b70b1ae --- /dev/null +++ b/src/bacnet/segmentack.c @@ -0,0 +1,79 @@ +/** + * @file + * @brief Segment Acknowledgment (SegmentAck) PDU encode and decode functions + * @author Julien Bennet + * @date 2010 + * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 + */ +#include "bacnet/segmentack.h" + +/** + * @brief Method to encode the segment ack . + * @param apdu[in] Pointer to the buffer for encoding. + * @param negativeack[in] Acknowledgment for the segment. + * @param server[in] Set to True if the acknowledgment is from the server, else + * false. + * @param invoke_id[in] Invoke Id + * @param sequence_number[in] Sequence number of the segment to be acknowledged + * @param actual_window_size[in] Actual window size. + * @return Length of encoded data or zero on error. + */ +int segmentack_encode_apdu( + uint8_t *apdu, + bool negativeack, + bool server, + uint8_t invoke_id, + uint8_t sequence_number, + uint8_t actual_window_size) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + uint8_t server_code = server ? 0x01 : 0x00; + uint8_t nak_code = negativeack ? 0x02 : 0x00; + + if (apdu) { + apdu[0] = PDU_TYPE_SEGMENT_ACK | server_code | nak_code; + apdu[1] = invoke_id; + apdu[2] = sequence_number; + apdu[3] = actual_window_size; + apdu_len = 4; + } + + return apdu_len; +} + +/** + * @brief Method to decode the segment ack service request + * @param apdu[in] The apdu portion of the ACK reply. + * @param apdu_len[in] The total length of the apdu. + * @param invoke_id[out] Invoke Id of the request. + * @param sequence_number[out] Sequence number of the segment received. + * @param actual_window_size[out] Actual window size. + * @return Length of decoded data or zero on error. + */ +int segmentack_decode_service_request( + uint8_t *apdu, + unsigned apdu_len, + uint8_t *invoke_id, + uint8_t *sequence_number, + uint8_t *actual_window_size) +{ + int len = 0; + int apdu_header_size = 3; + + if (apdu_len >= apdu_header_size) { + if (invoke_id) { + *invoke_id = apdu[0]; + } + if (sequence_number) { + *sequence_number = apdu[1]; + } + if (actual_window_size) { + *actual_window_size = apdu[2]; + } + /* three bytes successfully decoded: invoke_id, sequence_number, + * and actual_window_size */ + len = apdu_header_size; + } + + return len; +} diff --git a/src/bacnet/segmentack.h b/src/bacnet/segmentack.h new file mode 100644 index 00000000..a3fa2865 --- /dev/null +++ b/src/bacnet/segmentack.h @@ -0,0 +1,41 @@ +/** + * @file + * @brief Segment Acknowledgment (SegmentAck) PDU encode and decode functions + * @author Julien Bennet + * @date 2010 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_SEGMENT_ACK_H +#define BACNET_SEGMENT_ACK_H + +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +int segmentack_encode_apdu( + uint8_t *apdu, + bool negativeack, + bool server, + uint8_t invoke_id, + uint8_t sequence_number, + uint8_t actual_window_size); + +BACNET_STACK_EXPORT +int segmentack_decode_service_request( + uint8_t *apdu, + unsigned apdu_len, + uint8_t *invoke_id, + uint8_t *sequence_number, + uint8_t *actual_window_size); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* BACNET_SEGMENT_ACK_H */