Added segmentation support for server reply. (#974)

Added segmentation support for server devices for some services. Configure BACNET_SEGMENTATION_ENABLED=1 to include in the library, and adjust BACNET_MAX_SEGMENTS_ACCEPTED for maximum number of segments.
This commit is contained in:
Steve Karg
2025-11-15 13:33:36 -06:00
committed by GitHub
parent bea2ceba11
commit 3ea710f92f
25 changed files with 2112 additions and 144 deletions
+40 -2
View File
@@ -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,19 @@ bool address_get_by_device(
return found;
}
/**
* @brief Return the cached addresBACNET_STACK_EXPORT
s 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.
*
+8
View File
@@ -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,
+8
View File
@@ -1143,7 +1143,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
}
/**
@@ -1614,8 +1618,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;
+290 -24
View File
@@ -26,6 +26,10 @@ static uint16_t Timeout_Milliseconds = 3000;
/* Number of APDU Retries */
static uint8_t Number_Of_Retries = 3;
static uint8_t Local_Network_Priority; /* Fixing test 10.1.2 Network priority */
#if BACNET_SEGMENTATION_ENABLED
/* APDU Segment Timeout in Milliseconds */
static uint16_t Segment_Timeout_Milliseconds = 5000;
#endif
/* a simple table for crossing the services supported */
static BACNET_SERVICES_SUPPORTED
@@ -552,6 +556,109 @@ static bool apdu_unconfirmed_dcc_disabled(uint8_t service_choice)
return status;
}
/* Invoke special handler for confirmed service */
static void invoke_confirmed_service_service_request(
BACNET_ADDRESS *src,
BACNET_CONFIRMED_SERVICE_DATA *service_data,
uint8_t service_choice,
uint8_t *service_request,
uint32_t service_request_len)
{
if (apdu_confirmed_dcc_disabled(service_choice)) {
/* When network communications are completely disabled,
only DeviceCommunicationControl and ReinitializeDevice
APDUs shall be processed and no messages shall be
initiated. */
return;
}
if ((service_choice < MAX_BACNET_CONFIRMED_SERVICE) &&
(Confirmed_Function[service_choice])) {
Confirmed_Function[service_choice](
service_request, service_request_len, src, service_data);
} else if (Unrecognized_Service_Handler) {
Unrecognized_Service_Handler(
service_request, service_request_len, src, service_data);
}
}
#if BACNET_SEGMENTATION_ENABLED
/** Handler for messages with segmentation :
- store new packet if sequence number is ok
- NACK packet if sequence number is not ok
- call the final functions with reassembled data when last packet ok is
received
*/
static void apdu_handler_confirmed_service_segment(
BACNET_ADDRESS *src,
uint8_t *apdu, /* APDU data */
uint32_t apdu_len)
{
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
uint8_t internal_service_id = 0;
uint8_t service_choice = 0;
uint8_t *service_request = NULL;
uint16_t service_request_len = 0;
uint32_t len = 0; /* counts where we are in PDU */
bool segment_ok;
len = apdu_decode_confirmed_service_request(
&apdu[0], /* APDU data */
apdu_len, &service_data, &service_choice, &service_request,
&service_request_len);
if (len == 0) {
/* service data unable to be decoded - simply drop */
return;
}
/* new segment : memorize it */
segment_ok = tsm_set_segmented_confirmed_service_received(
src, &service_data, &internal_service_id, &service_request,
&service_request_len);
/* last segment */
if (segment_ok && !service_data.more_follows) {
/* Clear peer information */
tsm_clear_peer_id(internal_service_id);
/* Invoke service handler */
invoke_confirmed_service_service_request(
src, &service_data, service_choice, service_request,
service_request_len);
/* We must free invoke_id, and associated data */
tsm_free_invoke_id_check(internal_service_id, NULL, true);
}
}
#endif
/* Handler for normal message without segmentation, or segmented complete
* message reassembled all-in-one */
static void apdu_handler_confirmed_service(
BACNET_ADDRESS *src,
uint8_t *apdu, /* APDU data */
uint32_t apdu_len)
{
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
uint8_t service_choice = 0;
uint8_t *service_request = NULL;
uint16_t service_request_len = 0;
uint32_t len = 0; /* counts where we are in PDU */
len = apdu_decode_confirmed_service_request(
&apdu[0], /* APDU data */
apdu_len, &service_data, &service_choice, &service_request,
&service_request_len);
if (len == 0) {
/* service data unable to be decoded - simply drop */
return;
}
#if BACNET_SEGMENTATION_ENABLED
/* Check for unexpected request is received in active TSM state */
if (tsm_is_invalid_apdu_in_this_state(src, &service_data)) {
return;
}
#endif
invoke_confirmed_service_service_request(
src, &service_data, service_choice, service_request,
service_request_len);
}
/** Process the APDU header and invoke the appropriate service handler
* to manage the received request.
* Almost all requests and ACKs invoke this function.
@@ -567,11 +674,15 @@ void apdu_handler(
uint16_t apdu_len)
{
BACNET_PDU_TYPE pdu_type;
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
uint8_t service_choice = 0;
uint8_t *service_request = NULL;
uint16_t service_request_len = 0;
int len = 0; /* counts where we are in PDU */
#if BACNET_SEGMENTATION_ENABLED
uint8_t sequence_number = 0;
uint8_t actual_window_size = 0;
bool nak = false;
#endif
#if !BACNET_SVC_SERVER
uint8_t invoke_id = 0;
BACNET_CONFIRMED_SERVICE_ACK_DATA service_ack_data = { 0 };
@@ -590,27 +701,14 @@ void apdu_handler(
pdu_type = apdu[0] & 0xF0;
switch (pdu_type) {
case PDU_TYPE_CONFIRMED_SERVICE_REQUEST:
len = apdu_decode_confirmed_service_request(
apdu, apdu_len, &service_data, &service_choice,
&service_request, &service_request_len);
if (len == 0) {
/* service data unable to be decoded - simply drop */
break;
}
if (apdu_confirmed_dcc_disabled(service_choice)) {
/* When network communications are completely disabled,
only DeviceCommunicationControl and ReinitializeDevice
APDUs shall be processed and no messages shall be
initiated. */
break;
}
if ((service_choice < MAX_BACNET_CONFIRMED_SERVICE) &&
(Confirmed_Function[service_choice])) {
Confirmed_Function[service_choice](
service_request, service_request_len, src, &service_data);
} else if (Unrecognized_Service_Handler) {
Unrecognized_Service_Handler(
service_request, service_request_len, src, &service_data);
/* segmented_message_reception ? */
#if BACNET_SEGMENTATION_ENABLED
if (apdu[0] & BIT(3)) {
apdu_handler_confirmed_service_segment(src, apdu, apdu_len);
} else
#endif
{
apdu_handler_confirmed_service(src, apdu, apdu_len);
}
break;
case PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST:
@@ -685,9 +783,24 @@ void apdu_handler(
}
break;
case PDU_TYPE_SEGMENT_ACK:
#if !BACNET_SEGMENTATION_ENABLED
/* FIXME: what about a denial of service attack here?
we could check src to see if that matched the tsm */
tsm_free_invoke_id(invoke_id);
#else
if (apdu_len < 4) {
break;
}
server = apdu[0] & 0x01;
nak = apdu[0] & 0x02;
invoke_id = apdu[1];
sequence_number = apdu[2];
actual_window_size = apdu[3];
/* we care because we support segmented message sending */
tsm_segmentack_received(
invoke_id, sequence_number, actual_window_size, nak, server,
src);
#endif
break;
case PDU_TYPE_ERROR:
if (apdu_len < 3) {
@@ -714,7 +827,12 @@ void apdu_handler(
(BACNET_ERROR_CODE)error_code);
}
}
#if BACNET_SEGMENTATION_ENABLED
/*Release the data*/
tsm_free_invoke_id_segmentation(src, invoke_id);
#else
tsm_free_invoke_id(invoke_id);
#endif
break;
case PDU_TYPE_REJECT:
if (apdu_len < 3) {
@@ -725,7 +843,12 @@ void apdu_handler(
if (Reject_Function) {
Reject_Function(src, invoke_id, reason);
}
#if BACNET_SEGMENTATION_ENABLED
/*Release the data*/
tsm_free_invoke_id_segmentation(src, invoke_id);
#else
tsm_free_invoke_id(invoke_id);
#endif
break;
case PDU_TYPE_ABORT:
if (apdu_len < 3) {
@@ -734,13 +857,156 @@ void apdu_handler(
server = apdu[0] & 0x01;
invoke_id = apdu[1];
reason = apdu[2];
if (Abort_Function) {
Abort_Function(src, invoke_id, reason, server);
if (!server) {
/*AbortPDU_Received*/
if (Abort_Function) {
Abort_Function(src, invoke_id, reason, server);
}
}
#if BACNET_SEGMENTATION_ENABLED
else {
/*SendAbort*/
tsm_abort_pdu_send(invoke_id, src, reason, server);
}
/*Release the data*/
tsm_free_invoke_id_segmentation(src, invoke_id);
#else
tsm_free_invoke_id(invoke_id);
#endif
break;
#endif
default:
break;
}
}
#if BACNET_SEGMENTATION_ENABLED
/*Return the APDU segment timeout*/
uint16_t apdu_segment_timeout(void)
{
return Segment_Timeout_Milliseconds;
}
/*Set the APDU segment timeout*/
void apdu_segment_timeout_set(uint16_t milliseconds)
{
Segment_Timeout_Milliseconds = milliseconds;
}
/** Process the APDU header and invoke the appropriate service handler
* to manage the received request.
* Almost all requests and ACKs invoke this function.
* @ingroup MISCHNDLR
*
* @param apdu [in] The apdu portion of the response, to be sent
* @param fixed_pdu_header [in] The apdu header for the response
* @return apdu_length[out] The length of the apdu header
*/
int apdu_encode_fixed_header(
uint8_t *apdu, BACNET_APDU_FIXED_HEADER *fixed_pdu_header)
{
int apdu_len = 0;
switch (fixed_pdu_header->pdu_type) {
case PDU_TYPE_CONFIRMED_SERVICE_REQUEST:
apdu[apdu_len++] = fixed_pdu_header->pdu_type
/* flag 'SA' if we accept many segments */
| (fixed_pdu_header->service_data.request_data
.segmented_response_accepted
? 0x02
: 0x00)
/* flag 'MOR' if we more segments are coming */
| (fixed_pdu_header->service_data.request_data.more_follows
? 0x04
: 0x00)
/* flag 'SEG' if we more segments are coming */
| (fixed_pdu_header->service_data.request_data.segmented_message
? 0x08
: 0x00);
apdu[apdu_len++] = encode_max_segs_max_apdu(
fixed_pdu_header->service_data.request_data.max_segs,
fixed_pdu_header->service_data.request_data.max_resp);
apdu[apdu_len++] =
fixed_pdu_header->service_data.request_data.invoke_id;
/* extra data for segmented messages sending */
if (fixed_pdu_header->service_data.request_data.segmented_message) {
/* packet sequence number */
apdu[apdu_len++] =
fixed_pdu_header->service_data.request_data.sequence_number;
/* window size proposal */
apdu[apdu_len++] = fixed_pdu_header->service_data.request_data
.proposed_window_number;
}
/* service choice */
apdu[apdu_len++] = fixed_pdu_header->service_choice;
break;
case PDU_TYPE_COMPLEX_ACK:
apdu[apdu_len++] = fixed_pdu_header->pdu_type
/* flag 'MOR' if we more segments are coming */
| (fixed_pdu_header->service_data.ack_data.more_follows ? 0x04
: 0x00)
/* flag 'SEG' if we more segments are coming */
| (fixed_pdu_header->service_data.ack_data.segmented_message
? 0x08
: 0x00);
apdu[apdu_len++] =
fixed_pdu_header->service_data.ack_data.invoke_id;
/* extra data for segmented messages sending */
if (fixed_pdu_header->service_data.ack_data.segmented_message) {
/* packet sequence number */
apdu[apdu_len++] =
fixed_pdu_header->service_data.ack_data.sequence_number;
/* window size proposal */
apdu[apdu_len++] = fixed_pdu_header->service_data.ack_data
.proposed_window_number;
}
/* service choice */
apdu[apdu_len++] = fixed_pdu_header->service_choice;
break;
default:
break;
}
return apdu_len;
}
/** Handler to assign the header fields to the response
* The response can be segmented/unsegmented
*
* @param fixed_pdu_header [in] The apdu header of the response, to be sent.
* @param pdu_type [in] The pdu_type of the response.
* @param invoke_id [in] The invoike_id of the response
* @param service[in] The service choice for which the response has to be
* processed
* @param max_apdu[in] The maximum apdu length
*/
void apdu_init_fixed_header(
BACNET_APDU_FIXED_HEADER *fixed_pdu_header,
uint8_t pdu_type,
uint8_t invoke_id,
uint8_t service,
int max_apdu)
{
fixed_pdu_header->pdu_type = pdu_type;
fixed_pdu_header->service_data.common_data.invoke_id = invoke_id;
fixed_pdu_header->service_data.common_data.more_follows = false;
fixed_pdu_header->service_data.common_data.proposed_window_number = 0;
fixed_pdu_header->service_data.common_data.sequence_number = 0;
fixed_pdu_header->service_data.common_data.segmented_message = false;
switch (pdu_type) {
case PDU_TYPE_CONFIRMED_SERVICE_REQUEST:
fixed_pdu_header->service_data.request_data.max_segs =
BACNET_MAX_SEGMENTS_ACCEPTED;
/* allow to specify a lower APDU size : support arbitrary reduction
* of APDU packets between peers */
fixed_pdu_header->service_data.request_data.max_resp =
max_apdu < MAX_APDU ? max_apdu : MAX_APDU;
fixed_pdu_header->service_data.request_data
.segmented_response_accepted = BACNET_MAX_SEGMENTS_ACCEPTED > 1;
break;
case PDU_TYPE_COMPLEX_ACK:
default:
break;
}
fixed_pdu_header->service_choice = service;
}
#endif
+18
View File
@@ -162,6 +162,24 @@ void apdu_handler(
uint8_t *apdu, /* APDU data */
uint16_t pdu_len); /* for confirmed messages */
BACNET_STACK_EXPORT
uint16_t apdu_segment_timeout(void);
BACNET_STACK_EXPORT
void apdu_segment_timeout_set(uint16_t milliseconds);
BACNET_STACK_EXPORT
int apdu_encode_fixed_header(
uint8_t *apdu, BACNET_APDU_FIXED_HEADER *fixed_pdu_header);
BACNET_STACK_EXPORT
void apdu_init_fixed_header(
BACNET_APDU_FIXED_HEADER *fixed_pdu_header,
uint8_t pdu_type,
uint8_t invoke_id,
uint8_t service,
int max_apdu);
#ifdef __cplusplus
}
#endif /* __cplusplus */
+4 -1
View File
@@ -108,7 +108,9 @@ void handler_device_communication_control(
debug_print("DeviceCommunicationControl: "
"Missing Required Parameter. Sending Reject!\n");
goto DCC_FAILURE;
} else if (service_data->segmented_message) {
}
#if !BACNET_SEGMENTATION_ENABLED
else if (service_data->segmented_message) {
len = abort_encode_apdu(
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true);
@@ -116,6 +118,7 @@ void handler_device_communication_control(
"Sending Abort - segmented message.\n");
goto DCC_FAILURE;
}
#endif
/* decode the service request only */
len = dcc_decode_service_request(
service_request, service_len, &timeDuration, &state, &password);
+4 -1
View File
@@ -71,13 +71,16 @@ void handler_reinitialize_device(
debug_print("ReinitializeDevice: Missing Required Parameter. "
"Sending Reject!\n");
goto RD_ABORT;
} else if (service_data->segmented_message) {
}
#if !BACNET_SEGMENTATION_ENABLED
else if (service_data->segmented_message) {
len = abort_encode_apdu(
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true);
debug_print("ReinitializeDevice: Sending Abort - segmented message.\n");
goto RD_ABORT;
}
#endif
/* decode the service request only */
len = rd_decode_service_request(
service_request, service_len, &rd_data.state, &rd_data.password);
+42 -3
View File
@@ -30,6 +30,12 @@
#include "bacnet/basic/sys/debug.h"
#include "bacnet/datalink/datalink.h"
#if BACNET_SEGMENTATION_ENABLED
#define BACNET_RP_BUFFER_OVERFLOW ERROR_CODE_ABORT_BUFFER_OVERFLOW
#else
#define BACNET_RP_BUFFER_OVERFLOW ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED
#endif
/** @file h_rp.c Handles Read Property requests. */
/** Handler for a ReadProperty Service request.
@@ -38,7 +44,7 @@
* by a call to apdu_set_confirmed_handler().
* This handler builds a response packet, which is
* - an Abort if
* - the message is segmented
* - the message is segmented and segmentation is not supported
* - if decoding fails
* - if the response would be too large
* - the result from Device_Read_Property(), if it succeeds
@@ -59,6 +65,7 @@ void handler_read_property(
{
BACNET_READ_PROPERTY_DATA rpdata;
int len = 0;
int max_resp = 0;
int pdu_len = 0;
int apdu_len = -1;
int npdu_len = -1;
@@ -66,9 +73,13 @@ void handler_read_property(
bool error = true; /* assume that there is an error */
int bytes_sent = 0;
BACNET_ADDRESS my_address;
#if BACNET_SEGMENTATION_ENABLED
BACNET_APDU_FIXED_HEADER apdu_fixed_header;
int apdu_header_len = 3;
#endif
/* configure default error code as an abort since it is common */
rpdata.error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
rpdata.error_code = BACNET_RP_BUFFER_OVERFLOW;
/* encode the NPDU portion of the packet */
datalink_get_my_address(&my_address);
npdu_encode_npdu_data(&npdu_data, false, service_data->priority);
@@ -82,10 +93,12 @@ void handler_read_property(
len = BACNET_STATUS_REJECT;
rpdata.error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER;
debug_print("RP: Missing Required Parameter. Sending Reject!\n");
#if !BACNET_SEGMENTATION_ENABLED
} else if (service_data->segmented_message) {
/* we don't support segmentation - send an abort */
len = BACNET_STATUS_ABORT;
debug_print("RP: Segmented message. Sending Abort!\n");
#endif
} else {
len = rp_decode_service_request(service_request, service_len, &rpdata);
if (len <= 0) {
@@ -116,6 +129,13 @@ void handler_read_property(
rpdata.object_instance = Network_Port_Index_To_Instance(0);
}
#endif
#if BACNET_SEGMENTATION_ENABLED
apdu_init_fixed_header(
&apdu_fixed_header, PDU_TYPE_COMPLEX_ACK,
service_data->invoke_id, SERVICE_CONFIRMED_READ_PROPERTY,
service_data->max_resp);
#endif
apdu_len = rp_ack_encode_apdu_init(
&Handler_Transmit_Buffer[npdu_len], service_data->invoke_id,
&rpdata);
@@ -134,7 +154,25 @@ void handler_read_property(
len = rp_ack_encode_apdu_object_property_end(
&Handler_Transmit_Buffer[npdu_len + apdu_len]);
apdu_len += len;
if (apdu_len > service_data->max_resp) {
/* pick the smaller response packet: ours or theirs */
max_resp = min(service_data->max_resp, MAX_APDU);
if (apdu_len > max_resp) {
#if BACNET_SEGMENTATION_ENABLED
if (service_data->segmented_response_accepted) {
npdu_encode_npdu_data(
&npdu_data, true, MESSAGE_PRIORITY_NORMAL);
npdu_len = npdu_encode_pdu(
&Handler_Transmit_Buffer[0], src, &my_address,
&npdu_data);
tsm_set_complexack_transaction(
src, &npdu_data, &apdu_fixed_header, service_data,
&Handler_Transmit_Buffer
[npdu_len + apdu_header_len],
(apdu_len - apdu_header_len));
return;
}
#else
/* too big for the sender - send an abort!
Setting of error code needed here as read property
processing may have overridden the default set at start
@@ -143,6 +181,7 @@ void handler_read_property(
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
len = BACNET_STATUS_ABORT;
debug_print("RP: Message too large.\n");
#endif
} else {
debug_print("RP: Sending Ack!\n");
error = false;
+95 -36
View File
@@ -12,6 +12,7 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
@@ -33,7 +34,17 @@
#include "bacnet/basic/sys/debug.h"
#include "bacnet/datalink/datalink.h"
static uint8_t Temp_Buf[MAX_APDU] = { 0 };
/* Smaller single threaded implementations prefer a
single buffer for encoding each property from the RPM request. */
#ifndef BACNET_RPM_PROPERTY_BUFFER_USE_CSTACK
static uint8_t RPM_Prop_Buffer[MAX_ASDU - MAX_NPDU] = { 0 };
#endif
#if BACNET_SEGMENTATION_ENABLED
#define BACNET_RPM_BUFFER_OVERFLOW ERROR_CODE_ABORT_BUFFER_OVERFLOW
#else
#define BACNET_RPM_BUFFER_OVERFLOW ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED
#endif
/**
* @brief Fetches the lists of properties (array of BACNET_PROPERTY_ID's) for
@@ -124,12 +135,15 @@ static int RPM_Encode_Property(
size_t copy_len = 0;
int apdu_len = 0;
BACNET_READ_PROPERTY_DATA rpdata;
#ifdef BACNET_RPM_PROPERTY_BUFFER_USE_CSTACK
uint8_t RPM_Prop_Buffer[MAX_ASDU - MAX_NPDU] = { 0 };
#endif
len = rpm_ack_encode_apdu_object_property(
&Temp_Buf[0], rpmdata->object_property, rpmdata->array_index);
copy_len = memcopy(&apdu[0], &Temp_Buf[0], offset, len, max_apdu);
&RPM_Prop_Buffer[0], rpmdata->object_property, rpmdata->array_index);
copy_len = memcopy(&apdu[0], &RPM_Prop_Buffer[0], offset, len, max_apdu);
if (copy_len == 0) {
rpmdata->error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
rpmdata->error_code = BACNET_RPM_BUFFER_OVERFLOW;
return BACNET_STATUS_ABORT;
}
apdu_len += len;
@@ -139,8 +153,8 @@ static int RPM_Encode_Property(
rpdata.object_instance = rpmdata->object_instance;
rpdata.object_property = rpmdata->object_property;
rpdata.array_index = rpmdata->array_index;
rpdata.application_data = &Temp_Buf[0];
rpdata.application_data_len = sizeof(Temp_Buf);
rpdata.application_data = &RPM_Prop_Buffer[0];
rpdata.application_data_len = sizeof(RPM_Prop_Buffer);
if ((rpmdata->object_property == PROP_ALL) ||
(rpmdata->object_property == PROP_REQUIRED) ||
@@ -161,21 +175,21 @@ static int RPM_Encode_Property(
}
/* error was returned - encode that for the response */
len = rpm_ack_encode_apdu_object_property_error(
&Temp_Buf[0], rpdata.error_class, rpdata.error_code);
copy_len =
memcopy(&apdu[0], &Temp_Buf[0], offset + apdu_len, len, max_apdu);
&RPM_Prop_Buffer[0], rpdata.error_class, rpdata.error_code);
copy_len = memcopy(
&apdu[0], &RPM_Prop_Buffer[0], offset + apdu_len, len, max_apdu);
if (copy_len == 0) {
rpmdata->error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
rpmdata->error_code = BACNET_RPM_BUFFER_OVERFLOW;
return BACNET_STATUS_ABORT;
}
} else if ((offset + apdu_len + 1 + len + 1) < max_apdu) {
/* enough room to fit the property value and tags */
len = rpm_ack_encode_apdu_object_property_value(
&apdu[offset + apdu_len], &Temp_Buf[0], len);
&apdu[offset + apdu_len], &RPM_Prop_Buffer[0], len);
} else {
/* not enough room - abort! */
rpmdata->error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
rpmdata->error_code = BACNET_RPM_BUFFER_OVERFLOW;
return BACNET_STATUS_ABORT;
}
apdu_len += len;
@@ -189,7 +203,7 @@ static int RPM_Encode_Property(
* by a call to apdu_set_confirmed_handler().
* This handler builds a response packet, which is
* - an Abort if
* - the message is segmented
* - the message is segmented and SEGMENTATION_NONE
* - if decoding fails
* - if the response would be too large
* - the result from each included read request, if it succeeds
@@ -220,6 +234,14 @@ void handler_read_property_multiple(
int apdu_len = 0;
int npdu_len = 0;
int error = 0;
int max_resp = 0;
#if BACNET_SEGMENTATION_ENABLED
BACNET_APDU_FIXED_HEADER apdu_fixed_header;
int apdu_header_len = 3;
#endif
#ifdef BACNET_RPM_PROPERTY_BUFFER_USE_CSTACK
uint8_t RPM_Prop_Buffer[MAX_ASDU - MAX_NPDU] = { 0 };
#endif
if (service_data) {
datalink_get_my_address(&my_address);
@@ -230,10 +252,12 @@ void handler_read_property_multiple(
rpmdata.error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER;
error = BACNET_STATUS_REJECT;
debug_print("RPM: Missing Required Parameter. Sending Reject!\n");
#if !BACNET_SEGMENTATION_ENABLED
} else if (service_data->segmented_message) {
rpmdata.error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
error = BACNET_STATUS_ABORT;
debug_print("RPM: Segmented message. Sending Abort!\r\n");
#endif
} else {
/* decode apdu request & encode apdu reply
encode complex ack, invoke id, service choice */
@@ -279,14 +303,14 @@ void handler_read_property_multiple(
}
#endif
/* Stick this object id into the reply - if it will fit */
len = rpm_ack_encode_apdu_object_begin(&Temp_Buf[0], &rpmdata);
len = rpm_ack_encode_apdu_object_begin(
&RPM_Prop_Buffer[0], &rpmdata);
copy_len = memcopy(
&Handler_Transmit_Buffer[npdu_len], &Temp_Buf[0], apdu_len,
len, MAX_APDU);
&Handler_Transmit_Buffer[npdu_len], &RPM_Prop_Buffer[0],
apdu_len, len, sizeof(RPM_Prop_Buffer));
if (copy_len == 0) {
debug_print("RPM: Response too big!\n");
rpmdata.error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
rpmdata.error_code = BACNET_RPM_BUFFER_OVERFLOW;
error = BACNET_STATUS_ABORT;
berror = true;
break;
@@ -321,7 +345,9 @@ void handler_read_property_multiple(
rpmdata.object_type, rpmdata.object_instance)) {
len = RPM_Encode_Property(
&Handler_Transmit_Buffer[npdu_len],
(uint16_t)apdu_len, MAX_APDU, &rpmdata);
(uint16_t)apdu_len,
sizeof(Handler_Transmit_Buffer) - npdu_len,
&rpmdata);
if (len > 0) {
apdu_len += len;
} else {
@@ -336,18 +362,18 @@ void handler_read_property_multiple(
/* No array index options for this special property.
Encode error for this object property response */
len = rpm_ack_encode_apdu_object_property(
&Temp_Buf[0], rpmdata.object_property,
&RPM_Prop_Buffer[0], rpmdata.object_property,
rpmdata.array_index);
copy_len = memcopy(
&Handler_Transmit_Buffer[npdu_len],
&Temp_Buf[0], apdu_len, len, MAX_APDU);
&RPM_Prop_Buffer[0], apdu_len, len,
sizeof(RPM_Prop_Buffer));
if (copy_len == 0) {
debug_print(
"RPM: Too full to encode property!\n");
rpmdata.error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
rpmdata.error_code = BACNET_RPM_BUFFER_OVERFLOW;
error = BACNET_STATUS_ABORT;
/* The berror flag ensures that
both loops will be broken! */
@@ -357,17 +383,17 @@ void handler_read_property_multiple(
apdu_len += len;
len = rpm_ack_encode_apdu_object_property_error(
&Temp_Buf[0], ERROR_CLASS_PROPERTY,
&RPM_Prop_Buffer[0], ERROR_CLASS_PROPERTY,
ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY);
copy_len = memcopy(
&Handler_Transmit_Buffer[npdu_len],
&Temp_Buf[0], apdu_len, len, MAX_APDU);
&RPM_Prop_Buffer[0], apdu_len, len,
sizeof(RPM_Prop_Buffer));
if (copy_len == 0) {
debug_print("RPM: Too full to encode error!\n");
rpmdata.error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
rpmdata.error_code = BACNET_RPM_BUFFER_OVERFLOW;
error = BACNET_STATUS_ABORT;
/* The berror flag ensures that
both loops will be broken! */
@@ -396,7 +422,10 @@ void handler_read_property_multiple(
rpmdata.object_instance)) {
len = RPM_Encode_Property(
&Handler_Transmit_Buffer[npdu_len],
(uint16_t)apdu_len, MAX_APDU, &rpmdata);
(uint16_t)apdu_len,
sizeof(Handler_Transmit_Buffer) -
npdu_len,
&rpmdata);
if (len > 0) {
apdu_len += len;
} else {
@@ -418,7 +447,10 @@ void handler_read_property_multiple(
special_object_property, index);
len = RPM_Encode_Property(
&Handler_Transmit_Buffer[npdu_len],
(uint16_t)apdu_len, MAX_APDU, &rpmdata);
(uint16_t)apdu_len,
sizeof(Handler_Transmit_Buffer) -
npdu_len,
&rpmdata);
if (len > 0) {
apdu_len += len;
} else {
@@ -437,7 +469,9 @@ void handler_read_property_multiple(
/* handle an individual property */
len = RPM_Encode_Property(
&Handler_Transmit_Buffer[npdu_len],
(uint16_t)apdu_len, MAX_APDU, &rpmdata);
(uint16_t)apdu_len,
sizeof(Handler_Transmit_Buffer) - npdu_len,
&rpmdata);
if (len > 0) {
apdu_len += len;
} else {
@@ -456,15 +490,16 @@ void handler_read_property_multiple(
/* Reached end of property list so cap the result list
*/
decode_len++;
len = rpm_ack_encode_apdu_object_end(&Temp_Buf[0]);
len =
rpm_ack_encode_apdu_object_end(&RPM_Prop_Buffer[0]);
copy_len = memcopy(
&Handler_Transmit_Buffer[npdu_len], &Temp_Buf[0],
apdu_len, len, MAX_APDU);
&Handler_Transmit_Buffer[npdu_len],
&RPM_Prop_Buffer[0], apdu_len, len,
sizeof(Handler_Transmit_Buffer) - npdu_len);
if (copy_len == 0) {
debug_print(
"RPM: Too full to encode object end!\n");
rpmdata.error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
rpmdata.error_code = BACNET_RPM_BUFFER_OVERFLOW;
error = BACNET_STATUS_ABORT;
/* The berror flag ensures that
both loops will be broken! */
@@ -487,12 +522,36 @@ void handler_read_property_multiple(
}
/* If not having an error so far, check the remaining space. */
if (!berror) {
if (apdu_len > service_data->max_resp) {
max_resp = min(service_data->max_resp, MAX_APDU);
if (apdu_len > max_resp) {
#if BACNET_SEGMENTATION_ENABLED
if (service_data->segmented_response_accepted) {
apdu_init_fixed_header(
&apdu_fixed_header, PDU_TYPE_COMPLEX_ACK,
service_data->invoke_id,
SERVICE_CONFIRMED_READ_PROP_MULTIPLE,
service_data->max_resp);
npdu_encode_npdu_data(
&npdu_data, true, MESSAGE_PRIORITY_NORMAL);
npdu_len = npdu_encode_pdu(
&Handler_Transmit_Buffer[0], src, &my_address,
&npdu_data);
tsm_set_complexack_transaction(
src, &npdu_data, &apdu_fixed_header, service_data,
&Handler_Transmit_Buffer
[npdu_len + apdu_header_len],
(apdu_len - apdu_header_len));
return;
}
#else
/* too big for the sender - send an abort */
rpmdata.error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
error = BACNET_STATUS_ABORT;
debug_print("RPM: Message too large. Sending Abort!\n");
#endif
}
}
}
+4 -1
View File
@@ -107,13 +107,16 @@ void handler_write_property(
REJECT_REASON_MISSING_REQUIRED_PARAMETER);
debug_print("WP: Missing Required Parameter. Sending Reject!\n");
bcontinue = false;
} else if (service_data->segmented_message) {
}
#if !BACNET_SEGMENTATION_ENABLED
else if (service_data->segmented_message) {
len = abort_encode_apdu(
&Handler_Transmit_Buffer[pdu_len], service_data->invoke_id,
ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, true);
debug_print("WP: Segmented message. Sending Abort!\n");
bcontinue = false;
}
#endif
if (bcontinue) {
/* decode the service request only */
len = wp_decode_service_request(service_request, service_len, &wp_data);
+2
View File
@@ -141,11 +141,13 @@ void handler_write_property_multiple(
len = BACNET_STATUS_REJECT;
debug_print("WPM: Missing Required Parameter. "
"Sending Reject!\n");
#if !BACNET_SEGMENTATION_ENABLED
} else if (service_data->segmented_message) {
wp_data.error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
len = BACNET_STATUS_ABORT;
debug_print("WPM: Segmented message. "
"Sending Abort!\n");
#endif
} else {
/* first time - detect malformed request before writing any data */
len = write_property_multiple_decode(
+2 -2
View File
@@ -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;
+1243 -63
View File
File diff suppressed because it is too large Load Diff
+102 -11
View File
@@ -15,6 +15,9 @@
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/npdu.h"
#if BACNET_SEGMENTATION_ENABLED
#include "bacnet/apdu.h"
#endif
/* note: TSM functionality is optional - only needed if we are
doing client requests */
@@ -24,7 +27,7 @@ extern "C" {
#endif /* __cplusplus */
/* FIXME: modify basic service handlers to use TSM rather than this buffer! */
BACNET_STACK_EXPORT extern uint8_t Handler_Transmit_Buffer[MAX_PDU];
BACNET_STACK_EXPORT extern uint8_t Handler_Transmit_Buffer[MAX_ASDU];
#ifdef __cplusplus
}
@@ -37,31 +40,47 @@ typedef enum {
TSM_STATE_IDLE,
TSM_STATE_AWAIT_CONFIRMATION,
TSM_STATE_AWAIT_RESPONSE,
TSM_STATE_SEGMENTED_REQUEST,
TSM_STATE_SEGMENTED_CONFIRMATION
TSM_STATE_SEGMENTED_REQUEST_SERVER,
TSM_STATE_SEGMENTED_CONFIRMATION,
TSM_STATE_SEGMENTED_RESPONSE_SERVER
} BACNET_TSM_STATE;
#if BACNET_SEGMENTATION_ENABLED
/* Indirect data state : */
typedef struct BACnet_TSM_Indirect_Data {
/* the address we received data from */
BACNET_ADDRESS PeerAddress;
/* the peer unique id */
uint8_t PeerInvokeID;
/* the unique id to use within our internal states.
zero means : "unused slot". */
uint8_t InternalInvokeID;
} BACNET_TSM_INDIRECT_DATA;
#endif
/* 5.4.1 Variables And Parameters */
/* The following variables are defined for each instance of */
/* Transaction State Machine: */
typedef struct BACnet_TSM_Data {
/* used to count APDU retries */
uint8_t RetryCount;
#if BACNET_SEGMENTATION_ENABLED
/* used to count segment retries */
/*uint8_t SegmentRetryCount; */
uint8_t SegmentRetryCount;
/* used to control APDU retries and the acceptance of server replies */
/*bool SentAllSegments; */
bool SentAllSegments;
/* stores the sequence number of the last segment received in order */
/*uint8_t LastSequenceNumber; */
uint8_t LastSequenceNumber;
/* stores the sequence number of the first segment of */
/* a sequence of segments that fill a window */
/*uint8_t InitialSequenceNumber; */
uint8_t InitialSequenceNumber;
/* stores the current window size */
/*uint8_t ActualWindowSize; */
uint8_t ActualWindowSize;
/* stores the window size proposed by the segment sender */
/*uint8_t ProposedWindowSize; */
uint8_t ProposedWindowSize;
/* used to perform timeout on PDU segments */
/*uint8_t SegmentTimer; */
uint16_t SegmentTimer;
#endif
/* used to perform timeout on Confirmed Requests */
/* in milliseconds */
uint16_t RequestTimer;
@@ -73,9 +92,28 @@ typedef struct BACnet_TSM_Data {
BACNET_ADDRESS dest;
/* the network layer info */
BACNET_NPDU_DATA npdu_data;
unsigned apdu_len;
#if BACNET_SEGMENTATION_ENABLED
/* APDU header information */
BACNET_APDU_FIXED_HEADER apdu_fixed_header;
/* calculated max APDU length / packet */
uint32_t apdu_maximum_length;
/* calculated max APDU length / total */
uint32_t maximum_transmittable_length;
/* Multiple APDU segments blob memorized here */
uint8_t *apdu_blob;
/* Size of allocated Multiple APDU segments blob */
uint32_t apdu_blob_allocated;
/* Size of data within the multiple APDU segments blob */
uint32_t apdu_blob_size;
/* Count received segments (prevents D.O.S.) */
uint32_t ReceivedSegmentsCount;
/* copy of the APDU, should we need to send it again */
uint8_t *apdu;
#else
/* copy of the APDU, should we need to send it again */
uint8_t apdu[MAX_PDU];
unsigned apdu_len;
#endif
} BACNET_TSM_DATA;
typedef void (*tsm_timeout_function)(uint8_t invoke_id);
@@ -123,6 +161,59 @@ bool tsm_invoke_id_free(uint8_t invokeID);
BACNET_STACK_EXPORT
bool tsm_invoke_id_failed(uint8_t invokeID);
#if BACNET_SEGMENTATION_ENABLED
/** Clear TSM Peer data */
BACNET_STACK_EXPORT
void tsm_clear_peer_id(uint8_t InternalInvokeID);
/* frees the invokeID and sets its state to IDLE */
BACNET_STACK_EXPORT
void tsm_free_invoke_id_check(
uint8_t invokeID, BACNET_ADDRESS *peer_address, bool cleanup);
/* Associates a Peer address and invoke ID with our TSM */
BACNET_STACK_EXPORT
uint8_t tsm_get_peer_id(BACNET_ADDRESS *src, uint8_t invokeID);
BACNET_STACK_EXPORT
bool tsm_set_segmented_confirmed_service_received(
BACNET_ADDRESS *src,
BACNET_CONFIRMED_SERVICE_DATA *service_data,
uint8_t *internal_invoke_id,
uint8_t **pservice_request, /* IN/OUT */
uint16_t *pservice_request_len /* IN/OUT */
);
BACNET_STACK_EXPORT
int tsm_set_complexack_transaction(
BACNET_ADDRESS *dest,
BACNET_NPDU_DATA *npdu_data,
BACNET_APDU_FIXED_HEADER *apdu_fixed_header,
BACNET_CONFIRMED_SERVICE_DATA *confirmed_service_data,
uint8_t *pdu,
uint32_t pdu_len);
BACNET_STACK_EXPORT
void tsm_segmentack_received(
uint8_t invoke_id,
uint8_t sequence_number,
uint8_t actual_window_size,
bool nak,
bool server,
BACNET_ADDRESS *src);
BACNET_STACK_EXPORT
bool tsm_is_invalid_apdu_in_this_state(
BACNET_ADDRESS *src, BACNET_CONFIRMED_SERVICE_DATA *service_data);
BACNET_STACK_EXPORT
void tsm_abort_pdu_send(
uint8_t invoke_id, BACNET_ADDRESS *dest, uint8_t reason, bool server);
BACNET_STACK_EXPORT
void tsm_free_invoke_id_segmentation(BACNET_ADDRESS *src, uint8_t invoke_id);
#endif
#ifdef __cplusplus
}
#endif /* __cplusplus */