Feature/comments and buffer checks (#73)
* Added comments * Replaced Goto construct * Added apdu_len check * Added comments * Added string limit and reworked printable check * Mainly comments * Just comments * Just comments * decode service request returns now non zero on success * eliminated warnings * Added character string init with length check. * Paranoic length check * Comments and object index checking on read/write. * Check name/desc strings before returning. * Eliminated Goto
This commit is contained in:
@@ -422,7 +422,9 @@ static int get_local_ifr_ioctl(char *ifname, struct ifreq *ifr, int request)
|
||||
int fd;
|
||||
int rv; /* return value */
|
||||
|
||||
strncpy(ifr->ifr_name, ifname, sizeof(ifr->ifr_name));
|
||||
strncpy(ifr->ifr_name, ifname, sizeof(ifr->ifr_name) - 1);
|
||||
ifr->ifr_name[sizeof(ifr->ifr_name) - 1] = 0;
|
||||
|
||||
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (fd < 0) {
|
||||
rv = fd;
|
||||
@@ -443,7 +445,7 @@ static int get_local_ifr_ioctl(char *ifname, struct ifreq *ifr, int request)
|
||||
*/
|
||||
int bip_get_local_address_ioctl(char *ifname, struct in_addr *addr, int request)
|
||||
{
|
||||
struct ifreq ifr = { { { 0 } } };
|
||||
struct ifreq ifr = { {{0}}, {{0}} };
|
||||
struct sockaddr_in *tcpip_address;
|
||||
int rv; /* return value */
|
||||
|
||||
@@ -535,7 +537,7 @@ bool bip_init(char *ifname)
|
||||
struct sockaddr_in sin;
|
||||
int sockopt = 0;
|
||||
int sock_fd = -1;
|
||||
char *ifname_default = "eth0";
|
||||
static char *ifname_default = "eth0";
|
||||
|
||||
if (ifname) {
|
||||
bip_set_interface(ifname);
|
||||
|
||||
+30
-4
@@ -133,7 +133,17 @@ BACNET_ERROR_CODE abort_convert_to_error_code(BACNET_ABORT_REASON abort_code)
|
||||
return (error_code);
|
||||
}
|
||||
|
||||
/* encode service */
|
||||
/**
|
||||
* @brief Encode the BACnet Abort Service, returning the reason
|
||||
* for the operation being aborted.
|
||||
*
|
||||
* @param apdu Transmit buffer
|
||||
* @param invoke_id ID invoked
|
||||
* @param abort_reason Abort reason, see ABORT_REASON_X enumeration for details
|
||||
* @param server True, if the abort has been issued by this device.
|
||||
*
|
||||
* @return Total length of the apdu, typically 3 on success, zero otherwise.
|
||||
*/
|
||||
int abort_encode_apdu(
|
||||
uint8_t *apdu, uint8_t invoke_id, uint8_t abort_reason, bool server)
|
||||
{
|
||||
@@ -154,7 +164,17 @@ int abort_encode_apdu(
|
||||
}
|
||||
|
||||
#if !BACNET_SVC_SERVER
|
||||
/* decode the service request only */
|
||||
/**
|
||||
* @brief Decode the BACnet Abort Service, returning the reason
|
||||
* for the operation being aborted.
|
||||
*
|
||||
* @param apdu Receive buffer
|
||||
* @param apdu_len Count of bytes valid in the received buffer.
|
||||
* @param invoke_id Pointer to a variable, taking the invoked ID from the message.
|
||||
* @param abort_reason Pointer to a variable, taking the abort reason, see ABORT_REASON_X enumeration for details
|
||||
*
|
||||
* @return Total length of the apdu, typically 2 on success, zero otherwise.
|
||||
*/
|
||||
int abort_decode_service_request(
|
||||
uint8_t *apdu, unsigned apdu_len, uint8_t *invoke_id, uint8_t *abort_reason)
|
||||
{
|
||||
@@ -164,8 +184,14 @@ int abort_decode_service_request(
|
||||
if (invoke_id) {
|
||||
*invoke_id = apdu[0];
|
||||
}
|
||||
if (abort_reason) {
|
||||
*abort_reason = apdu[1];
|
||||
len++;
|
||||
|
||||
if (apdu_len > 1) {
|
||||
if (abort_reason) {
|
||||
*abort_reason = apdu[1];
|
||||
}
|
||||
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-15
@@ -37,11 +37,15 @@
|
||||
|
||||
/** @file alarm_ack.c Handles Event Notifications (ACKs) */
|
||||
|
||||
/***************************************************
|
||||
**
|
||||
** Creates an Unconfirmed Event Notification APDU
|
||||
**
|
||||
****************************************************/
|
||||
/**
|
||||
* @brief Creates an Unconfirmed Event Notification APDU
|
||||
*
|
||||
* @param apdu Transmission buffer
|
||||
* @param invoke_id Invoked ID
|
||||
* @param data Alarm acknowledge data that will be copied into the payload.
|
||||
*
|
||||
* @return Length of the APDU
|
||||
*/
|
||||
int alarm_ack_encode_apdu(
|
||||
uint8_t *apdu, uint8_t invoke_id, BACNET_ALARM_ACK_DATA *data)
|
||||
{
|
||||
@@ -62,11 +66,15 @@ int alarm_ack_encode_apdu(
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/***************************************************
|
||||
**
|
||||
** Encodes the service data part of Event Notification
|
||||
**
|
||||
****************************************************/
|
||||
/**
|
||||
* @brief Encodes the service data part of Event Notification.
|
||||
*
|
||||
* @param apdu Receive buffer
|
||||
* @param data Alarm acknowledge data that will be filled in
|
||||
* with the data from the payload.
|
||||
*
|
||||
* @return Total length of the apdu
|
||||
*/
|
||||
int alarm_ack_encode_service_request(uint8_t *apdu, BACNET_ALARM_ACK_DATA *data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
@@ -102,11 +110,15 @@ int alarm_ack_encode_service_request(uint8_t *apdu, BACNET_ALARM_ACK_DATA *data)
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/***************************************************
|
||||
**
|
||||
** Decodes the service data part of Event Notification
|
||||
**
|
||||
****************************************************/
|
||||
/**
|
||||
* @brief Decodes the service data part of Event Notification.
|
||||
*
|
||||
* @param apdu Receive buffer
|
||||
* @param apdu_len Bytes valid in the buffer.
|
||||
* @param data Alarm acknowledge data that will be filled.
|
||||
*
|
||||
* @return Total length of the apdu
|
||||
*/
|
||||
int alarm_ack_decode_service_request(
|
||||
uint8_t *apdu, unsigned apdu_len, BACNET_ALARM_ACK_DATA *data)
|
||||
{
|
||||
|
||||
+12
-2
@@ -158,8 +158,18 @@ int bacapp_encode_application_data(
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the data and store it into value.
|
||||
Return the number of octets consumed. */
|
||||
/**
|
||||
* @brief Decode the data and store it into value.
|
||||
* Return the number of octets consumed.
|
||||
*
|
||||
* @param apdu Receive buffer
|
||||
* @param tag_data_type Data type of the given tag
|
||||
* @param len_value_type Count of bytes of given tag
|
||||
* @param value Pointer to the application value structure,
|
||||
* used to store the decoded value to.
|
||||
*
|
||||
* @return Number of octets consumed.
|
||||
*/
|
||||
int bacapp_decode_data(uint8_t *apdu,
|
||||
uint8_t tag_data_type,
|
||||
uint32_t len_value_type,
|
||||
|
||||
+56
-14
@@ -1981,9 +1981,15 @@ int encode_context_signed(uint8_t *apdu, uint8_t tag_number, int32_t value)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* from clause 20.2.6 Encoding of a Real Number Value */
|
||||
/* and 20.2.1 General Rules for Encoding BACnet Tags */
|
||||
/* returns the number of apdu bytes consumed */
|
||||
/**
|
||||
* @brief Encode a real floating value. From clause 20.2.6 Encoding of a
|
||||
* Real Number Value and 20.2.1 General Rules for Encoding BACnet Tags.
|
||||
*
|
||||
* @param apdu Transmit buffer
|
||||
* @param value The float value to be encoded.
|
||||
*
|
||||
* @return Returns the number of apdu bytes consumed.
|
||||
*/
|
||||
int encode_application_real(uint8_t *apdu, float value)
|
||||
{
|
||||
int len = 0;
|
||||
@@ -1996,6 +2002,17 @@ int encode_application_real(uint8_t *apdu, float value)
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encode a real floating value with a given tag. From clause 20.2.6
|
||||
* Encoding of a Real Number Value and 20.2.1 General Rules for
|
||||
* Encoding BACnet Tags.
|
||||
*
|
||||
* @param apdu Transmit buffer
|
||||
* @param tag_number Tag number to be used
|
||||
* @param value The float value to be encoded.
|
||||
*
|
||||
* @return Returns the number of apdu bytes consumed.
|
||||
*/
|
||||
int encode_context_real(uint8_t *apdu, uint8_t tag_number, float value)
|
||||
{
|
||||
int len = 0;
|
||||
@@ -2356,7 +2373,15 @@ int decode_context_date(uint8_t *apdu, uint8_t tag_number, BACNET_DATE *bdate)
|
||||
return len;
|
||||
}
|
||||
|
||||
/* returns the number of apdu bytes consumed */
|
||||
/**
|
||||
* Encode a simple ACK and returns the number of apdu bytes consumed.
|
||||
*
|
||||
* @param apdu Transmit buffer
|
||||
* @param invoke_id ID invoked
|
||||
* @param service_choice Service being acked
|
||||
*
|
||||
* @return number of apdu bytes consumed
|
||||
*/
|
||||
int encode_simple_ack(uint8_t *apdu, uint8_t invoke_id, uint8_t service_choice)
|
||||
{
|
||||
apdu[0] = PDU_TYPE_SIMPLE_ACK;
|
||||
@@ -2366,24 +2391,41 @@ int encode_simple_ack(uint8_t *apdu, uint8_t invoke_id, uint8_t service_choice)
|
||||
return 3;
|
||||
}
|
||||
|
||||
/* BACnetAddress */
|
||||
/**
|
||||
* Encode a BACnetAddress and returns the number of apdu bytes consumed.
|
||||
*
|
||||
* @param apdu Transmit buffer
|
||||
* @param destination Pointer to the destination address to be encoded.
|
||||
*
|
||||
* @return number of apdu bytes created
|
||||
*/
|
||||
int encode_bacnet_address(uint8_t *apdu, BACNET_ADDRESS *destination)
|
||||
{
|
||||
int apdu_len = 0;
|
||||
BACNET_OCTET_STRING mac_addr;
|
||||
/* network number */
|
||||
apdu_len += encode_application_unsigned(&apdu[apdu_len], destination->net);
|
||||
/* encode mac address as an octet-string */
|
||||
if (destination->len != 0) {
|
||||
octetstring_init(&mac_addr, destination->adr, destination->len);
|
||||
} else {
|
||||
octetstring_init(&mac_addr, destination->mac, destination->mac_len);
|
||||
|
||||
if (destination) {
|
||||
/* network number */
|
||||
apdu_len += encode_application_unsigned(&apdu[apdu_len], destination->net);
|
||||
/* encode mac address as an octet-string */
|
||||
if (destination->len != 0) {
|
||||
octetstring_init(&mac_addr, destination->adr, destination->len);
|
||||
} else {
|
||||
octetstring_init(&mac_addr, destination->mac, destination->mac_len);
|
||||
}
|
||||
apdu_len += encode_application_octet_string(&apdu[apdu_len], &mac_addr);
|
||||
}
|
||||
apdu_len += encode_application_octet_string(&apdu[apdu_len], &mac_addr);
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* BACnetAddress */
|
||||
/**
|
||||
* Dencode a BACnetAddress and returns the number of apdu bytes consumed.
|
||||
*
|
||||
* @param apdu Receive buffer
|
||||
* @param destination Pointer to the destination address structure to be filled in.
|
||||
*
|
||||
* @return number of apdu bytes consumed
|
||||
*/
|
||||
int decode_bacnet_address(uint8_t *apdu, BACNET_ADDRESS *destination)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
+80
-23
@@ -299,6 +299,15 @@ bool characterstring_init(BACNET_CHARACTER_STRING *char_string,
|
||||
return status;
|
||||
}
|
||||
|
||||
bool characterstring_init_ansi_safe(
|
||||
BACNET_CHARACTER_STRING * char_string,
|
||||
const char *value,
|
||||
size_t tmax)
|
||||
{
|
||||
return characterstring_init(char_string, CHARACTER_ANSI_X34, value,
|
||||
value ? strnlen(value, tmax) : 0);
|
||||
}
|
||||
|
||||
bool characterstring_init_ansi(
|
||||
BACNET_CHARACTER_STRING *char_string, const char *value)
|
||||
{
|
||||
@@ -431,7 +440,13 @@ bool characterstring_truncate(
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Returns the value. */
|
||||
/**
|
||||
* @brief Returns the pointer to the C-string for the given BACnet string.
|
||||
*
|
||||
* @param char_string Pointer to the character string.
|
||||
*
|
||||
* @return Pointer to a zero-terminated C-string.
|
||||
*/
|
||||
char *characterstring_value(BACNET_CHARACTER_STRING *char_string)
|
||||
{
|
||||
char *value = NULL;
|
||||
@@ -443,19 +458,37 @@ char *characterstring_value(BACNET_CHARACTER_STRING *char_string)
|
||||
return value;
|
||||
}
|
||||
|
||||
/* returns the length. */
|
||||
/**
|
||||
* @brief Returns the length for the given BACnet string.
|
||||
*
|
||||
* @param char_string Pointer to the character string.
|
||||
*
|
||||
* @return Length of the charcater string, but
|
||||
* maximum MAX_CHARACTER_STRING_BYTES.
|
||||
*/
|
||||
size_t characterstring_length(BACNET_CHARACTER_STRING *char_string)
|
||||
{
|
||||
size_t length = 0;
|
||||
|
||||
if (char_string) {
|
||||
/* FIXME: validate length is within bounds? */
|
||||
length = char_string->length;
|
||||
|
||||
/* Length within bounds? */
|
||||
if (length > CHARACTER_STRING_CAPACITY) {
|
||||
length = CHARACTER_STRING_CAPACITY;
|
||||
}
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the possible capacity for the given BACnet string.
|
||||
*
|
||||
* @param char_string Pointer to the character string.
|
||||
*
|
||||
* @return MAX_CHARACTER_STRING_BYTES
|
||||
*/
|
||||
size_t characterstring_capacity(BACNET_CHARACTER_STRING *char_string)
|
||||
{
|
||||
size_t length = 0;
|
||||
@@ -467,7 +500,13 @@ size_t characterstring_capacity(BACNET_CHARACTER_STRING *char_string)
|
||||
return length;
|
||||
}
|
||||
|
||||
/* returns the encoding. */
|
||||
/**
|
||||
* @brief Returns the character encoding for the given BACnet string.
|
||||
*
|
||||
* @param char_string Pointer to the character string.
|
||||
*
|
||||
* @return Encoding, like CHARACTER_ANSI_X34
|
||||
*/
|
||||
uint8_t characterstring_encoding(BACNET_CHARACTER_STRING *char_string)
|
||||
{
|
||||
uint8_t encoding = 0;
|
||||
@@ -479,7 +518,14 @@ uint8_t characterstring_encoding(BACNET_CHARACTER_STRING *char_string)
|
||||
return encoding;
|
||||
}
|
||||
|
||||
/* returns the encoding. */
|
||||
/**
|
||||
* @brief Set the character encoding for the given BACnet string.
|
||||
*
|
||||
* @param char_string Pointer to the character string.
|
||||
* @param Encoding, like CHARACTER_ANSI_X34
|
||||
*
|
||||
* @return true/false on error
|
||||
*/
|
||||
bool characterstring_set_encoding(
|
||||
BACNET_CHARACTER_STRING *char_string, uint8_t encoding)
|
||||
{
|
||||
@@ -493,32 +539,43 @@ bool characterstring_set_encoding(
|
||||
return status;
|
||||
}
|
||||
|
||||
/* returns true if string is printable */
|
||||
/* used to assist in the requirement that
|
||||
"The set of characters used in the Object_Name shall be
|
||||
restricted to printable characters." */
|
||||
/* printable character: a character that represents a printable
|
||||
symbol as opposed to a device control character. These
|
||||
include, but are not limited to, upper- and lowercase letters,
|
||||
punctuation marks, and mathematical symbols. The exact set
|
||||
depends upon the character set being used. In ANSI X3.4 the
|
||||
printable characters are represented by single octets in the range
|
||||
X'20' - X'7E'.*/
|
||||
/**
|
||||
* @brief Returns true if string is printable.
|
||||
*
|
||||
* Used to assist in the requirement that
|
||||
* "The set of characters used in the Object_Name shall be
|
||||
* restricted to printable characters."
|
||||
*
|
||||
* Printable character: a character that represents a printable
|
||||
* symbol as opposed to a device control character. These
|
||||
* include, but are not limited to, upper- and lowercase letters,
|
||||
* punctuation marks, and mathematical symbols. The exact set
|
||||
* depends upon the character set being used. In ANSI X3.4 the
|
||||
* printable characters are represented by single octets in the range
|
||||
* X'20' - X'7E'.
|
||||
*
|
||||
* @param char_string Pointer to the character string.
|
||||
*
|
||||
* @return true/false on error
|
||||
*/
|
||||
bool characterstring_printable(BACNET_CHARACTER_STRING *char_string)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
size_t i; /* counter */
|
||||
size_t imax;
|
||||
char chr;
|
||||
|
||||
if (char_string) {
|
||||
if (char_string->encoding == CHARACTER_ANSI_X34) {
|
||||
status = true;
|
||||
for (i = 0; i < MAX_CHARACTER_STRING_BYTES; i++) {
|
||||
if (i < char_string->length) {
|
||||
if ((char_string->value[i] < 0x20) ||
|
||||
(char_string->value[i] > 0x7E)) {
|
||||
status = false;
|
||||
}
|
||||
} else {
|
||||
imax = char_string->length;
|
||||
if (imax > CHARACTER_STRING_CAPACITY) {
|
||||
imax = CHARACTER_STRING_CAPACITY;
|
||||
}
|
||||
for (i = 0; i < imax; i++) {
|
||||
chr = char_string->value[i];
|
||||
if ((chr < 0x20) || (chr > 0x7E)) {
|
||||
status = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,10 @@ extern "C" {
|
||||
bool characterstring_init_ansi(
|
||||
BACNET_CHARACTER_STRING * char_string,
|
||||
const char *value);
|
||||
bool characterstring_init_ansi_safe(
|
||||
BACNET_CHARACTER_STRING * char_string,
|
||||
const char *value,
|
||||
size_t tmax);
|
||||
bool characterstring_copy(
|
||||
BACNET_CHARACTER_STRING * dest,
|
||||
BACNET_CHARACTER_STRING * src);
|
||||
|
||||
@@ -94,11 +94,23 @@ void address_own_device_id_set(uint32_t own_id)
|
||||
Own_Device_ID = own_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if the given source and destination address can be matched
|
||||
* by checking the length, net and MAC address.
|
||||
*
|
||||
* @param dest Destination address
|
||||
* @param src Source address
|
||||
*
|
||||
* @return true for a match, false otherwise
|
||||
*/
|
||||
bool address_match(BACNET_ADDRESS *dest, BACNET_ADDRESS *src)
|
||||
{
|
||||
uint8_t i = 0;
|
||||
uint8_t max_len = 0;
|
||||
|
||||
if (dest == src) {
|
||||
return(true);
|
||||
}
|
||||
if (dest->mac_len != src->mac_len) {
|
||||
return false;
|
||||
}
|
||||
@@ -136,6 +148,11 @@ bool address_match(BACNET_ADDRESS *dest, BACNET_ADDRESS *src)
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove a device from the address list.
|
||||
*
|
||||
* @param device_id ID of the device
|
||||
*/
|
||||
void address_remove_device(uint32_t device_id)
|
||||
{
|
||||
struct Address_Cache_Entry *pMatch;
|
||||
@@ -158,13 +175,15 @@ void address_remove_device(uint32_t device_id)
|
||||
return;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Search the cache for the entry nearest expiry and delete it. Mark the *
|
||||
* entry as reserved with a 1 hour TTL and return a pointer to the reserved *
|
||||
* entry. Will not delete a static entry and returns NULL pointer if no *
|
||||
* entry available to free up. Does not check for free entries as it is *
|
||||
* assumed we are calling this due to the lack of those. *
|
||||
*****************************************************************************/
|
||||
/**
|
||||
* @brief Search the cache for the entry nearest expiry and delete it. Mark the
|
||||
* entry as reserved with a 1 hour TTL and return a pointer to the reserved
|
||||
* entry. Will not delete a static entry and returns NULL pointer if no
|
||||
* entry available to free up. Does not check for free entries as it is
|
||||
* assumed we are calling this due to the lack of those.
|
||||
*
|
||||
* @return Candidate for deletation.
|
||||
*/
|
||||
|
||||
static struct Address_Cache_Entry *address_remove_oldest(void)
|
||||
{
|
||||
@@ -224,7 +243,8 @@ static struct Address_Cache_Entry *address_remove_oldest(void)
|
||||
return (pCandidate);
|
||||
}
|
||||
|
||||
/** Initialize a BACNET_MAC_ADDRESS
|
||||
/**
|
||||
* Initialize a BACNET_MAC_ADDRESS
|
||||
*
|
||||
* @param mac [out] BACNET_MAC_ADDRESS structure
|
||||
* @param adr [in] address to initialize, null if empty
|
||||
|
||||
@@ -70,6 +70,10 @@ void npdu_handler(BACNET_ADDRESS *src, /* source address */
|
||||
BACNET_ADDRESS dest = { 0 };
|
||||
BACNET_NPDU_DATA npdu_data = { 0 };
|
||||
|
||||
if (pdu_len < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* only handle the version that we know how to handle */
|
||||
if (pdu[0] == BACNET_PROTOCOL_VERSION) {
|
||||
apdu_offset = npdu_decode(&pdu[0], &dest, src, &npdu_data);
|
||||
|
||||
+113
-24
@@ -63,6 +63,13 @@ static const int Analog_Value_Properties_Optional[] = { PROP_DESCRIPTION,
|
||||
|
||||
static const int Analog_Value_Properties_Proprietary[] = { -1 };
|
||||
|
||||
/**
|
||||
* Initialize the pointers for the required, the optional and the properitary value properties.
|
||||
*
|
||||
* @param pRequired - Pointer to the pointer of required values.
|
||||
* @param pOptional - Pointer to the pointer of optional values.
|
||||
* @param pProprietary - Pointer to the pointer of properitary values.
|
||||
*/
|
||||
void Analog_Value_Property_Lists(
|
||||
const int **pRequired, const int **pOptional, const int **pProprietary)
|
||||
{
|
||||
@@ -79,6 +86,9 @@ void Analog_Value_Property_Lists(
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the analog values.
|
||||
*/
|
||||
void Analog_Value_Init(void)
|
||||
{
|
||||
unsigned i;
|
||||
@@ -116,9 +126,15 @@ void Analog_Value_Init(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then you need validate that the */
|
||||
/* given instance exists */
|
||||
/**
|
||||
* We simply have 0-n object instances. Yours might be
|
||||
* more complex, and then you need validate that the
|
||||
* given instance exists.
|
||||
*
|
||||
* @param object_instance Object instance
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
bool Analog_Value_Valid_Instance(uint32_t object_instance)
|
||||
{
|
||||
if (object_instance < MAX_ANALOG_VALUES) {
|
||||
@@ -128,24 +144,39 @@ bool Analog_Value_Valid_Instance(uint32_t object_instance)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then count how many you have */
|
||||
/**
|
||||
* Return the count of analog values.
|
||||
*
|
||||
* @return Count of analog values.
|
||||
*/
|
||||
unsigned Analog_Value_Count(void)
|
||||
{
|
||||
return MAX_ANALOG_VALUES;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then you need to return the instance */
|
||||
/* that correlates to the correct index */
|
||||
/**
|
||||
* We simply have 0-n object instances. Yours might be
|
||||
* more complex, and then you need to return the instance
|
||||
* that correlates to the correct index.
|
||||
*
|
||||
* @param index Index
|
||||
*
|
||||
* @return Object instance
|
||||
*/
|
||||
uint32_t Analog_Value_Index_To_Instance(unsigned index)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then you need to return the index */
|
||||
/* that correlates to the correct instance number */
|
||||
/**
|
||||
* We simply have 0-n object instances. Yours might be
|
||||
* more complex, and then you need to return the index
|
||||
* that correlates to the correct instance number
|
||||
*
|
||||
* @param object_instance Object instance
|
||||
*
|
||||
* @return Index in the object table.
|
||||
*/
|
||||
unsigned Analog_Value_Instance_To_Index(uint32_t object_instance)
|
||||
{
|
||||
unsigned index = MAX_ANALOG_VALUES;
|
||||
@@ -157,6 +188,16 @@ unsigned Analog_Value_Instance_To_Index(uint32_t object_instance)
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used to detect a value change,
|
||||
* using the new value compared against the prior
|
||||
* value, using a delta as threshold.
|
||||
*
|
||||
* This method will update the COV-changed attribute.
|
||||
*
|
||||
* @param index Object index
|
||||
* @param value Given present value.
|
||||
*/
|
||||
static void Analog_Value_COV_Detect(unsigned int index, float value)
|
||||
{
|
||||
float prior_value = 0.0;
|
||||
@@ -203,6 +244,13 @@ bool Analog_Value_Present_Value_Set(
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given object instance-number, return the present value.
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
*
|
||||
* @return Present value
|
||||
*/
|
||||
float Analog_Value_Present_Value(uint32_t object_instance)
|
||||
{
|
||||
float value = 0;
|
||||
@@ -216,7 +264,16 @@ float Analog_Value_Present_Value(uint32_t object_instance)
|
||||
return value;
|
||||
}
|
||||
|
||||
/* note: the object name must be unique within this device */
|
||||
/**
|
||||
* For a given object instance-number, return the name.
|
||||
*
|
||||
* Note: the object name must be unique within this device
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param object_name - object name/string pointer
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
bool Analog_Value_Object_Name(
|
||||
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
|
||||
{
|
||||
@@ -370,7 +427,13 @@ void Analog_Value_Out_Of_Service_Set(uint32_t object_instance, bool value)
|
||||
}
|
||||
}
|
||||
|
||||
/* return apdu len, or BACNET_STATUS_ERROR on error */
|
||||
/**
|
||||
* Return the requested property of the analog value.
|
||||
*
|
||||
* @param rpdata Property requested, see for BACNET_READ_PROPERTY_DATA details.
|
||||
*
|
||||
* @return apdu len, or BACNET_STATUS_ERROR on error
|
||||
*/
|
||||
int Analog_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
{
|
||||
int apdu_len = 0; /* return value */
|
||||
@@ -386,7 +449,11 @@ int Analog_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
unsigned i = 0;
|
||||
#endif
|
||||
|
||||
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
|
||||
/* Valid data? */
|
||||
if (rpdata == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if ((rpdata->application_data == NULL) ||
|
||||
(rpdata->application_data_len == 0)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -394,12 +461,14 @@ int Analog_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
apdu = rpdata->application_data;
|
||||
|
||||
object_index = Analog_Value_Instance_To_Index(rpdata->object_instance);
|
||||
if (object_index < MAX_ANALOG_VALUES) {
|
||||
CurrentAV = &AV_Descr[object_index];
|
||||
} else {
|
||||
if (object_index >= MAX_ANALOG_VALUES) {
|
||||
rpdata->error_class = ERROR_CLASS_OBJECT;
|
||||
rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
|
||||
CurrentAV = &AV_Descr[object_index];
|
||||
|
||||
switch (rpdata->object_property) {
|
||||
case PROP_OBJECT_IDENTIFIER:
|
||||
apdu_len = encode_application_object_id(
|
||||
@@ -408,9 +477,10 @@ int Analog_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
|
||||
case PROP_OBJECT_NAME:
|
||||
case PROP_DESCRIPTION:
|
||||
Analog_Value_Object_Name(rpdata->object_instance, &char_string);
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
if (Analog_Value_Object_Name(rpdata->object_instance, &char_string)) {
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
}
|
||||
break;
|
||||
|
||||
case PROP_OBJECT_TYPE:
|
||||
@@ -594,7 +664,13 @@ int Analog_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* returns true if successful */
|
||||
/**
|
||||
* Set the requested property of the analog value.
|
||||
*
|
||||
* @param wp_data Property requested, see for BACNET_WRITE_PROPERTY_DATA details.
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool Analog_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
@@ -603,6 +679,15 @@ bool Analog_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
BACNET_APPLICATION_DATA_VALUE value;
|
||||
ANALOG_VALUE_DESCR *CurrentAV;
|
||||
|
||||
/* Valid data? */
|
||||
if (wp_data == NULL) {
|
||||
return false;
|
||||
}
|
||||
if ((wp_data->application_data == NULL) ||
|
||||
(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);
|
||||
@@ -621,13 +706,17 @@ bool Analog_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Valid object? */
|
||||
object_index = Analog_Value_Instance_To_Index(wp_data->object_instance);
|
||||
if (object_index < MAX_ANALOG_VALUES) {
|
||||
CurrentAV = &AV_Descr[object_index];
|
||||
} else {
|
||||
if (object_index >= MAX_ANALOG_VALUES) {
|
||||
wp_data->error_class = ERROR_CLASS_OBJECT;
|
||||
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
return false;
|
||||
}
|
||||
|
||||
CurrentAV = &AV_Descr[object_index];
|
||||
|
||||
switch (wp_data->object_property) {
|
||||
case PROP_PRESENT_VALUE:
|
||||
if (value.tag == BACNET_APPLICATION_TAG_REAL) {
|
||||
|
||||
+126
-29
@@ -62,6 +62,13 @@ static const int Binary_Value_Properties_Optional[] = { PROP_DESCRIPTION,
|
||||
|
||||
static const int Binary_Value_Properties_Proprietary[] = { -1 };
|
||||
|
||||
/**
|
||||
* Initialize the pointers for the required, the optional and the properitary value properties.
|
||||
*
|
||||
* @param pRequired - Pointer to the pointer of required values.
|
||||
* @param pOptional - Pointer to the pointer of optional values.
|
||||
* @param pProprietary - Pointer to the pointer of properitary values.
|
||||
*/
|
||||
void Binary_Value_Property_Lists(
|
||||
const int **pRequired, const int **pOptional, const int **pProprietary)
|
||||
{
|
||||
@@ -78,6 +85,9 @@ void Binary_Value_Property_Lists(
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the binary values.
|
||||
*/
|
||||
void Binary_Value_Init(void)
|
||||
{
|
||||
unsigned i, j;
|
||||
@@ -97,9 +107,15 @@ void Binary_Value_Init(void)
|
||||
return;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then you need validate that the */
|
||||
/* given instance exists */
|
||||
/**
|
||||
* We simply have 0-n object instances. Yours might be
|
||||
* more complex, and then you need validate that the
|
||||
* given instance exists.
|
||||
*
|
||||
* @param object_instance Object instance
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
bool Binary_Value_Valid_Instance(uint32_t object_instance)
|
||||
{
|
||||
if (object_instance < MAX_BINARY_VALUES) {
|
||||
@@ -109,24 +125,39 @@ bool Binary_Value_Valid_Instance(uint32_t object_instance)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then count how many you have */
|
||||
/**
|
||||
* Return the count of analog values.
|
||||
*
|
||||
* @return Count of binary values.
|
||||
*/
|
||||
unsigned Binary_Value_Count(void)
|
||||
{
|
||||
return MAX_BINARY_VALUES;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then you need to return the instance */
|
||||
/* that correlates to the correct index */
|
||||
/**
|
||||
* We simply have 0-n object instances. Yours might be
|
||||
* more complex, and then you need to return the instance
|
||||
* that correlates to the correct index.
|
||||
*
|
||||
* @param index Index
|
||||
*
|
||||
* @return Object instance
|
||||
*/
|
||||
uint32_t Binary_Value_Index_To_Instance(unsigned index)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then you need to return the index */
|
||||
/* that correlates to the correct instance number */
|
||||
/**
|
||||
* We simply have 0-n object instances. Yours might be
|
||||
* more complex, and then you need to return the index
|
||||
* that correlates to the correct instance number
|
||||
*
|
||||
* @param object_instance Object instance
|
||||
*
|
||||
* @return Index in the object table.
|
||||
*/
|
||||
unsigned Binary_Value_Instance_To_Index(uint32_t object_instance)
|
||||
{
|
||||
unsigned index = MAX_BINARY_VALUES;
|
||||
@@ -138,6 +169,13 @@ unsigned Binary_Value_Instance_To_Index(uint32_t object_instance)
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given object instance-number, return the present value.
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
*
|
||||
* @return Present value
|
||||
*/
|
||||
BACNET_BINARY_PV Binary_Value_Present_Value(uint32_t object_instance)
|
||||
{
|
||||
BACNET_BINARY_PV value = RELINQUISH_DEFAULT;
|
||||
@@ -157,7 +195,16 @@ BACNET_BINARY_PV Binary_Value_Present_Value(uint32_t object_instance)
|
||||
return value;
|
||||
}
|
||||
|
||||
/* note: the object name must be unique within this device */
|
||||
/**
|
||||
* For a given object instance-number, return the name.
|
||||
*
|
||||
* Note: the object name must be unique within this device
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param object_name - object name/string pointer
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
bool Binary_Value_Object_Name(
|
||||
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
|
||||
{
|
||||
@@ -173,6 +220,13 @@ bool Binary_Value_Object_Name(
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the OOO value, if any.
|
||||
*
|
||||
* @param instance Object instance.
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
bool Binary_Value_Out_Of_Service(uint32_t instance)
|
||||
{
|
||||
unsigned index = 0;
|
||||
@@ -186,6 +240,12 @@ bool Binary_Value_Out_Of_Service(uint32_t instance)
|
||||
return oos_flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the OOO value, if any.
|
||||
*
|
||||
* @param instance Object instance.
|
||||
* @param oos_flag New OOO value.
|
||||
*/
|
||||
void Binary_Value_Out_Of_Service_Set(uint32_t instance, bool oos_flag)
|
||||
{
|
||||
unsigned index = 0;
|
||||
@@ -196,7 +256,13 @@ void Binary_Value_Out_Of_Service_Set(uint32_t instance, bool oos_flag)
|
||||
}
|
||||
}
|
||||
|
||||
/* return apdu len, or BACNET_STATUS_ERROR on error */
|
||||
/**
|
||||
* Return the requested property of the binary value.
|
||||
*
|
||||
* @param rpdata Property requested, see for BACNET_READ_PROPERTY_DATA details.
|
||||
*
|
||||
* @return apdu len, or BACNET_STATUS_ERROR on error
|
||||
*/
|
||||
int Binary_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
{
|
||||
int len = 0;
|
||||
@@ -209,10 +275,23 @@ int Binary_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
bool state = false;
|
||||
uint8_t *apdu = NULL;
|
||||
|
||||
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
|
||||
/* Valid data? */
|
||||
if (rpdata == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if ((rpdata->application_data == NULL) ||
|
||||
(rpdata->application_data_len == 0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Valid object index? */
|
||||
object_index = Binary_Value_Instance_To_Index(rpdata->object_instance);
|
||||
if (object_index >= MAX_BINARY_VALUES) {
|
||||
rpdata->error_class = ERROR_CLASS_OBJECT;
|
||||
rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
|
||||
apdu = rpdata->application_data;
|
||||
switch (rpdata->object_property) {
|
||||
case PROP_OBJECT_IDENTIFIER:
|
||||
@@ -223,9 +302,10 @@ int Binary_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
You could make Description writable and different */
|
||||
case PROP_OBJECT_NAME:
|
||||
case PROP_DESCRIPTION:
|
||||
Binary_Value_Object_Name(rpdata->object_instance, &char_string);
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
if (Binary_Value_Object_Name(rpdata->object_instance, &char_string)) {
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
}
|
||||
break;
|
||||
case PROP_OBJECT_TYPE:
|
||||
apdu_len =
|
||||
@@ -263,8 +343,6 @@ int Binary_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
*/
|
||||
/* into one packet. */
|
||||
} else if (rpdata->array_index == BACNET_ARRAY_ALL) {
|
||||
object_index =
|
||||
Binary_Value_Instance_To_Index(rpdata->object_instance);
|
||||
for (i = 0; i < BACNET_MAX_PRIORITY; i++) {
|
||||
/* FIXME: check if we have room before adding it to APDU */
|
||||
if (Binary_Value_Level[object_index][i] == BINARY_NULL) {
|
||||
@@ -285,8 +363,6 @@ int Binary_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
object_index =
|
||||
Binary_Value_Instance_To_Index(rpdata->object_instance);
|
||||
if (rpdata->array_index <= BACNET_MAX_PRIORITY) {
|
||||
if (Binary_Value_Level[object_index][rpdata->array_index] ==
|
||||
BINARY_NULL) {
|
||||
@@ -314,7 +390,8 @@ int Binary_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
apdu_len = BACNET_STATUS_ERROR;
|
||||
break;
|
||||
}
|
||||
/* only array properties can have array options */
|
||||
|
||||
/* Only array properties can have array options. */
|
||||
if ((apdu_len >= 0) && (rpdata->object_property != PROP_PRIORITY_ARRAY) &&
|
||||
(rpdata->array_index != BACNET_ARRAY_ALL)) {
|
||||
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
||||
@@ -325,7 +402,13 @@ int Binary_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* returns true if successful */
|
||||
/**
|
||||
* Set the requested property of the binary value.
|
||||
*
|
||||
* @param wp_data Property requested, see for BACNET_WRITE_PROPERTY_DATA details.
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool Binary_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
@@ -335,7 +418,16 @@ bool Binary_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
int len = 0;
|
||||
BACNET_APPLICATION_DATA_VALUE value;
|
||||
|
||||
/* decode the some of the request */
|
||||
/* Valid data? */
|
||||
if (wp_data == NULL) {
|
||||
return false;
|
||||
}
|
||||
if ((wp_data->application_data == NULL) || \
|
||||
(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);
|
||||
/* FIXME: len < application_data_len: more data? */
|
||||
@@ -345,13 +437,22 @@ bool Binary_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
return false;
|
||||
}
|
||||
/* only array properties can have array options */
|
||||
/* Only array properties can have array options. */
|
||||
if ((wp_data->object_property != PROP_PRIORITY_ARRAY) &&
|
||||
(wp_data->array_index != BACNET_ARRAY_ALL)) {
|
||||
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
||||
wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Valid object index? */
|
||||
object_index = Binary_Value_Instance_To_Index(wp_data->object_instance);
|
||||
if (object_index >= MAX_BINARY_VALUES) {
|
||||
wp_data->error_class = ERROR_CLASS_OBJECT;
|
||||
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (wp_data->object_property) {
|
||||
case PROP_PRESENT_VALUE:
|
||||
if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
|
||||
@@ -363,8 +464,6 @@ bool Binary_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
(priority != 6 /* reserved */) &&
|
||||
(value.type.Enumerated <= MAX_BINARY_PV)) {
|
||||
level = (BACNET_BINARY_PV)value.type.Enumerated;
|
||||
object_index = Binary_Value_Instance_To_Index(
|
||||
wp_data->object_instance);
|
||||
priority--;
|
||||
Binary_Value_Level[object_index][priority] = level;
|
||||
/* Note: you could set the physical output here if we
|
||||
@@ -389,8 +488,6 @@ bool Binary_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
&wp_data->error_class, &wp_data->error_code);
|
||||
if (status) {
|
||||
level = BINARY_NULL;
|
||||
object_index = Binary_Value_Instance_To_Index(
|
||||
wp_data->object_instance);
|
||||
priority = wp_data->priority;
|
||||
if (priority && (priority <= BACNET_MAX_PRIORITY)) {
|
||||
priority--;
|
||||
|
||||
+172
-33
@@ -28,6 +28,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "bacnet/bacdef.h"
|
||||
#include "bacnet/bacdcode.h"
|
||||
#include "bacnet/bacenum.h"
|
||||
@@ -60,6 +61,13 @@ static const int Properties_Optional[] = { PROP_EVENT_STATE,
|
||||
|
||||
static const int Properties_Proprietary[] = { -1 };
|
||||
|
||||
/**
|
||||
* Initialize the pointers for the required, the optional and the properitary value properties.
|
||||
*
|
||||
* @param pRequired - Pointer to the pointer of required values.
|
||||
* @param pOptional - Pointer to the pointer of optional values.
|
||||
* @param pProprietary - Pointer to the pointer of properitary values.
|
||||
*/
|
||||
void CharacterString_Value_Property_Lists(
|
||||
const int **pRequired, const int **pOptional, const int **pProprietary)
|
||||
{
|
||||
@@ -76,6 +84,9 @@ void CharacterString_Value_Property_Lists(
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the character string values.
|
||||
*/
|
||||
void CharacterString_Value_Init(void)
|
||||
{
|
||||
unsigned i;
|
||||
@@ -92,9 +103,15 @@ void CharacterString_Value_Init(void)
|
||||
return;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then you need to return the index */
|
||||
/* that correlates to the correct instance number */
|
||||
/**
|
||||
* We simply have 0-n object instances. Yours might be
|
||||
* more complex, and then you need to return the instance
|
||||
* that correlates to the correct index.
|
||||
*
|
||||
* @param object_instance Object instance
|
||||
*
|
||||
* @return Object instance
|
||||
*/
|
||||
unsigned CharacterString_Value_Instance_To_Index(uint32_t object_instance)
|
||||
{
|
||||
unsigned index = MAX_CHARACTERSTRING_VALUES;
|
||||
@@ -106,21 +123,39 @@ unsigned CharacterString_Value_Instance_To_Index(uint32_t object_instance)
|
||||
return index;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then you need to return the instance */
|
||||
/* that correlates to the correct index */
|
||||
/**
|
||||
* We simply have 0-n object instances. Yours might be
|
||||
* more complex, and then you need to return the index
|
||||
* that correlates to the correct instance number
|
||||
*
|
||||
* @param index Object index
|
||||
*
|
||||
* @return Index in the object table.
|
||||
*/
|
||||
uint32_t CharacterString_Value_Index_To_Instance(unsigned index)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
/* we simply have 0-n object instances. Yours might be */
|
||||
/* more complex, and then count how many you have */
|
||||
/**
|
||||
* Return the count of character string values.
|
||||
*
|
||||
* @return Count of character string values.
|
||||
*/
|
||||
unsigned CharacterString_Value_Count(void)
|
||||
{
|
||||
return MAX_CHARACTERSTRING_VALUES;
|
||||
}
|
||||
|
||||
/**
|
||||
* We simply have 0-n object instances. Yours might be
|
||||
* more complex, and then you need validate that the
|
||||
* given instance exists.
|
||||
*
|
||||
* @param object_instance Object instance
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
bool CharacterString_Value_Valid_Instance(uint32_t object_instance)
|
||||
{
|
||||
unsigned index = 0; /* offset from instance lookup */
|
||||
@@ -133,6 +168,16 @@ bool CharacterString_Value_Valid_Instance(uint32_t object_instance)
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given object instance-number, read the present-value.
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param object_name - Pointer to the new BACnet string value,
|
||||
* taking the value.
|
||||
*
|
||||
* @return true if values are within range and present-value
|
||||
* is returned.
|
||||
*/
|
||||
bool CharacterString_Value_Present_Value(
|
||||
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
|
||||
{
|
||||
@@ -147,6 +192,15 @@ bool CharacterString_Value_Present_Value(
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given object instance-number, sets the present-value,
|
||||
* taken from another BACnet string.
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param object_name - Pointer to the new BACnet string value.
|
||||
*
|
||||
* @return true if values are within range and present-value is set.
|
||||
*/
|
||||
bool CharacterString_Value_Present_Value_Set(
|
||||
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
|
||||
{
|
||||
@@ -161,6 +215,13 @@ bool CharacterString_Value_Present_Value_Set(
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given object instance-number, read the out of service value.
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
bool CharacterString_Value_Out_Of_Service(uint32_t object_instance)
|
||||
{
|
||||
bool value = false;
|
||||
@@ -174,6 +235,12 @@ bool CharacterString_Value_Out_Of_Service(uint32_t object_instance)
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given object instance-number, set the out of service value.
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param true/false
|
||||
*/
|
||||
static void CharacterString_Value_Out_Of_Service_Set(
|
||||
uint32_t object_instance, bool value)
|
||||
{
|
||||
@@ -187,6 +254,13 @@ static void CharacterString_Value_Out_Of_Service_Set(
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given object instance-number, return the description.
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
*
|
||||
* @return C-string pointer to the description.
|
||||
*/
|
||||
static char *CharacterString_Value_Description(uint32_t object_instance)
|
||||
{
|
||||
unsigned index = 0; /* offset from instance lookup */
|
||||
@@ -200,8 +274,16 @@ static char *CharacterString_Value_Description(uint32_t object_instance)
|
||||
return pName;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given object instance-number, set the description text.
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param new_descr - C-String pointer to the string, representing the description text
|
||||
*
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool CharacterString_Value_Description_Set(
|
||||
uint32_t object_instance, char *new_name)
|
||||
uint32_t object_instance, char *new_descr)
|
||||
{
|
||||
unsigned index = 0; /* offset from instance lookup */
|
||||
size_t i = 0; /* loop counter */
|
||||
@@ -210,23 +292,29 @@ bool CharacterString_Value_Description_Set(
|
||||
index = CharacterString_Value_Instance_To_Index(object_instance);
|
||||
if (index < MAX_CHARACTERSTRING_VALUES) {
|
||||
status = true;
|
||||
if (new_name) {
|
||||
if (new_descr) {
|
||||
for (i = 0; i < sizeof(Object_Description[index]); i++) {
|
||||
Object_Description[index][i] = new_name[i];
|
||||
if (new_name[i] == 0) {
|
||||
Object_Description[index][i] = new_descr[i];
|
||||
if (new_descr[i] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < sizeof(Object_Description[index]); i++) {
|
||||
Object_Description[index][i] = 0;
|
||||
}
|
||||
memset(&Object_Description[index][0], 0, sizeof(Object_Description[index]));
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given object instance-number, return the object text.
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param object_name - Pointer to the BACnet string object that shall take the object name
|
||||
*
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool CharacterString_Value_Object_Name(
|
||||
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
|
||||
{
|
||||
@@ -241,7 +329,16 @@ bool CharacterString_Value_Object_Name(
|
||||
return status;
|
||||
}
|
||||
|
||||
/* note: the object name must be unique within this device */
|
||||
/**
|
||||
* For a given object instance-number, set the object text.
|
||||
*
|
||||
* Note: the object name must be unique within this device
|
||||
*
|
||||
* @param object_instance - object-instance number of the object
|
||||
* @param new_name - C-String pointer to the text, representing the object name
|
||||
*
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
bool CharacterString_Value_Name_Set(uint32_t object_instance, char *new_name)
|
||||
{
|
||||
unsigned index = 0; /* offset from instance lookup */
|
||||
@@ -260,16 +357,20 @@ bool CharacterString_Value_Name_Set(uint32_t object_instance, char *new_name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < sizeof(Object_Name[index]); i++) {
|
||||
Object_Name[index][i] = 0;
|
||||
}
|
||||
memset(&Object_Name[index][0], 0, sizeof(Object_Name[index]));
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* return apdu len, or BACNET_STATUS_ERROR on error */
|
||||
/**
|
||||
* Return the requested property of the character string value.
|
||||
*
|
||||
* @param rpdata Property requested, see for BACNET_READ_PROPERTY_DATA details.
|
||||
*
|
||||
* @return apdu len, or BACNET_STATUS_ERROR on error
|
||||
*/
|
||||
int CharacterString_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
{
|
||||
int apdu_len = 0; /* return value */
|
||||
@@ -279,11 +380,25 @@ int CharacterString_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
bool state = false;
|
||||
uint8_t *apdu = NULL;
|
||||
|
||||
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
|
||||
/* Valid data? */
|
||||
if (rpdata == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if ((rpdata->application_data == NULL) || \
|
||||
(rpdata->application_data_len == 0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Valid object? */
|
||||
object_index = CharacterString_Value_Instance_To_Index(rpdata->object_instance);
|
||||
if (object_index >= MAX_CHARACTERSTRING_VALUES) {
|
||||
rpdata->error_class = ERROR_CLASS_OBJECT;
|
||||
rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
|
||||
apdu = rpdata->application_data;
|
||||
|
||||
switch (rpdata->object_property) {
|
||||
case PROP_OBJECT_IDENTIFIER:
|
||||
apdu_len = encode_application_object_id(&apdu[0],
|
||||
@@ -292,16 +407,18 @@ int CharacterString_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
/* note: Name and Description don't have to be the same.
|
||||
You could make Description writable and different */
|
||||
case PROP_OBJECT_NAME:
|
||||
CharacterString_Value_Object_Name(
|
||||
rpdata->object_instance, &char_string);
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
if (CharacterString_Value_Object_Name(
|
||||
rpdata->object_instance, &char_string)) {
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
}
|
||||
break;
|
||||
case PROP_DESCRIPTION:
|
||||
characterstring_init_ansi(&char_string,
|
||||
CharacterString_Value_Description(rpdata->object_instance));
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
if (characterstring_init_ansi(&char_string,
|
||||
CharacterString_Value_Description(rpdata->object_instance))) {
|
||||
apdu_len =
|
||||
encode_application_character_string(&apdu[0], &char_string);
|
||||
}
|
||||
break;
|
||||
case PROP_OBJECT_TYPE:
|
||||
apdu_len = encode_application_enumerated(
|
||||
@@ -334,8 +451,6 @@ int CharacterString_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
|
||||
break;
|
||||
case PROP_OUT_OF_SERVICE:
|
||||
object_index = CharacterString_Value_Instance_To_Index(
|
||||
rpdata->object_instance);
|
||||
state = Out_Of_Service[object_index];
|
||||
apdu_len = encode_application_boolean(&apdu[0], state);
|
||||
break;
|
||||
@@ -356,14 +471,29 @@ int CharacterString_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* returns true if successful */
|
||||
/**
|
||||
* Set the requested property of the character string value.
|
||||
*
|
||||
* @param wp_data Property requested, see for BACNET_WRITE_PROPERTY_DATA details.
|
||||
*
|
||||
* @return true if successful
|
||||
*/
|
||||
bool CharacterString_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
int len = 0;
|
||||
unsigned object_index = 0;
|
||||
BACNET_APPLICATION_DATA_VALUE value;
|
||||
|
||||
/* decode the some of the request */
|
||||
if (wp_data == NULL) {
|
||||
return false;
|
||||
}
|
||||
if ((wp_data->application_data == NULL) || \
|
||||
(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);
|
||||
/* FIXME: len < application_data_len: more data? */
|
||||
@@ -373,6 +503,15 @@ bool CharacterString_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
||||
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Valid object? */
|
||||
object_index = CharacterString_Value_Instance_To_Index(wp_data->object_instance);
|
||||
if (object_index >= MAX_CHARACTERSTRING_VALUES) {
|
||||
wp_data->error_class = ERROR_CLASS_OBJECT;
|
||||
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (wp_data->object_property) {
|
||||
case PROP_PRESENT_VALUE:
|
||||
status = WPValidateArgType(&value,
|
||||
|
||||
+228
-142
@@ -94,6 +94,12 @@ static BACNET_SERVICES_SUPPORTED
|
||||
/* If they are not set, they are handled by a reject message */
|
||||
static confirmed_function Confirmed_Function[MAX_BACNET_CONFIRMED_SERVICE];
|
||||
|
||||
/**
|
||||
* @brief Set a handler function for the given confirmed service.
|
||||
*
|
||||
* @param service_choice Service, see SERVICE_CONFIRMED_X enumeration.
|
||||
* @param pFunction Pointer to the function, being in charge of the service.
|
||||
*/
|
||||
void apdu_set_confirmed_handler(
|
||||
BACNET_CONFIRMED_SERVICE service_choice, confirmed_function pFunction)
|
||||
{
|
||||
@@ -105,6 +111,12 @@ void apdu_set_confirmed_handler(
|
||||
/* Allow the APDU handler to automatically reject */
|
||||
static confirmed_function Unrecognized_Service_Handler;
|
||||
|
||||
/**
|
||||
* @brief Set a handler function called for an unsupported service.
|
||||
*
|
||||
* @param pFunction Pointer to the function, being in charge,
|
||||
* if a unsupported service has been requested.
|
||||
*/
|
||||
void apdu_set_unrecognized_service_handler_handler(confirmed_function pFunction)
|
||||
{
|
||||
Unrecognized_Service_Handler = pFunction;
|
||||
@@ -115,6 +127,12 @@ void apdu_set_unrecognized_service_handler_handler(confirmed_function pFunction)
|
||||
static unconfirmed_function
|
||||
Unconfirmed_Function[MAX_BACNET_UNCONFIRMED_SERVICE];
|
||||
|
||||
/**
|
||||
* @brief Set a handler function for the given unconfirmed service.
|
||||
*
|
||||
* @param service_choice Service, see SERVICE_UNCONFIRMED_X enumeration.
|
||||
* @param pFunction Pointer to the function, being in charge of the service.
|
||||
*/
|
||||
void apdu_set_unconfirmed_handler(
|
||||
BACNET_UNCONFIRMED_SERVICE service_choice, unconfirmed_function pFunction)
|
||||
{
|
||||
@@ -123,6 +141,13 @@ void apdu_set_unconfirmed_handler(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the given service is supported or not.
|
||||
*
|
||||
* @param service_supported Service in question
|
||||
*
|
||||
* @return true/false
|
||||
*/
|
||||
bool apdu_service_supported(BACNET_SERVICES_SUPPORTED service_supported)
|
||||
{
|
||||
int i = 0;
|
||||
@@ -278,6 +303,12 @@ void apdu_set_confirmed_ack_handler(
|
||||
|
||||
static error_function Error_Function[MAX_BACNET_CONFIRMED_SERVICE];
|
||||
|
||||
/**
|
||||
* @brief Set a error handler function for the given confirmed service.
|
||||
*
|
||||
* @param service_choice Service, see SERVICE_CONFIRMED_X enumeration.
|
||||
* @param pFunction Pointer to the function, being in charge of the error handling.
|
||||
*/
|
||||
void apdu_set_error_handler(
|
||||
BACNET_CONFIRMED_SERVICE service_choice, error_function pFunction)
|
||||
{
|
||||
@@ -295,11 +326,35 @@ void apdu_set_abort_handler(abort_function pFunction)
|
||||
|
||||
static reject_function Reject_Function;
|
||||
|
||||
/**
|
||||
* @brief Set a handler function called for a rejected service.
|
||||
*
|
||||
* @param pFunction Pointer to the function, being in charge,
|
||||
* if a service request will be rejected.
|
||||
*/
|
||||
void apdu_set_reject_handler(reject_function pFunction)
|
||||
{
|
||||
Reject_Function = pFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode the given confirmed service request from the received data.
|
||||
*
|
||||
* @param apdu Received data buffer.
|
||||
* @param apdu_len Count of valid bytes in the receive buffer.
|
||||
* @param service_data Pointer of the structure being filled with the service
|
||||
* data.
|
||||
* @param service_choice Pointer to a variable taking the service requested,
|
||||
* see SERVICE_CONFIRMED_X
|
||||
* @param service_request Pointer to a pointer that will take the pointer
|
||||
* were the service request data can be found in
|
||||
* the receive buffer.
|
||||
* @param service_request_len Pointer to a variable that takes the length
|
||||
* of the service request data.
|
||||
*
|
||||
* @return The length of the service data and reflects the offset, where
|
||||
* we are in PDU after the service had been decoded.
|
||||
*/
|
||||
uint16_t apdu_decode_confirmed_service_request(uint8_t *apdu, /* APDU data */
|
||||
uint16_t apdu_len,
|
||||
BACNET_CONFIRMED_SERVICE_DATA *service_data,
|
||||
@@ -382,13 +437,19 @@ static bool apdu_confirmed_dcc_disabled(uint8_t service_choice)
|
||||
return status;
|
||||
}
|
||||
|
||||
/* When network communications are completely disabled,
|
||||
only DeviceCommunicationControl and ReinitializeDevice APDUs
|
||||
shall be processed and no messages shall be initiated. */
|
||||
/* If the request is valid and the 'Enable/Disable' parameter is
|
||||
DISABLE_INITIATION, the responding BACnet-user shall
|
||||
discontinue the initiation of messages except for I-Am
|
||||
requests issued in accordance with the Who-Is service procedure.*/
|
||||
/**
|
||||
* When network communications are completely disabled,
|
||||
* only DeviceCommunicationControl and ReinitializeDevice APDUs
|
||||
* shall be processed and no messages shall be initiated.
|
||||
* If the request is valid and the 'Enable/Disable' parameter is
|
||||
* DISABLE_INITIATION, the responding BACnet-user shall
|
||||
* discontinue the initiation of messages except for I-Am
|
||||
* requests issued in accordance with the Who-Is service procedure.
|
||||
*
|
||||
* @param service_choice Service, like SERVICE_UNCONFIRMED_WHO_IS
|
||||
*
|
||||
* @return true, if being disabled.
|
||||
*/
|
||||
static bool apdu_unconfirmed_dcc_disabled(uint8_t service_choice)
|
||||
{
|
||||
bool status = false;
|
||||
@@ -462,101 +523,107 @@ void apdu_handler(BACNET_ADDRESS *src,
|
||||
}
|
||||
break;
|
||||
case PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST:
|
||||
service_choice = apdu[1];
|
||||
service_request = &apdu[2];
|
||||
service_request_len = apdu_len - 2;
|
||||
if (apdu_unconfirmed_dcc_disabled(service_choice)) {
|
||||
/* When network communications are disabled,
|
||||
only DeviceCommunicationControl and ReinitializeDevice
|
||||
APDUs shall be processed and no messages shall be
|
||||
initiated. If communications have been initiation
|
||||
disabled, then WhoIs may be processed. */
|
||||
break;
|
||||
}
|
||||
if (service_choice < MAX_BACNET_UNCONFIRMED_SERVICE) {
|
||||
if (Unconfirmed_Function[service_choice]) {
|
||||
Unconfirmed_Function[service_choice](
|
||||
service_request, service_request_len, src);
|
||||
if (apdu_len >= 2) {
|
||||
service_choice = apdu[1];
|
||||
service_request = &apdu[2];
|
||||
service_request_len = apdu_len - 2;
|
||||
if (apdu_unconfirmed_dcc_disabled(service_choice)) {
|
||||
/* When network communications are disabled,
|
||||
only DeviceCommunicationControl and ReinitializeDevice
|
||||
APDUs shall be processed and no messages shall be
|
||||
initiated. If communications have been initiation
|
||||
disabled, then WhoIs may be processed. */
|
||||
break;
|
||||
}
|
||||
if (service_choice < MAX_BACNET_UNCONFIRMED_SERVICE) {
|
||||
if (Unconfirmed_Function[service_choice]) {
|
||||
Unconfirmed_Function[service_choice](
|
||||
service_request, service_request_len, src);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PDU_TYPE_SIMPLE_ACK:
|
||||
invoke_id = apdu[1];
|
||||
service_choice = apdu[2];
|
||||
switch (service_choice) {
|
||||
case SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM:
|
||||
case SERVICE_CONFIRMED_COV_NOTIFICATION:
|
||||
case SERVICE_CONFIRMED_EVENT_NOTIFICATION:
|
||||
case SERVICE_CONFIRMED_SUBSCRIBE_COV:
|
||||
case SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY:
|
||||
case SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION:
|
||||
/* Object Access Services */
|
||||
case SERVICE_CONFIRMED_ADD_LIST_ELEMENT:
|
||||
case SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT:
|
||||
case SERVICE_CONFIRMED_DELETE_OBJECT:
|
||||
case SERVICE_CONFIRMED_WRITE_PROPERTY:
|
||||
case SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE:
|
||||
/* Remote Device Management Services */
|
||||
case SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL:
|
||||
case SERVICE_CONFIRMED_REINITIALIZE_DEVICE:
|
||||
case SERVICE_CONFIRMED_TEXT_MESSAGE:
|
||||
/* Virtual Terminal Services */
|
||||
case SERVICE_CONFIRMED_VT_CLOSE:
|
||||
/* Security Services */
|
||||
case SERVICE_CONFIRMED_REQUEST_KEY:
|
||||
if (Confirmed_ACK_Function[service_choice] != NULL) {
|
||||
((confirmed_simple_ack_function)
|
||||
Confirmed_ACK_Function[service_choice])(
|
||||
src, invoke_id);
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (apdu_len >= 3) {
|
||||
invoke_id = apdu[1];
|
||||
service_choice = apdu[2];
|
||||
switch (service_choice) {
|
||||
case SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM:
|
||||
case SERVICE_CONFIRMED_COV_NOTIFICATION:
|
||||
case SERVICE_CONFIRMED_EVENT_NOTIFICATION:
|
||||
case SERVICE_CONFIRMED_SUBSCRIBE_COV:
|
||||
case SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY:
|
||||
case SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION:
|
||||
/* Object Access Services */
|
||||
case SERVICE_CONFIRMED_ADD_LIST_ELEMENT:
|
||||
case SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT:
|
||||
case SERVICE_CONFIRMED_DELETE_OBJECT:
|
||||
case SERVICE_CONFIRMED_WRITE_PROPERTY:
|
||||
case SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE:
|
||||
/* Remote Device Management Services */
|
||||
case SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL:
|
||||
case SERVICE_CONFIRMED_REINITIALIZE_DEVICE:
|
||||
case SERVICE_CONFIRMED_TEXT_MESSAGE:
|
||||
/* Virtual Terminal Services */
|
||||
case SERVICE_CONFIRMED_VT_CLOSE:
|
||||
/* Security Services */
|
||||
case SERVICE_CONFIRMED_REQUEST_KEY:
|
||||
if (Confirmed_ACK_Function[service_choice] != NULL) {
|
||||
((confirmed_simple_ack_function)
|
||||
Confirmed_ACK_Function[service_choice])(
|
||||
src, invoke_id);
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PDU_TYPE_COMPLEX_ACK:
|
||||
service_ack_data.segmented_message =
|
||||
(apdu[0] & BIT(3)) ? true : false;
|
||||
service_ack_data.more_follows =
|
||||
(apdu[0] & BIT(2)) ? true : false;
|
||||
invoke_id = service_ack_data.invoke_id = apdu[1];
|
||||
len = 2;
|
||||
if (service_ack_data.segmented_message) {
|
||||
service_ack_data.sequence_number = apdu[len++];
|
||||
service_ack_data.proposed_window_number = apdu[len++];
|
||||
}
|
||||
service_choice = apdu[len++];
|
||||
service_request = &apdu[len];
|
||||
service_request_len = apdu_len - (uint16_t)len;
|
||||
switch (service_choice) {
|
||||
case SERVICE_CONFIRMED_GET_ALARM_SUMMARY:
|
||||
case SERVICE_CONFIRMED_GET_ENROLLMENT_SUMMARY:
|
||||
case SERVICE_CONFIRMED_GET_EVENT_INFORMATION:
|
||||
/* File Access Services */
|
||||
case SERVICE_CONFIRMED_ATOMIC_READ_FILE:
|
||||
case SERVICE_CONFIRMED_ATOMIC_WRITE_FILE:
|
||||
/* Object Access Services */
|
||||
case SERVICE_CONFIRMED_CREATE_OBJECT:
|
||||
case SERVICE_CONFIRMED_READ_PROPERTY:
|
||||
case SERVICE_CONFIRMED_READ_PROP_CONDITIONAL:
|
||||
case SERVICE_CONFIRMED_READ_PROP_MULTIPLE:
|
||||
case SERVICE_CONFIRMED_READ_RANGE:
|
||||
case SERVICE_CONFIRMED_PRIVATE_TRANSFER:
|
||||
/* Virtual Terminal Services */
|
||||
case SERVICE_CONFIRMED_VT_OPEN:
|
||||
case SERVICE_CONFIRMED_VT_DATA:
|
||||
/* Security Services */
|
||||
case SERVICE_CONFIRMED_AUTHENTICATE:
|
||||
if (Confirmed_ACK_Function[service_choice] != NULL) {
|
||||
(Confirmed_ACK_Function[service_choice])(
|
||||
service_request, service_request_len, src,
|
||||
&service_ack_data);
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (apdu_len >= 3) {
|
||||
service_ack_data.segmented_message =
|
||||
(apdu[0] & BIT(3)) ? true : false;
|
||||
service_ack_data.more_follows =
|
||||
(apdu[0] & BIT(2)) ? true : false;
|
||||
invoke_id = service_ack_data.invoke_id = apdu[1];
|
||||
len = 2;
|
||||
if (service_ack_data.segmented_message) {
|
||||
service_ack_data.sequence_number = apdu[len++];
|
||||
service_ack_data.proposed_window_number = apdu[len++];
|
||||
}
|
||||
service_choice = apdu[len++];
|
||||
service_request = &apdu[len];
|
||||
service_request_len = apdu_len - (uint16_t)len;
|
||||
switch (service_choice) {
|
||||
case SERVICE_CONFIRMED_GET_ALARM_SUMMARY:
|
||||
case SERVICE_CONFIRMED_GET_ENROLLMENT_SUMMARY:
|
||||
case SERVICE_CONFIRMED_GET_EVENT_INFORMATION:
|
||||
/* File Access Services */
|
||||
case SERVICE_CONFIRMED_ATOMIC_READ_FILE:
|
||||
case SERVICE_CONFIRMED_ATOMIC_WRITE_FILE:
|
||||
/* Object Access Services */
|
||||
case SERVICE_CONFIRMED_CREATE_OBJECT:
|
||||
case SERVICE_CONFIRMED_READ_PROPERTY:
|
||||
case SERVICE_CONFIRMED_READ_PROP_CONDITIONAL:
|
||||
case SERVICE_CONFIRMED_READ_PROP_MULTIPLE:
|
||||
case SERVICE_CONFIRMED_READ_RANGE:
|
||||
case SERVICE_CONFIRMED_PRIVATE_TRANSFER:
|
||||
/* Virtual Terminal Services */
|
||||
case SERVICE_CONFIRMED_VT_OPEN:
|
||||
case SERVICE_CONFIRMED_VT_DATA:
|
||||
/* Security Services */
|
||||
case SERVICE_CONFIRMED_AUTHENTICATE:
|
||||
if (Confirmed_ACK_Function[service_choice] != NULL) {
|
||||
(Confirmed_ACK_Function[service_choice])(
|
||||
service_request, service_request_len, src,
|
||||
&service_ack_data);
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PDU_TYPE_SEGMENT_ACK:
|
||||
@@ -565,66 +632,85 @@ void apdu_handler(BACNET_ADDRESS *src,
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
break;
|
||||
case PDU_TYPE_ERROR:
|
||||
invoke_id = apdu[1];
|
||||
service_choice = apdu[2];
|
||||
len = 3;
|
||||
if (apdu_len >= 3) {
|
||||
invoke_id = apdu[1];
|
||||
service_choice = apdu[2];
|
||||
len = 3;
|
||||
|
||||
/* FIXME: Currently special case for C_P_T but there are others
|
||||
which may need consideration such as ChangeList-Error,
|
||||
CreateObject-Error, WritePropertyMultiple-Error and
|
||||
VTClose_Error but they may be left as is for now until
|
||||
support for these services is added */
|
||||
/* FIXME: Currently special case for C_P_T but there are others
|
||||
which may need consideration such as ChangeList-Error,
|
||||
CreateObject-Error, WritePropertyMultiple-Error and
|
||||
VTClose_Error but they may be left as is for now until
|
||||
support for these services is added */
|
||||
|
||||
if (service_choice ==
|
||||
SERVICE_CONFIRMED_PRIVATE_TRANSFER) { /* skip over
|
||||
opening tag 0 */
|
||||
if (decode_is_opening_tag_number(&apdu[len], 0)) {
|
||||
len++; /* a tag number of 0 is not extended so only one
|
||||
octet */
|
||||
if (service_choice ==
|
||||
SERVICE_CONFIRMED_PRIVATE_TRANSFER) { /* skip over
|
||||
opening tag 0 */
|
||||
if (decode_is_opening_tag_number(&apdu[len], 0)) {
|
||||
len++; /* a tag number of 0 is not extended so only one
|
||||
octet */
|
||||
}
|
||||
}
|
||||
}
|
||||
len += decode_tag_number_and_value(
|
||||
&apdu[len], &tag_number, &len_value);
|
||||
/* FIXME: we could validate that the tag is enumerated... */
|
||||
len += decode_enumerated(&apdu[len], len_value, &error_class);
|
||||
len += decode_tag_number_and_value(
|
||||
&apdu[len], &tag_number, &len_value);
|
||||
/* FIXME: we could validate that the tag is enumerated... */
|
||||
len += decode_enumerated(&apdu[len], len_value, &error_code);
|
||||
|
||||
if (service_choice ==
|
||||
SERVICE_CONFIRMED_PRIVATE_TRANSFER) { /* skip over
|
||||
closing tag 0 */
|
||||
if (decode_is_closing_tag_number(&apdu[len], 0)) {
|
||||
len++; /* a tag number of 0 is not extended so only one
|
||||
octet */
|
||||
if (len < apdu_len) {
|
||||
len += decode_tag_number_and_value(
|
||||
&apdu[len], &tag_number, &len_value);
|
||||
|
||||
if (len < apdu_len) {
|
||||
/* FIXME: we could validate that the tag is enumerated... */
|
||||
len += decode_enumerated(&apdu[len], len_value, &error_class);
|
||||
|
||||
if (len < apdu_len) {
|
||||
len += decode_tag_number_and_value(
|
||||
&apdu[len], &tag_number, &len_value);
|
||||
|
||||
if (len < apdu_len) {
|
||||
/* FIXME: we could validate that the tag is enumerated... */
|
||||
len += decode_enumerated(&apdu[len], len_value, &error_code);
|
||||
|
||||
if (service_choice ==
|
||||
SERVICE_CONFIRMED_PRIVATE_TRANSFER) { /* skip over
|
||||
closing tag 0 */
|
||||
if (decode_is_closing_tag_number(&apdu[len], 0)) {
|
||||
len++; /* a tag number of 0 is not extended so only one
|
||||
octet */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) {
|
||||
if (Error_Function[service_choice]) {
|
||||
Error_Function[service_choice](src, invoke_id,
|
||||
(BACNET_ERROR_CLASS)error_class,
|
||||
(BACNET_ERROR_CODE)error_code);
|
||||
|
||||
if (service_choice < MAX_BACNET_CONFIRMED_SERVICE) {
|
||||
if (Error_Function[service_choice]) {
|
||||
Error_Function[service_choice](src, invoke_id,
|
||||
(BACNET_ERROR_CLASS)error_class,
|
||||
(BACNET_ERROR_CODE)error_code);
|
||||
}
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
break;
|
||||
case PDU_TYPE_REJECT:
|
||||
invoke_id = apdu[1];
|
||||
reason = apdu[2];
|
||||
if (Reject_Function) {
|
||||
Reject_Function(src, invoke_id, reason);
|
||||
if (apdu_len >= 3) {
|
||||
invoke_id = apdu[1];
|
||||
reason = apdu[2];
|
||||
if (Reject_Function) {
|
||||
Reject_Function(src, invoke_id, reason);
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
break;
|
||||
case PDU_TYPE_ABORT:
|
||||
server = apdu[0] & 0x01;
|
||||
invoke_id = apdu[1];
|
||||
reason = apdu[2];
|
||||
if (Abort_Function) {
|
||||
Abort_Function(src, invoke_id, reason, server);
|
||||
if (apdu_len >= 3) {
|
||||
server = apdu[0] & 0x01;
|
||||
invoke_id = apdu[1];
|
||||
reason = apdu[2];
|
||||
if (Abort_Function) {
|
||||
Abort_Function(src, invoke_id, reason, server);
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -802,36 +802,37 @@ void handler_cov_subscribe(uint8_t *service_request,
|
||||
fprintf(stderr, "SubscribeCOV: Segmented message. Sending Abort!\n");
|
||||
#endif
|
||||
error = true;
|
||||
goto COV_ABORT;
|
||||
}
|
||||
len = cov_subscribe_decode_service_request(
|
||||
service_request, service_len, &cov_data);
|
||||
#if PRINT_ENABLED
|
||||
if (len <= 0)
|
||||
fprintf(stderr, "SubscribeCOV: Unable to decode Request!\n");
|
||||
#endif
|
||||
if (len < 0) {
|
||||
error = true;
|
||||
goto COV_ABORT;
|
||||
}
|
||||
cov_data.error_class = ERROR_CLASS_OBJECT;
|
||||
cov_data.error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
success = cov_subscribe(
|
||||
src, &cov_data, &cov_data.error_class, &cov_data.error_code);
|
||||
if (success) {
|
||||
apdu_len = encode_simple_ack(&Handler_Transmit_Buffer[npdu_len],
|
||||
service_data->invoke_id, SERVICE_CONFIRMED_SUBSCRIBE_COV);
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "SubscribeCOV: Sending Simple Ack!\n");
|
||||
#endif
|
||||
} else {
|
||||
len = BACNET_STATUS_ERROR;
|
||||
error = true;
|
||||
len = cov_subscribe_decode_service_request(
|
||||
service_request, service_len, &cov_data);
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "SubscribeCOV: Sending Error!\n");
|
||||
if (len <= 0)
|
||||
fprintf(stderr, "SubscribeCOV: Unable to decode Request!\n");
|
||||
#endif
|
||||
if (len < 0) {
|
||||
error = true;
|
||||
} else {
|
||||
cov_data.error_class = ERROR_CLASS_OBJECT;
|
||||
cov_data.error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
||||
success = cov_subscribe(
|
||||
src, &cov_data, &cov_data.error_class, &cov_data.error_code);
|
||||
if (success) {
|
||||
apdu_len = encode_simple_ack(&Handler_Transmit_Buffer[npdu_len],
|
||||
service_data->invoke_id, SERVICE_CONFIRMED_SUBSCRIBE_COV);
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "SubscribeCOV: Sending Simple Ack!\n");
|
||||
#endif
|
||||
} else {
|
||||
len = BACNET_STATUS_ERROR;
|
||||
error = true;
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "SubscribeCOV: Sending Error!\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
COV_ABORT:
|
||||
|
||||
/* Error? */
|
||||
if (error) {
|
||||
if (len == BACNET_STATUS_ABORT) {
|
||||
apdu_len = abort_encode_apdu(&Handler_Transmit_Buffer[npdu_len],
|
||||
|
||||
@@ -68,6 +68,7 @@ void handler_write_property(uint8_t *service_request,
|
||||
{
|
||||
BACNET_WRITE_PROPERTY_DATA wp_data;
|
||||
int len = 0;
|
||||
bool bcontinue = true;
|
||||
int pdu_len = 0;
|
||||
BACNET_NPDU_DATA npdu_data;
|
||||
int bytes_sent = 0;
|
||||
@@ -88,47 +89,55 @@ void handler_write_property(uint8_t *service_request,
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "WP: Segmented message. Sending Abort!\n");
|
||||
#endif
|
||||
goto WP_ABORT;
|
||||
} /* decode the service request only */
|
||||
len = wp_decode_service_request(service_request, service_len, &wp_data);
|
||||
#if PRINT_ENABLED
|
||||
if (len > 0)
|
||||
fprintf(stderr,
|
||||
"WP: type=%lu instance=%lu property=%lu priority=%lu index=%ld\n",
|
||||
(unsigned long)wp_data.object_type,
|
||||
(unsigned long)wp_data.object_instance,
|
||||
(unsigned long)wp_data.object_property,
|
||||
(unsigned long)wp_data.priority, (long)wp_data.array_index);
|
||||
else
|
||||
fprintf(stderr, "WP: Unable to decode Request!\n");
|
||||
#endif
|
||||
/* bad decoding or something we didn't understand - send an abort */
|
||||
if (len <= 0) {
|
||||
len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id, ABORT_REASON_OTHER, true);
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "WP: Bad Encoding. Sending Abort!\n");
|
||||
#endif
|
||||
goto WP_ABORT;
|
||||
bcontinue = false;
|
||||
}
|
||||
if (Device_Write_Property(&wp_data)) {
|
||||
len = encode_simple_ack(&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id, SERVICE_CONFIRMED_WRITE_PROPERTY);
|
||||
|
||||
if (bcontinue) {
|
||||
/* decode the service request only */
|
||||
len = wp_decode_service_request(service_request, service_len, &wp_data);
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "WP: Sending Simple Ack!\n");
|
||||
if (len > 0)
|
||||
fprintf(stderr,
|
||||
"WP: type=%lu instance=%lu property=%lu priority=%lu index=%ld\n",
|
||||
(unsigned long)wp_data.object_type,
|
||||
(unsigned long)wp_data.object_instance,
|
||||
(unsigned long)wp_data.object_property,
|
||||
(unsigned long)wp_data.priority, (long)wp_data.array_index);
|
||||
else
|
||||
fprintf(stderr, "WP: Unable to decode Request!\n");
|
||||
#endif
|
||||
} else {
|
||||
len = bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id, SERVICE_CONFIRMED_WRITE_PROPERTY,
|
||||
wp_data.error_class, wp_data.error_code);
|
||||
/* bad decoding or something we didn't understand - send an abort */
|
||||
if (len <= 0) {
|
||||
len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id, ABORT_REASON_OTHER, true);
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "WP: Sending Error!\n");
|
||||
fprintf(stderr, "WP: Bad Encoding. Sending Abort!\n");
|
||||
#endif
|
||||
bcontinue = false;
|
||||
}
|
||||
|
||||
if (bcontinue) {
|
||||
if (Device_Write_Property(&wp_data)) {
|
||||
len = encode_simple_ack(&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id, SERVICE_CONFIRMED_WRITE_PROPERTY);
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "WP: Sending Simple Ack!\n");
|
||||
#endif
|
||||
} else {
|
||||
len = bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len],
|
||||
service_data->invoke_id, SERVICE_CONFIRMED_WRITE_PROPERTY,
|
||||
wp_data.error_class, wp_data.error_code);
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "WP: Sending Error!\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
WP_ABORT:
|
||||
|
||||
/* Send PDU */
|
||||
pdu_len += len;
|
||||
bytes_sent = datalink_send_pdu(
|
||||
src, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len);
|
||||
bytes_sent = datalink_send_pdu( \
|
||||
src, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len);
|
||||
if (bytes_sent <= 0) {
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "WP: Failed to send PDU (%s)!\n", strerror(errno));
|
||||
@@ -138,12 +147,20 @@ WP_ABORT:
|
||||
return;
|
||||
}
|
||||
|
||||
/** Perform basic validation of Write Property argument based on
|
||||
/**
|
||||
* @brief Perform basic validation of Write Property argument based on
|
||||
* the assumption that it is a string. Check for correct data type,
|
||||
* correct encoding (fixed here as ANSI X34),correct length, and
|
||||
* finally if it is allowed to be empty.
|
||||
*
|
||||
* @param pValue Pointer to the application data value representing the string.
|
||||
* @param iMaxLen Maximum string length allowed.
|
||||
* @param bEmptyAllowed true, if empty strings shall be allowed.
|
||||
* @param pErrorClass Pointer to a variable taking the error class.
|
||||
* @param pErrorCode Pointer to a variable taking the error code.
|
||||
*
|
||||
* @return True on success, false otherwise.
|
||||
*/
|
||||
|
||||
bool WPValidateString(BACNET_APPLICATION_DATA_VALUE *pValue,
|
||||
int iMaxLen,
|
||||
bool bEmptyAllowed,
|
||||
|
||||
+22
-1
@@ -41,7 +41,17 @@
|
||||
|
||||
/** @file iam.c Encode/Decode I-Am service */
|
||||
|
||||
/* encode I-Am service */
|
||||
/**
|
||||
* @brief Encode the I-Am service.
|
||||
*
|
||||
* @param apdu Transmit buffer
|
||||
* @param device_id Device Id
|
||||
* @param max_apdu Transmit buffer size.
|
||||
* @param segmentation True, if segmentation shall be featured.
|
||||
* @param vendor_id Vendor Id
|
||||
*
|
||||
* @return Total length of the apdu, zero otherwise.
|
||||
*/
|
||||
int iam_encode_apdu(uint8_t *apdu,
|
||||
uint32_t device_id,
|
||||
unsigned max_apdu,
|
||||
@@ -70,6 +80,17 @@ int iam_encode_apdu(uint8_t *apdu,
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode the I-Am service.
|
||||
*
|
||||
* @param apdu Receive buffer
|
||||
* @param pDevice_id Pointer to the variable that shal ltake the device Id.
|
||||
* @param pMax_apdu Pointer to a variable that shall take the decoded length.
|
||||
* @param pSegmentation Pointer to a variable taking if segmentation is used.
|
||||
* @param pVendor_id Pointer to a variable taking the vendor id.
|
||||
*
|
||||
* @return Total length of the apdu, zero otherwise.
|
||||
*/
|
||||
int iam_decode_service_request(uint8_t *apdu,
|
||||
uint32_t *pDevice_id,
|
||||
unsigned *pMax_apdu,
|
||||
|
||||
Reference in New Issue
Block a user