Moved header files into the include directory. Moved source files into the src directory. Still need to updated the makefiles and projects.
This commit is contained in:
@@ -0,0 +1,348 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2004 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include "config.h"
|
||||
#include "bacaddr.h"
|
||||
#include "address.h"
|
||||
#include "bacdef.h"
|
||||
#include "bacdcode.h"
|
||||
|
||||
/* This module is used to handle the address binding that */
|
||||
/* occurs in BACnet. A device id is bound to a MAC address. */
|
||||
/* The normal method is using Who-Is, and using the data from I-Am */
|
||||
|
||||
static struct Address_Cache_Entry {
|
||||
bool valid;
|
||||
bool bind_request;
|
||||
uint32_t device_id;
|
||||
unsigned max_apdu;
|
||||
BACNET_ADDRESS address;
|
||||
} Address_Cache[MAX_ADDRESS_CACHE];
|
||||
|
||||
void address_remove_device(uint32_t device_id)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
if ((Address_Cache[i].valid ||
|
||||
Address_Cache[i].bind_request) &&
|
||||
(Address_Cache[i].device_id == device_id)) {
|
||||
Address_Cache[i].valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void address_init(void)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
Address_Cache[i].valid = false;
|
||||
Address_Cache[i].bind_request = false;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool address_get_by_device(uint32_t device_id,
|
||||
unsigned *max_apdu, BACNET_ADDRESS * src)
|
||||
{
|
||||
unsigned i;
|
||||
bool found = false; /* return value */
|
||||
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
if (Address_Cache[i].valid &&
|
||||
(Address_Cache[i].device_id == device_id)) {
|
||||
bacnet_address_copy(src, &Address_Cache[i].address);
|
||||
*max_apdu = Address_Cache[i].max_apdu;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void address_add(uint32_t device_id,
|
||||
unsigned max_apdu, BACNET_ADDRESS * src)
|
||||
{
|
||||
unsigned i;
|
||||
bool found = false; /* return value */
|
||||
|
||||
/* existing device - update address */
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
if (Address_Cache[i].valid &&
|
||||
(Address_Cache[i].device_id == device_id)) {
|
||||
bacnet_address_copy(&Address_Cache[i].address, src);
|
||||
Address_Cache[i].max_apdu = max_apdu;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* new device */
|
||||
if (!found) {
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
if (!Address_Cache[i].valid) {
|
||||
Address_Cache[i].valid = true;
|
||||
Address_Cache[i].device_id = device_id;
|
||||
Address_Cache[i].max_apdu = max_apdu;
|
||||
bacnet_address_copy(&Address_Cache[i].address, src);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* returns true if device is already bound */
|
||||
/* also returns the address and max apdu if already bound */
|
||||
bool address_bind_request(uint32_t device_id,
|
||||
unsigned *max_apdu, BACNET_ADDRESS * src)
|
||||
{
|
||||
unsigned i;
|
||||
bool found = false; /* return value */
|
||||
|
||||
/* existing device - update address */
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
if (Address_Cache[i].valid &&
|
||||
(Address_Cache[i].device_id == device_id)) {
|
||||
found = true;
|
||||
bacnet_address_copy(src, &Address_Cache[i].address);
|
||||
*max_apdu = Address_Cache[i].max_apdu;
|
||||
break;
|
||||
}
|
||||
/* already have a bind request active for this puppy */
|
||||
else if (Address_Cache[i].bind_request &&
|
||||
(Address_Cache[i].device_id == device_id)) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
if (!(Address_Cache[i].bind_request || Address_Cache[i].valid)) {
|
||||
Address_Cache[i].bind_request = true;
|
||||
Address_Cache[i].device_id = device_id;
|
||||
/* now would be a good time to do a Who-Is request */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
void address_add_binding(uint32_t device_id,
|
||||
unsigned max_apdu, BACNET_ADDRESS * src)
|
||||
{
|
||||
unsigned i;
|
||||
bool found = false; /* return value */
|
||||
|
||||
/* existing device - update address */
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
if (Address_Cache[i].valid &&
|
||||
(Address_Cache[i].device_id == device_id)) {
|
||||
bacnet_address_copy(&Address_Cache[i].address, src);
|
||||
Address_Cache[i].max_apdu = max_apdu;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* add new device - but only if bind requested */
|
||||
if (!found) {
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
if (!Address_Cache[i].valid && Address_Cache[i].bind_request) {
|
||||
Address_Cache[i].valid = true;
|
||||
Address_Cache[i].bind_request = false;
|
||||
Address_Cache[i].device_id = device_id;
|
||||
Address_Cache[i].max_apdu = max_apdu;
|
||||
bacnet_address_copy(&Address_Cache[i].address, src);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool address_get_by_index(unsigned index,
|
||||
uint32_t * device_id, unsigned *max_apdu, BACNET_ADDRESS * src)
|
||||
{
|
||||
bool found = false; /* return value */
|
||||
|
||||
if (index < MAX_ADDRESS_CACHE) {
|
||||
if (Address_Cache[index].valid) {
|
||||
bacnet_address_copy(src, &Address_Cache[index].address);
|
||||
*device_id = Address_Cache[index].device_id;
|
||||
*max_apdu = Address_Cache[index].max_apdu;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
unsigned address_count(void)
|
||||
{
|
||||
unsigned i;
|
||||
unsigned count = 0; /* return value */
|
||||
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
if (Address_Cache[i].valid)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool address_match(BACNET_ADDRESS * dest, BACNET_ADDRESS * src)
|
||||
{
|
||||
unsigned i;
|
||||
unsigned max_len;
|
||||
bool match = true; /* return value */
|
||||
|
||||
if (dest->mac_len != src->mac_len)
|
||||
match = false;
|
||||
max_len = dest->mac_len;
|
||||
if (max_len > MAX_MAC_LEN)
|
||||
max_len = MAX_MAC_LEN;
|
||||
for (i = 0; i < max_len; i++) {
|
||||
if (dest->mac[i] != src->mac[i])
|
||||
match = false;
|
||||
}
|
||||
if (dest->net != src->net)
|
||||
match = false;
|
||||
if (dest->len != src->len)
|
||||
match = false;
|
||||
max_len = dest->len;
|
||||
if (max_len > MAX_MAC_LEN)
|
||||
max_len = MAX_MAC_LEN;
|
||||
for (i = 0; i < max_len; i++) {
|
||||
if (dest->adr[i] != src->adr[i])
|
||||
match = false;
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
static void set_address(unsigned index, BACNET_ADDRESS * dest)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
dest->mac[i] = index;
|
||||
}
|
||||
dest->mac_len = MAX_MAC_LEN;
|
||||
dest->net = 7;
|
||||
dest->len = MAX_MAC_LEN;
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
dest->adr[i] = index;
|
||||
}
|
||||
}
|
||||
|
||||
void testAddress(Test * pTest)
|
||||
{
|
||||
unsigned i, count;
|
||||
BACNET_ADDRESS src;
|
||||
uint32_t device_id = 0;
|
||||
unsigned max_apdu = 480;
|
||||
BACNET_ADDRESS test_address;
|
||||
uint32_t test_device_id = 0;
|
||||
unsigned test_max_apdu = 0;
|
||||
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
set_address(i, &src);
|
||||
device_id = i * 255;
|
||||
address_add(device_id, max_apdu, &src);
|
||||
count = address_count();
|
||||
ct_test(pTest, count == (i + 1));
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
device_id = i * 255;
|
||||
ct_test(pTest, address_get_by_device(device_id, &test_max_apdu,
|
||||
&test_address));
|
||||
set_address(i, &src);
|
||||
ct_test(pTest, test_max_apdu == max_apdu);
|
||||
ct_test(pTest, address_match(&test_address, &src));
|
||||
ct_test(pTest, address_get_by_index(i, &test_device_id,
|
||||
&test_max_apdu, &test_address));
|
||||
ct_test(pTest, test_device_id == device_id);
|
||||
ct_test(pTest, test_max_apdu == max_apdu);
|
||||
ct_test(pTest, address_match(&test_address, &src));
|
||||
ct_test(pTest, address_count() == MAX_ADDRESS_CACHE);
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_ADDRESS_CACHE; i++) {
|
||||
device_id = i * 255;
|
||||
address_remove_device(device_id);
|
||||
ct_test(pTest, !address_get_by_device(device_id, &test_max_apdu,
|
||||
&test_address));
|
||||
count = address_count();
|
||||
ct_test(pTest, count == (MAX_ADDRESS_CACHE - i - 1));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TEST_ADDRESS
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Address", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testAddress);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_ADDRESS */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,457 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "bits.h"
|
||||
#include "apdu.h"
|
||||
#include "bacdef.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacenum.h"
|
||||
#include "tsm.h"
|
||||
#include "dcc.h"
|
||||
#include "iam.h"
|
||||
|
||||
/* a simple table for crossing the services supported */
|
||||
static BACNET_SERVICES_SUPPORTED
|
||||
confirmed_service_supported[MAX_BACNET_CONFIRMED_SERVICE] = {
|
||||
SERVICE_SUPPORTED_ACKNOWLEDGE_ALARM,
|
||||
SERVICE_SUPPORTED_CONFIRMED_COV_NOTIFICATION,
|
||||
SERVICE_SUPPORTED_CONFIRMED_EVENT_NOTIFICATION,
|
||||
SERVICE_SUPPORTED_GET_ALARM_SUMMARY,
|
||||
SERVICE_SUPPORTED_GET_ENROLLMENT_SUMMARY,
|
||||
SERVICE_SUPPORTED_SUBSCRIBE_COV,
|
||||
SERVICE_SUPPORTED_ATOMIC_READ_FILE,
|
||||
SERVICE_SUPPORTED_ATOMIC_WRITE_FILE,
|
||||
SERVICE_SUPPORTED_ADD_LIST_ELEMENT,
|
||||
SERVICE_SUPPORTED_REMOVE_LIST_ELEMENT,
|
||||
SERVICE_SUPPORTED_CREATE_OBJECT,
|
||||
SERVICE_SUPPORTED_DELETE_OBJECT,
|
||||
SERVICE_SUPPORTED_READ_PROPERTY,
|
||||
SERVICE_SUPPORTED_READ_PROPERTY_CONDITIONAL,
|
||||
SERVICE_SUPPORTED_READ_PROPERTY_MULTIPLE,
|
||||
SERVICE_SUPPORTED_WRITE_PROPERTY,
|
||||
SERVICE_SUPPORTED_WRITE_PROPERTY_MULTIPLE,
|
||||
SERVICE_SUPPORTED_DEVICE_COMMUNICATION_CONTROL,
|
||||
SERVICE_SUPPORTED_PRIVATE_TRANSFER,
|
||||
SERVICE_SUPPORTED_TEXT_MESSAGE,
|
||||
SERVICE_SUPPORTED_REINITIALIZE_DEVICE,
|
||||
SERVICE_SUPPORTED_VT_OPEN,
|
||||
SERVICE_SUPPORTED_VT_CLOSE,
|
||||
SERVICE_SUPPORTED_VT_DATA,
|
||||
SERVICE_SUPPORTED_AUTHENTICATE,
|
||||
SERVICE_SUPPORTED_REQUEST_KEY,
|
||||
SERVICE_SUPPORTED_READ_RANGE,
|
||||
SERVICE_SUPPORTED_LIFE_SAFETY_OPERATION,
|
||||
SERVICE_SUPPORTED_SUBSCRIBE_COV_PROPERTY,
|
||||
SERVICE_SUPPORTED_GET_EVENT_INFORMATION
|
||||
};
|
||||
|
||||
/* a simple table for crossing the services supported */
|
||||
static BACNET_SERVICES_SUPPORTED
|
||||
unconfirmed_service_supported[MAX_BACNET_UNCONFIRMED_SERVICE] = {
|
||||
SERVICE_SUPPORTED_I_AM,
|
||||
SERVICE_SUPPORTED_I_HAVE,
|
||||
SERVICE_SUPPORTED_UNCONFIRMED_COV_NOTIFICATION,
|
||||
SERVICE_SUPPORTED_UNCONFIRMED_EVENT_NOTIFICATION,
|
||||
SERVICE_SUPPORTED_UNCONFIRMED_PRIVATE_TRANSFER,
|
||||
SERVICE_SUPPORTED_UNCONFIRMED_TEXT_MESSAGE,
|
||||
SERVICE_SUPPORTED_TIME_SYNCHRONIZATION,
|
||||
SERVICE_SUPPORTED_WHO_HAS,
|
||||
SERVICE_SUPPORTED_WHO_IS,
|
||||
SERVICE_SUPPORTED_UTC_TIME_SYNCHRONIZATION
|
||||
};
|
||||
|
||||
/* Confirmed Function Handlers */
|
||||
/* If they are not set, they are handled by a reject message */
|
||||
static confirmed_function Confirmed_Function[MAX_BACNET_CONFIRMED_SERVICE];
|
||||
|
||||
void apdu_set_confirmed_handler(BACNET_CONFIRMED_SERVICE service_choice,
|
||||
confirmed_function pFunction)
|
||||
{
|
||||
if (service_choice < MAX_BACNET_CONFIRMED_SERVICE)
|
||||
Confirmed_Function[service_choice] = pFunction;
|
||||
}
|
||||
|
||||
/* Allow the APDU handler to automatically reject */
|
||||
static confirmed_function Unrecognized_Service_Handler;
|
||||
|
||||
void apdu_set_unrecognized_service_handler_handler(confirmed_function
|
||||
pFunction)
|
||||
{
|
||||
Unrecognized_Service_Handler = pFunction;
|
||||
}
|
||||
|
||||
/* Unconfirmed Function Handlers */
|
||||
/* If they are not set, they are not handled */
|
||||
static unconfirmed_function
|
||||
Unconfirmed_Function[MAX_BACNET_UNCONFIRMED_SERVICE] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
void apdu_set_unconfirmed_handler(BACNET_UNCONFIRMED_SERVICE
|
||||
service_choice, unconfirmed_function pFunction)
|
||||
{
|
||||
if (service_choice < MAX_BACNET_UNCONFIRMED_SERVICE)
|
||||
Unconfirmed_Function[service_choice] = pFunction;
|
||||
}
|
||||
|
||||
bool apdu_service_supported(BACNET_SERVICES_SUPPORTED service_supported)
|
||||
{
|
||||
int i = 0;
|
||||
bool status = false;
|
||||
bool found = false;
|
||||
|
||||
if (service_supported < MAX_BACNET_SERVICES_SUPPORTED) {
|
||||
/* is it a confirmed service? */
|
||||
for (i = 0; i < MAX_BACNET_CONFIRMED_SERVICE; i++) {
|
||||
if (confirmed_service_supported[i] == service_supported) {
|
||||
if (Confirmed_Function[i] != NULL)
|
||||
status = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
/* is it an unconfirmed service? */
|
||||
for (i = 0; i < MAX_BACNET_UNCONFIRMED_SERVICE; i++) {
|
||||
if (unconfirmed_service_supported[i] == service_supported) {
|
||||
if (Unconfirmed_Function[i] != NULL)
|
||||
status = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Confirmed ACK Function Handlers */
|
||||
static void *Confirmed_ACK_Function[MAX_BACNET_CONFIRMED_SERVICE];
|
||||
|
||||
void apdu_set_confirmed_simple_ack_handler(BACNET_CONFIRMED_SERVICE
|
||||
service_choice, confirmed_simple_ack_function pFunction)
|
||||
{
|
||||
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_PROPERTY_MULTIPLE:
|
||||
/* Remote Device Management Services */
|
||||
case SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL:
|
||||
case SERVICE_CONFIRMED_TEXT_MESSAGE:
|
||||
case SERVICE_CONFIRMED_REINITIALIZE_DEVICE:
|
||||
/* Virtual Terminal Services */
|
||||
case SERVICE_CONFIRMED_VT_CLOSE:
|
||||
/* Security Services */
|
||||
case SERVICE_CONFIRMED_REQUEST_KEY:
|
||||
Confirmed_ACK_Function[service_choice] = (void *) pFunction;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void apdu_set_confirmed_ack_handler(BACNET_CONFIRMED_SERVICE
|
||||
service_choice, confirmed_ack_function pFunction)
|
||||
{
|
||||
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_PROPERTY_CONDITIONAL:
|
||||
case SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE:
|
||||
case SERVICE_CONFIRMED_READ_RANGE:
|
||||
/* Remote Device Management Services */
|
||||
case SERVICE_CONFIRMED_PRIVATE_TRANSFER:
|
||||
/* Virtual Terminal Services */
|
||||
case SERVICE_CONFIRMED_VT_OPEN:
|
||||
case SERVICE_CONFIRMED_VT_DATA:
|
||||
/* Security Services */
|
||||
case SERVICE_CONFIRMED_AUTHENTICATE:
|
||||
Confirmed_ACK_Function[service_choice] = (void *) pFunction;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static error_function Error_Function[MAX_BACNET_CONFIRMED_SERVICE];
|
||||
|
||||
void apdu_set_error_handler(BACNET_CONFIRMED_SERVICE service_choice,
|
||||
error_function pFunction)
|
||||
{
|
||||
if (service_choice < MAX_BACNET_CONFIRMED_SERVICE)
|
||||
Error_Function[service_choice] = pFunction;
|
||||
}
|
||||
|
||||
static abort_function Abort_Function;
|
||||
|
||||
void apdu_set_abort_handler(abort_function pFunction)
|
||||
{
|
||||
Abort_Function = pFunction;
|
||||
}
|
||||
|
||||
static reject_function Reject_Function;
|
||||
|
||||
void apdu_set_reject_handler(reject_function pFunction)
|
||||
{
|
||||
Reject_Function = pFunction;
|
||||
}
|
||||
|
||||
uint16_t apdu_decode_confirmed_service_request(uint8_t * apdu, /* APDU data */
|
||||
uint16_t apdu_len,
|
||||
BACNET_CONFIRMED_SERVICE_DATA * service_data,
|
||||
uint8_t * service_choice,
|
||||
uint8_t ** service_request, uint16_t * service_request_len)
|
||||
{
|
||||
uint16_t len = 0; /* counts where we are in PDU */
|
||||
|
||||
service_data->segmented_message = (apdu[0] & BIT3) ? true : false;
|
||||
service_data->more_follows = (apdu[0] & BIT2) ? true : false;
|
||||
service_data->segmented_response_accepted =
|
||||
(apdu[0] & BIT1) ? true : false;
|
||||
service_data->max_segs = decode_max_segs(apdu[1]);
|
||||
service_data->max_resp = decode_max_apdu(apdu[1]);
|
||||
service_data->invoke_id = apdu[2];
|
||||
len = 3;
|
||||
if (service_data->segmented_message) {
|
||||
service_data->sequence_number = apdu[len++];
|
||||
service_data->proposed_window_number = apdu[len++];
|
||||
}
|
||||
*service_choice = apdu[len++];
|
||||
*service_request = &apdu[len];
|
||||
*service_request_len = apdu_len - len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void apdu_handler(BACNET_ADDRESS * src, uint8_t * apdu, /* APDU data */
|
||||
uint16_t apdu_len)
|
||||
{
|
||||
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
|
||||
BACNET_CONFIRMED_SERVICE_ACK_DATA service_ack_data = { 0 };
|
||||
uint8_t invoke_id = 0;
|
||||
uint8_t service_choice = 0;
|
||||
uint8_t *service_request = NULL;
|
||||
uint16_t service_request_len = 0;
|
||||
uint16_t len = 0; /* counts where we are in PDU */
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
int error_code = 0;
|
||||
int error_class = 0;
|
||||
uint8_t reason = 0;
|
||||
bool server = false;
|
||||
|
||||
if (apdu) {
|
||||
/* PDU Type */
|
||||
switch (apdu[0] & 0xF0) {
|
||||
case PDU_TYPE_CONFIRMED_SERVICE_REQUEST:
|
||||
len = apdu_decode_confirmed_service_request(&apdu[0], /* APDU data */
|
||||
apdu_len,
|
||||
&service_data,
|
||||
&service_choice, &service_request, &service_request_len);
|
||||
/* When network communications are completely disabled,
|
||||
only DeviceCommunicationControl and ReinitializeDevice APDUs
|
||||
shall be processed and no messages shall be initiated. */
|
||||
if (dcc_communication_disabled() &&
|
||||
((service_choice !=
|
||||
SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL)
|
||||
&& (service_choice !=
|
||||
SERVICE_CONFIRMED_REINITIALIZE_DEVICE)))
|
||||
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);
|
||||
break;
|
||||
case PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST:
|
||||
if (dcc_communication_disabled())
|
||||
break;
|
||||
service_choice = apdu[1];
|
||||
service_request = &apdu[2];
|
||||
service_request_len = apdu_len - 2;
|
||||
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_PROPERTY_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]) {
|
||||
((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] & BIT3) ? true : false;
|
||||
service_ack_data.more_follows =
|
||||
(apdu[0] & BIT2) ? 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 - 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_PROPERTY_CONDITIONAL:
|
||||
case SERVICE_CONFIRMED_READ_PROPERTY_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]) {
|
||||
((confirmed_ack_function)
|
||||
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:
|
||||
/* 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);
|
||||
break;
|
||||
case PDU_TYPE_ERROR:
|
||||
invoke_id = apdu[1];
|
||||
service_choice = apdu[2];
|
||||
len = 3;
|
||||
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 < 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);
|
||||
break;
|
||||
case PDU_TYPE_REJECT:
|
||||
invoke_id = apdu[1];
|
||||
reason = apdu[2];
|
||||
if (Reject_Function)
|
||||
Reject_Function(src, invoke_id, reason);
|
||||
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);
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,473 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "device.h"
|
||||
#include "arf.h"
|
||||
|
||||
/* Atomic Read File */
|
||||
|
||||
/* encode service */
|
||||
int arf_encode_apdu(uint8_t * apdu,
|
||||
uint8_t invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] =
|
||||
encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted());
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_ATOMIC_READ_FILE; /* service choice */
|
||||
apdu_len = 4;
|
||||
apdu_len += encode_tagged_object_id(&apdu[apdu_len],
|
||||
data->object_type, data->object_instance);
|
||||
switch (data->access) {
|
||||
case FILE_STREAM_ACCESS:
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 0);
|
||||
apdu_len += encode_tagged_signed(&apdu[apdu_len],
|
||||
data->type.stream.fileStartPosition);
|
||||
apdu_len += encode_tagged_unsigned(&apdu[apdu_len],
|
||||
data->type.stream.requestedOctetCount);
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 0);
|
||||
break;
|
||||
case FILE_RECORD_ACCESS:
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 1);
|
||||
apdu_len += encode_tagged_signed(&apdu[apdu_len],
|
||||
data->type.record.fileStartRecord);
|
||||
apdu_len += encode_tagged_unsigned(&apdu[apdu_len],
|
||||
data->type.record.RecordCount);
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int arf_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_ATOMIC_READ_FILE_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
int tag_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
int type = 0; /* for decoding */
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && data) {
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[0], &tag_number,
|
||||
&len_value_type);
|
||||
if (tag_number != BACNET_APPLICATION_TAG_OBJECT_ID)
|
||||
return -1;
|
||||
len += decode_object_id(&apdu[len], &type, &data->object_instance);
|
||||
data->object_type = type;
|
||||
if (decode_is_opening_tag_number(&apdu[len], 0)) {
|
||||
data->access = FILE_STREAM_ACCESS;
|
||||
/* a tag number is not extended so only one octet */
|
||||
len++;
|
||||
/* fileStartPosition */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT)
|
||||
return -1;
|
||||
len += decode_signed(&apdu[len],
|
||||
len_value_type, &data->type.stream.fileStartPosition);
|
||||
/* requestedOctetCount */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT)
|
||||
return -1;
|
||||
len += decode_unsigned(&apdu[len],
|
||||
len_value_type, &data->type.stream.requestedOctetCount);
|
||||
if (!decode_is_closing_tag_number(&apdu[len], 0))
|
||||
return -1;
|
||||
/* a tag number is not extended so only one octet */
|
||||
len++;
|
||||
} else if (decode_is_opening_tag_number(&apdu[len], 1)) {
|
||||
data->access = FILE_RECORD_ACCESS;
|
||||
/* a tag number is not extended so only one octet */
|
||||
len++;
|
||||
/* fileStartRecord */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT)
|
||||
return -1;
|
||||
len += decode_signed(&apdu[len],
|
||||
len_value_type, &data->type.record.fileStartRecord);
|
||||
/* RecordCount */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT)
|
||||
return -1;
|
||||
len += decode_unsigned(&apdu[len],
|
||||
len_value_type, &data->type.record.RecordCount);
|
||||
if (!decode_is_closing_tag_number(&apdu[len], 1))
|
||||
return -1;
|
||||
/* a tag number is not extended so only one octet */
|
||||
len++;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int arf_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_ATOMIC_READ_FILE)
|
||||
return -1;
|
||||
offset = 4;
|
||||
|
||||
if (apdu_len > offset) {
|
||||
len = arf_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* encode service */
|
||||
int arf_ack_encode_apdu(uint8_t * apdu,
|
||||
uint8_t invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_COMPLEX_ACK;
|
||||
apdu[1] = invoke_id;
|
||||
apdu[2] = SERVICE_CONFIRMED_ATOMIC_READ_FILE; /* service choice */
|
||||
apdu_len = 3;
|
||||
/* endOfFile */
|
||||
apdu_len +=
|
||||
encode_tagged_boolean(&apdu[apdu_len], data->endOfFile);
|
||||
switch (data->access) {
|
||||
case FILE_STREAM_ACCESS:
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 0);
|
||||
apdu_len += encode_tagged_signed(&apdu[apdu_len],
|
||||
data->type.stream.fileStartPosition);
|
||||
apdu_len += encode_tagged_octet_string(&apdu[apdu_len],
|
||||
&data->fileData);
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 0);
|
||||
break;
|
||||
case FILE_RECORD_ACCESS:
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 1);
|
||||
apdu_len += encode_tagged_signed(&apdu[apdu_len],
|
||||
data->type.record.fileStartRecord);
|
||||
apdu_len += encode_tagged_unsigned(&apdu[apdu_len],
|
||||
data->type.record.RecordCount);
|
||||
apdu_len += encode_tagged_octet_string(&apdu[apdu_len],
|
||||
&data->fileData);
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int arf_ack_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_ATOMIC_READ_FILE_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
int tag_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && data) {
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[0], &tag_number,
|
||||
&len_value_type);
|
||||
if (tag_number != BACNET_APPLICATION_TAG_BOOLEAN)
|
||||
return -1;
|
||||
data->endOfFile = decode_boolean(len_value_type);
|
||||
if (decode_is_opening_tag_number(&apdu[len], 0)) {
|
||||
data->access = FILE_STREAM_ACCESS;
|
||||
/* a tag number is not extended so only one octet */
|
||||
len++;
|
||||
/* fileStartPosition */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT)
|
||||
return -1;
|
||||
len += decode_signed(&apdu[len],
|
||||
len_value_type, &data->type.stream.fileStartPosition);
|
||||
/* fileData */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_OCTET_STRING)
|
||||
return -1;
|
||||
len += decode_octet_string(&apdu[len],
|
||||
len_value_type, &data->fileData);
|
||||
if (!decode_is_closing_tag_number(&apdu[len], 0))
|
||||
return -1;
|
||||
/* a tag number is not extended so only one octet */
|
||||
len++;
|
||||
} else if (decode_is_opening_tag_number(&apdu[len], 1)) {
|
||||
data->access = FILE_RECORD_ACCESS;
|
||||
/* a tag number is not extended so only one octet */
|
||||
len++;
|
||||
/* fileStartRecord */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT)
|
||||
return -1;
|
||||
len += decode_signed(&apdu[len],
|
||||
len_value_type, &data->type.record.fileStartRecord);
|
||||
/* returnedRecordCount */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT)
|
||||
return -1;
|
||||
len += decode_unsigned(&apdu[len],
|
||||
len_value_type, &data->type.record.RecordCount);
|
||||
/* fileData */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_OCTET_STRING)
|
||||
return -1;
|
||||
len += decode_octet_string(&apdu[len],
|
||||
len_value_type, &data->fileData);
|
||||
if (!decode_is_closing_tag_number(&apdu[len], 1))
|
||||
return -1;
|
||||
/* a tag number is not extended so only one octet */
|
||||
len++;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int arf_ack_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id, BACNET_ATOMIC_READ_FILE_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_COMPLEX_ACK)
|
||||
return -1;
|
||||
*invoke_id = apdu[1]; /* invoke id - filled in by net layer */
|
||||
if (apdu[2] != SERVICE_CONFIRMED_ATOMIC_READ_FILE)
|
||||
return -1;
|
||||
offset = 3;
|
||||
|
||||
if (apdu_len > offset) {
|
||||
len = arf_ack_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testAtomicReadFileAckAccess(Test * pTest,
|
||||
BACNET_ATOMIC_READ_FILE_DATA * data)
|
||||
{
|
||||
BACNET_ATOMIC_READ_FILE_DATA test_data = { 0 };
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 128;
|
||||
uint8_t test_invoke_id = 0;
|
||||
|
||||
len = arf_ack_encode_apdu(&apdu[0], invoke_id, data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = arf_ack_decode_apdu(&apdu[0],
|
||||
apdu_len, &test_invoke_id, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_data.endOfFile == data->endOfFile);
|
||||
ct_test(pTest, test_data.access == data->access);
|
||||
if (test_data.access == FILE_STREAM_ACCESS) {
|
||||
ct_test(pTest, test_data.type.stream.fileStartPosition ==
|
||||
data->type.stream.fileStartPosition);
|
||||
} else if (test_data.access == FILE_RECORD_ACCESS) {
|
||||
ct_test(pTest, test_data.type.record.fileStartRecord ==
|
||||
data->type.record.fileStartRecord);
|
||||
ct_test(pTest, test_data.type.record.RecordCount ==
|
||||
data->type.record.RecordCount);
|
||||
}
|
||||
ct_test(pTest, octetstring_length(&test_data.fileData) ==
|
||||
octetstring_length(&data->fileData));
|
||||
ct_test(pTest, memcmp(octetstring_value(&test_data.fileData),
|
||||
octetstring_value(&data->fileData),
|
||||
octetstring_length(&test_data.fileData)) == 0);
|
||||
}
|
||||
|
||||
void testAtomicReadFileAck(Test * pTest)
|
||||
{
|
||||
BACNET_ATOMIC_READ_FILE_DATA data = { 0 };
|
||||
uint8_t test_octet_string[32] = "Joshua-Mary-Anna-Christopher";
|
||||
|
||||
|
||||
data.endOfFile = true;
|
||||
data.access = FILE_STREAM_ACCESS;
|
||||
data.type.stream.fileStartPosition = 0;
|
||||
octetstring_init(&data.fileData,
|
||||
test_octet_string, sizeof(test_octet_string));
|
||||
testAtomicReadFileAckAccess(pTest, &data);
|
||||
|
||||
data.endOfFile = false;
|
||||
data.access = FILE_RECORD_ACCESS;
|
||||
data.type.record.fileStartRecord = 1;
|
||||
data.type.record.RecordCount = 2;
|
||||
octetstring_init(&data.fileData,
|
||||
test_octet_string, sizeof(test_octet_string));
|
||||
testAtomicReadFileAckAccess(pTest, &data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void testAtomicReadFileAccess(Test * pTest,
|
||||
BACNET_ATOMIC_READ_FILE_DATA * data)
|
||||
{
|
||||
BACNET_ATOMIC_READ_FILE_DATA test_data = { 0 };
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 128;
|
||||
uint8_t test_invoke_id = 0;
|
||||
|
||||
len = arf_encode_apdu(&apdu[0], invoke_id, data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = arf_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_data.object_type == data->object_type);
|
||||
ct_test(pTest, test_data.object_instance == data->object_instance);
|
||||
ct_test(pTest, test_data.access == data->access);
|
||||
if (test_data.access == FILE_STREAM_ACCESS) {
|
||||
ct_test(pTest, test_data.type.stream.fileStartPosition ==
|
||||
data->type.stream.fileStartPosition);
|
||||
ct_test(pTest, test_data.type.stream.requestedOctetCount ==
|
||||
data->type.stream.requestedOctetCount);
|
||||
} else if (test_data.access == FILE_RECORD_ACCESS) {
|
||||
ct_test(pTest, test_data.type.record.fileStartRecord ==
|
||||
data->type.record.fileStartRecord);
|
||||
ct_test(pTest, test_data.type.record.RecordCount ==
|
||||
data->type.record.RecordCount);
|
||||
}
|
||||
}
|
||||
|
||||
void testAtomicReadFile(Test * pTest)
|
||||
{
|
||||
BACNET_ATOMIC_READ_FILE_DATA data = { 0 };
|
||||
|
||||
data.object_type = OBJECT_FILE;
|
||||
data.object_instance = 1;
|
||||
data.access = FILE_STREAM_ACCESS;
|
||||
data.type.stream.fileStartPosition = 0;
|
||||
data.type.stream.requestedOctetCount = 128;
|
||||
testAtomicReadFileAccess(pTest, &data);
|
||||
|
||||
data.object_type = OBJECT_FILE;
|
||||
data.object_instance = 2;
|
||||
data.access = FILE_RECORD_ACCESS;
|
||||
data.type.record.fileStartRecord = 1;
|
||||
data.type.record.RecordCount = 2;
|
||||
testAtomicReadFileAccess(pTest, &data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_ATOMIC_READ_FILE
|
||||
uint16_t Device_Max_APDU_Length_Accepted(void)
|
||||
{
|
||||
return MAX_APDU;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet AtomicReadFile", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testAtomicReadFile);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testAtomicReadFileAck);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_xxx */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,408 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "device.h"
|
||||
#include "awf.h"
|
||||
|
||||
/* Atomic Write File */
|
||||
|
||||
/* encode service */
|
||||
int awf_encode_apdu(uint8_t * apdu,
|
||||
uint8_t invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] =
|
||||
encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted());
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_ATOMIC_WRITE_FILE; /* service choice */
|
||||
apdu_len = 4;
|
||||
apdu_len += encode_tagged_object_id(&apdu[apdu_len],
|
||||
data->object_type, data->object_instance);
|
||||
switch (data->access) {
|
||||
case FILE_STREAM_ACCESS:
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 0);
|
||||
apdu_len += encode_tagged_signed(&apdu[apdu_len],
|
||||
data->type.stream.fileStartPosition);
|
||||
apdu_len += encode_tagged_octet_string(&apdu[apdu_len],
|
||||
&data->fileData);
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 0);
|
||||
break;
|
||||
case FILE_RECORD_ACCESS:
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 1);
|
||||
apdu_len += encode_tagged_signed(&apdu[apdu_len],
|
||||
data->type.record.fileStartRecord);
|
||||
apdu_len += encode_tagged_unsigned(&apdu[apdu_len],
|
||||
data->type.record.returnedRecordCount);
|
||||
apdu_len += encode_tagged_octet_string(&apdu[apdu_len],
|
||||
&data->fileData);
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int awf_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_ATOMIC_WRITE_FILE_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
int tag_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
int32_t signed_value = 0;
|
||||
uint32_t unsigned_value = 0;
|
||||
int type = 0; /* for decoding */
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && data) {
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[0], &tag_number,
|
||||
&len_value_type);
|
||||
if (tag_number != BACNET_APPLICATION_TAG_OBJECT_ID)
|
||||
return -1;
|
||||
len += decode_object_id(&apdu[len], &type, &data->object_instance);
|
||||
data->object_type = type;
|
||||
if (decode_is_opening_tag_number(&apdu[len], 0)) {
|
||||
data->access = FILE_STREAM_ACCESS;
|
||||
/* a tag number of 2 is not extended so only one octet */
|
||||
len++;
|
||||
/* fileStartPosition */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT)
|
||||
return -1;
|
||||
len += decode_signed(&apdu[len], len_value_type, &signed_value);
|
||||
data->type.stream.fileStartPosition = signed_value;
|
||||
/* fileData */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_OCTET_STRING)
|
||||
return -1;
|
||||
len += decode_octet_string(&apdu[len],
|
||||
len_value_type, &data->fileData);
|
||||
if (!decode_is_closing_tag_number(&apdu[len], 0))
|
||||
return -1;
|
||||
/* a tag number is not extended so only one octet */
|
||||
len++;
|
||||
} else if (decode_is_opening_tag_number(&apdu[len], 1)) {
|
||||
data->access = FILE_RECORD_ACCESS;
|
||||
/* a tag number is not extended so only one octet */
|
||||
len++;
|
||||
/* fileStartRecord */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_SIGNED_INT)
|
||||
return -1;
|
||||
len += decode_signed(&apdu[len], len_value_type, &signed_value);
|
||||
data->type.record.fileStartRecord = signed_value;
|
||||
/* returnedRecordCount */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT)
|
||||
return -1;
|
||||
len += decode_unsigned(&apdu[len], len_value_type,
|
||||
&unsigned_value);
|
||||
data->type.record.returnedRecordCount = unsigned_value;
|
||||
/* fileData */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_OCTET_STRING)
|
||||
return -1;
|
||||
len += decode_octet_string(&apdu[len],
|
||||
len_value_type, &data->fileData);
|
||||
if (!decode_is_closing_tag_number(&apdu[len], 1))
|
||||
return -1;
|
||||
/* a tag number is not extended so only one octet */
|
||||
len++;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int awf_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_ATOMIC_WRITE_FILE)
|
||||
return -1;
|
||||
offset = 4;
|
||||
|
||||
if (apdu_len > offset) {
|
||||
len = awf_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int awf_ack_encode_apdu(uint8_t * apdu,
|
||||
uint8_t invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_COMPLEX_ACK;
|
||||
apdu[1] = invoke_id;
|
||||
apdu[2] = SERVICE_CONFIRMED_ATOMIC_WRITE_FILE; /* service choice */
|
||||
apdu_len = 3;
|
||||
switch (data->access) {
|
||||
case FILE_STREAM_ACCESS:
|
||||
apdu_len += encode_context_signed(&apdu[apdu_len], 0,
|
||||
data->type.stream.fileStartPosition);
|
||||
break;
|
||||
case FILE_RECORD_ACCESS:
|
||||
apdu_len += encode_context_signed(&apdu[apdu_len], 1,
|
||||
data->type.record.fileStartRecord);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int awf_ack_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_ATOMIC_WRITE_FILE_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && data) {
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[0], &tag_number,
|
||||
&len_value_type);
|
||||
if (tag_number == 0) {
|
||||
data->access = FILE_STREAM_ACCESS;
|
||||
len += decode_signed(&apdu[len],
|
||||
len_value_type, &data->type.stream.fileStartPosition);
|
||||
} else if (tag_number == 1) {
|
||||
data->access = FILE_RECORD_ACCESS;
|
||||
len += decode_signed(&apdu[len],
|
||||
len_value_type, &data->type.record.fileStartRecord);
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int awf_ack_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id, BACNET_ATOMIC_WRITE_FILE_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_COMPLEX_ACK)
|
||||
return -1;
|
||||
*invoke_id = apdu[1]; /* invoke id - filled in by net layer */
|
||||
if (apdu[2] != SERVICE_CONFIRMED_ATOMIC_WRITE_FILE)
|
||||
return -1;
|
||||
offset = 3;
|
||||
|
||||
if (apdu_len > offset) {
|
||||
len = awf_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testAtomicWriteFileAccess(Test * pTest,
|
||||
BACNET_ATOMIC_WRITE_FILE_DATA * data)
|
||||
{
|
||||
BACNET_ATOMIC_WRITE_FILE_DATA test_data = { 0 };
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 128;
|
||||
uint8_t test_invoke_id = 0;
|
||||
|
||||
len = awf_encode_apdu(&apdu[0], invoke_id, data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = awf_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_data.object_type == data->object_type);
|
||||
ct_test(pTest, test_data.object_instance == data->object_instance);
|
||||
ct_test(pTest, test_data.access == data->access);
|
||||
if (test_data.access == FILE_STREAM_ACCESS) {
|
||||
ct_test(pTest, test_data.type.stream.fileStartPosition ==
|
||||
data->type.stream.fileStartPosition);
|
||||
} else if (test_data.access == FILE_RECORD_ACCESS) {
|
||||
ct_test(pTest, test_data.type.record.fileStartRecord ==
|
||||
data->type.record.fileStartRecord);
|
||||
ct_test(pTest, test_data.type.record.returnedRecordCount ==
|
||||
data->type.record.returnedRecordCount);
|
||||
}
|
||||
ct_test(pTest, octetstring_length(&test_data.fileData) ==
|
||||
octetstring_length(&data->fileData));
|
||||
ct_test(pTest, memcmp(octetstring_value(&test_data.fileData),
|
||||
octetstring_value(&data->fileData),
|
||||
octetstring_length(&test_data.fileData)) == 0);
|
||||
}
|
||||
|
||||
void testAtomicWriteFile(Test * pTest)
|
||||
{
|
||||
BACNET_ATOMIC_WRITE_FILE_DATA data = { 0 };
|
||||
uint8_t test_octet_string[32] = "Joshua-Mary-Anna-Christopher";
|
||||
|
||||
data.object_type = OBJECT_FILE;
|
||||
data.object_instance = 1;
|
||||
data.access = FILE_STREAM_ACCESS;
|
||||
data.type.stream.fileStartPosition = 0;
|
||||
octetstring_init(&data.fileData,
|
||||
test_octet_string, sizeof(test_octet_string));
|
||||
testAtomicWriteFileAccess(pTest, &data);
|
||||
|
||||
data.object_type = OBJECT_FILE;
|
||||
data.object_instance = 1;
|
||||
data.access = FILE_RECORD_ACCESS;
|
||||
data.type.record.fileStartRecord = 1;
|
||||
data.type.record.returnedRecordCount = 2;
|
||||
octetstring_init(&data.fileData,
|
||||
test_octet_string, sizeof(test_octet_string));
|
||||
testAtomicWriteFileAccess(pTest, &data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void testAtomicWriteFileAckAccess(Test * pTest,
|
||||
BACNET_ATOMIC_WRITE_FILE_DATA * data)
|
||||
{
|
||||
BACNET_ATOMIC_WRITE_FILE_DATA test_data = { 0 };
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 128;
|
||||
uint8_t test_invoke_id = 0;
|
||||
|
||||
len = awf_encode_apdu(&apdu[0], invoke_id, data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = awf_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_data.access == data->access);
|
||||
if (test_data.access == FILE_STREAM_ACCESS) {
|
||||
ct_test(pTest, test_data.type.stream.fileStartPosition ==
|
||||
data->type.stream.fileStartPosition);
|
||||
} else if (test_data.access == FILE_RECORD_ACCESS) {
|
||||
ct_test(pTest, test_data.type.record.fileStartRecord ==
|
||||
data->type.record.fileStartRecord);
|
||||
}
|
||||
}
|
||||
|
||||
void testAtomicWriteFileAck(Test * pTest)
|
||||
{
|
||||
BACNET_ATOMIC_WRITE_FILE_DATA data = { 0 };
|
||||
|
||||
data.access = FILE_STREAM_ACCESS;
|
||||
data.type.stream.fileStartPosition = 42;
|
||||
testAtomicWriteFileAckAccess(pTest, &data);
|
||||
|
||||
data.access = FILE_RECORD_ACCESS;
|
||||
data.type.record.fileStartRecord = 54;
|
||||
testAtomicWriteFileAckAccess(pTest, &data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_ATOMIC_WRITE_FILE
|
||||
uint16_t Device_Max_APDU_Length_Accepted(void)
|
||||
{
|
||||
return MAX_APDU;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet AtomicWriteFile", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testAtomicWriteFile);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testAtomicWriteFileAck);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_WRITE_PROPERTY */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,79 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2007 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "config.h"
|
||||
#include "bacdef.h"
|
||||
|
||||
void bacnet_address_copy(BACNET_ADDRESS * dest, BACNET_ADDRESS * src)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (dest && src) {
|
||||
dest->mac_len = src->mac_len;
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
dest->mac[i] = src->mac[i];
|
||||
}
|
||||
dest->net = src->net;
|
||||
dest->len = src->len;
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
dest->adr[i] = src->adr[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool bacnet_address_same(BACNET_ADDRESS * dest, BACNET_ADDRESS * src)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (!dest || !src)
|
||||
return false;
|
||||
if (dest->mac_len != src->mac_len)
|
||||
return false;
|
||||
for (i = 0; i < dest->mac_len; i++) {
|
||||
if (dest->mac[i] != src->mac[i])
|
||||
return false;
|
||||
}
|
||||
if (dest->net != src->net)
|
||||
return false;
|
||||
if (dest->len != src->len)
|
||||
return false;
|
||||
for (i = 0; i < dest->len; i++) {
|
||||
if (dest->adr[i] != src->adr[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,259 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
|
||||
/* encode service */
|
||||
int bacerror_encode_apdu(uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_CONFIRMED_SERVICE service,
|
||||
BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_ERROR;
|
||||
apdu[1] = invoke_id;
|
||||
apdu[2] = service;
|
||||
apdu_len = 3;
|
||||
/* service parameters */
|
||||
apdu_len += encode_tagged_enumerated(&apdu[apdu_len], error_class);
|
||||
apdu_len += encode_tagged_enumerated(&apdu[apdu_len], error_code);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the application class and code */
|
||||
int bacerror_decode_error_class_and_code(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code)
|
||||
{
|
||||
int len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
int decoded_value = 0;
|
||||
|
||||
if (apdu_len) {
|
||||
/* error class */
|
||||
len += decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
if (tag_number != BACNET_APPLICATION_TAG_ENUMERATED)
|
||||
return 0;
|
||||
len +=
|
||||
decode_enumerated(&apdu[len], len_value_type, &decoded_value);
|
||||
if (error_class)
|
||||
*error_class = (BACNET_ERROR_CLASS)decoded_value;
|
||||
/* error code */
|
||||
len += decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
if (tag_number != BACNET_APPLICATION_TAG_ENUMERATED)
|
||||
return 0;
|
||||
len +=
|
||||
decode_enumerated(&apdu[len], len_value_type, &decoded_value);
|
||||
if (error_code)
|
||||
*error_code = (BACNET_ERROR_CODE)decoded_value;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int bacerror_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id,
|
||||
BACNET_CONFIRMED_SERVICE * service,
|
||||
BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (apdu_len > 2) {
|
||||
if (invoke_id)
|
||||
*invoke_id = apdu[0];
|
||||
if (service)
|
||||
*service = (BACNET_CONFIRMED_SERVICE)apdu[1];
|
||||
/* decode the application class and code */
|
||||
len = bacerror_decode_error_class_and_code(&apdu[2],
|
||||
apdu_len - 2, error_class, error_code);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
/* decode the whole APDU - mainly used for unit testing */
|
||||
int bacerror_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id,
|
||||
BACNET_CONFIRMED_SERVICE * service,
|
||||
BACNET_ERROR_CLASS * error_class, BACNET_ERROR_CODE * error_code)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu_len) {
|
||||
if (apdu[0] != PDU_TYPE_ERROR)
|
||||
return -1;
|
||||
if (apdu_len > 1) {
|
||||
len = bacerror_decode_service_request(&apdu[1],
|
||||
apdu_len - 1, invoke_id, service, error_class, error_code);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void testBACError(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 0;
|
||||
BACNET_CONFIRMED_SERVICE service = 0;
|
||||
BACNET_ERROR_CLASS error_class = 0;
|
||||
BACNET_ERROR_CODE error_code = 0;
|
||||
uint8_t test_invoke_id = 0;
|
||||
BACNET_CONFIRMED_SERVICE test_service = 0;
|
||||
BACNET_ERROR_CLASS test_error_class = 0;
|
||||
BACNET_ERROR_CODE test_error_code = 0;
|
||||
|
||||
len = bacerror_encode_apdu(&apdu[0],
|
||||
invoke_id, service, error_class, error_code);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = bacerror_decode_apdu(&apdu[0],
|
||||
apdu_len,
|
||||
&test_invoke_id,
|
||||
&test_service, &test_error_class, &test_error_code);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, test_service == service);
|
||||
ct_test(pTest, test_error_class == error_class);
|
||||
ct_test(pTest, test_error_code == error_code);
|
||||
|
||||
/* change type to get negative response */
|
||||
apdu[0] = PDU_TYPE_ABORT;
|
||||
len = bacerror_decode_apdu(&apdu[0],
|
||||
apdu_len,
|
||||
&test_invoke_id,
|
||||
&test_service, &test_error_class, &test_error_code);
|
||||
ct_test(pTest, len == -1);
|
||||
|
||||
/* test NULL APDU */
|
||||
len = bacerror_decode_apdu(NULL,
|
||||
apdu_len,
|
||||
&test_invoke_id,
|
||||
&test_service, &test_error_class, &test_error_code);
|
||||
ct_test(pTest, len == -1);
|
||||
|
||||
/* force a zero length */
|
||||
len = bacerror_decode_apdu(&apdu[0],
|
||||
0,
|
||||
&test_invoke_id,
|
||||
&test_service, &test_error_class, &test_error_code);
|
||||
ct_test(pTest, len == 0);
|
||||
|
||||
|
||||
/* check them all... */
|
||||
for (service = 0; service < MAX_BACNET_CONFIRMED_SERVICE; service++) {
|
||||
for (error_class = 0;
|
||||
error_class < MAX_BACNET_ERROR_CLASS; error_class++) {
|
||||
for (error_code = 0;
|
||||
error_code < MAX_BACNET_ERROR_CODE; error_code++) {
|
||||
len = bacerror_encode_apdu(&apdu[0],
|
||||
invoke_id, service, error_class, error_code);
|
||||
apdu_len = len;
|
||||
ct_test(pTest, len != 0);
|
||||
len = bacerror_decode_apdu(&apdu[0],
|
||||
apdu_len,
|
||||
&test_invoke_id,
|
||||
&test_service, &test_error_class, &test_error_code);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, test_service == service);
|
||||
ct_test(pTest, test_error_class == error_class);
|
||||
ct_test(pTest, test_error_code == error_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* max boundaries */
|
||||
service = 255;
|
||||
error_class = LAST_PROPRIETARY_ERROR_CLASS;
|
||||
error_code = LAST_PROPRIETARY_ERROR_CODE;
|
||||
len = bacerror_encode_apdu(&apdu[0],
|
||||
invoke_id, service, error_class, error_code);
|
||||
apdu_len = len;
|
||||
ct_test(pTest, len != 0);
|
||||
len = bacerror_decode_apdu(&apdu[0],
|
||||
apdu_len,
|
||||
&test_invoke_id,
|
||||
&test_service, &test_error_class, &test_error_code);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, test_service == service);
|
||||
ct_test(pTest, test_error_class == error_class);
|
||||
ct_test(pTest, test_error_code == error_code);
|
||||
|
||||
}
|
||||
|
||||
#ifdef TEST_BACERROR
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Error", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testBACError);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_ERROR */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,434 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2004 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
/* BACnet Integer encoding and decoding */
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
int encode_unsigned16(uint8_t * apdu, uint16_t value)
|
||||
{
|
||||
#if BIG_ENDIAN
|
||||
apdu[0] = (uint8_t)(value & 0x00ff);
|
||||
apdu[1] = (uint8_t)((value & 0xff00) >> 8);
|
||||
#else
|
||||
apdu[0] = (uint8_t)((value & 0xff00) >> 8);
|
||||
apdu[1] = (uint8_t)(value & 0x00ff);
|
||||
#endif
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int decode_unsigned16(uint8_t * apdu, uint16_t * value)
|
||||
{
|
||||
if (value) {
|
||||
#if BIG_ENDIAN
|
||||
*value = (uint16_t)(((uint16_t)apdu[0]) & 0x00ff);
|
||||
*value |= ((uint16_t)((((uint16_t)apdu[1]) << 8) & 0xff00));
|
||||
#else
|
||||
*value = (uint16_t)((((uint16_t)apdu[0]) << 8) & 0xff00);
|
||||
*value |= ((uint16_t)(((uint16_t)apdu[1]) & 0x00ff));
|
||||
#endif
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int encode_unsigned24(uint8_t * apdu, uint32_t value)
|
||||
{
|
||||
#if BIG_ENDIAN
|
||||
apdu[0] = (uint8_t)(value & 0x0000ff);
|
||||
apdu[1] = (uint8_t)((value & 0x00ff00) >> 8);
|
||||
apdu[2] = (uint8_t)((value & 0xff0000) >> 16);
|
||||
#else
|
||||
apdu[0] = (uint8_t)((value & 0xff0000) >> 16);
|
||||
apdu[1] = (uint8_t)((value & 0x00ff00) >> 8);
|
||||
apdu[2] = (uint8_t)(value & 0x0000ff);
|
||||
#endif
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
int decode_unsigned24(uint8_t * apdu, uint32_t * value)
|
||||
{
|
||||
if (value) {
|
||||
#if BIG_ENDIAN
|
||||
*value = (uint32_t)(apdu[0] & 0x000000ff);
|
||||
*value |= ((uint32_t)((((uint32_t)apdu[1]) << 8) & 0x0000ff00));
|
||||
*value |= ((uint32_t)((((uint32_t)apdu[2]) << 16) & 0x00ff0000));
|
||||
#else
|
||||
*value = ((uint32_t)((((uint32_t)apdu[0]) << 16) & 0x00ff0000));
|
||||
*value |= (uint32_t)((((uint32_t)apdu[1]) << 8) & 0x0000ff00);
|
||||
*value |= ((uint32_t)(((uint32_t)apdu[2]) & 0x000000ff));
|
||||
#endif
|
||||
}
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
int encode_unsigned32(uint8_t * apdu, uint32_t value)
|
||||
{
|
||||
#if BIG_ENDIAN
|
||||
apdu[0] = (uint8_t)(value & 0x000000ff);
|
||||
apdu[1] = (uint8_t)((value & 0x0000ff00) >> 8);
|
||||
apdu[2] = (uint8_t)((value & 0x00ff0000) >> 16);
|
||||
apdu[3] = (uint8_t)((value & 0xff000000) >> 24);
|
||||
#else
|
||||
apdu[0] = (uint8_t)((value & 0xff000000) >> 24);
|
||||
apdu[1] = (uint8_t)((value & 0x00ff0000) >> 16);
|
||||
apdu[2] = (uint8_t)((value & 0x0000ff00) >> 8);
|
||||
apdu[3] = (uint8_t)(value & 0x000000ff);
|
||||
#endif
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
int decode_unsigned32(uint8_t * apdu, uint32_t * value)
|
||||
{
|
||||
if (value) {
|
||||
#if BIG_ENDIAN
|
||||
*value = (uint32_t)(((uint32_t)apdu[0]) & 0x000000ff);
|
||||
*value |= ((uint32_t)((((uint32_t)apdu[1]) << 8) & 0x0000ff00));
|
||||
*value |= ((uint32_t)((((uint32_t)apdu[2]) << 16) & 0x00ff0000));
|
||||
*value |= ((uint32_t)((((uint32_t)apdu[3]) << 24) & 0xff000000));
|
||||
#else
|
||||
*value = ((uint32_t)((((uint32_t)apdu[0]) << 24) & 0xff000000));
|
||||
*value |= ((uint32_t)((((uint32_t)apdu[1]) << 16) & 0x00ff0000));
|
||||
*value |= ((uint32_t)((((uint32_t)apdu[2]) << 8) & 0x0000ff00));
|
||||
*value |= ((uint32_t)(((uint32_t)apdu[3]) & 0x000000ff));
|
||||
#endif
|
||||
}
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
int encode_signed8(uint8_t * apdu, int8_t value)
|
||||
{
|
||||
apdu[0] = (uint8_t)value;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int decode_signed8(uint8_t * apdu, int32_t * value)
|
||||
{
|
||||
if (value) {
|
||||
#if BIG_ENDIAN
|
||||
/* negative - bit 7 is set */
|
||||
if (apdu[0] & 0x80)
|
||||
*value = 0x00FFFFFF;
|
||||
else
|
||||
*value = 0;
|
||||
*value |= ((int32_t)((((int32_t)apdu[0]) << 24) & 0xff000000));
|
||||
#else
|
||||
/* negative - bit 7 is set */
|
||||
if (apdu[0] & 0x80)
|
||||
*value = 0xFFFFFF00;
|
||||
else
|
||||
*value = 0;
|
||||
*value |= ((int32_t)(((int32_t)apdu[0]) & 0x000000ff));
|
||||
#endif
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int encode_signed16(uint8_t * apdu, int16_t value)
|
||||
{
|
||||
#if BIG_ENDIAN
|
||||
apdu[0] = (uint8_t)(value & 0x00ff);
|
||||
apdu[1] = (uint8_t)((value & 0xff00) >> 8);
|
||||
#else
|
||||
apdu[0] = (uint8_t)((value & 0xff00) >> 8);
|
||||
apdu[1] = (uint8_t)(value & 0x00ff);
|
||||
#endif
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int decode_signed16(uint8_t * apdu, int32_t * value)
|
||||
{
|
||||
if (value) {
|
||||
#if BIG_ENDIAN
|
||||
/* negative - bit 7 is set */
|
||||
if (apdu[0] & 0x80)
|
||||
*value = 0x0000FFFF;
|
||||
else
|
||||
*value = 0;
|
||||
*value |= ((int32_t)((((int32_t)apdu[0]) << 24) & 0xff000000));
|
||||
*value |= ((int32_t)((((int32_t)apdu[1]) << 16) & 0x00ff0000));
|
||||
#else
|
||||
/* negative - bit 7 is set */
|
||||
if (apdu[0] & 0x80)
|
||||
*value = 0xFFFF0000;
|
||||
else
|
||||
*value = 0;
|
||||
*value |= ((int32_t)((((int32_t)apdu[0]) << 8) & 0x0000ff00));
|
||||
*value |= ((int32_t)(((int32_t)apdu[1]) & 0x000000ff));
|
||||
#endif
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int encode_signed24(uint8_t * apdu, int32_t value)
|
||||
{
|
||||
#if BIG_ENDIAN
|
||||
apdu[0] = (uint8_t)(value & 0x0000ff);
|
||||
apdu[1] = (uint8_t)((value & 0x00ff00) >> 8);
|
||||
apdu[2] = (uint8_t)((value & 0xff0000) >> 16);
|
||||
#else
|
||||
apdu[0] = (uint8_t)((value & 0xff0000) >> 16);
|
||||
apdu[1] = (uint8_t)((value & 0x00ff00) >> 8);
|
||||
apdu[2] = (uint8_t)(value & 0x0000ff);
|
||||
#endif
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
int decode_signed24(uint8_t * apdu, int32_t * value)
|
||||
{
|
||||
if (value) {
|
||||
#if BIG_ENDIAN
|
||||
/* negative - bit 7 is set */
|
||||
if (apdu[0] & 0x80)
|
||||
*value = 0x000000FF;
|
||||
else
|
||||
*value = 0;
|
||||
*value |= ((int32_t)((((int32_t)apdu[0]) << 8) & 0x0000ff00));
|
||||
*value |= ((int32_t)((((int32_t)apdu[1]) << 16) & 0x00ff0000));
|
||||
*value |= ((int32_t)((((int32_t)apdu[2]) << 24) & 0xff000000));
|
||||
#else
|
||||
/* negative - bit 7 is set */
|
||||
if (apdu[0] & 0x80)
|
||||
*value = 0xFF000000;
|
||||
else
|
||||
*value = 0;
|
||||
*value |= ((int32_t)((((int32_t)apdu[0]) << 16) & 0x00ff0000));
|
||||
*value |= ((int32_t)((((int32_t)apdu[1]) << 8) & 0x0000ff00));
|
||||
*value |= ((int32_t)(((int32_t)apdu[2]) & 0x000000ff));
|
||||
#endif
|
||||
}
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
int encode_signed32(uint8_t * apdu, int32_t value)
|
||||
{
|
||||
#if BIG_ENDIAN
|
||||
apdu[0] = (uint8_t)(value & 0x000000ff);
|
||||
apdu[1] = (uint8_t)((value & 0x0000ff00) >> 8);
|
||||
apdu[2] = (uint8_t)((value & 0x00ff0000) >> 16);
|
||||
apdu[3] = (uint8_t)((value & 0xff000000) >> 24);
|
||||
#else
|
||||
apdu[0] = (uint8_t)((value & 0xff000000) >> 24);
|
||||
apdu[1] = (uint8_t)((value & 0x00ff0000) >> 16);
|
||||
apdu[2] = (uint8_t)((value & 0x0000ff00) >> 8);
|
||||
apdu[3] = (uint8_t)(value & 0x000000ff);
|
||||
#endif
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
int decode_signed32(uint8_t * apdu, int32_t * value)
|
||||
{
|
||||
if (value) {
|
||||
#if BIG_ENDIAN
|
||||
*value = (int32_t)(((int32_t)apdu[0]) & 0x000000ff);
|
||||
*value |= ((int32_t)((((int32_t)apdu[1]) << 8) & 0x0000ff00));
|
||||
*value |= ((int32_t)((((int32_t)apdu[2]) << 16) & 0x00ff0000));
|
||||
*value |= ((int32_t)((((int32_t)apdu[3]) << 24) & 0xff000000));
|
||||
#else
|
||||
*value = ((int32_t)((((int32_t)apdu[0]) << 24) & 0xff000000));
|
||||
*value |= ((int32_t)((((int32_t)apdu[1]) << 16) & 0x00ff0000));
|
||||
*value |= ((int32_t)((((int32_t)apdu[2]) << 8) & 0x0000ff00));
|
||||
*value |= ((int32_t)(((int32_t)apdu[3]) & 0x000000ff));
|
||||
#endif
|
||||
}
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
/* end of decoding_encoding.c */
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testBACnetUnsigned16(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[32] = { 0 };
|
||||
uint16_t value = 0, test_value = 0;
|
||||
int len = 0;
|
||||
|
||||
for (value = 0; ; value++) {
|
||||
len = encode_unsigned16(&apdu[0], value);
|
||||
ct_test(pTest, len == 2);
|
||||
len = decode_unsigned16(&apdu[0], &test_value);
|
||||
ct_test(pTest, value == test_value);
|
||||
if (value == 0xFFFF)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void testBACnetUnsigned24(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[32] = { 0 };
|
||||
uint32_t value = 0, test_value = 0;
|
||||
int len = 0;
|
||||
|
||||
for (value = 0; ; value+=0xf) {
|
||||
len = encode_unsigned24(&apdu[0], value);
|
||||
ct_test(pTest, len == 3);
|
||||
len = decode_unsigned24(&apdu[0], &test_value);
|
||||
ct_test(pTest, value == test_value);
|
||||
if (value == 0xffffff)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void testBACnetUnsigned32(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[32] = { 0 };
|
||||
uint32_t value = 0, test_value = 0;
|
||||
int len = 0;
|
||||
|
||||
for (value = 0; ; value+=0xff) {
|
||||
len = encode_unsigned32(&apdu[0], value);
|
||||
ct_test(pTest, len == 4);
|
||||
len = decode_unsigned32(&apdu[0], &test_value);
|
||||
ct_test(pTest, value == test_value);
|
||||
if (value == 0xffffffff)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void testBACnetSigned8(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[32] = { 0 };
|
||||
int32_t value = 0, test_value = 0;
|
||||
int len = 0;
|
||||
|
||||
for (value = -127; ; value++) {
|
||||
len = encode_signed8(&apdu[0], value);
|
||||
ct_test(pTest, len == 1);
|
||||
len = decode_signed8(&apdu[0], &test_value);
|
||||
ct_test(pTest, value == test_value);
|
||||
if (value == 127)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void testBACnetSigned16(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[32] = { 0 };
|
||||
int32_t value = 0, test_value = 0;
|
||||
int len = 0;
|
||||
|
||||
for (value = -32767; ; value++) {
|
||||
len = encode_signed16(&apdu[0], value);
|
||||
ct_test(pTest, len == 2);
|
||||
len = decode_signed16(&apdu[0], &test_value);
|
||||
ct_test(pTest, value == test_value);
|
||||
if (value == 32767)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void testBACnetSigned24(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[32] = { 0 };
|
||||
int32_t value = 0, test_value = 0;
|
||||
int len = 0;
|
||||
|
||||
for (value = -8388607; value <= 8388607; value+=15) {
|
||||
len = encode_signed24(&apdu[0], value);
|
||||
ct_test(pTest, len == 3);
|
||||
len = decode_signed24(&apdu[0], &test_value);
|
||||
ct_test(pTest, value == test_value);
|
||||
}
|
||||
}
|
||||
|
||||
void testBACnetSigned32(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[32] = { 0 };
|
||||
int32_t value = 0, test_value = 0;
|
||||
int len = 0;
|
||||
|
||||
for (value = -2147483647; value < 0; value+=127) {
|
||||
len = encode_signed32(&apdu[0], value);
|
||||
ct_test(pTest, len == 4);
|
||||
len = decode_signed32(&apdu[0], &test_value);
|
||||
ct_test(pTest, value == test_value);
|
||||
}
|
||||
for (value = 2147483647; value > 0; value-=127) {
|
||||
len = encode_signed32(&apdu[0], value);
|
||||
ct_test(pTest, len == 4);
|
||||
len = decode_signed32(&apdu[0], &test_value);
|
||||
ct_test(pTest, value == test_value);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TEST_BACINT
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACint", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testBACnetUnsigned16);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetUnsigned24);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetUnsigned32);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetSigned8);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetSigned16);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetSigned24);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetSigned32);
|
||||
assert(rc);
|
||||
/* configure output */
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_BACINT */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,111 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 John Goulah
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "bacprop.h"
|
||||
|
||||
PROP_TAG_DATA bacnet_object_device_property_tag_map[] = {
|
||||
{PROP_OBJECT_IDENTIFIER, BACNET_APPLICATION_TAG_OBJECT_ID}
|
||||
,
|
||||
{PROP_OBJECT_NAME, BACNET_APPLICATION_TAG_CHARACTER_STRING}
|
||||
,
|
||||
{PROP_OBJECT_TYPE, BACNET_APPLICATION_TAG_ENUMERATED}
|
||||
,
|
||||
{PROP_SYSTEM_STATUS, BACNET_APPLICATION_TAG_ENUMERATED}
|
||||
,
|
||||
{PROP_VENDOR_NAME, BACNET_APPLICATION_TAG_CHARACTER_STRING}
|
||||
,
|
||||
{PROP_VENDOR_IDENTIFIER, BACNET_APPLICATION_TAG_UNSIGNED_INT}
|
||||
,
|
||||
{PROP_MODEL_NAME, BACNET_APPLICATION_TAG_CHARACTER_STRING}
|
||||
,
|
||||
{PROP_FIRMWARE_REVISION, BACNET_APPLICATION_TAG_CHARACTER_STRING}
|
||||
,
|
||||
{PROP_APPLICATION_SOFTWARE_VERSION,
|
||||
BACNET_APPLICATION_TAG_CHARACTER_STRING}
|
||||
,
|
||||
{PROP_PROTOCOL_VERSION, BACNET_APPLICATION_TAG_UNSIGNED_INT}
|
||||
,
|
||||
{PROP_PROTOCOL_CONFORMANCE_CLASS, BACNET_APPLICATION_TAG_UNSIGNED_INT}
|
||||
,
|
||||
{PROP_PROTOCOL_SERVICES_SUPPORTED, BACNET_APPLICATION_TAG_BIT_STRING}
|
||||
,
|
||||
{PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED,
|
||||
BACNET_APPLICATION_TAG_BIT_STRING}
|
||||
,
|
||||
{PROP_MAX_APDU_LENGTH_ACCEPTED, BACNET_APPLICATION_TAG_UNSIGNED_INT}
|
||||
,
|
||||
{PROP_SEGMENTATION_SUPPORTED, BACNET_APPLICATION_TAG_ENUMERATED}
|
||||
,
|
||||
{PROP_APDU_TIMEOUT, BACNET_APPLICATION_TAG_UNSIGNED_INT}
|
||||
,
|
||||
{PROP_NUMBER_OF_APDU_RETRIES, BACNET_APPLICATION_TAG_UNSIGNED_INT}
|
||||
,
|
||||
{-1, -1}
|
||||
};
|
||||
|
||||
signed bacprop_tag_by_index_default(PROP_TAG_DATA * data_list,
|
||||
signed index, signed default_ret)
|
||||
{
|
||||
signed pUnsigned = 0;
|
||||
|
||||
if (data_list) {
|
||||
while (data_list->prop_id != -1) {
|
||||
if (data_list->prop_id == index) {
|
||||
pUnsigned = data_list->tag_id;
|
||||
break;
|
||||
}
|
||||
data_list++;
|
||||
}
|
||||
}
|
||||
|
||||
return pUnsigned ? pUnsigned : default_ret;
|
||||
}
|
||||
|
||||
|
||||
signed bacprop_property_tag(BACNET_OBJECT_TYPE type, signed prop)
|
||||
{
|
||||
switch (type) {
|
||||
case OBJECT_DEVICE:
|
||||
return
|
||||
bacprop_tag_by_index_default
|
||||
(bacnet_object_device_property_tag_map, prop, -1);
|
||||
default:
|
||||
fprintf(stderr, "Unsupported object type");
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,637 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2004 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h> /* for strlen */
|
||||
#include "bacstr.h"
|
||||
#include "bits.h"
|
||||
|
||||
void bitstring_init(BACNET_BIT_STRING * bit_string)
|
||||
{
|
||||
int i;
|
||||
|
||||
bit_string->bits_used = 0;
|
||||
for (i = 0; i < MAX_BITSTRING_BYTES; i++) {
|
||||
bit_string->value[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void bitstring_set_bit(BACNET_BIT_STRING * bit_string, uint8_t bit,
|
||||
bool value)
|
||||
{
|
||||
uint8_t byte_number = bit / 8;
|
||||
uint8_t bit_mask = 1;
|
||||
|
||||
if (byte_number < MAX_BITSTRING_BYTES) {
|
||||
/* set max bits used */
|
||||
if (bit_string->bits_used < (bit + 1))
|
||||
bit_string->bits_used = bit + 1;
|
||||
bit_mask = bit_mask << (bit - (byte_number * 8));
|
||||
if (value)
|
||||
bit_string->value[byte_number] |= bit_mask;
|
||||
else
|
||||
bit_string->value[byte_number] &= (~(bit_mask));
|
||||
}
|
||||
}
|
||||
|
||||
bool bitstring_bit(BACNET_BIT_STRING * bit_string, uint8_t bit)
|
||||
{
|
||||
bool value = false;
|
||||
uint8_t byte_number = bit / 8;
|
||||
uint8_t bit_mask = 1;
|
||||
|
||||
if (bit < (MAX_BITSTRING_BYTES * 8)) {
|
||||
bit_mask = bit_mask << (bit - (byte_number * 8));
|
||||
if (bit_string->value[byte_number] & bit_mask)
|
||||
value = true;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
uint8_t bitstring_bits_used(BACNET_BIT_STRING * bit_string)
|
||||
{
|
||||
return bit_string->bits_used;
|
||||
}
|
||||
|
||||
/* returns the number of bytes that a bit string is using */
|
||||
int bitstring_bytes_used(BACNET_BIT_STRING * bit_string)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
uint8_t used_bytes = 0;
|
||||
uint8_t last_bit = 0;
|
||||
|
||||
if (bit_string->bits_used) {
|
||||
last_bit = bit_string->bits_used - 1;
|
||||
used_bytes = last_bit / 8;
|
||||
/* add one for the first byte */
|
||||
used_bytes++;
|
||||
len = used_bytes;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
uint8_t bitstring_octet(BACNET_BIT_STRING * bit_string, uint8_t index)
|
||||
{
|
||||
uint8_t octet = 0;
|
||||
|
||||
if (bit_string) {
|
||||
if (index < MAX_BITSTRING_BYTES) {
|
||||
octet = bit_string->value[index];
|
||||
}
|
||||
}
|
||||
|
||||
return octet;
|
||||
}
|
||||
|
||||
bool bitstring_set_octet(BACNET_BIT_STRING * bit_string,
|
||||
uint8_t index, uint8_t octet)
|
||||
{
|
||||
bool status = false;
|
||||
|
||||
if (bit_string) {
|
||||
if (index < MAX_BITSTRING_BYTES) {
|
||||
bit_string->value[index] = octet;
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool bitstring_set_bits_used(BACNET_BIT_STRING * bit_string,
|
||||
uint8_t bytes_used, uint8_t unused_bits)
|
||||
{
|
||||
bool status = false;
|
||||
|
||||
if (bit_string) {
|
||||
/* FIXME: check that bytes_used is at least one? */
|
||||
bit_string->bits_used = bytes_used * 8;
|
||||
bit_string->bits_used -= unused_bits;
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t bitstring_bits_capacity(BACNET_BIT_STRING * bit_string)
|
||||
{
|
||||
if (bit_string)
|
||||
return (sizeof(bit_string->value) * 8);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CHARACTER_STRING_CAPACITY (MAX_CHARACTER_STRING_BYTES - 1)
|
||||
/* returns false if the string exceeds capacity
|
||||
initialize by using length=0 */
|
||||
bool characterstring_init(BACNET_CHARACTER_STRING * char_string,
|
||||
uint8_t encoding, char *value, size_t length)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
size_t i; /* counter */
|
||||
|
||||
if (char_string) {
|
||||
char_string->length = 0;
|
||||
char_string->encoding = encoding;
|
||||
/* save a byte at the end for NULL -
|
||||
note: assumes printable characters */
|
||||
if (length <= CHARACTER_STRING_CAPACITY) {
|
||||
if (value) {
|
||||
for (i = 0; i < MAX_CHARACTER_STRING_BYTES; i++) {
|
||||
if (i < length) {
|
||||
char_string->value[char_string->length] = value[i];
|
||||
char_string->length++;
|
||||
} else
|
||||
char_string->value[i] = 0;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < MAX_CHARACTER_STRING_BYTES; i++) {
|
||||
char_string->value[i] = 0;
|
||||
}
|
||||
}
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool characterstring_init_ansi(BACNET_CHARACTER_STRING * char_string,
|
||||
char *value)
|
||||
{
|
||||
return characterstring_init(char_string,
|
||||
CHARACTER_ANSI_X34, value, value ? strlen(value) : 0);
|
||||
}
|
||||
|
||||
bool characterstring_copy(BACNET_CHARACTER_STRING * dest,
|
||||
BACNET_CHARACTER_STRING * src)
|
||||
{
|
||||
return characterstring_init(dest,
|
||||
characterstring_encoding(src),
|
||||
characterstring_value(src), characterstring_length(src));
|
||||
}
|
||||
|
||||
bool characterstring_same(BACNET_CHARACTER_STRING * dest,
|
||||
BACNET_CHARACTER_STRING * src)
|
||||
{
|
||||
size_t i; /* counter */
|
||||
bool same_status = false;
|
||||
|
||||
if (src && dest) {
|
||||
if ((src->length == dest->length) &&
|
||||
(src->encoding == dest->encoding)) {
|
||||
same_status = true;
|
||||
for (i = 0; i < src->length; i++) {
|
||||
if (src->value[i] != dest->value[i]) {
|
||||
same_status = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (src) {
|
||||
if (src->length == 0)
|
||||
same_status = true;
|
||||
} else if (dest) {
|
||||
if (dest->length == 0)
|
||||
same_status = true;
|
||||
}
|
||||
|
||||
return same_status;
|
||||
}
|
||||
|
||||
bool characterstring_ansi_same(BACNET_CHARACTER_STRING * dest,
|
||||
const char *src)
|
||||
{
|
||||
size_t i; /* counter */
|
||||
bool same_status = false;
|
||||
|
||||
if (src && dest) {
|
||||
if ((dest->length == strlen(src)) &&
|
||||
(dest->encoding == CHARACTER_ANSI_X34)) {
|
||||
same_status = true;
|
||||
for (i = 0; i < dest->length; i++) {
|
||||
if (src[i] != dest->value[i]) {
|
||||
same_status = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* NULL matches an empty string in our world */
|
||||
else if (src) {
|
||||
if (strlen(src) == 0)
|
||||
same_status = true;
|
||||
} else if (dest) {
|
||||
if (dest->length == 0)
|
||||
same_status = true;
|
||||
}
|
||||
|
||||
return same_status;
|
||||
}
|
||||
|
||||
/* returns false if the string exceeds capacity */
|
||||
bool characterstring_append(BACNET_CHARACTER_STRING * char_string,
|
||||
char *value, size_t length)
|
||||
{
|
||||
size_t i; /* counter */
|
||||
bool status = false; /* return value */
|
||||
|
||||
if (char_string) {
|
||||
if ((length + char_string->length) <= CHARACTER_STRING_CAPACITY) {
|
||||
for (i = 0; i < length; i++) {
|
||||
char_string->value[char_string->length] = value[i];
|
||||
char_string->length++;
|
||||
}
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* This function sets a new length without changing the value.
|
||||
If length exceeds capacity, no modification happens and
|
||||
function returns false. */
|
||||
bool characterstring_truncate(BACNET_CHARACTER_STRING * char_string,
|
||||
size_t length)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
|
||||
if (char_string) {
|
||||
if (length <= CHARACTER_STRING_CAPACITY) {
|
||||
char_string->length = length;
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Returns the value. */
|
||||
char *characterstring_value(BACNET_CHARACTER_STRING * char_string)
|
||||
{
|
||||
char *value = NULL;
|
||||
|
||||
if (char_string) {
|
||||
value = char_string->value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* returns the length. */
|
||||
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;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
size_t characterstring_capacity(BACNET_CHARACTER_STRING * char_string)
|
||||
{
|
||||
size_t length = 0;
|
||||
|
||||
if (char_string) {
|
||||
length = CHARACTER_STRING_CAPACITY;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/* returns the encoding. */
|
||||
uint8_t characterstring_encoding(BACNET_CHARACTER_STRING * char_string)
|
||||
{
|
||||
uint8_t encoding = 0;
|
||||
|
||||
if (char_string) {
|
||||
encoding = char_string->encoding;
|
||||
}
|
||||
|
||||
return encoding;
|
||||
}
|
||||
|
||||
/* returns false if the string exceeds capacity
|
||||
initialize by using length=0 */
|
||||
bool octetstring_init(BACNET_OCTET_STRING * octet_string,
|
||||
uint8_t * value, size_t length)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
size_t i; /* counter */
|
||||
|
||||
if (octet_string) {
|
||||
octet_string->length = 0;
|
||||
if (length <= sizeof(octet_string->value)) {
|
||||
if (value) {
|
||||
for (i = 0; i < length; i++) {
|
||||
octet_string->value[octet_string->length] = value[i];
|
||||
octet_string->length++;
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < sizeof(octet_string->value); i++) {
|
||||
octet_string->value[i] = 0;
|
||||
}
|
||||
}
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool octetstring_copy(BACNET_OCTET_STRING * dest,
|
||||
BACNET_OCTET_STRING * src)
|
||||
{
|
||||
return octetstring_init(dest,
|
||||
octetstring_value(src), octetstring_length(src));
|
||||
}
|
||||
|
||||
/* returns false if the string exceeds capacity */
|
||||
bool octetstring_append(BACNET_OCTET_STRING * octet_string,
|
||||
uint8_t * value, size_t length)
|
||||
{
|
||||
size_t i; /* counter */
|
||||
bool status = false; /* return value */
|
||||
|
||||
if (octet_string) {
|
||||
if ((length + octet_string->length) <= sizeof(octet_string->value)) {
|
||||
for (i = 0; i < length; i++) {
|
||||
octet_string->value[octet_string->length] = value[i];
|
||||
octet_string->length++;
|
||||
}
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* This function sets a new length without changing the value.
|
||||
If length exceeds capacity, no modification happens and
|
||||
function returns false. */
|
||||
bool octetstring_truncate(BACNET_OCTET_STRING * octet_string,
|
||||
size_t length)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
|
||||
if (octet_string) {
|
||||
if (length <= sizeof(octet_string->value)) {
|
||||
octet_string->length = length;
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* returns the length. Returns the value in parameter. */
|
||||
uint8_t *octetstring_value(BACNET_OCTET_STRING * octet_string)
|
||||
{
|
||||
uint8_t *value = NULL;
|
||||
|
||||
if (octet_string) {
|
||||
value = octet_string->value;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/* returns the length. */
|
||||
size_t octetstring_length(BACNET_OCTET_STRING * octet_string)
|
||||
{
|
||||
size_t length = 0;
|
||||
|
||||
if (octet_string) {
|
||||
/* FIXME: validate length is within bounds? */
|
||||
length = octet_string->length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/* returns the length. */
|
||||
size_t octetstring_capacity(BACNET_OCTET_STRING * octet_string)
|
||||
{
|
||||
size_t length = 0;
|
||||
|
||||
if (octet_string) {
|
||||
/* FIXME: validate length is within bounds? */
|
||||
length = sizeof(octet_string->value);
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testBitString(Test * pTest)
|
||||
{
|
||||
uint8_t bit = 0;
|
||||
BACNET_BIT_STRING bit_string;
|
||||
|
||||
bitstring_init(&bit_string);
|
||||
/* verify initialization */
|
||||
ct_test(pTest, bitstring_bits_used(&bit_string) == 0);
|
||||
for (bit = 0; bit < (MAX_BITSTRING_BYTES * 8); bit++) {
|
||||
ct_test(pTest, bitstring_bit(&bit_string, bit) == false);
|
||||
}
|
||||
|
||||
/* test for true */
|
||||
for (bit = 0; bit < (MAX_BITSTRING_BYTES * 8); bit++) {
|
||||
bitstring_set_bit(&bit_string, bit, true);
|
||||
ct_test(pTest, bitstring_bits_used(&bit_string) == (bit + 1));
|
||||
ct_test(pTest, bitstring_bit(&bit_string, bit) == true);
|
||||
}
|
||||
/* test for false */
|
||||
bitstring_init(&bit_string);
|
||||
for (bit = 0; bit < (MAX_BITSTRING_BYTES * 8); bit++) {
|
||||
bitstring_set_bit(&bit_string, bit, false);
|
||||
ct_test(pTest, bitstring_bits_used(&bit_string) == (bit + 1));
|
||||
ct_test(pTest, bitstring_bit(&bit_string, bit) == false);
|
||||
}
|
||||
}
|
||||
|
||||
void testCharacterString(Test * pTest)
|
||||
{
|
||||
BACNET_CHARACTER_STRING bacnet_string;
|
||||
char *value = "Joshua,Mary,Anna,Christopher";
|
||||
char test_value[MAX_APDU] = "Patricia";
|
||||
char test_append_value[MAX_APDU] = " and the Kids";
|
||||
char test_append_string[MAX_APDU] = "";
|
||||
bool status = false;
|
||||
size_t length = 0;
|
||||
size_t test_length = 0;
|
||||
size_t i = 0;
|
||||
|
||||
/* verify initialization */
|
||||
status =
|
||||
characterstring_init(&bacnet_string, CHARACTER_ANSI_X34, NULL, 0);
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, characterstring_length(&bacnet_string) == 0);
|
||||
ct_test(pTest,
|
||||
characterstring_encoding(&bacnet_string) == CHARACTER_ANSI_X34);
|
||||
/* bounds check */
|
||||
status = characterstring_init(&bacnet_string,
|
||||
CHARACTER_ANSI_X34,
|
||||
NULL, characterstring_capacity(&bacnet_string) + 1);
|
||||
ct_test(pTest, status == false);
|
||||
status =
|
||||
characterstring_truncate(&bacnet_string,
|
||||
characterstring_capacity(&bacnet_string) + 1);
|
||||
ct_test(pTest, status == false);
|
||||
status =
|
||||
characterstring_truncate(&bacnet_string,
|
||||
characterstring_capacity(&bacnet_string));
|
||||
ct_test(pTest, status == true);
|
||||
|
||||
test_length = strlen(test_value);
|
||||
status = characterstring_init(&bacnet_string,
|
||||
CHARACTER_ANSI_X34, &test_value[0], test_length);
|
||||
ct_test(pTest, status == true);
|
||||
value = characterstring_value(&bacnet_string);
|
||||
length = characterstring_length(&bacnet_string);
|
||||
ct_test(pTest, length == test_length);
|
||||
for (i = 0; i < test_length; i++) {
|
||||
ct_test(pTest, value[i] == test_value[i]);
|
||||
}
|
||||
test_length = strlen(test_append_value);
|
||||
status = characterstring_append(&bacnet_string,
|
||||
&test_append_value[0], test_length);
|
||||
strcat(test_append_string, test_value);
|
||||
strcat(test_append_string, test_append_value);
|
||||
test_length = strlen(test_append_string);
|
||||
ct_test(pTest, status == true);
|
||||
length = characterstring_length(&bacnet_string);
|
||||
value = characterstring_value(&bacnet_string);
|
||||
ct_test(pTest, length == test_length);
|
||||
for (i = 0; i < test_length; i++) {
|
||||
ct_test(pTest, value[i] == test_append_string[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void testOctetString(Test * pTest)
|
||||
{
|
||||
BACNET_OCTET_STRING bacnet_string;
|
||||
uint8_t *value = NULL;
|
||||
uint8_t test_value[MAX_APDU] = "Patricia";
|
||||
uint8_t test_append_value[MAX_APDU] = " and the Kids";
|
||||
uint8_t test_append_string[MAX_APDU] = "";
|
||||
bool status = false;
|
||||
size_t length = 0;
|
||||
size_t test_length = 0;
|
||||
size_t i = 0;
|
||||
|
||||
/* verify initialization */
|
||||
status = octetstring_init(&bacnet_string, NULL, 0);
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, octetstring_length(&bacnet_string) == 0);
|
||||
value = octetstring_value(&bacnet_string);
|
||||
for (i = 0; i < octetstring_capacity(&bacnet_string); i++) {
|
||||
ct_test(pTest, value[i] == 0);
|
||||
}
|
||||
/* bounds check */
|
||||
status = octetstring_init(&bacnet_string, NULL,
|
||||
octetstring_capacity(&bacnet_string) + 1);
|
||||
ct_test(pTest, status == false);
|
||||
status = octetstring_init(&bacnet_string, NULL,
|
||||
octetstring_capacity(&bacnet_string));
|
||||
ct_test(pTest, status == true);
|
||||
status =
|
||||
octetstring_truncate(&bacnet_string,
|
||||
octetstring_capacity(&bacnet_string) + 1);
|
||||
ct_test(pTest, status == false);
|
||||
status =
|
||||
octetstring_truncate(&bacnet_string,
|
||||
octetstring_capacity(&bacnet_string));
|
||||
ct_test(pTest, status == true);
|
||||
|
||||
test_length = strlen((char *) test_value);
|
||||
status = octetstring_init(&bacnet_string, &test_value[0], test_length);
|
||||
ct_test(pTest, status == true);
|
||||
length = octetstring_length(&bacnet_string);
|
||||
value = octetstring_value(&bacnet_string);
|
||||
ct_test(pTest, length == test_length);
|
||||
for (i = 0; i < test_length; i++) {
|
||||
ct_test(pTest, value[i] == test_value[i]);
|
||||
}
|
||||
|
||||
test_length = strlen((char *) test_append_value);
|
||||
status = octetstring_append(&bacnet_string,
|
||||
&test_append_value[0], test_length);
|
||||
strcat((char *) test_append_string, (char *) test_value);
|
||||
strcat((char *) test_append_string, (char *) test_append_value);
|
||||
test_length = strlen((char *) test_append_string);
|
||||
ct_test(pTest, status == true);
|
||||
length = octetstring_length(&bacnet_string);
|
||||
value = octetstring_value(&bacnet_string);
|
||||
ct_test(pTest, length == test_length);
|
||||
for (i = 0; i < test_length; i++) {
|
||||
ct_test(pTest, value[i] == test_append_string[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TEST_BACSTR
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Strings", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testBitString);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testCharacterString);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testOctetString);
|
||||
assert(rc);
|
||||
/* configure output */
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_BACSTR */
|
||||
#endif /* TEST */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
/* Big-Endian systems save the most significant byte first. */
|
||||
/* Sun and Motorola processors, IBM-370s and PDP-10s are big-endian. */
|
||||
/* "Network Byte Order" is also know as "Big-Endian Byte Order" */
|
||||
/* for example, a 4 byte integer 67305985 is 0x04030201 in hexidecimal. */
|
||||
/* x[0] = 0x04 */
|
||||
/* x[1] = 0x03 */
|
||||
/* x[2] = 0x02 */
|
||||
/* x[3] = 0x01 */
|
||||
|
||||
/* Little-Endian systems save the least significant byte first. */
|
||||
/* The entire Intel x86 family, Vaxes, Alphas and PDP-11s are little-endian. */
|
||||
/* for example, a 4 byte integer 67305985 is 0x04030201 in hexidecimal. */
|
||||
/* x[0] = 0x01 */
|
||||
/* x[1] = 0x02 */
|
||||
/* x[2] = 0x03 */
|
||||
/* x[3] = 0x04 */
|
||||
|
||||
int big_endian(void)
|
||||
{
|
||||
union {
|
||||
long l;
|
||||
char c[sizeof(long)];
|
||||
} u;
|
||||
|
||||
u.l = 1;
|
||||
|
||||
return (u.c[sizeof(long) - 1] == 1);
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
#include <stdint.h> /* for standard integer types uint8_t etc. */
|
||||
#include <stdbool.h> /* for the standard bool type. */
|
||||
#include "bacdcode.h"
|
||||
#include "bacint.h"
|
||||
#include "bip.h"
|
||||
#include "net.h" /* custom per port */
|
||||
|
||||
static int BIP_Socket = -1;
|
||||
/* port to use - stored in host byte order */
|
||||
static uint16_t BIP_Port = 0xBAC0;
|
||||
/* IP Address - stored in host byte order */
|
||||
static struct in_addr BIP_Address;
|
||||
/* Broadcast Address - stored in host byte order */
|
||||
static struct in_addr BIP_Broadcast_Address;
|
||||
|
||||
void bip_set_socket(int sock_fd)
|
||||
{
|
||||
BIP_Socket = sock_fd;
|
||||
}
|
||||
|
||||
int bip_socket(void)
|
||||
{
|
||||
return BIP_Socket;
|
||||
}
|
||||
|
||||
bool bip_valid(void)
|
||||
{
|
||||
return (BIP_Socket != -1);
|
||||
}
|
||||
|
||||
void bip_cleanup(void)
|
||||
{
|
||||
if (bip_valid())
|
||||
close(BIP_Socket);
|
||||
BIP_Socket = -1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* set using network byte order */
|
||||
void bip_set_addr(uint32_t net_address)
|
||||
{
|
||||
BIP_Address.s_addr = ntohl(net_address);
|
||||
}
|
||||
|
||||
/* returns host byte order */
|
||||
uint32_t bip_get_addr(void)
|
||||
{
|
||||
return BIP_Address.s_addr;
|
||||
}
|
||||
|
||||
/* set using network byte order */
|
||||
void bip_set_broadcast_addr(uint32_t net_address)
|
||||
{
|
||||
BIP_Broadcast_Address.s_addr = ntohl(net_address);
|
||||
}
|
||||
|
||||
/* returns host byte order */
|
||||
uint32_t bip_get_broadcast_addr(void)
|
||||
{
|
||||
return BIP_Broadcast_Address.s_addr;
|
||||
}
|
||||
|
||||
/* set using host byte order */
|
||||
void bip_set_port(uint16_t port)
|
||||
{
|
||||
BIP_Port = port;
|
||||
}
|
||||
|
||||
/* returns host byte order */
|
||||
uint16_t bip_get_port(void)
|
||||
{
|
||||
return BIP_Port;
|
||||
}
|
||||
|
||||
/* function to send a packet out the BACnet/IP socket (Annex J) */
|
||||
/* returns number of bytes sent on success, negative number on failure */
|
||||
int bip_send_pdu(BACNET_ADDRESS * dest, /* destination address */
|
||||
BACNET_NPDU_DATA * npdu_data, /* network information */
|
||||
uint8_t * pdu, /* any data to be sent - may be null */
|
||||
unsigned pdu_len)
|
||||
{ /* number of bytes of data */
|
||||
struct sockaddr_in bip_dest;
|
||||
uint8_t mtu[MAX_MPDU] = { 0 };
|
||||
int mtu_len = 0;
|
||||
int bytes_sent = 0;
|
||||
|
||||
(void) npdu_data;
|
||||
/* assumes that the driver has already been initialized */
|
||||
if (BIP_Socket < 0)
|
||||
return BIP_Socket;
|
||||
|
||||
mtu[0] = BVLL_TYPE_BACNET_IP;
|
||||
bip_dest.sin_family = AF_INET;
|
||||
if (dest->net == BACNET_BROADCAST_NETWORK) {
|
||||
/* broadcast */
|
||||
bip_dest.sin_addr.s_addr = htonl(BIP_Broadcast_Address.s_addr);
|
||||
bip_dest.sin_port = htons(BIP_Port);
|
||||
memset(&(bip_dest.sin_zero), '\0', 8);
|
||||
mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU;
|
||||
} else if (dest->mac_len == 6) {
|
||||
/* valid unicast */
|
||||
(void) decode_unsigned32(&dest->mac[0],
|
||||
(uint32_t *)&(bip_dest.sin_addr.s_addr));
|
||||
(void) decode_unsigned16(&dest->mac[4], &(bip_dest.sin_port));
|
||||
memset(&(bip_dest.sin_zero), '\0', 8);
|
||||
mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU;
|
||||
} else {
|
||||
/* invalid address */
|
||||
return -1;
|
||||
}
|
||||
|
||||
mtu_len = 2;
|
||||
mtu_len +=
|
||||
encode_unsigned16(&mtu[mtu_len],
|
||||
(uint16_t) (pdu_len + 4 /*inclusive */ ));
|
||||
memcpy(&mtu[mtu_len], pdu, pdu_len);
|
||||
mtu_len += pdu_len;
|
||||
|
||||
/* Send the packet */
|
||||
bytes_sent = sendto(BIP_Socket, (char *) mtu, mtu_len, 0,
|
||||
(struct sockaddr *) &bip_dest, sizeof(struct sockaddr));
|
||||
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
/* receives a BACnet/IP packet */
|
||||
/* returns the number of octets in the PDU, or zero on failure */
|
||||
uint16_t bip_receive(BACNET_ADDRESS * src, /* source address */
|
||||
uint8_t * pdu, /* PDU data */
|
||||
uint16_t max_pdu, /* amount of space available in the PDU */
|
||||
unsigned timeout)
|
||||
{ /* number of milliseconds to wait for a packet */
|
||||
int received_bytes = 0;
|
||||
uint16_t pdu_len = 0; /* return value */
|
||||
fd_set read_fds;
|
||||
int max = 0;
|
||||
struct timeval select_timeout;
|
||||
struct sockaddr_in sin = { -1 };
|
||||
socklen_t sin_len = sizeof(sin);
|
||||
unsigned i = 0;
|
||||
|
||||
/* Make sure the socket is open */
|
||||
if (BIP_Socket < 0)
|
||||
return 0;
|
||||
|
||||
/* we could just use a non-blocking socket, but that consumes all
|
||||
the CPU time. We can use a timeout; it is only supported as
|
||||
a select. */
|
||||
if (timeout >= 1000) {
|
||||
select_timeout.tv_sec = timeout / 1000;
|
||||
select_timeout.tv_usec =
|
||||
1000 * (timeout - select_timeout.tv_sec * 1000);
|
||||
} else {
|
||||
select_timeout.tv_sec = 0;
|
||||
select_timeout.tv_usec = 1000 * timeout;
|
||||
}
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET((unsigned int) BIP_Socket, &read_fds);
|
||||
max = BIP_Socket;
|
||||
/* see if there is a packet for us */
|
||||
if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0)
|
||||
received_bytes = recvfrom(BIP_Socket,
|
||||
(char *) &pdu[0], max_pdu, 0,
|
||||
(struct sockaddr *) &sin, &sin_len);
|
||||
else
|
||||
return 0;
|
||||
|
||||
/* See if there is a problem */
|
||||
if (received_bytes < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* no problem, just no bytes */
|
||||
if (received_bytes == 0)
|
||||
return 0;
|
||||
|
||||
/* the signature of a BACnet/IP packet */
|
||||
if (pdu[0] != BVLL_TYPE_BACNET_IP)
|
||||
return 0;
|
||||
if ((pdu[1] == BVLC_ORIGINAL_UNICAST_NPDU) ||
|
||||
(pdu[1] == BVLC_ORIGINAL_BROADCAST_NPDU)) {
|
||||
/* ignore messages from me */
|
||||
if (sin.sin_addr.s_addr == htonl(BIP_Address.s_addr))
|
||||
pdu_len = 0;
|
||||
else {
|
||||
/* copy the source address
|
||||
FIXME: IPv6? */
|
||||
src->mac_len = 6;
|
||||
(void) encode_unsigned32(&src->mac[0], sin.sin_addr.s_addr);
|
||||
(void) encode_unsigned16(&src->mac[4], sin.sin_port);
|
||||
/* FIXME: check destination address */
|
||||
/* see if it is broadcast or for us */
|
||||
/* decode the length of the PDU - length is inclusive of BVLC */
|
||||
(void) decode_unsigned16(&pdu[2], &pdu_len);
|
||||
/* subtract off the BVLC header */
|
||||
pdu_len -= 4;
|
||||
if (pdu_len < max_pdu) {
|
||||
/* shift the buffer to return a valid PDU */
|
||||
for (i = 0; i < pdu_len; i++) {
|
||||
pdu[i] = pdu[i+4];
|
||||
}
|
||||
}
|
||||
/* ignore packets that are too large */
|
||||
/* clients should check my max-apdu first */
|
||||
else
|
||||
pdu_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return pdu_len;
|
||||
}
|
||||
|
||||
void bip_get_my_address(BACNET_ADDRESS * my_address)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
my_address->mac_len = 6;
|
||||
(void) encode_unsigned32(&my_address->mac[0],
|
||||
htonl(BIP_Address.s_addr));
|
||||
(void) encode_unsigned16(&my_address->mac[4], htons(BIP_Port));
|
||||
my_address->net = 0; /* local only, no routing */
|
||||
my_address->len = 0; /* no SLEN */
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
/* no SADR */
|
||||
my_address->adr[i] = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void bip_get_broadcast_address(BACNET_ADDRESS * dest)
|
||||
{ /* destination address */
|
||||
int i = 0; /* counter */
|
||||
|
||||
if (dest) {
|
||||
dest->mac_len = 6;
|
||||
(void) encode_unsigned32(&dest->mac[0],
|
||||
htonl(BIP_Broadcast_Address.s_addr));
|
||||
(void) encode_unsigned16(&dest->mac[4], htons(BIP_Port));
|
||||
dest->net = BACNET_BROADCAST_NETWORK;
|
||||
dest->len = 0; /* no SLEN */
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
/* no SADR */
|
||||
dest->adr[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -0,0 +1,831 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2006 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
#include <stdint.h> /* for standard integer types uint8_t etc. */
|
||||
#include <stdbool.h> /* for the standard bool type. */
|
||||
#include <time.h> /* for the standard bool type. */
|
||||
#include "bacdcode.h"
|
||||
#include "bacint.h"
|
||||
#include "bvlc.h"
|
||||
#include "bip.h"
|
||||
|
||||
/* Handle the BACnet Virtual Link Control (BVLC), which includes:
|
||||
BACnet Broadcast Management Device,
|
||||
Broadcast Distribution Table, and
|
||||
Foreign Device Registration */
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* true if valid entry - false if not */
|
||||
bool valid;
|
||||
/* BACnet/IP address */
|
||||
struct in_addr dest_address;
|
||||
/* BACnet/IP port number - not always 47808=BAC0h */
|
||||
uint16_t dest_port;
|
||||
/* Broadcast Distribution Mask - stored in host byte order */
|
||||
struct in_addr broadcast_mask;
|
||||
} BBMD_TABLE_ENTRY;
|
||||
|
||||
#define MAX_BBMD_ENTRIES 128
|
||||
static BBMD_TABLE_ENTRY BBMD_Table[MAX_BBMD_ENTRIES];
|
||||
|
||||
/*Each device that registers as a foreign device shall be placed
|
||||
in an entry in the BBMD's Foreign Device Table (FDT). Each
|
||||
entry shall consist of the 6-octet B/IP address of the registrant;
|
||||
the 2-octet Time-to-Live value supplied at the time of
|
||||
registration; and a 2-octet value representing the number of
|
||||
seconds remaining before the BBMD will purge the registrant's FDT
|
||||
entry if no re-registration occurs. This value will be initialized
|
||||
to the 2-octet Time-to-Live value supplied at the time of
|
||||
registration.*/
|
||||
typedef struct
|
||||
{
|
||||
bool valid;
|
||||
/* BACnet/IP address */
|
||||
struct in_addr dest_address;
|
||||
/* BACnet/IP port number - not always 47808=BAC0h */
|
||||
uint16_t dest_port;
|
||||
/* seconds for valid entry lifetime */
|
||||
uint16_t time_to_live;
|
||||
/* our counter */
|
||||
time_t seconds_remaining; /* includes 30 second grace period */
|
||||
} FD_TABLE_ENTRY;
|
||||
|
||||
#define MAX_FD_ENTRIES 128
|
||||
static FD_TABLE_ENTRY FD_Table[MAX_FD_ENTRIES];
|
||||
|
||||
void bvlc_maintenance_timer(unsigned seconds)
|
||||
{
|
||||
unsigned i = 0;
|
||||
|
||||
for (i = 0; i < MAX_FD_ENTRIES; i++) {
|
||||
if (FD_Table[i].valid) {
|
||||
if (FD_Table[i].seconds_remaining) {
|
||||
if (FD_Table[i].seconds_remaining < seconds) {
|
||||
FD_Table[i].seconds_remaining = 0;
|
||||
} else {
|
||||
FD_Table[i].seconds_remaining -= seconds;
|
||||
}
|
||||
if (FD_Table[i].seconds_remaining == 0) {
|
||||
FD_Table[i].valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int bvlc_encode_bip_address(
|
||||
uint8_t * pdu, /* buffer to store encoding */
|
||||
struct in_addr *address, /* in host format */
|
||||
uint16_t port)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (pdu) {
|
||||
len = encode_unsigned32(&pdu[0], address->s_addr);
|
||||
len += encode_unsigned16(&pdu[len], port);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bvlc_decode_bip_address(
|
||||
uint8_t * pdu, /* buffer to extract encoded address */
|
||||
struct in_addr * address, /* in host format */
|
||||
uint16_t * port)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (pdu) {
|
||||
(void) decode_unsigned32(&pdu[0], &(address->s_addr));
|
||||
(void) decode_unsigned16(&pdu[4], port);
|
||||
len = 6;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* used for both read and write entries */
|
||||
int bvlc_encode_address_entry(uint8_t * pdu,
|
||||
struct in_addr *address,
|
||||
uint16_t port,
|
||||
struct in_addr *mask)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (pdu) {
|
||||
len = bvlc_encode_bip_address(pdu, address, port);
|
||||
len += encode_unsigned32(&pdu[len], mask->s_addr);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bvlc_encode_bvlc_result(
|
||||
uint8_t * pdu,
|
||||
BACNET_BVLC_RESULT result_code)
|
||||
{
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_RESULT;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
encode_unsigned16(&pdu[2], 6);
|
||||
encode_unsigned16(&pdu[4], result_code);
|
||||
}
|
||||
|
||||
return 6;
|
||||
}
|
||||
|
||||
int bvlc_encode_write_bdt_init(
|
||||
uint8_t * pdu,
|
||||
unsigned entries)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
encode_unsigned16(&pdu[2], 4 + entries * 10);
|
||||
len = 4;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bvlc_encode_read_bdt(
|
||||
uint8_t * pdu)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_READ_BROADCAST_DISTRIBUTION_TABLE;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
encode_unsigned16(&pdu[2], 4);
|
||||
len = 4;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bvlc_encode_read_bdt_ack_init(
|
||||
uint8_t * pdu,
|
||||
unsigned entries)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_READ_BROADCAST_DISTRIBUTION_TABLE_ACK;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
encode_unsigned16(&pdu[2], 4 + entries * 10);
|
||||
len = 4;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bvlc_encode_forwarded_npdu(uint8_t * pdu,
|
||||
BACNET_ADDRESS * src,
|
||||
uint8_t * npdu,
|
||||
unsigned npdu_length)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
unsigned i; /* for loop counter */
|
||||
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_FORWARDED_NPDU;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
encode_unsigned16(&pdu[2], 4 + 6 + npdu_length);
|
||||
len = 4;
|
||||
for (i = 0; i < 6; i++) {
|
||||
pdu[len] = src->adr[i];
|
||||
len++;
|
||||
}
|
||||
for (i = 0; i < npdu_length; i++) {
|
||||
pdu[len] = npdu[i];
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
int bvlc_encode_register_foreign_device(uint8_t * pdu,
|
||||
uint16_t time_to_live_seconds)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_REGISTER_FOREIGN_DEVICE;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
encode_unsigned16(&pdu[2], 6);
|
||||
encode_unsigned16(&pdu[2], time_to_live_seconds);
|
||||
len = 6;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bvlc_encode_read_fdt(
|
||||
uint8_t * pdu)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_READ_FOREIGN_DEVICE_TABLE;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
encode_unsigned16(&pdu[2], 4);
|
||||
len = 4;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bvlc_encode_read_fdt_ack_init(
|
||||
uint8_t * pdu,
|
||||
unsigned entries)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_READ_FOREIGN_DEVICE_TABLE_ACK;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
encode_unsigned16(&pdu[2], 4 + entries * 10);
|
||||
len = 4;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bvlc_encode_delete_fdt_entry(uint8_t * pdu,
|
||||
struct in_addr *address,
|
||||
uint16_t port)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_READ_FOREIGN_DEVICE_TABLE;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
encode_unsigned16(&pdu[2], 10);
|
||||
/* FDT Entry */
|
||||
encode_unsigned32(&pdu[0], address->s_addr);
|
||||
encode_unsigned16(&pdu[4], port);
|
||||
len = 10;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bvlc_encode_distribute_broadcast_to_network(uint8_t * pdu,
|
||||
uint8_t * npdu,
|
||||
unsigned npdu_length)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
unsigned i; /* for loop counter */
|
||||
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
len = encode_unsigned16(&pdu[2], 4 + npdu_length) + 2;
|
||||
for (i = 0; i < npdu_length; i++) {
|
||||
pdu[len] = npdu[i];
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bvlc_encode_original_unicast_npdu(uint8_t * pdu,
|
||||
uint8_t * npdu,
|
||||
unsigned npdu_length)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
unsigned i = 0; /* loop counter */
|
||||
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_ORIGINAL_UNICAST_NPDU;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
len = encode_unsigned16(&pdu[2], 4 + npdu_length) + 2;
|
||||
for (i = 0; i < npdu_length; i++) {
|
||||
pdu[len] = npdu[i];
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
int bvlc_encode_original_broadcast_npdu(uint8_t * pdu,
|
||||
uint8_t * npdu,
|
||||
unsigned npdu_length)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
unsigned i = 0; /* loop counter */
|
||||
|
||||
if (pdu) {
|
||||
pdu[0] = BVLL_TYPE_BACNET_IP;
|
||||
pdu[1] = BVLC_ORIGINAL_BROADCAST_NPDU;
|
||||
/* The 2-octet BVLC Length field is the length, in octets,
|
||||
of the entire BVLL message, including the two octets of the
|
||||
length field itself, most significant octet first. */
|
||||
len = encode_unsigned16(&pdu[2], 4 + npdu_length) + 2;
|
||||
for (i = 0; i < npdu_length; i++) {
|
||||
pdu[len] = npdu[i];
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* copy the source internet address to the BACnet address */
|
||||
/* FIXME: IPv6? */
|
||||
void bvlc_internet_to_bacnet_address(
|
||||
BACNET_ADDRESS * src, /* returns the BACnet source address */
|
||||
struct sockaddr_in *sin)
|
||||
{ /* source internet address */
|
||||
int len = 0;
|
||||
uint32_t address;
|
||||
uint16_t port;
|
||||
|
||||
if (src && sin) {
|
||||
address = ntohl(sin->sin_addr.s_addr);
|
||||
len = encode_unsigned32(&src->mac[0], address);
|
||||
port = ntohs(sin->sin_port);
|
||||
len += encode_unsigned16(&src->mac[4], port);
|
||||
src->mac_len = len;
|
||||
src->net = 0;
|
||||
src->len = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* copy the source internet address to the BACnet address */
|
||||
/* FIXME: IPv6? */
|
||||
/* FIXME: is sockaddr_in host or network order? */
|
||||
void bvlc_bacnet_to_internet_address(
|
||||
struct sockaddr_in *sin, /* source internet address */
|
||||
BACNET_ADDRESS * src) /* returns the BACnet source address */
|
||||
{
|
||||
int len = 0;
|
||||
uint32_t address;
|
||||
uint16_t port;
|
||||
|
||||
if (src && sin) {
|
||||
if (src->mac_len == 6) {
|
||||
len = decode_unsigned32(&src->mac[0], &address);
|
||||
len += decode_unsigned16(&src->mac[4], &port );
|
||||
sin->sin_addr.s_addr = htonl(address);
|
||||
sin->sin_port = htons(port);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void bvlc_bdt_forward_npdu(
|
||||
struct sockaddr_in *sin, /* the source address */
|
||||
uint8_t * npdu, /* the NPDU */
|
||||
uint16_t npdu_length)
|
||||
{ /* length of the NPDU */
|
||||
uint8_t mtu[MAX_MPDU] = {0};
|
||||
int mtu_len = 0;
|
||||
int bytes_sent = 0;
|
||||
unsigned i = 0; /* loop counter */
|
||||
struct sockaddr_in bip_dest;
|
||||
BACNET_ADDRESS src;
|
||||
|
||||
/* assumes that the driver has already been initialized */
|
||||
if (bip_socket() < 0) {
|
||||
return;
|
||||
}
|
||||
bvlc_internet_to_bacnet_address(&src, sin);
|
||||
mtu_len = bvlc_encode_forwarded_npdu(
|
||||
&mtu[0],
|
||||
&src,
|
||||
npdu,
|
||||
npdu_length);
|
||||
/* load destination IP address */
|
||||
bip_dest.sin_family = AF_INET;
|
||||
/* loop through the BDT and send one to each entry, except us */
|
||||
for (i = 0; i < MAX_BBMD_ENTRIES; i++) {
|
||||
if (BBMD_Table[i].valid) {
|
||||
/* The B/IP address to which the Forwarded-NPDU message is
|
||||
sent is formed by inverting the broadcast distribution
|
||||
mask in the BDT entry and logically ORing it with the
|
||||
BBMD address of the same entry. */
|
||||
bip_dest.sin_addr.s_addr =
|
||||
htonl(((~BBMD_Table[i].broadcast_mask.s_addr) |
|
||||
BBMD_Table[i].dest_address.s_addr));
|
||||
bip_dest.sin_port = htons(BBMD_Table[i].dest_port);
|
||||
/* don't send to my broadcast address and same port */
|
||||
if ((bip_dest.sin_addr.s_addr == htonl(bip_get_broadcast_addr())) &&
|
||||
(bip_dest.sin_port == htons(bip_get_port))) {
|
||||
continue;
|
||||
}
|
||||
/* don't send to my ip address and same port */
|
||||
if ((bip_dest.sin_addr.s_addr == htonl(bip_get_addr())) &&
|
||||
(bip_dest.sin_port == htons(bip_get_port))) {
|
||||
continue;
|
||||
}
|
||||
/* Send the packet */
|
||||
bytes_sent =
|
||||
sendto(bip_socket(), (char *) mtu, mtu_len, 0,
|
||||
(struct sockaddr *) &bip_dest,
|
||||
sizeof(struct sockaddr));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void bvlc_fdt_forward_npdu(
|
||||
struct sockaddr_in *sin, /* the source address */
|
||||
uint8_t * npdu, /* returns the NPDU */
|
||||
uint16_t max_npdu) /* amount of space available in the NPDU */
|
||||
{
|
||||
uint8_t mtu[MAX_MPDU] = {0};
|
||||
int mtu_len = 0;
|
||||
int bytes_sent = 0;
|
||||
unsigned i = 0; /* loop counter */
|
||||
struct sockaddr_in bvlc_dest;
|
||||
BACNET_ADDRESS src;
|
||||
|
||||
/* assumes that the driver has already been initialized */
|
||||
if (bip_socket() < 0) {
|
||||
return;
|
||||
}
|
||||
bvlc_internet_to_bacnet_address(&src, sin);
|
||||
mtu_len = bvlc_encode_forwarded_npdu(
|
||||
&mtu[0],
|
||||
&src,
|
||||
npdu,
|
||||
npdu_length);
|
||||
/* load destination IP address */
|
||||
bvlc_dest.sin_family = AF_INET;
|
||||
/* loop through the FDT and send one to each entry */
|
||||
for (i = 0; i < MAX_FD_ENTRIES; i++) {
|
||||
if (FD_Table[i].valid && FD_Table[i].seconds_remaining) {
|
||||
bvlc_dest.sin_addr.s_addr =
|
||||
htonl(FD_Table[i].dest_address.s_addr);
|
||||
bvlc_dest.sin_port = htons(FD_Table[i].dest_port);
|
||||
/* Send the packet */
|
||||
bytes_sent =
|
||||
sendto(bip_socket(), (char *) mtu, mtu_len, 0,
|
||||
(struct sockaddr *) &bvlc_dest,
|
||||
sizeof(struct sockaddr));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* returns:
|
||||
Number of bytes received, or 0 if none or timeout. */
|
||||
uint16_t bvlc_receive(
|
||||
BACNET_ADDRESS * src, /* returns the source address */
|
||||
uint8_t * npdu, /* returns the NPDU */
|
||||
uint16_t max_npdu, /* amount of space available in the NPDU */
|
||||
unsigned timeout) /* number of milliseconds to wait for a packet */
|
||||
{
|
||||
uint16_t pdu_len = 0; /* return value */
|
||||
fd_set read_fds;
|
||||
int max = 0;
|
||||
struct timeval select_timeout;
|
||||
struct sockaddr_in sin = { -1 };
|
||||
socklen_t sin_len = sizeof(sin);
|
||||
int function_type = 0;
|
||||
int received_bytes = 0;
|
||||
unsigned i =0;
|
||||
|
||||
/* Make sure the socket is open */
|
||||
if (bip_socket() < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* we could just use a non-blocking socket, but that consumes all
|
||||
the CPU time. We can use a timeout; it is only supported as
|
||||
a select. */
|
||||
if (timeout >= 1000) {
|
||||
select_timeout.tv_sec = timeout / 1000;
|
||||
select_timeout.tv_usec =
|
||||
1000 * (timeout - select_timeout.tv_sec * 1000);
|
||||
} else {
|
||||
select_timeout.tv_sec = 0;
|
||||
select_timeout.tv_usec = 1000 * timeout;
|
||||
}
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET((unsigned int) bip_socket(), &read_fds);
|
||||
max = bip_socket();
|
||||
/* see if there is a packet for us */
|
||||
if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) {
|
||||
received_bytes = recvfrom(
|
||||
bip_socket(),
|
||||
(char *) &npdu[0],
|
||||
max_npdu, 0,
|
||||
(struct sockaddr *) &sin, &sin_len);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
/* See if there is a problem */
|
||||
if (received_bytes < 0) {
|
||||
return 0;
|
||||
}
|
||||
/* no problem, just no bytes */
|
||||
if (received_bytes == 0) {
|
||||
return 0;
|
||||
}
|
||||
/* the signature of a BACnet/IP packet */
|
||||
if (buf[0] != BVLL_TYPE_BACNET_IP) {
|
||||
return 0;
|
||||
}
|
||||
function_type = npdu[1];
|
||||
/* decode the length of the PDU - length is inclusive of BVLC */
|
||||
(void) decode_unsigned16(&npdu[2], &npdu_len);
|
||||
/* subtract off the BVLC header */
|
||||
npdu_len -= 4;
|
||||
switch (function_type) {
|
||||
case BVLC_RESULT:
|
||||
break;
|
||||
case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE:
|
||||
/* Upon receipt of a BVLL Write-Broadcast-Distribution-Table
|
||||
message, a BBMD shall attempt to create or replace its BDT,
|
||||
depending on whether or not a BDT has previously existed.
|
||||
If the creation or replacement of the BDT is successful, the BBMD
|
||||
shall return a BVLC-Result message to the originating device with
|
||||
a result code of X'0000'. Otherwise, the BBMD shall return a
|
||||
BVLC-Result message to the originating device with a result code
|
||||
of X'0010' indicating that the write attempt has failed. */
|
||||
break;
|
||||
case BVLC_READ_BROADCAST_DISTRIBUTION_TABLE:
|
||||
break;
|
||||
case BVLC_READ_BROADCAST_DISTRIBUTION_TABLE_ACK:
|
||||
break;
|
||||
case BVLC_FORWARDED_NPDU:
|
||||
/* Upon receipt of a BVLL Forwarded-NPDU message, a BBMD shall
|
||||
process it according to whether it was received from a peer
|
||||
BBMD as the result of a directed broadcast or a unicast
|
||||
transmission. A BBMD may ascertain the method by which Forwarded-
|
||||
NPDU messages will arrive by inspecting the broadcast distribution
|
||||
mask field in its own BDT entry since all BDTs are required
|
||||
to be identical. If the message arrived via directed broadcast,
|
||||
it was also received by the other devices on the BBMD's subnet. In
|
||||
this case the BBMD merely retransmits the message directly to each
|
||||
foreign device currently in the BBMD's FDT. If the
|
||||
message arrived via a unicast transmission it has not yet been
|
||||
received by the other devices on the BBMD's subnet. In this case,
|
||||
the message is sent to the devices on the BBMD's subnet using the
|
||||
B/IP broadcast address as well as to each foreign device
|
||||
currently in the BBMD's FDT. A BBMD on a subnet with no other
|
||||
BACnet devices may omit the broadcast using the B/IP
|
||||
broadcast address. The method by which a BBMD determines whether
|
||||
or not other BACnet devices are present is a local matter. */
|
||||
bvlc_broadcast_npdu(&sin, &npdu[4], npdu_len);
|
||||
bvlc_fdt_forward_npdu(&sin, &npdu[4], npdu_len);
|
||||
break;
|
||||
case BVLC_REGISTER_FOREIGN_DEVICE:
|
||||
break;
|
||||
case BVLC_READ_FOREIGN_DEVICE_TABLE:
|
||||
break;
|
||||
case BVLC_READ_FOREIGN_DEVICE_TABLE_ACK:
|
||||
break;
|
||||
case BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY:
|
||||
break;
|
||||
case BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK:
|
||||
bvlc_broadcast_forward_npdu(&sin, &npdu[4], npdu_len);
|
||||
bvlc_fdt_forward_npdu(&sin, &npdu[4], npdu_len);
|
||||
break;
|
||||
case BVLC_ORIGINAL_UNICAST_NPDU:
|
||||
/* ignore messages from me */
|
||||
if (sin.sin_addr.s_addr == htonl(bip_get_addr())) {
|
||||
npdu_len = 0;
|
||||
} else {
|
||||
bvlc_internet_to_bacnet_address(src, &sin);
|
||||
if (npdu_len < max_npdu) {
|
||||
/* shift the buffer to return a valid PDU */
|
||||
for (i = 0; i < npdu_len; i++) {
|
||||
npdu[i] = npdu[i+4];
|
||||
}
|
||||
} else {
|
||||
/* ignore packets that are too large */
|
||||
/* clients should check my max-apdu first */
|
||||
npdu_len = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case BVLC_ORIGINAL_BROADCAST_NPDU:
|
||||
/* Upon receipt of a BVLL Original-Broadcast-NPDU message,
|
||||
a BBMD shall construct a BVLL Forwarded-NPDU message and
|
||||
send it to each IP subnet in its BDT with the exception
|
||||
of its own. The B/IP address to which the Forwarded-NPDU
|
||||
message is sent is formed by inverting the broadcast
|
||||
distribution mask in the BDT entry and logically ORing it
|
||||
with the BBMD address of the same entry. This process
|
||||
produces either the directed broadcast address of the remote
|
||||
subnet or the unicast address of the BBMD on that subnet
|
||||
depending on the contents of the broadcast distribution
|
||||
mask. See J.4.3.2.. In addition, the received BACnet NPDU
|
||||
shall be sent directly to each foreign device currently in
|
||||
the BBMD's FDT also using the BVLL Forwarded-NPDU message. */
|
||||
bvlc_internet_to_bacnet_address(src, &sin);
|
||||
if (npdu_len < max_npdu) {
|
||||
/* shift the buffer to return a valid PDU */
|
||||
for (i = 0; i < npdu_len; i++) {
|
||||
npdu[i] = npdu[i+4];
|
||||
}
|
||||
/* if BDT or FDT entries exist, Forward the NPDU */
|
||||
bvlc_bdt_forward_npdu(&sin, &npdu[0], npdu_len);
|
||||
bvlc_fdt_forward_npdu(&sin, &npdu[0], npdu_len);
|
||||
} else {
|
||||
/* ignore packets that are too large */
|
||||
npdu_len = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return npdu_len;
|
||||
}
|
||||
|
||||
/* function to send a packet out the BACnet/IP socket (Annex J) */
|
||||
/* returns number of bytes sent on success, negative number on failure */
|
||||
int bvlc_send_pdu(BACNET_ADDRESS * dest, /* destination address */
|
||||
BACNET_NPDU_DATA * npdu_data, /* network information */
|
||||
uint8_t * pdu, /* any data to be sent - may be null */
|
||||
unsigned pdu_len)
|
||||
{ /* number of bytes of data */
|
||||
struct sockaddr_in bvlc_dest;
|
||||
uint8_t mtu[MAX_MPDU] = { 0 };
|
||||
int mtu_len = 0;
|
||||
int bytes_sent = 0;
|
||||
|
||||
/* bip datalink doesn't need to know the npdu data */
|
||||
(void) npdu_data;
|
||||
/* assumes that the driver has already been initialized */
|
||||
if (BIP_Socket < 0)
|
||||
return BIP_Socket;
|
||||
|
||||
mtu[0] = BVLL_TYPE_BACNET_IP;
|
||||
bip_dest.sin_family = AF_INET;
|
||||
if (dest->net == BACNET_BROADCAST_NETWORK) {
|
||||
/* broadcast */
|
||||
bip_dest.sin_addr.s_addr = htonl(BIP_Broadcast_Address.s_addr);
|
||||
bip_dest.sin_port = htons(BIP_Port);
|
||||
memset(&(bip_dest.sin_zero), '\0', 8);
|
||||
mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU;
|
||||
} else if (dest->mac_len == 6) {
|
||||
/* valid unicast */
|
||||
(void) decode_unsigned32(&dest->mac[0],
|
||||
&(bip_dest.sin_addr.s_addr));
|
||||
(void) decode_unsigned16(&dest->mac[4], &(bip_dest.sin_port));
|
||||
memset(&(bip_dest.sin_zero), '\0', 8);
|
||||
mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU;
|
||||
} else {
|
||||
/* invalid address */
|
||||
return -1;
|
||||
}
|
||||
|
||||
mtu_len = 2;
|
||||
mtu_len +=
|
||||
encode_unsigned16(&mtu[mtu_len],
|
||||
(uint16_t) (pdu_len + 4 /*inclusive */ ));
|
||||
memcpy(&mtu[mtu_len], pdu, pdu_len);
|
||||
mtu_len += pdu_len;
|
||||
|
||||
/* Send the packet */
|
||||
bytes_sent = sendto(BIP_Socket, (char *) mtu, mtu_len, 0,
|
||||
(struct sockaddr *) &bip_dest, sizeof(struct sockaddr));
|
||||
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testBIPAddress(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[50] = { 0 };
|
||||
uint32_t value = 0, test_value = 0;
|
||||
int len = 0, test_len = 0;
|
||||
struct in_addr address;
|
||||
struct in_addr test_address;
|
||||
uint16_t port = 0, test_port = 0;
|
||||
|
||||
address.s_addr = 42;
|
||||
len = bvlc_encode_bip_address(&apdu[0],
|
||||
&address, port);
|
||||
test_len = bvlc_decode_bip_address(&apdu[0],
|
||||
&test_address, &test_port);
|
||||
ct_test(pTest, len == test_len);
|
||||
ct_test(pTest, address.s_addr == test_address.s_addr);
|
||||
ct_test(pTest, port == test_port);
|
||||
}
|
||||
|
||||
void testInternetAddress(Test * pTest)
|
||||
{
|
||||
BACNET_ADDRESS src;
|
||||
BACNET_ADDRESS test_src;
|
||||
struct sockaddr_in sin;
|
||||
struct sockaddr_in test_sin;
|
||||
|
||||
sin.sin_port = htons(0xBAC0);
|
||||
sin.sin_addr.s_addr = inet_addr("192.168.0.1");
|
||||
bvlc_internet_to_bacnet_address(&src, &sin);
|
||||
bvlc_bacnet_to_internet_address(&test_sin, &src);
|
||||
ct_test(pTest, sin.sin_port == test_sin.sin_port);
|
||||
ct_test(pTest, sin.sin_addr.s_addr == test_sin.sin_addr.s_addr);
|
||||
}
|
||||
|
||||
#ifdef TEST_BVLC
|
||||
int main(void)
|
||||
{
|
||||
Test * pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Virtual Link Control", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testBIPAddress);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testInternetAddress);
|
||||
assert(rc);
|
||||
/* configure output */
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* TEST_BBMD */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,979 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2006 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "bacapp.h"
|
||||
#include "cov.h"
|
||||
#include "device.h"
|
||||
#include "datalink.h"
|
||||
#include "npdu.h"
|
||||
|
||||
/* encode service */
|
||||
|
||||
/* Change-Of-Value Services
|
||||
COV Subscribe
|
||||
COV Subscribe Property
|
||||
COV Notification
|
||||
Unconfirmed COV Notification
|
||||
*/
|
||||
static int notify_encode_adpu(uint8_t * apdu, BACNET_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
BACNET_PROPERTY_VALUE *value = NULL; /* value in list */
|
||||
|
||||
if (apdu) {
|
||||
/* tag 0 - subscriberProcessIdentifier */
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
0, data->subscriberProcessIdentifier);
|
||||
apdu_len += len;
|
||||
/* tag 1 - initiatingDeviceIdentifier */
|
||||
len = encode_context_object_id(&apdu[apdu_len],
|
||||
1, OBJECT_DEVICE, data->initiatingDeviceIdentifier);
|
||||
apdu_len += len;
|
||||
/* tag 2 - monitoredObjectIdentifier */
|
||||
len = encode_context_object_id(&apdu[apdu_len],
|
||||
2,
|
||||
data->monitoredObjectIdentifier.type,
|
||||
data->monitoredObjectIdentifier.instance);
|
||||
apdu_len += len;
|
||||
/* tag 3 - timeRemaining */
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
3, data->timeRemaining);
|
||||
apdu_len += len;
|
||||
/* tag 4 - listOfValues */
|
||||
len = encode_opening_tag(&apdu[apdu_len], 4);
|
||||
apdu_len += len;
|
||||
/* the first value includes a pointer to the next value, etc */
|
||||
/* FIXME: for small implementations, we might try a partial
|
||||
approach like the rpm.c where the values are encoded with
|
||||
a separate function */
|
||||
value = &data->listOfValues;
|
||||
while (value != NULL) {
|
||||
/* tag 0 - propertyIdentifier */
|
||||
len = encode_context_enumerated(&apdu[apdu_len],
|
||||
0, value->propertyIdentifier);
|
||||
apdu_len += len;
|
||||
/* tag 1 - propertyArrayIndex OPTIONAL */
|
||||
if (value->propertyArrayIndex != BACNET_ARRAY_ALL) {
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
1, value->propertyArrayIndex);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* tag 2 - value */
|
||||
/* abstract syntax gets enclosed in a context tag */
|
||||
len = encode_opening_tag(&apdu[apdu_len], 2);
|
||||
apdu_len += len;
|
||||
len = bacapp_encode_application_data(&apdu[apdu_len],
|
||||
&value->value);
|
||||
apdu_len += len;
|
||||
len = encode_closing_tag(&apdu[apdu_len], 2);
|
||||
apdu_len += len;
|
||||
/* tag 3 - priority OPTIONAL */
|
||||
if (value->priority != BACNET_NO_PRIORITY) {
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 3,
|
||||
value->priority);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* is there another one to encode? */
|
||||
/* FIXME: check to see if there is room in the APDU */
|
||||
value = value->next;
|
||||
}
|
||||
len = encode_closing_tag(&apdu[apdu_len], 4);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int ccov_notify_encode_apdu(uint8_t * apdu,
|
||||
uint8_t invoke_id, BACNET_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
uint16_t max_apdu = Device_Max_APDU_Length_Accepted();
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = encode_max_segs_max_apdu(0, max_apdu);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_COV_NOTIFICATION;
|
||||
apdu_len = 4;
|
||||
len = notify_encode_adpu(&apdu[apdu_len], data);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int ucov_notify_encode_apdu(uint8_t * apdu, BACNET_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu && data) {
|
||||
apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = SERVICE_UNCONFIRMED_COV_NOTIFICATION; /* service choice */
|
||||
apdu_len = 2;
|
||||
len = notify_encode_adpu(&apdu[apdu_len], data);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
/* COV and Unconfirmed COV are the same */
|
||||
int cov_notify_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
uint32_t decoded_value = 0; /* for decoding */
|
||||
int decoded_type = 0; /* for decoding */
|
||||
int property = 0; /* for decoding */
|
||||
BACNET_PROPERTY_VALUE *value = NULL; /* value in list */
|
||||
|
||||
if (apdu_len && data) {
|
||||
/* tag 0 - subscriberProcessIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 0)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
data->subscriberProcessIdentifier = decoded_value;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 1 - initiatingDeviceIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 1)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_object_id(&apdu[len], &decoded_type,
|
||||
&data->initiatingDeviceIdentifier);
|
||||
if (decoded_type != OBJECT_DEVICE)
|
||||
return -1;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 2 - monitoredObjectIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 2)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_object_id(&apdu[len], &decoded_type,
|
||||
&data->monitoredObjectIdentifier.instance);
|
||||
data->monitoredObjectIdentifier.type = decoded_type;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 3 - timeRemaining */
|
||||
if (decode_is_context_tag(&apdu[len], 3)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
data->timeRemaining = decoded_value;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 4: opening context tag - listOfValues */
|
||||
if (!decode_is_opening_tag_number(&apdu[len], 4))
|
||||
return -1;
|
||||
/* a tag number of 4 is not extended so only one octet */
|
||||
len++;
|
||||
/* the first value includes a pointer to the next value, etc */
|
||||
value = &data->listOfValues;
|
||||
while (value != NULL) {
|
||||
/* tag 0 - propertyIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 0)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_enumerated(&apdu[len], len_value, &property);
|
||||
value->propertyIdentifier = property;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 1 - propertyArrayIndex OPTIONAL */
|
||||
if (decode_is_context_tag(&apdu[len], 1)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
value->propertyArrayIndex = decoded_value;
|
||||
} else
|
||||
value->propertyArrayIndex = BACNET_ARRAY_ALL;
|
||||
/* tag 2: opening context tag - value */
|
||||
if (!decode_is_opening_tag_number(&apdu[len], 2))
|
||||
return -1;
|
||||
/* a tag number of 2 is not extended so only one octet */
|
||||
len++;
|
||||
len += bacapp_decode_application_data(&apdu[len],
|
||||
apdu_len - len, &value->value);
|
||||
/* FIXME: check the return value; abort if no valid data? */
|
||||
/* FIXME: there might be more than one data element in here! */
|
||||
if (!decode_is_closing_tag_number(&apdu[len], 2))
|
||||
return -1;
|
||||
/* a tag number of 2 is not extended so only one octet */
|
||||
len++;
|
||||
/* tag 3 - priority OPTIONAL */
|
||||
if (decode_is_context_tag(&apdu[len], 3)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
value->priority = decoded_value;
|
||||
} else
|
||||
value->priority = BACNET_NO_PRIORITY;
|
||||
/* end of list? */
|
||||
if (decode_is_closing_tag_number(&apdu[len], 4))
|
||||
break;
|
||||
/* is there another one to decode? */
|
||||
value = value->next;
|
||||
/* out of room to store more values */
|
||||
if (value == NULL)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
12.11.38Active_COV_Subscriptions
|
||||
The Active_COV_Subscriptions property is a List of BACnetCOVSubscription, each of which consists of a Recipient, a
|
||||
Monitored Property Reference, an Issue Confirmed Notifications flag, a Time Remaining value and an optional COV
|
||||
Increment. This property provides a network-visible indication of those COV subscriptions that are active at any given time.
|
||||
Whenever a COV Subscription is created with the SubscribeCOV or SubscribeCOVProperty service, a new entry is added to
|
||||
the Active_COV_Subscriptions list. Similarly, whenever a COV Subscription is terminated, the corresponding entry shall be
|
||||
removed from the Active_COV_Subscriptions list.
|
||||
*/
|
||||
/*
|
||||
SubscribeCOV-Request ::= SEQUENCE {
|
||||
subscriberProcessIdentifier [0] Unsigned32,
|
||||
monitoredObjectIdentifier [1] BACnetObjectIdentifier,
|
||||
issueConfirmedNotifications [2] BOOLEAN OPTIONAL,
|
||||
lifetime [3] Unsigned OPTIONAL
|
||||
}
|
||||
*/
|
||||
|
||||
int cov_subscribe_encode_adpu(uint8_t * apdu,
|
||||
uint8_t invoke_id, BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
uint16_t max_apdu = Device_Max_APDU_Length_Accepted();
|
||||
|
||||
if (apdu && data) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = encode_max_segs_max_apdu(0, max_apdu);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_SUBSCRIBE_COV;
|
||||
apdu_len = 4;
|
||||
/* tag 0 - subscriberProcessIdentifier */
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
0, data->subscriberProcessIdentifier);
|
||||
apdu_len += len;
|
||||
/* tag 1 - monitoredObjectIdentifier */
|
||||
len = encode_context_object_id(&apdu[apdu_len],
|
||||
1,
|
||||
data->monitoredObjectIdentifier.type,
|
||||
data->monitoredObjectIdentifier.instance);
|
||||
apdu_len += len;
|
||||
/*
|
||||
If both the 'Issue Confirmed Notifications' and
|
||||
'Lifetime' parameters are absent, then this shall
|
||||
indicate a cancellation request.
|
||||
*/
|
||||
if (!data->cancellationRequest) {
|
||||
/* tag 2 - issueConfirmedNotifications */
|
||||
len = encode_context_boolean(&apdu[apdu_len],
|
||||
2, data->issueConfirmedNotifications);
|
||||
apdu_len += len;
|
||||
/* tag 3 - lifetime */
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
3, data->lifetime);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int cov_subscribe_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
uint32_t decoded_value = 0; /* for decoding */
|
||||
int decoded_type = 0; /* for decoding */
|
||||
|
||||
if (apdu_len && data) {
|
||||
/* tag 0 - subscriberProcessIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 0)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
data->subscriberProcessIdentifier = decoded_value;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 1 - monitoredObjectIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 1)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_object_id(&apdu[len], &decoded_type,
|
||||
&data->monitoredObjectIdentifier.instance);
|
||||
data->monitoredObjectIdentifier.type = decoded_type;
|
||||
} else
|
||||
return -1;
|
||||
/* optional parameters - if missing, means cancellation */
|
||||
if (len < apdu_len) {
|
||||
/* tag 2 - issueConfirmedNotifications - optional */
|
||||
if (decode_is_context_tag(&apdu[len], 2)) {
|
||||
data->cancellationRequest = false;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
data->issueConfirmedNotifications =
|
||||
decode_context_boolean(&apdu[len]);
|
||||
len += len_value;
|
||||
} else {
|
||||
data->cancellationRequest = true;
|
||||
}
|
||||
/* tag 3 - lifetime - optional */
|
||||
if (decode_is_context_tag(&apdu[len], 3)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
data->lifetime = decoded_value;
|
||||
} else
|
||||
data->lifetime = 0;
|
||||
} else {
|
||||
data->cancellationRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
SubscribeCOVProperty-Request ::= SEQUENCE {
|
||||
subscriberProcessIdentifier [0] Unsigned32,
|
||||
monitoredObjectIdentifier [1] BACnetObjectIdentifier,
|
||||
issueConfirmedNotifications [2] BOOLEAN OPTIONAL,
|
||||
lifetime [3] Unsigned OPTIONAL,
|
||||
monitoredPropertyIdentifier [4] BACnetPropertyReference,
|
||||
covIncrement [5] REAL OPTIONAL
|
||||
}
|
||||
|
||||
BACnetPropertyReference ::= SEQUENCE {
|
||||
propertyIdentifier [0] BACnetPropertyIdentifier,
|
||||
propertyArrayIndex [1] Unsigned OPTIONAL
|
||||
-- used only with array datatype
|
||||
-- if omitted with an array the entire array is referenced
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
int cov_subscribe_property_encode_adpu(uint8_t * apdu,
|
||||
uint8_t invoke_id, BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
uint16_t max_apdu = Device_Max_APDU_Length_Accepted();
|
||||
|
||||
if (apdu && data) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = encode_max_segs_max_apdu(0, max_apdu);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY;
|
||||
apdu_len = 4;
|
||||
/* tag 0 - subscriberProcessIdentifier */
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
0, data->subscriberProcessIdentifier);
|
||||
apdu_len += len;
|
||||
/* tag 1 - monitoredObjectIdentifier */
|
||||
len = encode_context_object_id(&apdu[apdu_len],
|
||||
1,
|
||||
data->monitoredObjectIdentifier.type,
|
||||
data->monitoredObjectIdentifier.instance);
|
||||
apdu_len += len;
|
||||
if (!data->cancellationRequest) {
|
||||
/* tag 2 - issueConfirmedNotifications */
|
||||
len = encode_context_boolean(&apdu[apdu_len],
|
||||
2, data->issueConfirmedNotifications);
|
||||
apdu_len += len;
|
||||
/* tag 3 - lifetime */
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
3, data->lifetime);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* tag 4 - monitoredPropertyIdentifier */
|
||||
len = encode_opening_tag(&apdu[apdu_len], 4);
|
||||
apdu_len += len;
|
||||
len = encode_context_enumerated(&apdu[apdu_len],
|
||||
0, data->monitoredProperty.propertyIdentifier);
|
||||
apdu_len += len;
|
||||
if (data->monitoredProperty.propertyArrayIndex != BACNET_ARRAY_ALL) {
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
1, data->monitoredProperty.propertyArrayIndex);
|
||||
apdu_len += len;
|
||||
|
||||
}
|
||||
len = encode_closing_tag(&apdu[apdu_len], 4);
|
||||
apdu_len += len;
|
||||
|
||||
/* tag 5 - covIncrement */
|
||||
if (data->covIncrementPresent) {
|
||||
len = encode_context_real(&apdu[apdu_len],
|
||||
5, data->covIncrement);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int cov_subscribe_property_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
uint32_t decoded_value = 0; /* for decoding */
|
||||
int decoded_type = 0; /* for decoding */
|
||||
int property = 0; /* for decoding */
|
||||
|
||||
if (apdu_len && data) {
|
||||
/* tag 0 - subscriberProcessIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 0)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
data->subscriberProcessIdentifier = decoded_value;
|
||||
} else
|
||||
return -1;
|
||||
/* tag 1 - monitoredObjectIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 1)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_object_id(&apdu[len], &decoded_type,
|
||||
&data->monitoredObjectIdentifier.instance);
|
||||
data->monitoredObjectIdentifier.type = decoded_type;
|
||||
} else
|
||||
return -2;
|
||||
/* tag 2 - issueConfirmedNotifications - optional */
|
||||
if (decode_is_context_tag(&apdu[len], 2)) {
|
||||
data->cancellationRequest = false;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
data->issueConfirmedNotifications =
|
||||
decode_context_boolean(&apdu[len]);
|
||||
len++;
|
||||
} else
|
||||
data->cancellationRequest = true;
|
||||
/* tag 3 - lifetime - optional */
|
||||
if (decode_is_context_tag(&apdu[len], 3)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
data->lifetime = decoded_value;
|
||||
} else
|
||||
data->lifetime = 0;
|
||||
/* tag 4 - monitoredPropertyIdentifier */
|
||||
if (!decode_is_opening_tag_number(&apdu[len], 4))
|
||||
return -3;
|
||||
/* a tag number of 4 is not extended so only one octet */
|
||||
len++;
|
||||
/* the propertyIdentifier is tag 0 */
|
||||
if (decode_is_context_tag(&apdu[len], 0)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_enumerated(&apdu[len], len_value, &property);
|
||||
data->monitoredProperty.propertyIdentifier = property;
|
||||
} else
|
||||
return -4;
|
||||
/* the optional array index is tag 1 */
|
||||
if (decode_is_context_tag(&apdu[len], 1)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
data->monitoredProperty.propertyArrayIndex = decoded_value;
|
||||
} else {
|
||||
data->monitoredProperty.propertyArrayIndex = BACNET_ARRAY_ALL;
|
||||
}
|
||||
|
||||
if (!decode_is_closing_tag_number(&apdu[len], 4))
|
||||
return -5;
|
||||
/* a tag number of 4 is not extended so only one octet */
|
||||
len++;
|
||||
/* tag 5 - covIncrement - optional */
|
||||
if (decode_is_context_tag(&apdu[len], 5)) {
|
||||
data->covIncrementPresent = true;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_real(&apdu[len], &data->covIncrement);
|
||||
} else
|
||||
data->covIncrementPresent = false;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int ucov_notify_send(uint8_t * buffer, BACNET_COV_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
int pdu_len = 0;
|
||||
BACNET_ADDRESS dest;
|
||||
int bytes_sent = 0;
|
||||
BACNET_NPDU_DATA npdu_data;
|
||||
|
||||
/* unconfirmed is a broadcast */
|
||||
datalink_get_broadcast_address(&dest);
|
||||
/* encode the NPDU portion of the packet */
|
||||
npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL);
|
||||
pdu_len = npdu_encode_pdu(&buffer[0], &dest, NULL, &npdu_data);
|
||||
/* encode the APDU portion of the packet */
|
||||
len = ucov_notify_encode_apdu(&buffer[pdu_len], data);
|
||||
pdu_len += len;
|
||||
/* send the data */
|
||||
bytes_sent = datalink_send_pdu(&dest, &npdu_data, &buffer[0], pdu_len);
|
||||
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
#include "bacapp.h"
|
||||
|
||||
int ccov_notify_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, uint8_t * invoke_id, BACNET_COV_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -2;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_COV_NOTIFICATION)
|
||||
return -3;
|
||||
offset = 4;
|
||||
|
||||
/* optional limits - must be used as a pair */
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
cov_notify_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int ucov_notify_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_COV_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST)
|
||||
return -2;
|
||||
if (apdu[1] != SERVICE_UNCONFIRMED_COV_NOTIFICATION)
|
||||
return -3;
|
||||
/* optional limits - must be used as a pair */
|
||||
offset = 2;
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
cov_notify_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int cov_subscribe_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, uint8_t * invoke_id,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -2;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_SUBSCRIBE_COV)
|
||||
return -3;
|
||||
offset = 4;
|
||||
|
||||
/* optional limits - must be used as a pair */
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
cov_subscribe_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int cov_subscribe_property_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, uint8_t * invoke_id,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -2;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY)
|
||||
return -3;
|
||||
offset = 4;
|
||||
|
||||
/* optional limits - must be used as a pair */
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
cov_subscribe_property_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* dummy function stubs */
|
||||
int npdu_encode_pdu(uint8_t * npdu,
|
||||
BACNET_ADDRESS * dest,
|
||||
BACNET_ADDRESS * src, BACNET_NPDU_DATA * npdu_data)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void npdu_encode_npdu_data(BACNET_NPDU_DATA * npdu,
|
||||
bool data_expecting_reply, BACNET_MESSAGE_PRIORITY priority)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/* dummy function stubs */
|
||||
int datalink_send_pdu(BACNET_ADDRESS * dest,
|
||||
BACNET_NPDU_DATA * npdu_data, uint8_t * pdu, unsigned pdu_len)
|
||||
{
|
||||
(void) dest;
|
||||
(void) npdu_data;
|
||||
(void) pdu;
|
||||
(void) pdu_len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* dummy function stubs */
|
||||
void datalink_get_broadcast_address(BACNET_ADDRESS * dest)
|
||||
{
|
||||
(void) dest;
|
||||
}
|
||||
|
||||
/* dummy function stubs */
|
||||
uint16_t Device_Max_APDU_Length_Accepted(void)
|
||||
{
|
||||
return MAX_APDU;
|
||||
}
|
||||
|
||||
void testCOVNotifyData(Test * pTest,
|
||||
BACNET_COV_DATA * data, BACNET_COV_DATA * test_data)
|
||||
{
|
||||
ct_test(pTest,
|
||||
test_data->subscriberProcessIdentifier ==
|
||||
data->subscriberProcessIdentifier);
|
||||
ct_test(pTest,
|
||||
test_data->initiatingDeviceIdentifier ==
|
||||
data->initiatingDeviceIdentifier);
|
||||
ct_test(pTest,
|
||||
test_data->monitoredObjectIdentifier.type ==
|
||||
data->monitoredObjectIdentifier.type);
|
||||
ct_test(pTest,
|
||||
test_data->monitoredObjectIdentifier.instance ==
|
||||
data->monitoredObjectIdentifier.instance);
|
||||
ct_test(pTest, test_data->timeRemaining == data->timeRemaining);
|
||||
/* FIXME: test the listOfValues in some clever manner */
|
||||
}
|
||||
|
||||
void testUCOVNotifyData(Test * pTest, BACNET_COV_DATA * data)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
BACNET_COV_DATA test_data;
|
||||
|
||||
len = ucov_notify_encode_apdu(&apdu[0], data);
|
||||
ct_test(pTest, len > 0);
|
||||
apdu_len = len;
|
||||
|
||||
test_data.listOfValues.next = NULL;
|
||||
len = ucov_notify_decode_apdu(&apdu[0], apdu_len, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
testCOVNotifyData(pTest, data, &test_data);
|
||||
}
|
||||
|
||||
void testCCOVNotifyData(Test * pTest, uint8_t invoke_id,
|
||||
BACNET_COV_DATA * data)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
BACNET_COV_DATA test_data;
|
||||
uint8_t test_invoke_id = 0;
|
||||
|
||||
len = ccov_notify_encode_apdu(&apdu[0], invoke_id, data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
test_data.listOfValues.next = NULL;
|
||||
len = ccov_notify_decode_apdu(&apdu[0], apdu_len,
|
||||
&test_invoke_id, &test_data);
|
||||
ct_test(pTest, len > 0);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
testCOVNotifyData(pTest, data, &test_data);
|
||||
}
|
||||
|
||||
void testCOVNotify(Test * pTest)
|
||||
{
|
||||
uint8_t invoke_id = 12;
|
||||
BACNET_COV_DATA data;
|
||||
/* BACNET_PROPERTY_VALUE value2; */
|
||||
|
||||
data.subscriberProcessIdentifier = 1;
|
||||
data.initiatingDeviceIdentifier = 123;
|
||||
data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT;
|
||||
data.monitoredObjectIdentifier.instance = 321;
|
||||
data.timeRemaining = 456;
|
||||
|
||||
data.listOfValues.propertyIdentifier = PROP_PRESENT_VALUE;
|
||||
data.listOfValues.propertyArrayIndex = BACNET_ARRAY_ALL;
|
||||
bacapp_parse_application_data(BACNET_APPLICATION_TAG_REAL,
|
||||
"21.0", &data.listOfValues.value);
|
||||
data.listOfValues.priority = 0;
|
||||
data.listOfValues.next = NULL;
|
||||
|
||||
testUCOVNotifyData(pTest, &data);
|
||||
testCCOVNotifyData(pTest, invoke_id, &data);
|
||||
|
||||
/* FIXME: add more values to the list of values */
|
||||
}
|
||||
|
||||
void testCOVSubscribeData(Test * pTest,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data,
|
||||
BACNET_SUBSCRIBE_COV_DATA * test_data)
|
||||
{
|
||||
ct_test(pTest,
|
||||
test_data->subscriberProcessIdentifier ==
|
||||
data->subscriberProcessIdentifier);
|
||||
ct_test(pTest,
|
||||
test_data->monitoredObjectIdentifier.type ==
|
||||
data->monitoredObjectIdentifier.type);
|
||||
ct_test(pTest,
|
||||
test_data->monitoredObjectIdentifier.instance ==
|
||||
data->monitoredObjectIdentifier.instance);
|
||||
ct_test(pTest,
|
||||
test_data->cancellationRequest == data->cancellationRequest);
|
||||
if (test_data->cancellationRequest != data->cancellationRequest) {
|
||||
printf("cancellation request failed!\n");
|
||||
}
|
||||
if (!test_data->cancellationRequest) {
|
||||
ct_test(pTest,
|
||||
test_data->issueConfirmedNotifications ==
|
||||
data->issueConfirmedNotifications);
|
||||
ct_test(pTest, test_data->lifetime == data->lifetime);
|
||||
}
|
||||
}
|
||||
|
||||
void testCOVSubscribePropertyData(Test * pTest,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data,
|
||||
BACNET_SUBSCRIBE_COV_DATA * test_data)
|
||||
{
|
||||
testCOVSubscribeData(pTest, data, test_data);
|
||||
ct_test(pTest,
|
||||
test_data->monitoredProperty.propertyIdentifier ==
|
||||
data->monitoredProperty.propertyIdentifier);
|
||||
ct_test(pTest,
|
||||
test_data->monitoredProperty.propertyArrayIndex ==
|
||||
data->monitoredProperty.propertyArrayIndex);
|
||||
ct_test(pTest,
|
||||
test_data->covIncrementPresent == data->covIncrementPresent);
|
||||
if (test_data->covIncrementPresent) {
|
||||
ct_test(pTest, test_data->covIncrement == data->covIncrement);
|
||||
}
|
||||
}
|
||||
|
||||
void testCOVSubscribeEncoding(Test * pTest, uint8_t invoke_id,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
BACNET_SUBSCRIBE_COV_DATA test_data;
|
||||
uint8_t test_invoke_id = 0;
|
||||
|
||||
len = cov_subscribe_encode_adpu(&apdu[0], invoke_id, data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = cov_subscribe_decode_apdu(&apdu[0], apdu_len,
|
||||
&test_invoke_id, &test_data);
|
||||
ct_test(pTest, len > 0);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
testCOVSubscribeData(pTest, data, &test_data);
|
||||
}
|
||||
|
||||
void testCOVSubscribePropertyEncoding(Test * pTest, uint8_t invoke_id,
|
||||
BACNET_SUBSCRIBE_COV_DATA * data)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
BACNET_SUBSCRIBE_COV_DATA test_data;
|
||||
uint8_t test_invoke_id = 0;
|
||||
|
||||
len = cov_subscribe_property_encode_adpu(&apdu[0], invoke_id, data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = cov_subscribe_property_decode_apdu(&apdu[0], apdu_len,
|
||||
&test_invoke_id, &test_data);
|
||||
ct_test(pTest, len > 0);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
testCOVSubscribePropertyData(pTest, data, &test_data);
|
||||
}
|
||||
|
||||
void testCOVSubscribe(Test * pTest)
|
||||
{
|
||||
uint8_t invoke_id = 12;
|
||||
BACNET_SUBSCRIBE_COV_DATA data;
|
||||
|
||||
data.subscriberProcessIdentifier = 1;
|
||||
data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT;
|
||||
data.monitoredObjectIdentifier.instance = 321;
|
||||
data.cancellationRequest = false;
|
||||
data.issueConfirmedNotifications = true;
|
||||
data.lifetime = 456;
|
||||
|
||||
testCOVSubscribeEncoding(pTest, invoke_id, &data);
|
||||
data.cancellationRequest = true;
|
||||
testCOVSubscribeEncoding(pTest, invoke_id, &data);
|
||||
}
|
||||
|
||||
void testCOVSubscribeProperty(Test * pTest)
|
||||
{
|
||||
uint8_t invoke_id = 12;
|
||||
BACNET_SUBSCRIBE_COV_DATA data;
|
||||
|
||||
data.subscriberProcessIdentifier = 1;
|
||||
data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT;
|
||||
data.monitoredObjectIdentifier.instance = 321;
|
||||
data.cancellationRequest = false;
|
||||
data.issueConfirmedNotifications = true;
|
||||
data.lifetime = 456;
|
||||
data.monitoredProperty.propertyIdentifier = PROP_FILE_SIZE;
|
||||
data.monitoredProperty.propertyArrayIndex = BACNET_ARRAY_ALL;
|
||||
data.covIncrementPresent = true;
|
||||
data.covIncrement = 1.0;
|
||||
|
||||
testCOVSubscribePropertyEncoding(pTest, invoke_id, &data);
|
||||
|
||||
data.cancellationRequest = true;
|
||||
testCOVSubscribePropertyEncoding(pTest, invoke_id, &data);
|
||||
|
||||
data.cancellationRequest = false;
|
||||
data.covIncrementPresent = false;
|
||||
testCOVSubscribePropertyEncoding(pTest, invoke_id, &data);
|
||||
}
|
||||
|
||||
#ifdef TEST_COV
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet COV", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testCOVNotify);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testCOVSubscribe);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testCOVSubscribeProperty);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_COV */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,288 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2004 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307
|
||||
USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#if defined(CRC_USE_TABLE)
|
||||
/* note: table is created using unit test below */
|
||||
static const uint8_t HeaderCRC[256] =
|
||||
{
|
||||
0x00, 0xfe, 0xff, 0x01, 0xfd, 0x03, 0x02, 0xfc,
|
||||
0xf9, 0x07, 0x06, 0xf8, 0x04, 0xfa, 0xfb, 0x05,
|
||||
0xf1, 0x0f, 0x0e, 0xf0, 0x0c, 0xf2, 0xf3, 0x0d,
|
||||
0x08, 0xf6, 0xf7, 0x09, 0xf5, 0x0b, 0x0a, 0xf4,
|
||||
0xe1, 0x1f, 0x1e, 0xe0, 0x1c, 0xe2, 0xe3, 0x1d,
|
||||
0x18, 0xe6, 0xe7, 0x19, 0xe5, 0x1b, 0x1a, 0xe4,
|
||||
0x10, 0xee, 0xef, 0x11, 0xed, 0x13, 0x12, 0xec,
|
||||
0xe9, 0x17, 0x16, 0xe8, 0x14, 0xea, 0xeb, 0x15,
|
||||
0xc1, 0x3f, 0x3e, 0xc0, 0x3c, 0xc2, 0xc3, 0x3d,
|
||||
0x38, 0xc6, 0xc7, 0x39, 0xc5, 0x3b, 0x3a, 0xc4,
|
||||
0x30, 0xce, 0xcf, 0x31, 0xcd, 0x33, 0x32, 0xcc,
|
||||
0xc9, 0x37, 0x36, 0xc8, 0x34, 0xca, 0xcb, 0x35,
|
||||
0x20, 0xde, 0xdf, 0x21, 0xdd, 0x23, 0x22, 0xdc,
|
||||
0xd9, 0x27, 0x26, 0xd8, 0x24, 0xda, 0xdb, 0x25,
|
||||
0xd1, 0x2f, 0x2e, 0xd0, 0x2c, 0xd2, 0xd3, 0x2d,
|
||||
0x28, 0xd6, 0xd7, 0x29, 0xd5, 0x2b, 0x2a, 0xd4,
|
||||
0x81, 0x7f, 0x7e, 0x80, 0x7c, 0x82, 0x83, 0x7d,
|
||||
0x78, 0x86, 0x87, 0x79, 0x85, 0x7b, 0x7a, 0x84,
|
||||
0x70, 0x8e, 0x8f, 0x71, 0x8d, 0x73, 0x72, 0x8c,
|
||||
0x89, 0x77, 0x76, 0x88, 0x74, 0x8a, 0x8b, 0x75,
|
||||
0x60, 0x9e, 0x9f, 0x61, 0x9d, 0x63, 0x62, 0x9c,
|
||||
0x99, 0x67, 0x66, 0x98, 0x64, 0x9a, 0x9b, 0x65,
|
||||
0x91, 0x6f, 0x6e, 0x90, 0x6c, 0x92, 0x93, 0x6d,
|
||||
0x68, 0x96, 0x97, 0x69, 0x95, 0x6b, 0x6a, 0x94,
|
||||
0x40, 0xbe, 0xbf, 0x41, 0xbd, 0x43, 0x42, 0xbc,
|
||||
0xb9, 0x47, 0x46, 0xb8, 0x44, 0xba, 0xbb, 0x45,
|
||||
0xb1, 0x4f, 0x4e, 0xb0, 0x4c, 0xb2, 0xb3, 0x4d,
|
||||
0x48, 0xb6, 0xb7, 0x49, 0xb5, 0x4b, 0x4a, 0xb4,
|
||||
0xa1, 0x5f, 0x5e, 0xa0, 0x5c, 0xa2, 0xa3, 0x5d,
|
||||
0x58, 0xa6, 0xa7, 0x59, 0xa5, 0x5b, 0x5a, 0xa4,
|
||||
0x50, 0xae, 0xaf, 0x51, 0xad, 0x53, 0x52, 0xac,
|
||||
0xa9, 0x57, 0x56, 0xa8, 0x54, 0xaa, 0xab, 0x55
|
||||
};
|
||||
|
||||
uint8_t CRC_Calc_Header(uint8_t dataValue, uint8_t crcValue)
|
||||
{
|
||||
return HeaderCRC[crcValue ^ dataValue];
|
||||
}
|
||||
|
||||
/* note: table is created using unit test below */
|
||||
static const uint16_t DataCRC[256] =
|
||||
{
|
||||
0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
|
||||
0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
|
||||
0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
|
||||
0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
|
||||
0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
|
||||
0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
|
||||
0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
|
||||
0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
|
||||
0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
|
||||
0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
|
||||
0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
|
||||
0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
|
||||
0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
|
||||
0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
|
||||
0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
|
||||
0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
|
||||
0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
|
||||
0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
|
||||
0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
|
||||
0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
|
||||
0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
|
||||
0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
|
||||
0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
|
||||
0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
|
||||
0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
|
||||
0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
|
||||
0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
|
||||
0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
|
||||
0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
|
||||
0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
|
||||
0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
|
||||
0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
|
||||
};
|
||||
|
||||
uint16_t CRC_Calc_Data(uint8_t dataValue, uint16_t crcValue)
|
||||
{
|
||||
return ((crcValue >> 8) ^ DataCRC[(crcValue & 0x00FF) ^ dataValue]);
|
||||
|
||||
}
|
||||
#else
|
||||
/* Accumulate "dataValue" into the CRC in crcValue. */
|
||||
/* Return value is updated CRC */
|
||||
/* */
|
||||
/* The ^ operator means exclusive OR. */
|
||||
/* Note: This function is copied directly from the BACnet standard. */
|
||||
uint8_t CRC_Calc_Header(uint8_t dataValue, uint8_t crcValue)
|
||||
{
|
||||
uint16_t crc;
|
||||
|
||||
crc = crcValue ^ dataValue; /* XOR C7..C0 with D7..D0 */
|
||||
|
||||
/* Exclusive OR the terms in the table (top down) */
|
||||
crc = crc ^ (crc << 1) ^ (crc << 2) ^ (crc << 3)
|
||||
^ (crc << 4) ^ (crc << 5) ^ (crc << 6)
|
||||
^ (crc << 7);
|
||||
|
||||
/* Combine bits shifted out left hand end */
|
||||
return (crc & 0xfe) ^ ((crc >> 8) & 1);
|
||||
}
|
||||
|
||||
/* Accumulate "dataValue" into the CRC in crcValue. */
|
||||
/* Return value is updated CRC */
|
||||
/* */
|
||||
/* The ^ operator means exclusive OR. */
|
||||
/* Note: This function is copied directly from the BACnet standard. */
|
||||
uint16_t CRC_Calc_Data(uint8_t dataValue, uint16_t crcValue)
|
||||
{
|
||||
uint16_t crcLow;
|
||||
|
||||
crcLow = (crcValue & 0xff) ^ dataValue; /* XOR C7..C0 with D7..D0 */
|
||||
|
||||
/* Exclusive OR the terms in the table (top down) */
|
||||
return (crcValue >> 8) ^ (crcLow << 8) ^ (crcLow << 3)
|
||||
^ (crcLow << 12) ^ (crcLow >> 4)
|
||||
^ (crcLow & 0x0f) ^ ((crcLow & 0x0f) << 7);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
#include "bytes.h"
|
||||
|
||||
/* test from Annex G 1.0 of BACnet Standard */
|
||||
void testCRC8(Test * pTest)
|
||||
{
|
||||
uint8_t crc = 0xff; /* accumulates the crc value */
|
||||
uint8_t frame_crc; /* appended to the end of the frame */
|
||||
|
||||
crc = CRC_Calc_Header(0x00, crc);
|
||||
ct_test(pTest, crc == 0x55);
|
||||
crc = CRC_Calc_Header(0x10, crc);
|
||||
ct_test(pTest, crc == 0xC2);
|
||||
crc = CRC_Calc_Header(0x05, crc);
|
||||
ct_test(pTest, crc == 0xBC);
|
||||
crc = CRC_Calc_Header(0x00, crc);
|
||||
ct_test(pTest, crc == 0x95);
|
||||
crc = CRC_Calc_Header(0x00, crc);
|
||||
ct_test(pTest, crc == 0x73);
|
||||
/* send the ones complement of the CRC in place of */
|
||||
/* the CRC, and the resulting CRC will always equal 0x55. */
|
||||
frame_crc = ~crc;
|
||||
ct_test(pTest, frame_crc == 0x8C);
|
||||
/* use the ones complement value and the next to last CRC value */
|
||||
crc = CRC_Calc_Header(frame_crc, crc);
|
||||
ct_test(pTest, crc == 0x55);
|
||||
}
|
||||
|
||||
/* test from Annex G 2.0 of BACnet Standard */
|
||||
void testCRC16(Test * pTest)
|
||||
{
|
||||
uint16_t crc = 0xffff;
|
||||
uint16_t data_crc;
|
||||
|
||||
crc = CRC_Calc_Data(0x01, crc);
|
||||
ct_test(pTest, crc == 0x1E0E);
|
||||
crc = CRC_Calc_Data(0x22, crc);
|
||||
ct_test(pTest, crc == 0xEB70);
|
||||
crc = CRC_Calc_Data(0x30, crc);
|
||||
ct_test(pTest, crc == 0x42EF);
|
||||
/* send the ones complement of the CRC in place of */
|
||||
/* the CRC, and the resulting CRC will always equal 0xF0B8. */
|
||||
data_crc = ~crc;
|
||||
ct_test(pTest, data_crc == 0xBD10);
|
||||
crc = CRC_Calc_Data(LO_BYTE(data_crc), crc);
|
||||
ct_test(pTest, crc == 0x0F3A);
|
||||
crc = CRC_Calc_Data(HI_BYTE(data_crc), crc);
|
||||
ct_test(pTest, crc == 0xF0B8);
|
||||
}
|
||||
|
||||
void testCRC8CreateTable(Test *pTest)
|
||||
{
|
||||
uint8_t crc = 0xff; /* accumulates the crc value */
|
||||
int i;
|
||||
|
||||
(void)pTest;
|
||||
printf("static const uint8_t HeaderCRC[256] =\n");
|
||||
printf("{\n");
|
||||
printf(" ");
|
||||
for (i = 0; i < 256; i++) {
|
||||
crc = CRC_Calc_Header(i, 0);
|
||||
printf("0x%02x, ", crc);
|
||||
if (!((i+1)%8)) {
|
||||
printf("\n");
|
||||
if (i != 255) {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("};\n");
|
||||
}
|
||||
|
||||
void testCRC16CreateTable(Test *pTest)
|
||||
{
|
||||
uint16_t crc;
|
||||
int i;
|
||||
|
||||
(void)pTest;
|
||||
printf("static const uint16_t DataCRC[256] =\n");
|
||||
printf("{\n");
|
||||
printf(" ");
|
||||
for (i = 0; i < 256; i++) {
|
||||
crc = CRC_Calc_Data(i, 0);
|
||||
printf("0x%04x, ", crc);
|
||||
if (!((i+1)%8)) {
|
||||
printf("\n");
|
||||
if (i != 255) {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
printf("};\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef TEST_CRC
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("crc", NULL);
|
||||
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testCRC8);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testCRC16);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testCRC8CreateTable);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testCRC16CreateTable);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,83 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2007 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include "datalink.h"
|
||||
|
||||
/* Function pointers - point to your datalink */
|
||||
bool (*datalink_init)(char *ifname);
|
||||
|
||||
int (*datalink_send_pdu)(BACNET_ADDRESS * dest,
|
||||
BACNET_NPDU_DATA * npdu_data, uint8_t * pdu, unsigned pdu_len);
|
||||
|
||||
uint16_t (*datalink_receive)(BACNET_ADDRESS * src,
|
||||
uint8_t * pdu, uint16_t max_pdu, unsigned timeout);
|
||||
|
||||
void (*datalink_cleanup)(void);
|
||||
|
||||
void (*datalink_get_broadcast_address)(BACNET_ADDRESS * dest);
|
||||
|
||||
void (*datalink_get_my_address)(BACNET_ADDRESS * my_address);
|
||||
|
||||
void datalink_configure(void)
|
||||
{
|
||||
#if defined(BACDL_ETHERNET)
|
||||
datalink_init = ethernet_init;
|
||||
datalink_send_pdu = ethernet_send_pdu;
|
||||
datalink_receive = ethernet_receive;
|
||||
datalink_cleanup = ethernet_cleanup;
|
||||
datalink_get_broadcast_address = ethernet_get_broadcast_address;
|
||||
datalink_get_my_address = ethernet_get_my_address;
|
||||
#elif defined(BACDL_ARCNET)
|
||||
datalink_init = arcnet_init;
|
||||
datalink_send_pdu = arcnet_send_pdu;
|
||||
datalink_receive = arcnet_receive;
|
||||
datalink_cleanup = arcnet_cleanup;
|
||||
datalink_get_broadcast_address = arcnet_get_broadcast_address;
|
||||
datalink_get_my_address = arcnet_get_my_address;
|
||||
#elif defined(BACDL_MSTP)
|
||||
datalink_init = dlmstp_init;
|
||||
datalink_send_pdu = dlmstp_send_pdu;
|
||||
datalink_receive = dlmstp_receive;
|
||||
datalink_cleanup = dlmstp_cleanup;
|
||||
datalink_get_broadcast_address = dlmstp_get_broadcast_address;
|
||||
datalink_get_my_address = dlmstp_get_my_address;
|
||||
#elif defined(BACDL_BIP)
|
||||
datalink_init = bip_init;
|
||||
datalink_send_pdu = bip_send_pdu;
|
||||
datalink_receive = bip_receive;
|
||||
datalink_cleanup = bip_cleanup;
|
||||
datalink_get_broadcast_address = bip_get_broadcast_address;
|
||||
datalink_get_my_address = bip_get_my_address;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,727 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2007 Steve Karg <skarg@users.sourceforge.net>
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include "datetime.h"
|
||||
|
||||
/* BACnet Date */
|
||||
/* year = years since 1900 */
|
||||
/* month 1=Jan */
|
||||
/* day = day of month 1..31 */
|
||||
/* wday 1=Monday...7=Sunday */
|
||||
|
||||
/* Wildcards:
|
||||
A value of X'FF' in any of the four octets
|
||||
shall indicate that the value is unspecified.
|
||||
If all four octets = X'FF', the corresponding
|
||||
time or date may be interpreted as "any" or "don't care"
|
||||
*/
|
||||
|
||||
static bool is_leap_year(uint16_t year)
|
||||
{
|
||||
if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0))
|
||||
return (true);
|
||||
else
|
||||
return (false);
|
||||
}
|
||||
|
||||
static uint8_t month_days(uint16_t year, uint8_t month)
|
||||
{
|
||||
/* note: start with a zero in the first element to save us from a
|
||||
month - 1 calculation in the lookup */
|
||||
int month_days[13] =
|
||||
{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
|
||||
|
||||
/* February */
|
||||
if ((month == 2) && is_leap_year(year))
|
||||
return 29;
|
||||
else if (month >= 1 && month <= 12)
|
||||
return month_days[month];
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t days_since_epoch(uint16_t year, uint8_t month, uint8_t day)
|
||||
{
|
||||
uint32_t days = 0; /* return value */
|
||||
uint8_t monthdays; /* days in a month */
|
||||
uint16_t years = 0; /* loop counter for years */
|
||||
uint8_t months = 0; /* loop counter for months */
|
||||
|
||||
monthdays = month_days(year, month);
|
||||
if ((year >= 1900) && (monthdays) && (day >= 1) && (day <= monthdays)) {
|
||||
for (years = 1900; years < year; years++) {
|
||||
days += 365;
|
||||
if (is_leap_year(years))
|
||||
days++;
|
||||
}
|
||||
for (months = 1; months < month; months++) {
|
||||
days += month_days(years, months);
|
||||
}
|
||||
days += (day - 1);
|
||||
}
|
||||
|
||||
return (days);
|
||||
}
|
||||
|
||||
static void days_since_epoch_into_ymd(uint32_t days,
|
||||
uint16_t * pYear, uint8_t * pMonth, uint8_t * pDay)
|
||||
{
|
||||
uint16_t year = 1900;
|
||||
uint8_t month = 1;
|
||||
uint8_t day = 1;
|
||||
|
||||
while (days >= 365) {
|
||||
if ((is_leap_year(year)) && (days == 365))
|
||||
break;
|
||||
days -= 365;
|
||||
if (is_leap_year(year))
|
||||
--days;
|
||||
year++;
|
||||
}
|
||||
|
||||
while (days >= (uint32_t) month_days(year, month)) {
|
||||
days -= month_days(year, month);
|
||||
month++;
|
||||
}
|
||||
|
||||
day += ((uint8_t) days);
|
||||
|
||||
if (pYear)
|
||||
*pYear = year;
|
||||
if (pMonth)
|
||||
*pMonth = month;
|
||||
if (pDay)
|
||||
*pDay = day;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* Jan 1, 1900 is a Monday */
|
||||
/* wday 1=Monday...7=Sunday */
|
||||
static uint8_t day_of_week(uint16_t year, uint8_t month, uint8_t day)
|
||||
{
|
||||
return ((uint8_t) (days_since_epoch(year, month, day) % 7) + 1);
|
||||
}
|
||||
|
||||
/* if the date1 is the same as date2, return is 0
|
||||
if date1 is after date2, returns positive
|
||||
if date1 is before date2, returns negative */
|
||||
int datetime_compare_date(BACNET_DATE * date1, BACNET_DATE * date2)
|
||||
{
|
||||
int diff = 0;
|
||||
|
||||
if (date1 && date2) {
|
||||
diff = (int) date1->year - (int) date2->year;
|
||||
if (diff == 0) {
|
||||
diff = (int) date1->month - (int) date2->month;
|
||||
if (diff == 0) {
|
||||
diff = (int) date1->day - (int) date2->day;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
/* if the time1 is the same as time2, return is 0
|
||||
if time1 is after time2, returns positive
|
||||
if time1 is before time2, returns negative */
|
||||
int datetime_compare_time(BACNET_TIME * time1, BACNET_TIME * time2)
|
||||
{
|
||||
int diff = 0;
|
||||
|
||||
if (time1 && time2) {
|
||||
diff = (int) time1->hour - (int) time2->hour;
|
||||
if (diff == 0) {
|
||||
diff = (int) time1->min - (int) time2->min;
|
||||
if (diff == 0) {
|
||||
diff = (int) time1->sec - (int) time2->sec;
|
||||
if (diff == 0) {
|
||||
diff =
|
||||
(int) time1->hundredths - (int) time2->hundredths;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
/* if the datetime1 is the same as datetime2, return is 0
|
||||
if datetime1 is before datetime2, returns negative
|
||||
if datetime1 is after datetime2, returns positive */
|
||||
int datetime_compare(BACNET_DATE_TIME * datetime1,
|
||||
BACNET_DATE_TIME * datetime2)
|
||||
{
|
||||
int diff = 0;
|
||||
|
||||
diff = datetime_compare_date(&datetime1->date, &datetime2->date);
|
||||
if (diff == 0) {
|
||||
diff = datetime_compare_time(&datetime1->time, &datetime2->time);
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
void datetime_copy_date(BACNET_DATE * dest_date, BACNET_DATE * src_date)
|
||||
{
|
||||
if (dest_date && src_date) {
|
||||
dest_date->year = src_date->year;
|
||||
dest_date->month = src_date->month;
|
||||
dest_date->day = src_date->day;
|
||||
dest_date->wday = src_date->wday;
|
||||
}
|
||||
}
|
||||
|
||||
void datetime_copy_time(BACNET_TIME * dest_time, BACNET_TIME * src_time)
|
||||
{
|
||||
if (dest_time && src_time) {
|
||||
dest_time->hour = src_time->hour;
|
||||
dest_time->min = src_time->min;
|
||||
dest_time->sec = src_time->sec;
|
||||
dest_time->hundredths = src_time->hundredths;
|
||||
}
|
||||
}
|
||||
|
||||
void datetime_copy(BACNET_DATE_TIME * dest_datetime,
|
||||
BACNET_DATE_TIME * src_datetime)
|
||||
{
|
||||
datetime_copy_time(&dest_datetime->time, &src_datetime->time);
|
||||
datetime_copy_date(&dest_datetime->date, &src_datetime->date);
|
||||
}
|
||||
|
||||
void datetime_set_date(BACNET_DATE * bdate,
|
||||
uint16_t year, uint8_t month, uint8_t day)
|
||||
{
|
||||
if (bdate) {
|
||||
bdate->year = year;
|
||||
bdate->month = month;
|
||||
bdate->day = day;
|
||||
bdate->wday = day_of_week(year, month, day);
|
||||
}
|
||||
}
|
||||
|
||||
void datetime_set_time(BACNET_TIME * btime,
|
||||
uint8_t hour, uint8_t minute, uint8_t seconds, uint8_t hundredths)
|
||||
{
|
||||
if (btime) {
|
||||
btime->hour = hour;
|
||||
btime->min = minute;
|
||||
btime->sec = seconds;
|
||||
btime->hundredths = hundredths;
|
||||
}
|
||||
}
|
||||
|
||||
void datetime_set(BACNET_DATE_TIME * bdatetime,
|
||||
BACNET_DATE * bdate, BACNET_TIME * btime)
|
||||
{
|
||||
if (bdate && btime && bdatetime) {
|
||||
bdatetime->time.hour = btime->hour;
|
||||
bdatetime->time.min = btime->min;
|
||||
bdatetime->time.sec = btime->sec;
|
||||
bdatetime->time.hundredths = btime->hundredths;
|
||||
bdatetime->date.year = bdate->year;
|
||||
bdatetime->date.month = bdate->month;
|
||||
bdatetime->date.day = bdate->day;
|
||||
bdatetime->date.wday = bdate->wday;
|
||||
}
|
||||
}
|
||||
|
||||
void datetime_set_values(BACNET_DATE_TIME * bdatetime,
|
||||
uint16_t year, uint8_t month, uint8_t day,
|
||||
uint8_t hour, uint8_t minute, uint8_t seconds, uint8_t hundredths)
|
||||
{
|
||||
if (bdatetime) {
|
||||
bdatetime->date.year = year;
|
||||
bdatetime->date.month = month;
|
||||
bdatetime->date.day = day;
|
||||
bdatetime->date.wday = day_of_week(year, month, day);
|
||||
bdatetime->time.hour = hour;
|
||||
bdatetime->time.min = minute;
|
||||
bdatetime->time.sec = seconds;
|
||||
bdatetime->time.hundredths = hundredths;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t seconds_since_midnight(uint8_t hours, uint8_t minutes,
|
||||
uint8_t seconds)
|
||||
{
|
||||
return ((hours * 60 * 60) + (minutes * 60) + seconds);
|
||||
}
|
||||
|
||||
static void seconds_since_midnight_into_hms(uint32_t seconds,
|
||||
uint8_t * pHours, uint8_t * pMinutes, uint8_t * pSeconds)
|
||||
{
|
||||
uint8_t hour = 0;
|
||||
uint8_t minute = 0;
|
||||
|
||||
hour = (uint8_t) (seconds / (60 * 60));
|
||||
seconds -= (hour * 60 * 60);
|
||||
minute = (uint8_t) (seconds / 60);
|
||||
seconds -= (minute * 60);
|
||||
|
||||
if (pHours)
|
||||
*pHours = hour;
|
||||
if (pMinutes)
|
||||
*pMinutes = minute;
|
||||
if (pSeconds)
|
||||
*pSeconds = (uint8_t) seconds;
|
||||
}
|
||||
|
||||
void datetime_add_minutes(BACNET_DATE_TIME * bdatetime, uint32_t minutes)
|
||||
{
|
||||
uint32_t bdatetime_minutes = 0;
|
||||
uint32_t bdatetime_days = 0;
|
||||
uint32_t days = 0;
|
||||
|
||||
/* convert bdatetime to seconds and days */
|
||||
bdatetime_minutes = seconds_since_midnight(bdatetime->time.hour,
|
||||
bdatetime->time.min, bdatetime->time.sec) / 60;
|
||||
bdatetime_days = days_since_epoch(bdatetime->date.year,
|
||||
bdatetime->date.month, bdatetime->date.day);
|
||||
|
||||
/* add */
|
||||
days = minutes / (24 * 60);
|
||||
bdatetime_days += days;
|
||||
minutes -= (days * 24 * 60);
|
||||
bdatetime_minutes += minutes;
|
||||
days = bdatetime_minutes / (24 * 60);
|
||||
bdatetime_days += days;
|
||||
|
||||
/* convert bdatetime from seconds and days */
|
||||
seconds_since_midnight_into_hms(bdatetime_minutes * 60,
|
||||
&bdatetime->time.hour, &bdatetime->time.min, &bdatetime->time.sec);
|
||||
days_since_epoch_into_ymd(bdatetime_days,
|
||||
&bdatetime->date.year,
|
||||
&bdatetime->date.month, &bdatetime->date.day);
|
||||
bdatetime->date.wday = day_of_week(bdatetime->date.year,
|
||||
bdatetime->date.month, bdatetime->date.day);
|
||||
}
|
||||
|
||||
bool datetime_wildcard(BACNET_DATE_TIME * bdatetime)
|
||||
{
|
||||
bool wildcard_present = false;
|
||||
|
||||
if (bdatetime) {
|
||||
if ((bdatetime->date.year == (1900 + 0xFF)) &&
|
||||
(bdatetime->date.month == 0xFF) &&
|
||||
(bdatetime->date.day == 0xFF) &&
|
||||
(bdatetime->date.wday == 0xFF) &&
|
||||
(bdatetime->time.hour == 0xFF) &&
|
||||
(bdatetime->time.min == 0xFF) &&
|
||||
(bdatetime->time.sec == 0xFF) &&
|
||||
(bdatetime->time.hundredths == 0xFF)) {
|
||||
wildcard_present = true;
|
||||
}
|
||||
}
|
||||
|
||||
return wildcard_present;
|
||||
}
|
||||
|
||||
void datetime_date_wildcard_set(BACNET_DATE * bdate)
|
||||
{
|
||||
if (bdate) {
|
||||
bdate->year = 1900 + 0xFF;
|
||||
bdate->month = 0xFF;
|
||||
bdate->day = 0xFF;
|
||||
bdate->wday = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void datetime_time_wildcard_set(BACNET_TIME * btime)
|
||||
{
|
||||
if (btime) {
|
||||
btime->hour = 0xFF;
|
||||
btime->min = 0xFF;
|
||||
btime->sec = 0xFF;
|
||||
btime->hundredths = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void datetime_wildcard_set(BACNET_DATE_TIME * bdatetime)
|
||||
{
|
||||
if (bdatetime) {
|
||||
datetime_date_wildcard_set(&bdatetime->date);
|
||||
datetime_time_wildcard_set(&bdatetime->time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testBACnetDateTimeWildcard(Test * pTest)
|
||||
{
|
||||
BACNET_DATE_TIME bdatetime;
|
||||
bool status = false;
|
||||
|
||||
datetime_set_values(&bdatetime, 1900, 1, 1, 0, 0, 0, 0);
|
||||
status = datetime_wildcard(&bdatetime);
|
||||
ct_test(pTest, status == false);
|
||||
|
||||
datetime_wildcard_set(&bdatetime);
|
||||
status = datetime_wildcard(&bdatetime);
|
||||
ct_test(pTest, status == true);
|
||||
}
|
||||
|
||||
void testBACnetDateTimeAdd(Test * pTest)
|
||||
{
|
||||
BACNET_DATE_TIME bdatetime, test_bdatetime;
|
||||
uint32_t minutes = 0;
|
||||
int diff = 0;
|
||||
|
||||
datetime_set_values(&bdatetime, 1900, 1, 1, 0, 0, 0, 0);
|
||||
datetime_copy(&test_bdatetime, &bdatetime);
|
||||
datetime_add_minutes(&bdatetime, minutes);
|
||||
diff = datetime_compare(&test_bdatetime, &bdatetime);
|
||||
ct_test(pTest, diff == 0);
|
||||
|
||||
datetime_set_values(&bdatetime, 1900, 1, 1, 0, 0, 0, 0);
|
||||
datetime_add_minutes(&bdatetime, 60);
|
||||
datetime_set_values(&test_bdatetime, 1900, 1, 1, 1, 0, 0, 0);
|
||||
diff = datetime_compare(&test_bdatetime, &bdatetime);
|
||||
ct_test(pTest, diff == 0);
|
||||
|
||||
datetime_set_values(&bdatetime, 1900, 1, 1, 0, 0, 0, 0);
|
||||
datetime_add_minutes(&bdatetime, (24 * 60));
|
||||
datetime_set_values(&test_bdatetime, 1900, 1, 2, 0, 0, 0, 0);
|
||||
diff = datetime_compare(&test_bdatetime, &bdatetime);
|
||||
ct_test(pTest, diff == 0);
|
||||
|
||||
datetime_set_values(&bdatetime, 1900, 1, 1, 0, 0, 0, 0);
|
||||
datetime_add_minutes(&bdatetime, (31 * 24 * 60));
|
||||
datetime_set_values(&test_bdatetime, 1900, 2, 1, 0, 0, 0, 0);
|
||||
diff = datetime_compare(&test_bdatetime, &bdatetime);
|
||||
ct_test(pTest, diff == 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void testBACnetDateTimeSeconds(Test * pTest)
|
||||
{
|
||||
uint8_t hour = 0, minute = 0, second = 0;
|
||||
uint8_t test_hour = 0, test_minute = 0, test_second = 0;
|
||||
uint32_t seconds = 0, test_seconds;
|
||||
|
||||
for (hour = 0; hour < 24; hour++) {
|
||||
for (minute = 0; minute < 60; minute += 3) {
|
||||
for (second = 0; second < 60; second += 17) {
|
||||
seconds = seconds_since_midnight(hour, minute, second);
|
||||
seconds_since_midnight_into_hms(seconds,
|
||||
&test_hour, &test_minute, &test_second);
|
||||
test_seconds =
|
||||
seconds_since_midnight(test_hour, test_minute,
|
||||
test_second);
|
||||
ct_test(pTest, seconds == test_seconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testBACnetDate(Test * pTest)
|
||||
{
|
||||
BACNET_DATE bdate1, bdate2;
|
||||
int diff = 0;
|
||||
|
||||
datetime_set_date(&bdate1, 1900, 1, 1);
|
||||
datetime_copy_date(&bdate2, &bdate1);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff == 0);
|
||||
datetime_set_date(&bdate2, 1900, 1, 2);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_date(&bdate2, 1900, 2, 1);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_date(&bdate2, 1901, 1, 1);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff < 0);
|
||||
|
||||
/* midpoint */
|
||||
datetime_set_date(&bdate1, 2007, 7, 15);
|
||||
datetime_copy_date(&bdate2, &bdate1);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff == 0);
|
||||
datetime_set_date(&bdate2, 2007, 7, 14);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_date(&bdate2, 2007, 7, 1);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_date(&bdate2, 2007, 7, 31);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_date(&bdate2, 2007, 8, 15);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_date(&bdate2, 2007, 12, 15);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_date(&bdate2, 2007, 6, 15);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_date(&bdate2, 2007, 1, 15);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_date(&bdate2, 2006, 7, 15);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_date(&bdate2, 1900, 7, 15);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_date(&bdate2, 2008, 7, 15);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_date(&bdate2, 2154, 7, 15);
|
||||
diff = datetime_compare_date(&bdate1, &bdate2);
|
||||
ct_test(pTest, diff < 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void testBACnetTime(Test * pTest)
|
||||
{
|
||||
BACNET_TIME btime1, btime2;
|
||||
int diff = 0;
|
||||
|
||||
datetime_set_time(&btime1, 0, 0, 0, 0);
|
||||
datetime_copy_time(&btime2, &btime1);
|
||||
diff = datetime_compare_time(&btime1, &btime2);
|
||||
ct_test(pTest, diff == 0);
|
||||
|
||||
datetime_set_time(&btime1, 23, 59, 59, 99);
|
||||
datetime_copy_time(&btime2, &btime1);
|
||||
diff = datetime_compare_time(&btime1, &btime2);
|
||||
ct_test(pTest, diff == 0);
|
||||
|
||||
/* midpoint */
|
||||
datetime_set_time(&btime1, 12, 30, 30, 50);
|
||||
datetime_copy_time(&btime2, &btime1);
|
||||
diff = datetime_compare_time(&btime1, &btime2);
|
||||
ct_test(pTest, diff == 0);
|
||||
datetime_set_time(&btime2, 12, 30, 30, 51);
|
||||
diff = datetime_compare_time(&btime1, &btime2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_time(&btime2, 12, 30, 31, 50);
|
||||
diff = datetime_compare_time(&btime1, &btime2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_time(&btime2, 12, 31, 30, 50);
|
||||
diff = datetime_compare_time(&btime1, &btime2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_time(&btime2, 13, 30, 30, 50);
|
||||
diff = datetime_compare_time(&btime1, &btime2);
|
||||
ct_test(pTest, diff < 0);
|
||||
|
||||
datetime_set_time(&btime2, 12, 30, 30, 49);
|
||||
diff = datetime_compare_time(&btime1, &btime2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_time(&btime2, 12, 30, 29, 50);
|
||||
diff = datetime_compare_time(&btime1, &btime2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_time(&btime2, 12, 29, 30, 50);
|
||||
diff = datetime_compare_time(&btime1, &btime2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_time(&btime2, 11, 30, 30, 50);
|
||||
diff = datetime_compare_time(&btime1, &btime2);
|
||||
ct_test(pTest, diff > 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void testBACnetDateTime(Test * pTest)
|
||||
{
|
||||
BACNET_DATE_TIME bdatetime1, bdatetime2;
|
||||
BACNET_DATE bdate;
|
||||
BACNET_TIME btime;
|
||||
int diff = 0;
|
||||
|
||||
datetime_set_values(&bdatetime1, 1900, 1, 1, 0, 0, 0, 0);
|
||||
datetime_copy(&bdatetime2, &bdatetime1);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff == 0);
|
||||
datetime_set_time(&btime, 0, 0, 0, 0);
|
||||
datetime_set_date(&bdate, 1900, 1, 1);
|
||||
datetime_set(&bdatetime1, &bdate, &btime);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff == 0);
|
||||
|
||||
/* midpoint */
|
||||
/* if datetime1 is before datetime2, returns negative */
|
||||
datetime_set_values(&bdatetime1, 2000, 7, 15, 12, 30, 30, 50);
|
||||
datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 30, 30, 51);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 30, 31, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 31, 30, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_values(&bdatetime2, 2000, 7, 15, 13, 30, 30, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_values(&bdatetime2, 2000, 7, 16, 12, 30, 30, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_values(&bdatetime2, 2000, 8, 15, 12, 30, 30, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_values(&bdatetime2, 2001, 7, 15, 12, 30, 30, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff < 0);
|
||||
datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 30, 30, 49);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 30, 29, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_values(&bdatetime2, 2000, 7, 15, 12, 29, 30, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_values(&bdatetime2, 2000, 7, 15, 11, 30, 30, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_values(&bdatetime2, 2000, 7, 14, 12, 30, 30, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_values(&bdatetime2, 2000, 6, 15, 12, 30, 30, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff > 0);
|
||||
datetime_set_values(&bdatetime2, 1999, 7, 15, 12, 30, 30, 50);
|
||||
diff = datetime_compare(&bdatetime1, &bdatetime2);
|
||||
ct_test(pTest, diff > 0);
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void testDateEpoch(Test * pTest)
|
||||
{
|
||||
uint32_t days = 0;
|
||||
uint16_t year = 0, test_year = 0;
|
||||
uint8_t month = 0, test_month = 0;
|
||||
uint8_t day = 0, test_day = 0;
|
||||
|
||||
days = days_since_epoch(1900, 1, 1);
|
||||
ct_test(pTest, days == 0);
|
||||
days_since_epoch_into_ymd(days, &year, &month, &day);
|
||||
ct_test(pTest, year == 1900);
|
||||
ct_test(pTest, month == 1);
|
||||
ct_test(pTest, day == 1);
|
||||
|
||||
|
||||
for (year = 1900; year <= 2154; year++) {
|
||||
for (month = 1; month <= 12; month++) {
|
||||
for (day = 1; day <= month_days(year, month); day++) {
|
||||
days = days_since_epoch(year, month, day);
|
||||
days_since_epoch_into_ymd(days,
|
||||
&test_year, &test_month, &test_day);
|
||||
ct_test(pTest, year == test_year);
|
||||
ct_test(pTest, month == test_month);
|
||||
ct_test(pTest, day == test_day);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testBACnetDayOfWeek(Test * pTest)
|
||||
{
|
||||
uint8_t dow = 0;
|
||||
|
||||
/* 1/1/1900 is a Monday */
|
||||
dow = day_of_week(1900, 1, 1);
|
||||
ct_test(pTest, dow == 1);
|
||||
|
||||
/* 1/1/2007 is a Monday */
|
||||
dow = day_of_week(2007, 1, 1);
|
||||
ct_test(pTest, dow == 1);
|
||||
dow = day_of_week(2007, 1, 2);
|
||||
ct_test(pTest, dow == 2);
|
||||
dow = day_of_week(2007, 1, 3);
|
||||
ct_test(pTest, dow == 3);
|
||||
dow = day_of_week(2007, 1, 4);
|
||||
ct_test(pTest, dow == 4);
|
||||
dow = day_of_week(2007, 1, 5);
|
||||
ct_test(pTest, dow == 5);
|
||||
dow = day_of_week(2007, 1, 6);
|
||||
ct_test(pTest, dow == 6);
|
||||
dow = day_of_week(2007, 1, 7);
|
||||
ct_test(pTest, dow == 7);
|
||||
|
||||
dow = day_of_week(2007, 1, 31);
|
||||
ct_test(pTest, dow == 3);
|
||||
}
|
||||
|
||||
#ifdef TEST_DATE_TIME
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Date Time", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testBACnetDate);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetTime);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetDateTime);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetDayOfWeek);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testDateEpoch);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetDateTimeSeconds);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetDateTimeAdd);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACnetDateTimeWildcard);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_DATE_TIME */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,308 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2006 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "dcc.h"
|
||||
|
||||
/* note: the disable and time are not expected to survive
|
||||
over a power cycle or reinitialization. */
|
||||
/* note: time duration is given in Minutes, but in order to be accurate,
|
||||
we need to count down in seconds. */
|
||||
static uint32_t DCC_Time_Duration_Seconds = 0;
|
||||
static BACNET_COMMUNICATION_ENABLE_DISABLE DCC_Enable_Disable =
|
||||
COMMUNICATION_ENABLE;
|
||||
/* password is optionally supported */
|
||||
|
||||
BACNET_COMMUNICATION_ENABLE_DISABLE dcc_enable_status(void)
|
||||
{
|
||||
return DCC_Enable_Disable;
|
||||
}
|
||||
|
||||
bool dcc_communication_enabled(void)
|
||||
{
|
||||
return (DCC_Enable_Disable == COMMUNICATION_ENABLE);
|
||||
}
|
||||
|
||||
/* When network communications are completely disabled,
|
||||
only DeviceCommunicationControl and ReinitializeDevice APDUs
|
||||
shall be processed and no messages shall be initiated.*/
|
||||
bool dcc_communication_disabled(void)
|
||||
{
|
||||
return (DCC_Enable_Disable == COMMUNICATION_DISABLE);
|
||||
}
|
||||
|
||||
/* When the initiation of communications is disabled,
|
||||
all APDUs shall be processed and responses returned as
|
||||
required and no messages shall be initiated with the
|
||||
exception of I-Am requests, which shall be initiated only in
|
||||
response to Who-Is messages. In this state, a device that
|
||||
supports I-Am request initiation shall send one I-Am request
|
||||
for any Who-Is request that is received if and only if
|
||||
the Who-Is request does not contain an address range or
|
||||
the device is included in the address range. */
|
||||
bool dcc_communication_initiation_disabled(void)
|
||||
{
|
||||
return (DCC_Enable_Disable == COMMUNICATION_DISABLE_INITIATION);
|
||||
}
|
||||
|
||||
uint32_t dcc_duration_seconds(void)
|
||||
{
|
||||
return DCC_Time_Duration_Seconds;
|
||||
}
|
||||
|
||||
/* called every second or so. If more than one second,
|
||||
then seconds should be the number of seconds to tick away */
|
||||
void dcc_timer_seconds(uint32_t seconds)
|
||||
{
|
||||
if (DCC_Time_Duration_Seconds) {
|
||||
if (DCC_Time_Duration_Seconds > seconds)
|
||||
DCC_Time_Duration_Seconds -= seconds;
|
||||
else
|
||||
DCC_Time_Duration_Seconds = 0;
|
||||
/* just expired - do something */
|
||||
if (DCC_Time_Duration_Seconds == 0)
|
||||
DCC_Enable_Disable = COMMUNICATION_ENABLE;
|
||||
}
|
||||
}
|
||||
|
||||
bool dcc_set_status_duration(BACNET_COMMUNICATION_ENABLE_DISABLE status,
|
||||
uint16_t minutes)
|
||||
{
|
||||
bool valid = false;
|
||||
|
||||
/* valid? */
|
||||
if (status < MAX_BACNET_COMMUNICATION_ENABLE_DISABLE) {
|
||||
DCC_Enable_Disable = status;
|
||||
if (status == COMMUNICATION_ENABLE)
|
||||
DCC_Time_Duration_Seconds = 0;
|
||||
else
|
||||
DCC_Time_Duration_Seconds = minutes * 60;
|
||||
valid = true;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
/* encode service */
|
||||
int dcc_encode_apdu(uint8_t * apdu, uint8_t invoke_id, uint16_t timeDuration, /* 0=optional */
|
||||
BACNET_COMMUNICATION_ENABLE_DISABLE enable_disable,
|
||||
BACNET_CHARACTER_STRING * password)
|
||||
{ /* NULL=optional */
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL;
|
||||
apdu_len = 4;
|
||||
/* optional timeDuration */
|
||||
if (timeDuration) {
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 0, timeDuration);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* enable disable */
|
||||
len =
|
||||
encode_context_enumerated(&apdu[apdu_len], 1, enable_disable);
|
||||
apdu_len += len;
|
||||
/* optional password */
|
||||
if (password) {
|
||||
/* FIXME: must be at least 1 character, limited to 20 characters */
|
||||
len =
|
||||
encode_context_character_string(&apdu[apdu_len], 2,
|
||||
password);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int dcc_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint16_t * timeDuration,
|
||||
BACNET_COMMUNICATION_ENABLE_DISABLE * enable_disable,
|
||||
BACNET_CHARACTER_STRING * password)
|
||||
{
|
||||
unsigned len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
int value = 0;
|
||||
uint32_t value32 = 0;
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len) {
|
||||
/* Tag 0: timeDuration --optional-- */
|
||||
if (decode_is_context_tag(&apdu[len], 0)) {
|
||||
len += decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
len += decode_unsigned(&apdu[len], len_value_type, &value32);
|
||||
if (timeDuration)
|
||||
*timeDuration = (uint16_t) value32;
|
||||
} else if (timeDuration)
|
||||
*timeDuration = 0;
|
||||
/* Tag 1: enable_disable */
|
||||
if (!decode_is_context_tag(&apdu[len], 1))
|
||||
return -1;
|
||||
len += decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += decode_enumerated(&apdu[len], len_value_type, &value);
|
||||
if (enable_disable)
|
||||
*enable_disable = (BACNET_COMMUNICATION_ENABLE_DISABLE)value;
|
||||
/* Tag 2: password --optional-- */
|
||||
if (len < apdu_len) {
|
||||
if (!decode_is_context_tag(&apdu[len], 2))
|
||||
return -1;
|
||||
len += decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len +=
|
||||
decode_character_string(&apdu[len], len_value_type,
|
||||
password);
|
||||
} else if (password)
|
||||
characterstring_init_ansi(password, NULL);
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
int dcc_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id,
|
||||
uint16_t * timeDuration,
|
||||
BACNET_COMMUNICATION_ENABLE_DISABLE * enable_disable,
|
||||
BACNET_CHARACTER_STRING * password)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL)
|
||||
return -1;
|
||||
offset = 4;
|
||||
|
||||
if (apdu_len > offset) {
|
||||
len = dcc_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, timeDuration, enable_disable, password);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void test_DeviceCommunicationControlData(Test * pTest,
|
||||
uint8_t invoke_id,
|
||||
uint16_t timeDuration,
|
||||
BACNET_COMMUNICATION_ENABLE_DISABLE enable_disable,
|
||||
BACNET_CHARACTER_STRING * password)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t test_invoke_id = 0;
|
||||
uint16_t test_timeDuration = 0;
|
||||
BACNET_COMMUNICATION_ENABLE_DISABLE test_enable_disable;
|
||||
BACNET_CHARACTER_STRING test_password;
|
||||
|
||||
len = dcc_encode_apdu(&apdu[0],
|
||||
invoke_id, timeDuration, enable_disable, password);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = dcc_decode_apdu(&apdu[0],
|
||||
apdu_len,
|
||||
&test_invoke_id,
|
||||
&test_timeDuration, &test_enable_disable, &test_password);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, test_timeDuration == timeDuration);
|
||||
ct_test(pTest, test_enable_disable == enable_disable);
|
||||
ct_test(pTest, characterstring_same(&test_password, password));
|
||||
}
|
||||
|
||||
void test_DeviceCommunicationControl(Test * pTest)
|
||||
{
|
||||
uint8_t invoke_id = 128;
|
||||
uint16_t timeDuration = 0;
|
||||
BACNET_COMMUNICATION_ENABLE_DISABLE enable_disable;
|
||||
BACNET_CHARACTER_STRING password;
|
||||
|
||||
timeDuration = 0;
|
||||
enable_disable = COMMUNICATION_DISABLE_INITIATION;
|
||||
characterstring_init_ansi(&password, "John 3:16");
|
||||
test_DeviceCommunicationControlData(pTest,
|
||||
invoke_id, timeDuration, enable_disable, &password);
|
||||
|
||||
timeDuration = 12345;
|
||||
enable_disable = COMMUNICATION_DISABLE;
|
||||
test_DeviceCommunicationControlData(pTest,
|
||||
invoke_id, timeDuration, enable_disable, NULL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_DEVICE_COMMUNICATION_CONTROL
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet DeviceCommunicationControl", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, test_DeviceCommunicationControl);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_DEVICE_COMMUNICATION_CONTROL */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,101 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2006 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
char *filename_remove_path(const char *filename_in)
|
||||
{
|
||||
char *filename_out = NULL;
|
||||
|
||||
/* allow the device ID to be set */
|
||||
if (filename_in) {
|
||||
filename_out = strrchr(filename_in, '\\');
|
||||
if (!filename_out)
|
||||
filename_out = strrchr(filename_in, '/');
|
||||
/* go beyond the slash */
|
||||
if (filename_out)
|
||||
filename_out++;
|
||||
}
|
||||
|
||||
return filename_out;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ctest.h"
|
||||
|
||||
void testFilename(Test * pTest)
|
||||
{
|
||||
char *data1 = "c:\\Joshua\\run";
|
||||
char *data2 = "/home/Anna/run";
|
||||
char *data3 = "c:\\Program Files\\Christopher\\run.exe";
|
||||
char *data4 = "//Mary/data/run";
|
||||
char *filename = NULL;
|
||||
|
||||
filename = filename_remove_path(data1);
|
||||
ct_test(pTest, strcmp("run", filename) == 0);
|
||||
filename = filename_remove_path(data2);
|
||||
ct_test(pTest, strcmp("run", filename) == 0);
|
||||
filename = filename_remove_path(data3);
|
||||
ct_test(pTest, strcmp("run.exe", filename) == 0);
|
||||
filename = filename_remove_path(data4);
|
||||
ct_test(pTest, strcmp("run", filename) == 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_FILENAME
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("filename remove path", NULL);
|
||||
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testFilename);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_FILENAME */
|
||||
#endif /* TEST */
|
||||
Executable
+281
@@ -0,0 +1,281 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdef.h"
|
||||
#include "npdu.h"
|
||||
#include "dcc.h"
|
||||
#include "datalink.h"
|
||||
#include "device.h"
|
||||
#include "bacdcode.h"
|
||||
#include "address.h"
|
||||
|
||||
/* encode I-Am service */
|
||||
int iam_encode_apdu(uint8_t * apdu,
|
||||
uint32_t device_id,
|
||||
unsigned max_apdu, int segmentation, uint16_t vendor_id)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = SERVICE_UNCONFIRMED_I_AM; /* service choice */
|
||||
apdu_len = 2;
|
||||
len = encode_tagged_object_id(&apdu[apdu_len],
|
||||
OBJECT_DEVICE, device_id);
|
||||
apdu_len += len;
|
||||
len = encode_tagged_unsigned(&apdu[apdu_len], max_apdu);
|
||||
apdu_len += len;
|
||||
len = encode_tagged_enumerated(&apdu[apdu_len], segmentation);
|
||||
apdu_len += len;
|
||||
len = encode_tagged_unsigned(&apdu[apdu_len], vendor_id);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int iam_decode_service_request(uint8_t * apdu,
|
||||
uint32_t * pDevice_id,
|
||||
unsigned *pMax_apdu, int *pSegmentation, uint16_t * pVendor_id)
|
||||
{
|
||||
int len = 0;
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
int object_type = 0; /* should be a Device Object */
|
||||
uint32_t object_instance = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
uint32_t decoded_value = 0;
|
||||
int decoded_integer = 0;
|
||||
|
||||
/* OBJECT ID - object id */
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value);
|
||||
apdu_len += len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_OBJECT_ID)
|
||||
return -1;
|
||||
len =
|
||||
decode_object_id(&apdu[apdu_len], &object_type, &object_instance);
|
||||
apdu_len += len;
|
||||
if (object_type != OBJECT_DEVICE)
|
||||
return -1;
|
||||
if (pDevice_id)
|
||||
*pDevice_id = object_instance;
|
||||
/* MAX APDU - unsigned */
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value);
|
||||
apdu_len += len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT)
|
||||
return -1;
|
||||
len = decode_unsigned(&apdu[apdu_len], len_value, &decoded_value);
|
||||
apdu_len += len;
|
||||
if (pMax_apdu)
|
||||
*pMax_apdu = decoded_value;
|
||||
/* Segmentation - enumerated */
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value);
|
||||
apdu_len += len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_ENUMERATED)
|
||||
return -1;
|
||||
len = decode_enumerated(&apdu[apdu_len], len_value, &decoded_integer);
|
||||
apdu_len += len;
|
||||
if (decoded_integer >= MAX_BACNET_SEGMENTATION)
|
||||
return -1;
|
||||
if (pSegmentation)
|
||||
*pSegmentation = decoded_integer;
|
||||
/* Vendor ID - unsigned16 */
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value);
|
||||
apdu_len += len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT)
|
||||
return -1;
|
||||
len = decode_unsigned(&apdu[apdu_len], len_value, &decoded_value);
|
||||
apdu_len += len;
|
||||
if (decoded_value > 0xFFFF)
|
||||
return -1;
|
||||
if (pVendor_id)
|
||||
*pVendor_id = (uint16_t) decoded_value;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int iam_send(uint8_t * buffer)
|
||||
{
|
||||
int len = 0;
|
||||
int pdu_len = 0;
|
||||
BACNET_ADDRESS dest;
|
||||
int bytes_sent = 0;
|
||||
BACNET_NPDU_DATA npdu_data;
|
||||
|
||||
/* if we are forbidden to send, don't send! */
|
||||
if (!dcc_communication_enabled())
|
||||
return 0;
|
||||
|
||||
/* I-Am is a global broadcast */
|
||||
datalink_get_broadcast_address(&dest);
|
||||
/* encode the NPDU portion of the packet */
|
||||
npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL);
|
||||
pdu_len = npdu_encode_pdu(&buffer[0], &dest, NULL, &npdu_data);
|
||||
/* 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());
|
||||
pdu_len += len;
|
||||
/* send data */
|
||||
bytes_sent = datalink_send_pdu(&dest, &npdu_data, &buffer[0], pdu_len);
|
||||
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
int iam_decode_apdu(uint8_t * apdu,
|
||||
uint32_t * pDevice_id,
|
||||
unsigned *pMax_apdu, int *pSegmentation, uint16_t * pVendor_id)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
/* valid data? */
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
if (apdu[1] != SERVICE_UNCONFIRMED_I_AM)
|
||||
return -1;
|
||||
apdu_len = iam_decode_service_request(&apdu[2],
|
||||
pDevice_id, pMax_apdu, pSegmentation, pVendor_id);
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
void testIAm(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
uint32_t device_id = 42;
|
||||
unsigned max_apdu = 480;
|
||||
int segmentation = SEGMENTATION_NONE;
|
||||
uint16_t vendor_id = 42;
|
||||
uint32_t test_device_id = 0;
|
||||
unsigned test_max_apdu = 0;
|
||||
int test_segmentation = 0;
|
||||
uint16_t test_vendor_id = 0;
|
||||
|
||||
len = iam_encode_apdu(&apdu[0],
|
||||
device_id, max_apdu, segmentation, vendor_id);
|
||||
ct_test(pTest, len != 0);
|
||||
|
||||
len = iam_decode_apdu(&apdu[0],
|
||||
&test_device_id,
|
||||
&test_max_apdu, &test_segmentation, &test_vendor_id);
|
||||
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_device_id == device_id);
|
||||
ct_test(pTest, test_vendor_id == vendor_id);
|
||||
ct_test(pTest, test_max_apdu == max_apdu);
|
||||
ct_test(pTest, test_segmentation == segmentation);
|
||||
}
|
||||
|
||||
#ifdef TEST_IAM
|
||||
/* dummy function stubs */
|
||||
int datalink_send_pdu(BACNET_ADDRESS * dest,
|
||||
BACNET_NPDU_DATA * npdu_data, uint8_t * pdu, unsigned pdu_len)
|
||||
{
|
||||
(void) dest;
|
||||
(void) npdu_data;
|
||||
(void) pdu;
|
||||
(void) pdu_len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void datalink_get_broadcast_address(BACNET_ADDRESS * dest)
|
||||
{ /* destination address */
|
||||
(void) dest;
|
||||
}
|
||||
|
||||
uint16_t Device_Vendor_Identifier(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t Device_Object_Instance_Number(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void address_add_binding(uint32_t device_id,
|
||||
unsigned max_apdu, BACNET_ADDRESS * src)
|
||||
{
|
||||
(void) device_id;
|
||||
(void) max_apdu;
|
||||
(void) src;
|
||||
}
|
||||
|
||||
/* dummy for apdu dependency */
|
||||
void tsm_free_invoke_id(uint8_t invokeID)
|
||||
{
|
||||
/* dummy stub for testing */
|
||||
(void) invokeID;
|
||||
}
|
||||
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet I-Am", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testIAm);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_IAM */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,196 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2006 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "ihave.h"
|
||||
|
||||
int ihave_encode_apdu(uint8_t * apdu, BACNET_I_HAVE_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu && data) {
|
||||
apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = SERVICE_UNCONFIRMED_I_HAVE;
|
||||
apdu_len = 2;
|
||||
/* deviceIdentifier */
|
||||
len = encode_tagged_object_id(&apdu[apdu_len],
|
||||
data->device_id.type, data->device_id.instance);
|
||||
apdu_len += len;
|
||||
/* objectIdentifier */
|
||||
len = encode_tagged_object_id(&apdu[apdu_len],
|
||||
data->object_id.type, data->object_id.instance);
|
||||
apdu_len += len;
|
||||
/* objectName */
|
||||
len = encode_tagged_character_string(&apdu[apdu_len],
|
||||
&data->object_name);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int ihave_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_I_HAVE_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
int decoded_type = 0; /* for decoding */
|
||||
|
||||
if (apdu_len && data) {
|
||||
/* deviceIdentifier */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
if (tag_number == BACNET_APPLICATION_TAG_OBJECT_ID) {
|
||||
len += decode_object_id(&apdu[len], &decoded_type,
|
||||
&data->device_id.instance);
|
||||
data->device_id.type = decoded_type;
|
||||
} else
|
||||
return -1;
|
||||
/* objectIdentifier */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
if (tag_number == BACNET_APPLICATION_TAG_OBJECT_ID) {
|
||||
len += decode_object_id(&apdu[len], &decoded_type,
|
||||
&data->object_id.instance);
|
||||
data->object_id.type = decoded_type;
|
||||
} else
|
||||
return -1;
|
||||
/* objectName */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
if (tag_number == BACNET_APPLICATION_TAG_CHARACTER_STRING) {
|
||||
len += decode_character_string(&apdu[len], len_value,
|
||||
&data->object_name);
|
||||
} else
|
||||
return -1;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int ihave_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_I_HAVE_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
if (apdu[1] != SERVICE_UNCONFIRMED_I_HAVE)
|
||||
return -1;
|
||||
len = ihave_decode_service_request(&apdu[2], apdu_len - 2, data);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testIHaveData(Test * pTest, BACNET_I_HAVE_DATA * data)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
BACNET_I_HAVE_DATA test_data;
|
||||
|
||||
len = ihave_encode_apdu(&apdu[0], data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = ihave_decode_apdu(&apdu[0], apdu_len, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_data.device_id.type == data->device_id.type);
|
||||
ct_test(pTest, test_data.device_id.instance ==
|
||||
data->device_id.instance);
|
||||
ct_test(pTest, test_data.object_id.type == data->object_id.type);
|
||||
ct_test(pTest, test_data.object_id.instance ==
|
||||
data->object_id.instance);
|
||||
ct_test(pTest, characterstring_same(&test_data.object_name,
|
||||
&data->object_name));
|
||||
}
|
||||
|
||||
void testIHave(Test * pTest)
|
||||
{
|
||||
BACNET_I_HAVE_DATA data;
|
||||
|
||||
characterstring_init_ansi(&data.object_name, "Patricia - my love!");
|
||||
data.device_id.type = OBJECT_DEVICE;
|
||||
for (data.device_id.instance = 1;
|
||||
data.device_id.instance <= BACNET_MAX_INSTANCE;
|
||||
data.device_id.instance <<= 1) {
|
||||
for (data.object_id.type = OBJECT_ANALOG_INPUT;
|
||||
data.object_id.type <= MAX_BACNET_OBJECT_TYPE;
|
||||
data.object_id.type++) {
|
||||
for (data.object_id.instance = 1;
|
||||
data.object_id.instance <= BACNET_MAX_INSTANCE;
|
||||
data.object_id.instance <<= 1) {
|
||||
testIHaveData(pTest, &data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TEST_I_HAVE
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet I-Have", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testIHave);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_WHOIS */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,228 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include "indtext.h"
|
||||
|
||||
#if defined(__BORLANDC__) || defined(_MSC_VER)
|
||||
#define strcasecmp stricmp
|
||||
#endif
|
||||
|
||||
bool indtext_by_string(INDTEXT_DATA * data_list,
|
||||
const char *search_name, unsigned *found_index)
|
||||
{
|
||||
bool found = false;
|
||||
unsigned index = 0;
|
||||
|
||||
if (data_list && search_name) {
|
||||
while (data_list->pString) {
|
||||
if (strcmp(data_list->pString, search_name) == 0) {
|
||||
index = data_list->index;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
data_list++;
|
||||
}
|
||||
}
|
||||
|
||||
if (found && found_index)
|
||||
*found_index = index;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/* case insensitive version */
|
||||
bool indtext_by_istring(INDTEXT_DATA * data_list,
|
||||
const char *search_name, unsigned *found_index)
|
||||
{
|
||||
bool found = false;
|
||||
unsigned index = 0;
|
||||
|
||||
if (data_list && search_name) {
|
||||
while (data_list->pString) {
|
||||
if (strcasecmp(data_list->pString, search_name) == 0) {
|
||||
index = data_list->index;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
data_list++;
|
||||
}
|
||||
}
|
||||
|
||||
if (found && found_index)
|
||||
*found_index = index;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
unsigned indtext_by_string_default(INDTEXT_DATA * data_list,
|
||||
const char *search_name, unsigned default_index)
|
||||
{
|
||||
unsigned index = 0;
|
||||
|
||||
if (!indtext_by_string(data_list, search_name, &index))
|
||||
index = default_index;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
unsigned indtext_by_istring_default(INDTEXT_DATA * data_list,
|
||||
const char *search_name, unsigned default_index)
|
||||
{
|
||||
unsigned index = 0;
|
||||
|
||||
if (!indtext_by_istring(data_list, search_name, &index))
|
||||
index = default_index;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
const char *indtext_by_index_default(INDTEXT_DATA * data_list,
|
||||
unsigned index, const char *default_string)
|
||||
{
|
||||
const char *pString = NULL;
|
||||
|
||||
if (data_list) {
|
||||
while (data_list->pString) {
|
||||
if (data_list->index == index) {
|
||||
pString = data_list->pString;
|
||||
break;
|
||||
}
|
||||
data_list++;
|
||||
}
|
||||
}
|
||||
|
||||
return pString ? pString : default_string;
|
||||
}
|
||||
|
||||
const char *indtext_by_index_split_default(INDTEXT_DATA * data_list,
|
||||
int index,
|
||||
int split_index,
|
||||
const char *before_split_default_name, const char *default_name)
|
||||
{
|
||||
if (index < split_index)
|
||||
return indtext_by_index_default(data_list, index,
|
||||
before_split_default_name);
|
||||
else
|
||||
return indtext_by_index_default(data_list, index, default_name);
|
||||
};
|
||||
|
||||
|
||||
const char *indtext_by_index(INDTEXT_DATA * data_list, unsigned index)
|
||||
{
|
||||
return indtext_by_index_default(data_list, index, NULL);
|
||||
}
|
||||
|
||||
unsigned indtext_count(INDTEXT_DATA * data_list)
|
||||
{
|
||||
unsigned count = 0; /* return value */
|
||||
|
||||
if (data_list) {
|
||||
while (data_list->pString) {
|
||||
count++;
|
||||
data_list++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include "ctest.h"
|
||||
|
||||
static INDTEXT_DATA data_list[] = {
|
||||
{1, "Joshua"},
|
||||
{2, "Mary"},
|
||||
{3, "Anna"},
|
||||
{4, "Christopher"},
|
||||
{5, "Patricia"},
|
||||
{0, NULL}
|
||||
};
|
||||
|
||||
void testIndexText(Test * pTest)
|
||||
{
|
||||
unsigned i; /*counter */
|
||||
const char *pString;
|
||||
unsigned index;
|
||||
bool valid;
|
||||
unsigned count = 0;
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
pString = indtext_by_index(data_list, i);
|
||||
if (pString) {
|
||||
count++;
|
||||
valid = indtext_by_string(data_list, pString, &index);
|
||||
ct_test(pTest, valid == true);
|
||||
ct_test(pTest, index == i);
|
||||
ct_test(pTest, index == indtext_by_string_default(data_list,
|
||||
pString, index));
|
||||
}
|
||||
}
|
||||
ct_test(pTest, indtext_count(data_list) == count);
|
||||
ct_test(pTest, indtext_by_string(data_list, "Harry", NULL) == false);
|
||||
ct_test(pTest, indtext_by_string(data_list, NULL, NULL) == false);
|
||||
ct_test(pTest, indtext_by_string(NULL, NULL, NULL) == false);
|
||||
ct_test(pTest, indtext_by_index(data_list, 0) == NULL);
|
||||
ct_test(pTest, indtext_by_index(data_list, 10) == NULL);
|
||||
ct_test(pTest, indtext_by_index(NULL, 10) == NULL);
|
||||
/* case insensitive versions */
|
||||
ct_test(pTest, indtext_by_istring(data_list, "JOSHUA", NULL) == true);
|
||||
ct_test(pTest, indtext_by_istring(data_list, "joshua", NULL) == true);
|
||||
valid = indtext_by_istring(data_list, "ANNA", &index);
|
||||
ct_test(pTest, index == indtext_by_istring_default(data_list, "ANNA",
|
||||
index));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TEST_INDEX_TEXT
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("index text", NULL);
|
||||
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testIndexText);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_INDEX_TEXT */
|
||||
@@ -0,0 +1,116 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2003 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
/*#define TEST */
|
||||
/*#define TEST_KEY */
|
||||
#include "key.h"
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ctest.h"
|
||||
|
||||
/* test the encode and decode macros */
|
||||
void testKeys(Test * pTest)
|
||||
{
|
||||
int type, id;
|
||||
int decoded_type, decoded_id;
|
||||
KEY key;
|
||||
|
||||
for (type = 0; type < KEY_TYPE_MAX; type++) {
|
||||
for (id = 0; id < KEY_ID_MAX; id++) {
|
||||
key = KEY_ENCODE(type, id);
|
||||
decoded_type = KEY_DECODE_TYPE(key);
|
||||
decoded_id = KEY_DECODE_ID(key);
|
||||
ct_test(pTest, decoded_type == type);
|
||||
ct_test(pTest, decoded_id == id);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* test the encode and decode macros */
|
||||
void testKeySample(Test * pTest)
|
||||
{
|
||||
int type, id;
|
||||
int type_list[] = { 0, 1, KEY_TYPE_MAX / 2, KEY_TYPE_MAX - 1, -1 };
|
||||
int id_list[] = { 0, 1, KEY_ID_MAX / 2, KEY_ID_MAX - 1, -1 };
|
||||
int type_index = 0;
|
||||
int id_index = 0;
|
||||
int decoded_type, decoded_id;
|
||||
KEY key;
|
||||
|
||||
while (type_list[type_index] != -1) {
|
||||
while (id_list[id_index] != -1) {
|
||||
type = type_list[type_index];
|
||||
id = id_list[id_index];
|
||||
key = KEY_ENCODE(type, id);
|
||||
decoded_type = KEY_DECODE_TYPE(key);
|
||||
decoded_id = KEY_DECODE_ID(key);
|
||||
ct_test(pTest, decoded_type == type);
|
||||
ct_test(pTest, decoded_id == id);
|
||||
|
||||
id_index++;
|
||||
}
|
||||
id_index = 0;
|
||||
type_index++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_KEY
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("key", NULL);
|
||||
/* add the individual tests */
|
||||
/* rc = ct_addTestFunction(pTest, testKeys); */
|
||||
/* assert(rc); */
|
||||
rc = ct_addTestFunction(pTest, testKeySample);
|
||||
assert(rc);
|
||||
/* run all the tests */
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
/* completed testing - cleanup */
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* LOCAL_TEST */
|
||||
#endif
|
||||
@@ -0,0 +1,716 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2003 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
/* Keyed Linked List Library */
|
||||
/* */
|
||||
/* This is an enhanced array of pointers to data. */
|
||||
/* The list is sorted, indexed, and keyed. */
|
||||
/* The array is much faster than a linked list. */
|
||||
/* It stores a pointer to data, which you must */
|
||||
/* malloc and free on your own, or just use */
|
||||
/* static data */
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "keylist.h" /* check for valid prototypes */
|
||||
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#endif
|
||||
|
||||
/*/////////////////////////////////////////////////////////////////// */
|
||||
/* Generic node routines */
|
||||
/*/////////////////////////////////////////////////////////////////// */
|
||||
|
||||
/* grab memory for a node */
|
||||
static struct Keylist_Node *NodeCreate(void)
|
||||
{
|
||||
return calloc(1, sizeof(struct Keylist_Node));
|
||||
}
|
||||
|
||||
/* grab memory for a list */
|
||||
static struct Keylist *KeylistCreate(void)
|
||||
{
|
||||
return calloc(1, sizeof(struct Keylist));
|
||||
}
|
||||
|
||||
/* check to see if the array is big enough for an addition */
|
||||
/* or is too big when we are deleting and we can shrink */
|
||||
/* returns TRUE if success, FALSE if failed */
|
||||
static int CheckArraySize(OS_Keylist list)
|
||||
{
|
||||
int new_size = 0; /* set it up so that no size change is the default */
|
||||
const int chunk = 8; /* minimum number of nodes to allocate memory for */
|
||||
struct Keylist_Node **new_array; /* new array of nodes, if needed */
|
||||
int i; /* counter */
|
||||
if (!list)
|
||||
return FALSE;
|
||||
|
||||
/* indicates the need for more memory allocation */
|
||||
if (list->count == list->size)
|
||||
new_size = list->size + chunk;
|
||||
|
||||
/* allow for shrinking memory */
|
||||
else if ((list->size > chunk) && (list->count < (list->size - chunk)))
|
||||
new_size = list->size - chunk;
|
||||
if (new_size) {
|
||||
|
||||
/* Allocate more room for node pointer array */
|
||||
new_array = calloc((size_t) new_size, sizeof(new_array));
|
||||
|
||||
/* See if we got the memory we wanted */
|
||||
if (!new_array)
|
||||
return FALSE;
|
||||
|
||||
/* copy the nodes from the old array to the new array */
|
||||
if (list->array) {
|
||||
for (i = 0; i < list->count; i++) {
|
||||
new_array[i] = list->array[i];
|
||||
}
|
||||
free(list->array);
|
||||
}
|
||||
list->array = new_array;
|
||||
list->size = new_size;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/* find the index of the key that we are looking for */
|
||||
/* since it is sorted, we can optimize the search */
|
||||
/* returns TRUE if found, and FALSE not found */
|
||||
/* returns the found key and the index where it was found in parameters */
|
||||
/* If the key is not found, the nearest index from the bottom will be returned, */
|
||||
/* allowing the ability to find where an key should go into the list. */
|
||||
static int FindIndex(OS_Keylist list, KEY key, int *pIndex)
|
||||
{
|
||||
struct Keylist_Node *node; /* holds the new node */
|
||||
int left = 0; /* the left branch of tree, beginning of list */
|
||||
int right = 0; /* the right branch on the tree, end of list */
|
||||
int index = 0; /* our current search place in the array */
|
||||
KEY current_key = 0; /* place holder for current node key */
|
||||
int status = FALSE; /* return value */
|
||||
if (!list || !list->array || !list->count) {
|
||||
*pIndex = 0;
|
||||
return (FALSE);
|
||||
}
|
||||
right = list->count - 1;
|
||||
/* assume that the list is sorted */
|
||||
do {
|
||||
|
||||
/* A binary search */
|
||||
index = (left + right) / 2;
|
||||
node = list->array[index];
|
||||
if (!node)
|
||||
break;
|
||||
current_key = node->key;
|
||||
if (key < current_key)
|
||||
right = index - 1;
|
||||
|
||||
else
|
||||
left = index + 1;
|
||||
}
|
||||
while ((key != current_key) && (left <= right));
|
||||
if (key == current_key) {
|
||||
status = TRUE;
|
||||
*pIndex = index;
|
||||
}
|
||||
|
||||
else {
|
||||
|
||||
/* where the index should be */
|
||||
if (key > current_key)
|
||||
*pIndex = index + 1;
|
||||
|
||||
else
|
||||
*pIndex = index;
|
||||
}
|
||||
return (status);
|
||||
}
|
||||
|
||||
|
||||
/*/////////////////////////////////////////////////////////////////// */
|
||||
/* list data functions */
|
||||
/*/////////////////////////////////////////////////////////////////// */
|
||||
/* inserts a node into its sorted position */
|
||||
int Keylist_Data_Add(OS_Keylist list, KEY key, void *data)
|
||||
{
|
||||
struct Keylist_Node *node; /* holds the new node */
|
||||
int index = -1; /* return value */
|
||||
int i; /* counts through the array */
|
||||
|
||||
if (list && CheckArraySize(list)) {
|
||||
/* figure out where to put the new node */
|
||||
if (list->count) {
|
||||
(void) FindIndex(list, key, &index);
|
||||
/* Add to the beginning of the list */
|
||||
if (index < 0)
|
||||
index = 0;
|
||||
|
||||
/* Add to the end of the list */
|
||||
else if (index > list->count)
|
||||
index = list->count;
|
||||
|
||||
/* Move all the items up to make room for the new one */
|
||||
for (i = list->count; i > index; i--) {
|
||||
list->array[i] = list->array[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
/* create and add the node */
|
||||
node = NodeCreate();
|
||||
if (node) {
|
||||
list->count++;
|
||||
node->key = key;
|
||||
node->data = data;
|
||||
list->array[index] = node;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
/* deletes a node specified by its index */
|
||||
/* returns the data from the node */
|
||||
void *Keylist_Data_Delete_By_Index(OS_Keylist list, int index)
|
||||
{
|
||||
struct Keylist_Node *node;
|
||||
void *data = NULL;
|
||||
|
||||
if (list && list->array && list->count &&
|
||||
(index >= 0) && (index < list->count)) {
|
||||
node = list->array[index];
|
||||
if (node)
|
||||
data = node->data;
|
||||
|
||||
/* move the nodes to account for the deleted one */
|
||||
if (list->count == 1) {
|
||||
|
||||
/* There is no node shifting to do */
|
||||
}
|
||||
/* We are the last one */
|
||||
else if (index == (list->count - 1)) {
|
||||
|
||||
/* There is no node shifting to do */
|
||||
}
|
||||
/* Move all the nodes down one */
|
||||
else {
|
||||
int i; /* counter */
|
||||
int count = list->count - 1;
|
||||
for (i = index; i < count; i++) {
|
||||
list->array[i] = list->array[i + 1];
|
||||
}
|
||||
}
|
||||
list->count--;
|
||||
if (node)
|
||||
free(node);
|
||||
|
||||
/* potentially reduce the size of the array */
|
||||
(void) CheckArraySize(list);
|
||||
}
|
||||
return (data);
|
||||
}
|
||||
|
||||
|
||||
/* deletes a node specified by its key */
|
||||
/* returns the data from the node */
|
||||
void *Keylist_Data_Delete(OS_Keylist list, KEY key)
|
||||
{
|
||||
void *data = NULL; /* return value */
|
||||
int index; /* where the node is in the array */
|
||||
|
||||
if (list) {
|
||||
if (FindIndex(list, key, &index))
|
||||
data = Keylist_Data_Delete_By_Index(list, index);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/* returns the data from last node, and removes it from the list */
|
||||
void *Keylist_Data_Pop(OS_Keylist list)
|
||||
{
|
||||
void *data = NULL; /* return value */
|
||||
int index; /* position in the array */
|
||||
|
||||
if (list && list->count) {
|
||||
index = list->count - 1;
|
||||
data = Keylist_Data_Delete_By_Index(list, index);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/* returns the data from the node specified by key */
|
||||
void *Keylist_Data(OS_Keylist list, KEY key)
|
||||
{
|
||||
struct Keylist_Node *node = NULL;
|
||||
int index = 0; /* used to look up the index of node */
|
||||
|
||||
if (list && list->array && list->count) {
|
||||
if (FindIndex(list, key, &index))
|
||||
node = list->array[index];
|
||||
}
|
||||
|
||||
return node ? node->data : NULL;
|
||||
}
|
||||
|
||||
/* returns the data specified by key */
|
||||
void *Keylist_Data_Index(OS_Keylist list, int index)
|
||||
{
|
||||
struct Keylist_Node *node = NULL;
|
||||
|
||||
if (list && list->array && list->count &&
|
||||
(index >= 0) && (index < list->count))
|
||||
node = list->array[index];
|
||||
|
||||
return node ? node->data : NULL;
|
||||
}
|
||||
|
||||
/* return the key at the given index */
|
||||
KEY Keylist_Key(OS_Keylist list, int index)
|
||||
{
|
||||
KEY key = 0; /* return value */
|
||||
struct Keylist_Node *node;
|
||||
|
||||
if (list && list->array && list->count &&
|
||||
(index >= 0) && (index < list->count)) {
|
||||
node = list->array[index];
|
||||
if (node)
|
||||
key = node->key;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/* returns the next empty key from the list */
|
||||
KEY Keylist_Next_Empty_Key(OS_Keylist list, KEY key)
|
||||
{
|
||||
int index;
|
||||
|
||||
if (list) {
|
||||
while (FindIndex(list, key, &index)) {
|
||||
if (KEY_LAST(key))
|
||||
break;
|
||||
key++;
|
||||
}
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/* return the number of nodes in this list */
|
||||
int Keylist_Count(OS_Keylist list)
|
||||
{
|
||||
return list->count;
|
||||
}
|
||||
|
||||
/*/////////////////////////////////////////////////////////////////// */
|
||||
/* Public List functions */
|
||||
/*/////////////////////////////////////////////////////////////////// */
|
||||
|
||||
/* returns head of the list or NULL on failure. */
|
||||
OS_Keylist Keylist_Create(void)
|
||||
{
|
||||
struct Keylist *list;
|
||||
|
||||
list = KeylistCreate();
|
||||
if (list)
|
||||
CheckArraySize(list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/* delete specified list */
|
||||
void Keylist_Delete(OS_Keylist list) /* list number to be deleted */
|
||||
{
|
||||
if (list) {
|
||||
/* clean out the list */
|
||||
while (list->count) {
|
||||
(void) Keylist_Data_Delete_By_Index(list, 0);
|
||||
}
|
||||
if (list->array)
|
||||
free(list->array);
|
||||
free(list);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ctest.h"
|
||||
|
||||
/* test the encode and decode macros */
|
||||
void testKeySample(Test * pTest)
|
||||
{
|
||||
int type, id;
|
||||
int type_list[] =
|
||||
{ 0, 1, KEY_TYPE_MAX / 2, KEY_TYPE_MAX - 2, KEY_TYPE_MAX - 1, -1 };
|
||||
int id_list[] =
|
||||
{ 0, 1, KEY_ID_MAX / 2, KEY_ID_MAX - 2, KEY_ID_MAX - 1, -1 };
|
||||
int type_index = 0;
|
||||
int id_index = 0;
|
||||
int decoded_type, decoded_id;
|
||||
KEY key;
|
||||
|
||||
while (type_list[type_index] != -1) {
|
||||
while (id_list[id_index] != -1) {
|
||||
type = type_list[type_index];
|
||||
id = id_list[id_index];
|
||||
key = KEY_ENCODE(type, id);
|
||||
decoded_type = KEY_DECODE_TYPE(key);
|
||||
decoded_id = KEY_DECODE_ID(key);
|
||||
ct_test(pTest, decoded_type == type);
|
||||
ct_test(pTest, decoded_id == id);
|
||||
|
||||
id_index++;
|
||||
}
|
||||
id_index = 0;
|
||||
type_index++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* test the FIFO */
|
||||
void testKeyListFIFO(Test * pTest)
|
||||
{
|
||||
OS_Keylist list;
|
||||
KEY key;
|
||||
int index;
|
||||
char *data1 = "Joshua";
|
||||
char *data2 = "Anna";
|
||||
char *data3 = "Mary";
|
||||
char *data;
|
||||
|
||||
list = Keylist_Create();
|
||||
ct_test(pTest, list != NULL);
|
||||
|
||||
key = 0;
|
||||
index = Keylist_Data_Add(list, key, data1);
|
||||
ct_test(pTest, index == 0);
|
||||
index = Keylist_Data_Add(list, key, data2);
|
||||
ct_test(pTest, index == 0);
|
||||
index = Keylist_Data_Add(list, key, data3);
|
||||
ct_test(pTest, index == 0);
|
||||
|
||||
ct_test(pTest, Keylist_Count(list) == 3);
|
||||
|
||||
data = Keylist_Data_Pop(list);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data1) == 0);
|
||||
data = Keylist_Data_Pop(list);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data2) == 0);
|
||||
data = Keylist_Data_Pop(list);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data3) == 0);
|
||||
data = Keylist_Data_Pop(list);
|
||||
ct_test(pTest, data == NULL);
|
||||
data = Keylist_Data_Pop(list);
|
||||
ct_test(pTest, data == NULL);
|
||||
|
||||
Keylist_Delete(list);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* test the FILO */
|
||||
void testKeyListFILO(Test * pTest)
|
||||
{
|
||||
OS_Keylist list;
|
||||
KEY key;
|
||||
int index;
|
||||
char *data1 = "Joshua";
|
||||
char *data2 = "Anna";
|
||||
char *data3 = "Mary";
|
||||
char *data;
|
||||
|
||||
list = Keylist_Create();
|
||||
ct_test(pTest, list != NULL);
|
||||
|
||||
key = 0;
|
||||
index = Keylist_Data_Add(list, key, data1);
|
||||
ct_test(pTest, index == 0);
|
||||
index = Keylist_Data_Add(list, key, data2);
|
||||
ct_test(pTest, index == 0);
|
||||
index = Keylist_Data_Add(list, key, data3);
|
||||
ct_test(pTest, index == 0);
|
||||
|
||||
ct_test(pTest, Keylist_Count(list) == 3);
|
||||
|
||||
data = Keylist_Data_Delete_By_Index(list, 0);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data3) == 0);
|
||||
|
||||
data = Keylist_Data_Delete_By_Index(list, 0);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data2) == 0);
|
||||
|
||||
data = Keylist_Data_Delete_By_Index(list, 0);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data1) == 0);
|
||||
|
||||
data = Keylist_Data_Delete_By_Index(list, 0);
|
||||
ct_test(pTest, data == NULL);
|
||||
|
||||
data = Keylist_Data_Delete_By_Index(list, 0);
|
||||
ct_test(pTest, data == NULL);
|
||||
|
||||
Keylist_Delete(list);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void testKeyListDataKey(Test * pTest)
|
||||
{
|
||||
OS_Keylist list;
|
||||
KEY key;
|
||||
KEY test_key;
|
||||
int index;
|
||||
char *data1 = "Joshua";
|
||||
char *data2 = "Anna";
|
||||
char *data3 = "Mary";
|
||||
char *data;
|
||||
|
||||
list = Keylist_Create();
|
||||
ct_test(pTest, list != NULL);
|
||||
|
||||
key = 1;
|
||||
index = Keylist_Data_Add(list, key, data1);
|
||||
ct_test(pTest, index == 0);
|
||||
test_key = Keylist_Key(list, index);
|
||||
ct_test(pTest, test_key == key);
|
||||
|
||||
key = 2;
|
||||
index = Keylist_Data_Add(list, key, data2);
|
||||
ct_test(pTest, index == 1);
|
||||
test_key = Keylist_Key(list, index);
|
||||
ct_test(pTest, test_key == key);
|
||||
|
||||
key = 3;
|
||||
index = Keylist_Data_Add(list, key, data3);
|
||||
ct_test(pTest, index == 2);
|
||||
test_key = Keylist_Key(list, index);
|
||||
ct_test(pTest, test_key == key);
|
||||
|
||||
ct_test(pTest, Keylist_Count(list) == 3);
|
||||
|
||||
/* look at the data */
|
||||
key = 2;
|
||||
data = Keylist_Data(list, key);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data2) == 0);
|
||||
|
||||
key = 1;
|
||||
data = Keylist_Data(list, key);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data1) == 0);
|
||||
|
||||
key = 3;
|
||||
data = Keylist_Data(list, key);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data3) == 0);
|
||||
|
||||
/* work the data */
|
||||
key = 2;
|
||||
data = Keylist_Data_Delete(list, key);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data2) == 0);
|
||||
data = Keylist_Data_Delete(list, key);
|
||||
ct_test(pTest, data == NULL);
|
||||
ct_test(pTest, Keylist_Count(list) == 2);
|
||||
|
||||
key = 1;
|
||||
data = Keylist_Data(list, key);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data1) == 0);
|
||||
|
||||
key = 3;
|
||||
data = Keylist_Data(list, key);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data3) == 0);
|
||||
|
||||
/* cleanup */
|
||||
do {
|
||||
data = Keylist_Data_Pop(list);
|
||||
}
|
||||
while (data);
|
||||
|
||||
Keylist_Delete(list);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void testKeyListDataIndex(Test * pTest)
|
||||
{
|
||||
OS_Keylist list;
|
||||
KEY key;
|
||||
int index;
|
||||
char *data1 = "Joshua";
|
||||
char *data2 = "Anna";
|
||||
char *data3 = "Mary";
|
||||
char *data;
|
||||
|
||||
list = Keylist_Create();
|
||||
ct_test(pTest, list != NULL);
|
||||
|
||||
key = 0;
|
||||
index = Keylist_Data_Add(list, key, data1);
|
||||
ct_test(pTest, index == 0);
|
||||
index = Keylist_Data_Add(list, key, data2);
|
||||
ct_test(pTest, index == 0);
|
||||
index = Keylist_Data_Add(list, key, data3);
|
||||
ct_test(pTest, index == 0);
|
||||
|
||||
|
||||
ct_test(pTest, Keylist_Count(list) == 3);
|
||||
|
||||
/* look at the data */
|
||||
data = Keylist_Data_Index(list, 0);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data3) == 0);
|
||||
|
||||
data = Keylist_Data_Index(list, 1);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data2) == 0);
|
||||
|
||||
data = Keylist_Data_Index(list, 2);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data1) == 0);
|
||||
|
||||
/* work the data */
|
||||
data = Keylist_Data_Delete_By_Index(list, 1);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data2) == 0);
|
||||
|
||||
ct_test(pTest, Keylist_Count(list) == 2);
|
||||
|
||||
data = Keylist_Data_Index(list, 0);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data3) == 0);
|
||||
|
||||
data = Keylist_Data_Index(list, 1);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data1) == 0);
|
||||
|
||||
data = Keylist_Data_Delete_By_Index(list, 1);
|
||||
ct_test(pTest, data != NULL);
|
||||
ct_test(pTest, strcmp(data, data1) == 0);
|
||||
|
||||
data = Keylist_Data_Delete_By_Index(list, 1);
|
||||
ct_test(pTest, data == NULL);
|
||||
|
||||
/* cleanup */
|
||||
do {
|
||||
data = Keylist_Data_Pop(list);
|
||||
}
|
||||
while (data);
|
||||
|
||||
Keylist_Delete(list);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* test access of a lot of entries */
|
||||
void testKeyListLarge(Test * pTest)
|
||||
{
|
||||
int data1 = 42;
|
||||
int *data;
|
||||
OS_Keylist list;
|
||||
KEY key;
|
||||
int index;
|
||||
const unsigned num_keys = 1024 * 16;
|
||||
|
||||
list = Keylist_Create();
|
||||
if (!list)
|
||||
return;
|
||||
|
||||
for (key = 0; key < num_keys; key++) {
|
||||
index = Keylist_Data_Add(list, key, &data1);
|
||||
|
||||
}
|
||||
for (key = 0; key < num_keys; key++) {
|
||||
data = Keylist_Data(list, key);
|
||||
ct_test(pTest, *data == data1);
|
||||
}
|
||||
for (index = 0; index < num_keys; index++) {
|
||||
data = Keylist_Data_Index(list, index);
|
||||
ct_test(pTest, *data == data1);
|
||||
}
|
||||
Keylist_Delete(list);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_KEYLIST
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("keylist", NULL);
|
||||
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testKeyListFIFO);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testKeyListFILO);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testKeyListDataKey);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testKeySample);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testKeyListDataIndex);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testKeyListLarge);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_KEYLIST */
|
||||
#endif /* TEST */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,58 @@
|
||||
#include <stdio.h>
|
||||
#include "mstp.h"
|
||||
#include "indtext.h"
|
||||
#include "bacenum.h"
|
||||
|
||||
static INDTEXT_DATA mstp_receive_state_text[] = {
|
||||
{MSTP_RECEIVE_STATE_IDLE, "IDLE"},
|
||||
{MSTP_RECEIVE_STATE_PREAMBLE, "PREAMBLE"},
|
||||
{MSTP_RECEIVE_STATE_HEADER, "HEADER"},
|
||||
{MSTP_RECEIVE_STATE_HEADER_CRC, "HEADER_CRC"},
|
||||
{MSTP_RECEIVE_STATE_DATA, "DATA"},
|
||||
{0, NULL}
|
||||
};
|
||||
|
||||
const char *mstptext_receive_state(int index)
|
||||
{
|
||||
return indtext_by_index_default(mstp_receive_state_text,
|
||||
index, "unknown");
|
||||
}
|
||||
|
||||
static INDTEXT_DATA mstp_master_state_text[] = {
|
||||
{MSTP_MASTER_STATE_INITIALIZE, "INITIALIZE"},
|
||||
{MSTP_MASTER_STATE_IDLE, "IDLE"},
|
||||
{MSTP_MASTER_STATE_USE_TOKEN, "USE_TOKEN"},
|
||||
{MSTP_MASTER_STATE_WAIT_FOR_REPLY, "WAIT_FOR_REPLY"},
|
||||
{MSTP_MASTER_STATE_DONE_WITH_TOKEN, "DONE_WITH_TOKEN"},
|
||||
{MSTP_MASTER_STATE_PASS_TOKEN, "PASS_TOKEN"},
|
||||
{MSTP_MASTER_STATE_NO_TOKEN, "NO_TOKEN"},
|
||||
{MSTP_MASTER_STATE_POLL_FOR_MASTER, "POLL_FOR_MASTER"},
|
||||
{MSTP_MASTER_STATE_ANSWER_DATA_REQUEST, "ANSWER_DATA_REQUEST"},
|
||||
{0, NULL}
|
||||
};
|
||||
|
||||
const char *mstptext_master_state(int index)
|
||||
{
|
||||
return indtext_by_index_default(mstp_master_state_text,
|
||||
index, "unknown");
|
||||
}
|
||||
|
||||
static INDTEXT_DATA mstp_frame_type_text[] = {
|
||||
{FRAME_TYPE_TOKEN, "TOKEN"},
|
||||
{FRAME_TYPE_POLL_FOR_MASTER, "POLL_FOR_MASTER"},
|
||||
{FRAME_TYPE_REPLY_TO_POLL_FOR_MASTER, "REPLY_TO_POLL_FOR_MASTER"},
|
||||
{FRAME_TYPE_TEST_REQUEST, "TEST_REQUEST"},
|
||||
{FRAME_TYPE_TEST_RESPONSE, "TEST_RESPONSE"},
|
||||
{FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY, "BACNET_DATA_EXPECTING_REPLY"},
|
||||
{FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY, "BACNET_DATA_NOT_EXPECTING_REPLY"},
|
||||
{FRAME_TYPE_REPLY_POSTPONED, "REPLY_POSTPONED"},
|
||||
{0, NULL}
|
||||
};
|
||||
|
||||
const char *mstptext_frame_type(int index)
|
||||
{
|
||||
return indtext_by_index_split_default(mstp_frame_type_text,
|
||||
index,
|
||||
FRAME_TYPE_PROPRIETARY_MIN,
|
||||
"UNKNOWN", "PROPRIETARY");
|
||||
}
|
||||
@@ -0,0 +1,545 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "bacdef.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacint.h"
|
||||
#include "bacenum.h"
|
||||
#include "bits.h"
|
||||
#include "npdu.h"
|
||||
#include "apdu.h"
|
||||
|
||||
void npdu_copy_data(BACNET_NPDU_DATA * dest, BACNET_NPDU_DATA * src)
|
||||
{
|
||||
if (dest && src) {
|
||||
dest->protocol_version = src->protocol_version;
|
||||
dest->data_expecting_reply = src->data_expecting_reply;
|
||||
dest->network_layer_message = src->network_layer_message;
|
||||
dest->priority = src->priority;
|
||||
dest->network_message_type = src->network_message_type;
|
||||
dest->vendor_id = src->vendor_id;
|
||||
dest->hop_count = src->hop_count;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
The following ICI parameters are exchanged with the
|
||||
various service primitives across an API:
|
||||
|
||||
'destination_address' (DA): the address of the device(s)
|
||||
intended to receive the service primitive. Its format (device name,
|
||||
network address, etc.) is a local matter. This address
|
||||
may also be a multicast, local broadcast or global broadcast type.
|
||||
|
||||
'source_address' (SA): the address of the device from which
|
||||
the service primitive was received. Its format (device name,
|
||||
network address, etc.) is a local matter.
|
||||
|
||||
'network_priority' (NP): a four-level network priority parameter
|
||||
described in 6.2.2.
|
||||
|
||||
'data_expecting_reply' (DER): a Boolean parameter that indicates
|
||||
whether (TRUE) or not (FALSE) a reply service primitive
|
||||
is expected for the service being issued.
|
||||
|
||||
|
||||
Table 5-1. Applicability of ICI parameters for abstract service primitives
|
||||
Service Primitive DA SA NP DER
|
||||
CONF_SERV.request Yes No Yes Yes
|
||||
CONF_SERV.indication Yes Yes Yes Yes
|
||||
CONF_SERV.response Yes No Yes Yes
|
||||
CONF_SERV.confirm Yes Yes Yes No
|
||||
UNCONF_SERV.request Yes No Yes No
|
||||
UNCONF_SERV.indication Yes Yes Yes No
|
||||
REJECT.request Yes No Yes No
|
||||
REJECT.indication Yes Yes Yes No
|
||||
SEGMENT_ACK.request Yes No Yes No
|
||||
SEGMENT_ACK.indication Yes Yes Yes No
|
||||
ABORT.request Yes No Yes No
|
||||
ABORT.indication Yes Yes Yes No
|
||||
*/
|
||||
|
||||
int npdu_encode_pdu(uint8_t * npdu,
|
||||
BACNET_ADDRESS * dest,
|
||||
BACNET_ADDRESS * src, BACNET_NPDU_DATA * npdu_data)
|
||||
{
|
||||
int len = 0; /* return value - number of octets loaded in this function */
|
||||
int i = 0; /* counter */
|
||||
|
||||
|
||||
if (npdu && npdu_data) {
|
||||
/* protocol version */
|
||||
npdu[0] = npdu_data->protocol_version;
|
||||
/* initialize the control octet */
|
||||
npdu[1] = 0;
|
||||
/* Bit 7: 1 indicates that the NSDU conveys a network layer message. */
|
||||
/* Message Type field is present. */
|
||||
/* 0 indicates that the NSDU contains a BACnet APDU. */
|
||||
/* Message Type field is absent. */
|
||||
if (npdu_data->network_layer_message)
|
||||
npdu[1] |= BIT7;
|
||||
/*Bit 6: Reserved. Shall be zero. */
|
||||
/*Bit 5: Destination specifier where: */
|
||||
/* 0 = DNET, DLEN, DADR, and Hop Count absent */
|
||||
/* 1 = DNET, DLEN, and Hop Count present */
|
||||
/* DLEN = 0 denotes broadcast MAC DADR and DADR field is absent */
|
||||
/* DLEN > 0 specifies length of DADR field */
|
||||
if (dest && dest->net)
|
||||
npdu[1] |= BIT5;
|
||||
/* Bit 4: Reserved. Shall be zero. */
|
||||
/* Bit 3: Source specifier where: */
|
||||
/* 0 = SNET, SLEN, and SADR absent */
|
||||
/* 1 = SNET, SLEN, and SADR present */
|
||||
/* SLEN = 0 Invalid */
|
||||
/* SLEN > 0 specifies length of SADR field */
|
||||
if (src && src->net)
|
||||
npdu[1] |= BIT3;
|
||||
/* Bit 2: The value of this bit corresponds to the */
|
||||
/* data_expecting_reply parameter in the N-UNITDATA primitives. */
|
||||
/* 1 indicates that a BACnet-Confirmed-Request-PDU, */
|
||||
/* a segment of a BACnet-ComplexACK-PDU, */
|
||||
/* or a network layer message expecting a reply is present. */
|
||||
/* 0 indicates that other than a BACnet-Confirmed-Request-PDU, */
|
||||
/* a segment of a BACnet-ComplexACK-PDU, */
|
||||
/* or a network layer message expecting a reply is present. */
|
||||
if (npdu_data->data_expecting_reply)
|
||||
npdu[1] |= BIT2;
|
||||
/* Bits 1,0: Network priority where: */
|
||||
/* B'11' = Life Safety message */
|
||||
/* B'10' = Critical Equipment message */
|
||||
/* B'01' = Urgent message */
|
||||
/* B'00' = Normal message */
|
||||
npdu[1] |= (npdu_data->priority & 0x03);
|
||||
len = 2;
|
||||
if (dest && dest->net) {
|
||||
len += encode_unsigned16(&npdu[len], dest->net);
|
||||
npdu[len++] = dest->len;
|
||||
/* DLEN = 0 denotes broadcast MAC DADR and DADR field is absent */
|
||||
/* DLEN > 0 specifies length of DADR field */
|
||||
if (dest->len) {
|
||||
for (i = 0; i < dest->len; i++) {
|
||||
npdu[len++] = dest->adr[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (src && src->net) {
|
||||
len += encode_unsigned16(&npdu[len], src->net);
|
||||
npdu[len++] = src->len;
|
||||
/* SLEN = 0 denotes broadcast MAC SADR and SADR field is absent */
|
||||
/* SLEN > 0 specifies length of SADR field */
|
||||
if (src->len) {
|
||||
for (i = 0; i < src->len; i++) {
|
||||
npdu[len++] = src->adr[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
/* The Hop Count field shall be present only if the message is */
|
||||
/* destined for a remote network, i.e., if DNET is present. */
|
||||
/* This is a one-octet field that is initialized to a value of 0xff. */
|
||||
if (dest && dest->net) {
|
||||
npdu[len] = 0xFF;
|
||||
len++;
|
||||
}
|
||||
if (npdu_data->network_layer_message) {
|
||||
npdu[len] = npdu_data->network_message_type;
|
||||
len++;
|
||||
/* Message Type field contains a value in the range 0x80 - 0xFF, */
|
||||
/* then a Vendor ID field shall be present */
|
||||
if (npdu_data->network_message_type >= 0x80)
|
||||
len += encode_unsigned16(&npdu[len], npdu_data->vendor_id);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Configure the NPDU portion of the packet for an APDU */
|
||||
/* This function does not handle the network messages, just APDUs. */
|
||||
/* From BACnet 5.1:
|
||||
Applicability of ICI parameters for abstract service primitives
|
||||
Service Primitive DA SA NP DER
|
||||
----------------- --- --- --- ---
|
||||
CONF_SERV.request Yes No Yes Yes
|
||||
CONF_SERV.indication Yes Yes Yes Yes
|
||||
CONF_SERV.response Yes No Yes Yes
|
||||
CONF_SERV.confirm Yes Yes Yes No
|
||||
UNCONF_SERV.request Yes No Yes No
|
||||
UNCONF_SERV.indication Yes Yes Yes No
|
||||
REJECT.request Yes No Yes No
|
||||
REJECT.indication Yes Yes Yes No
|
||||
SEGMENT_ACK.request Yes No Yes No
|
||||
SEGMENT_ACK.indication Yes Yes Yes No
|
||||
ABORT.request Yes No Yes No
|
||||
ABORT.indication Yes Yes Yes No
|
||||
|
||||
Where:
|
||||
'destination_address' (DA): the address of the device(s) intended
|
||||
to receive the service primitive. Its format (device name,
|
||||
network address, etc.) is a local matter. This address may
|
||||
also be a multicast, local broadcast or global broadcast type.
|
||||
'source_address' (SA): the address of the device from which
|
||||
the service primitive was received. Its format (device name,
|
||||
network address, etc.) is a local matter.
|
||||
'network_priority' (NP): a four-level network priority parameter
|
||||
described in 6.2.2.
|
||||
'data_expecting_reply' (DER): a Boolean parameter that indicates
|
||||
whether (TRUE) or not (FALSE) a reply service primitive
|
||||
is expected for the service being issued.
|
||||
*/
|
||||
void npdu_encode_npdu_data(BACNET_NPDU_DATA * npdu_data,
|
||||
bool data_expecting_reply, BACNET_MESSAGE_PRIORITY priority)
|
||||
{
|
||||
if (npdu_data) {
|
||||
npdu_data->data_expecting_reply = data_expecting_reply;
|
||||
npdu_data->protocol_version = BACNET_PROTOCOL_VERSION;
|
||||
npdu_data->network_layer_message = false; /* false if APDU */
|
||||
npdu_data->network_message_type = NETWORK_MESSAGE_INVALID; /* optional */
|
||||
npdu_data->vendor_id = 0; /* optional, if net message type is > 0x80 */
|
||||
npdu_data->priority = priority;
|
||||
npdu_data->hop_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int npdu_decode(uint8_t * npdu,
|
||||
BACNET_ADDRESS * dest,
|
||||
BACNET_ADDRESS * src, BACNET_NPDU_DATA * npdu_data)
|
||||
{
|
||||
int len = 0; /* return value - number of octets loaded in this function */
|
||||
int i = 0; /* counter */
|
||||
uint16_t src_net = 0;
|
||||
uint16_t dest_net = 0;
|
||||
uint8_t address_len = 0;
|
||||
uint8_t mac_octet = 0;
|
||||
|
||||
if (npdu && npdu_data) {
|
||||
/* Protocol Version */
|
||||
npdu_data->protocol_version = npdu[0];
|
||||
/* control octet */
|
||||
/* Bit 7: 1 indicates that the NSDU conveys a network layer message. */
|
||||
/* Message Type field is present. */
|
||||
/* 0 indicates that the NSDU contains a BACnet APDU. */
|
||||
/* Message Type field is absent. */
|
||||
npdu_data->network_layer_message = (npdu[1] & BIT7) ? true : false;
|
||||
/*Bit 6: Reserved. Shall be zero. */
|
||||
/* Bit 4: Reserved. Shall be zero. */
|
||||
/* Bit 2: The value of this bit corresponds to data expecting reply */
|
||||
/* parameter in the N-UNITDATA primitives. */
|
||||
/* 1 indicates that a BACnet-Confirmed-Request-PDU, */
|
||||
/* a segment of a BACnet-ComplexACK-PDU, */
|
||||
/* or a network layer message expecting a reply is present. */
|
||||
/* 0 indicates that other than a BACnet-Confirmed-Request-PDU, */
|
||||
/* a segment of a BACnet-ComplexACK-PDU, */
|
||||
/* or a network layer message expecting a reply is present. */
|
||||
npdu_data->data_expecting_reply = (npdu[1] & BIT2) ? true : false;
|
||||
/* Bits 1,0: Network priority where: */
|
||||
/* B'11' = Life Safety message */
|
||||
/* B'10' = Critical Equipment message */
|
||||
/* B'01' = Urgent message */
|
||||
/* B'00' = Normal message */
|
||||
npdu_data->priority = (BACNET_MESSAGE_PRIORITY)(npdu[1] & 0x03);
|
||||
/* set the offset to where the optional stuff starts */
|
||||
len = 2;
|
||||
/*Bit 5: Destination specifier where: */
|
||||
/* 0 = DNET, DLEN, DADR, and Hop Count absent */
|
||||
/* 1 = DNET, DLEN, and Hop Count present */
|
||||
/* DLEN = 0 denotes broadcast MAC DADR and DADR field is absent */
|
||||
/* DLEN > 0 specifies length of DADR field */
|
||||
if (npdu[1] & BIT5) {
|
||||
len += decode_unsigned16(&npdu[len], &dest_net);
|
||||
/* DLEN = 0 denotes broadcast MAC DADR and DADR field is absent */
|
||||
/* DLEN > 0 specifies length of DADR field */
|
||||
address_len = npdu[len++];
|
||||
if (dest) {
|
||||
dest->net = dest_net;
|
||||
dest->len = address_len;
|
||||
}
|
||||
if (address_len) {
|
||||
for (i = 0; i < address_len; i++) {
|
||||
mac_octet = npdu[len++];
|
||||
if (dest)
|
||||
dest->adr[i] = mac_octet;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* zero out the destination address */
|
||||
else if (dest) {
|
||||
dest->net = 0;
|
||||
dest->len = 0;
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
dest->adr[i] = 0;
|
||||
}
|
||||
}
|
||||
/* Bit 3: Source specifier where: */
|
||||
/* 0 = SNET, SLEN, and SADR absent */
|
||||
/* 1 = SNET, SLEN, and SADR present */
|
||||
/* SLEN = 0 Invalid */
|
||||
/* SLEN > 0 specifies length of SADR field */
|
||||
if (npdu[1] & BIT3) {
|
||||
len += decode_unsigned16(&npdu[len], &src_net);
|
||||
/* SLEN = 0 denotes broadcast MAC SADR and SADR field is absent */
|
||||
/* SLEN > 0 specifies length of SADR field */
|
||||
address_len = npdu[len++];
|
||||
if (src) {
|
||||
src->net = src_net;
|
||||
src->len = address_len;
|
||||
}
|
||||
if (address_len) {
|
||||
for (i = 0; i < address_len; i++) {
|
||||
mac_octet = npdu[len++];
|
||||
if (src)
|
||||
src->adr[i] = mac_octet;
|
||||
}
|
||||
}
|
||||
} else if (src) {
|
||||
src->net = 0;
|
||||
src->len = 0;
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
src->adr[i] = 0;
|
||||
}
|
||||
}
|
||||
/* The Hop Count field shall be present only if the message is */
|
||||
/* destined for a remote network, i.e., if DNET is present. */
|
||||
/* This is a one-octet field that is initialized to a value of 0xff. */
|
||||
if (dest_net)
|
||||
npdu_data->hop_count = npdu[len++];
|
||||
else
|
||||
npdu_data->hop_count = 0;
|
||||
/* Indicates that the NSDU conveys a network layer message. */
|
||||
/* Message Type field is present. */
|
||||
if (npdu_data->network_layer_message) {
|
||||
npdu_data->network_message_type = (BACNET_NETWORK_MESSAGE_TYPE)npdu[len++];
|
||||
/* Message Type field contains a value in the range 0x80 - 0xFF, */
|
||||
/* then a Vendor ID field shall be present */
|
||||
if (npdu_data->network_message_type >= 0x80)
|
||||
len +=
|
||||
decode_unsigned16(&npdu[len], &npdu_data->vendor_id);
|
||||
} else {
|
||||
/* FIXME: another value for this? */
|
||||
npdu_data->network_message_type = NETWORK_MESSAGE_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void npdu_handler(BACNET_ADDRESS * src, /* source address */
|
||||
uint8_t * pdu, /* PDU data */
|
||||
uint16_t pdu_len)
|
||||
{ /* length PDU */
|
||||
int apdu_offset = 0;
|
||||
BACNET_ADDRESS dest = { 0 };
|
||||
BACNET_NPDU_DATA npdu_data = { 0 };
|
||||
|
||||
apdu_offset = npdu_decode(&pdu[0], &dest, src, &npdu_data);
|
||||
if (npdu_data.network_layer_message) {
|
||||
/*FIXME: network layer message received! Handle it! */
|
||||
} else if ((apdu_offset > 0) && (apdu_offset <= pdu_len)) {
|
||||
/* only handle the version that we know how to handle */
|
||||
if (npdu_data.protocol_version == BACNET_PROTOCOL_VERSION)
|
||||
apdu_handler(src, &pdu[apdu_offset],
|
||||
(uint16_t) (pdu_len - apdu_offset));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testNPDU2(Test * pTest)
|
||||
{
|
||||
uint8_t pdu[480] = { 0 };
|
||||
BACNET_ADDRESS dest = { 0 };
|
||||
BACNET_ADDRESS src = { 0 };
|
||||
BACNET_ADDRESS npdu_dest = { 0 };
|
||||
BACNET_ADDRESS npdu_src = { 0 };
|
||||
int len = 0;
|
||||
bool data_expecting_reply = true;
|
||||
BACNET_MESSAGE_PRIORITY priority = MESSAGE_PRIORITY_NORMAL;
|
||||
BACNET_NPDU_DATA npdu_data = { 0 };
|
||||
int i = 0; /* counter */
|
||||
int npdu_len = 0;
|
||||
bool network_layer_message = false; /* false if APDU */
|
||||
BACNET_NETWORK_MESSAGE_TYPE network_message_type = 0; /* optional */
|
||||
uint16_t vendor_id = 0; /* optional, if net message type is > 0x80 */
|
||||
|
||||
dest.mac_len = 6;
|
||||
for (i = 0; i < dest.mac_len; i++) {
|
||||
dest.mac[i] = i;
|
||||
}
|
||||
/* DNET,DLEN,DADR */
|
||||
dest.net = 1;
|
||||
dest.len = 6;
|
||||
for (i = 0; i < dest.len; i++) {
|
||||
dest.adr[i] = i * 10;
|
||||
}
|
||||
src.mac_len = 1;
|
||||
for (i = 0; i < src.mac_len; i++) {
|
||||
src.mac[i] = 0x80;
|
||||
}
|
||||
/* SNET,SLEN,SADR */
|
||||
src.net = 2;
|
||||
src.len = 1;
|
||||
for (i = 0; i < src.len; i++) {
|
||||
src.adr[i] = 0x40;
|
||||
}
|
||||
npdu_encode_npdu_data(&npdu_data, true, priority);
|
||||
len = npdu_encode_pdu(&pdu[0], &dest, &src, &npdu_data);
|
||||
ct_test(pTest, len != 0);
|
||||
/* can we get the info back? */
|
||||
npdu_len = npdu_decode(&pdu[0], &npdu_dest, &npdu_src, &npdu_data);
|
||||
ct_test(pTest, npdu_len != 0);
|
||||
ct_test(pTest, npdu_data.data_expecting_reply == data_expecting_reply);
|
||||
ct_test(pTest,
|
||||
npdu_data.network_layer_message == network_layer_message);
|
||||
if (npdu_data.network_layer_message) {
|
||||
ct_test(pTest,
|
||||
npdu_data.network_message_type == network_message_type);
|
||||
}
|
||||
ct_test(pTest, npdu_data.vendor_id == vendor_id);
|
||||
ct_test(pTest, npdu_data.priority == priority);
|
||||
/* DNET,DLEN,DADR */
|
||||
ct_test(pTest, npdu_dest.net == dest.net);
|
||||
ct_test(pTest, npdu_dest.len == dest.len);
|
||||
for (i = 0; i < dest.len; i++) {
|
||||
ct_test(pTest, npdu_dest.adr[i] == dest.adr[i]);
|
||||
}
|
||||
/* SNET,SLEN,SADR */
|
||||
ct_test(pTest, npdu_src.net == src.net);
|
||||
ct_test(pTest, npdu_src.len == src.len);
|
||||
for (i = 0; i < src.len; i++) {
|
||||
ct_test(pTest, npdu_src.adr[i] == src.adr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void testNPDU1(Test * pTest)
|
||||
{
|
||||
uint8_t pdu[480] = { 0 };
|
||||
BACNET_ADDRESS dest = { 0 };
|
||||
BACNET_ADDRESS src = { 0 };
|
||||
BACNET_ADDRESS npdu_dest = { 0 };
|
||||
BACNET_ADDRESS npdu_src = { 0 };
|
||||
int len = 0;
|
||||
bool data_expecting_reply = false;
|
||||
BACNET_MESSAGE_PRIORITY priority = MESSAGE_PRIORITY_NORMAL;
|
||||
BACNET_NPDU_DATA npdu_data = { 0 };
|
||||
int i = 0; /* counter */
|
||||
int npdu_len = 0;
|
||||
bool network_layer_message = false; /* false if APDU */
|
||||
BACNET_NETWORK_MESSAGE_TYPE network_message_type = 0; /* optional */
|
||||
uint16_t vendor_id = 0; /* optional, if net message type is > 0x80 */
|
||||
|
||||
/* mac_len = 0 if global address */
|
||||
dest.mac_len = 0;
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
dest.mac[i] = 0;
|
||||
}
|
||||
/* DNET,DLEN,DADR */
|
||||
dest.net = 0;
|
||||
dest.len = 0;
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
dest.adr[i] = 0;
|
||||
}
|
||||
src.mac_len = 0;
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
src.mac[i] = 0;
|
||||
}
|
||||
/* SNET,SLEN,SADR */
|
||||
src.net = 0;
|
||||
src.len = 0;
|
||||
for (i = 0; i < MAX_MAC_LEN; i++) {
|
||||
src.adr[i] = 0;
|
||||
}
|
||||
npdu_encode_npdu_data(&npdu_data, false, priority);
|
||||
len = npdu_encode_pdu(&pdu[0], &dest, &src, &npdu_data);
|
||||
ct_test(pTest, len != 0);
|
||||
/* can we get the info back? */
|
||||
npdu_len = npdu_decode(&pdu[0], &npdu_dest, &npdu_src, &npdu_data);
|
||||
ct_test(pTest, npdu_len != 0);
|
||||
ct_test(pTest, npdu_data.data_expecting_reply == data_expecting_reply);
|
||||
ct_test(pTest,
|
||||
npdu_data.network_layer_message == network_layer_message);
|
||||
if (npdu_data.network_layer_message) {
|
||||
ct_test(pTest,
|
||||
npdu_data.network_message_type == network_message_type);
|
||||
}
|
||||
ct_test(pTest, npdu_data.vendor_id == vendor_id);
|
||||
ct_test(pTest, npdu_data.priority == priority);
|
||||
ct_test(pTest, npdu_dest.mac_len == src.mac_len);
|
||||
ct_test(pTest, npdu_src.mac_len == dest.mac_len);
|
||||
}
|
||||
|
||||
#ifdef TEST_NPDU
|
||||
/* dummy stub for testing */
|
||||
void tsm_free_invoke_id(uint8_t invokeID)
|
||||
{
|
||||
(void) invokeID;
|
||||
}
|
||||
|
||||
void iam_handler(uint8_t * service_request,
|
||||
uint16_t service_len, BACNET_ADDRESS * src)
|
||||
{
|
||||
(void) service_request;
|
||||
(void) service_len;
|
||||
(void) src;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet NPDU", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testNPDU1);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testNPDU2);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_NPDU */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,182 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "rd.h"
|
||||
|
||||
/* encode service */
|
||||
int rd_encode_apdu(uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_REINITIALIZED_STATE state, BACNET_CHARACTER_STRING * password)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_REINITIALIZE_DEVICE;
|
||||
apdu_len = 4;
|
||||
len = encode_context_enumerated(&apdu[apdu_len], 0, state);
|
||||
apdu_len += len;
|
||||
/* optional password */
|
||||
if (password) {
|
||||
/* FIXME: must be at least 1 character, limited to 20 characters */
|
||||
len = encode_context_character_string(&apdu[apdu_len], 1,
|
||||
password);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int rd_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_REINITIALIZED_STATE * state, BACNET_CHARACTER_STRING * password)
|
||||
{
|
||||
unsigned len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
int value = 0;
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len) {
|
||||
/* Tag 0: reinitializedStateOfDevice */
|
||||
if (!decode_is_context_tag(&apdu[len], 0))
|
||||
return -1;
|
||||
len += decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len += decode_enumerated(&apdu[len], len_value_type, &value);
|
||||
if (state)
|
||||
*state = (BACNET_REINITIALIZED_STATE)value;
|
||||
/* Tag 1: password - optional */
|
||||
if (len < apdu_len) {
|
||||
if (!decode_is_context_tag(&apdu[len], 1))
|
||||
return -1;
|
||||
len += decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
len +=
|
||||
decode_character_string(&apdu[len], len_value_type,
|
||||
password);
|
||||
}
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
int rd_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id,
|
||||
BACNET_REINITIALIZED_STATE * state, BACNET_CHARACTER_STRING * password)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_REINITIALIZE_DEVICE)
|
||||
return -1;
|
||||
offset = 4;
|
||||
|
||||
if (apdu_len > offset) {
|
||||
len = rd_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, state, password);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void test_ReinitializeDevice(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 128;
|
||||
uint8_t test_invoke_id = 0;
|
||||
BACNET_REINITIALIZED_STATE state;
|
||||
BACNET_REINITIALIZED_STATE test_state;
|
||||
BACNET_CHARACTER_STRING password;
|
||||
BACNET_CHARACTER_STRING test_password;
|
||||
|
||||
state = BACNET_REINIT_WARMSTART;
|
||||
characterstring_init_ansi(&password, "John 3:16");
|
||||
len = rd_encode_apdu(&apdu[0], invoke_id, state, &password);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = rd_decode_apdu(&apdu[0],
|
||||
apdu_len, &test_invoke_id, &test_state, &test_password);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, test_state == state);
|
||||
ct_test(pTest, characterstring_same(&test_password, &password));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_REINITIALIZE_DEVICE
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet ReinitializeDevice", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, test_ReinitializeDevice);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_REINITIALIZE_DEVICE */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,168 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
|
||||
/* encode service */
|
||||
int reject_encode_apdu(uint8_t * apdu,
|
||||
uint8_t invoke_id, uint8_t reject_reason)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_REJECT;
|
||||
apdu[1] = invoke_id;
|
||||
apdu[2] = reject_reason;
|
||||
apdu_len = 3;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int reject_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, uint8_t * invoke_id, uint8_t * reject_reason)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (apdu_len) {
|
||||
if (invoke_id)
|
||||
*invoke_id = apdu[0];
|
||||
if (reject_reason)
|
||||
*reject_reason = apdu[1];
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
/* decode the whole APDU - mainly used for unit testing */
|
||||
int reject_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, uint8_t * invoke_id, uint8_t * reject_reason)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu_len) {
|
||||
if (apdu[0] != PDU_TYPE_REJECT)
|
||||
return -1;
|
||||
if (apdu_len > 1) {
|
||||
len = reject_decode_service_request(&apdu[1],
|
||||
apdu_len - 1, invoke_id, reject_reason);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void testReject(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 0;
|
||||
uint8_t reject_reason = 0;
|
||||
uint8_t test_invoke_id = 0;
|
||||
uint8_t test_reject_reason = 0;
|
||||
|
||||
len = reject_encode_apdu(&apdu[0], invoke_id, reject_reason);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = reject_decode_apdu(&apdu[0],
|
||||
apdu_len, &test_invoke_id, &test_reject_reason);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, test_reject_reason == reject_reason);
|
||||
|
||||
/* change type to get negative response */
|
||||
apdu[0] = PDU_TYPE_ABORT;
|
||||
len = reject_decode_apdu(&apdu[0],
|
||||
apdu_len, &test_invoke_id, &test_reject_reason);
|
||||
ct_test(pTest, len == -1);
|
||||
|
||||
/* test NULL APDU */
|
||||
len = reject_decode_apdu(NULL,
|
||||
apdu_len, &test_invoke_id, &test_reject_reason);
|
||||
ct_test(pTest, len == -1);
|
||||
|
||||
/* force a zero length */
|
||||
len = reject_decode_apdu(&apdu[0],
|
||||
0, &test_invoke_id, &test_reject_reason);
|
||||
ct_test(pTest, len == 0);
|
||||
|
||||
|
||||
/* check them all... */
|
||||
for (invoke_id = 0; invoke_id < 255; invoke_id++) {
|
||||
for (reject_reason = 0; reject_reason < 255; reject_reason++) {
|
||||
len = reject_encode_apdu(&apdu[0], invoke_id, reject_reason);
|
||||
apdu_len = len;
|
||||
ct_test(pTest, len != 0);
|
||||
len = reject_decode_apdu(&apdu[0],
|
||||
apdu_len, &test_invoke_id, &test_reject_reason);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, test_reject_reason == reject_reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TEST_REJECT
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Reject", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testReject);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_REJECT */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,273 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2004 by Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307
|
||||
USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
/* Functional Description: Generic ring buffer library for deeply
|
||||
embedded system. See the unit tests for usage examples. */
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "ringbuf.h"
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Returns the empty/full status of the ring buffer
|
||||
* RETURN: true if the ring buffer is empty, false if it is not.
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
bool Ringbuf_Empty(RING_BUFFER const *b)
|
||||
{
|
||||
return (b->count == 0);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Looks at the data from the head of the list without removing it
|
||||
* RETURN: pointer to the data, or NULL if nothing in the list
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
char *Ringbuf_Get_Front(RING_BUFFER const *b)
|
||||
{
|
||||
return (b->count ? &(b->data[b->head * b->element_size]) : NULL);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Gets the data from the front of the list, and removes it
|
||||
* RETURN: pointer to the data, or NULL if nothing in the list
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
char *Ringbuf_Pop_Front(RING_BUFFER * b)
|
||||
{
|
||||
char *data = NULL; /* return value */
|
||||
|
||||
if (b->count) {
|
||||
data = &(b->data[b->head * b->element_size]);
|
||||
b->head++;
|
||||
if (b->head >= b->element_count)
|
||||
b->head = 0;
|
||||
b->count--;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Adds an element of data to the ring buffer
|
||||
* RETURN: true on succesful add, false if not added
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
bool Ringbuf_Put(RING_BUFFER * b, /* ring buffer structure */
|
||||
char *data_element)
|
||||
{ /* one element to add to the ring */
|
||||
bool status = false; /* return value */
|
||||
unsigned offset = 0; /* offset into array of data */
|
||||
char *ring_data = NULL; /* used to help point ring data */
|
||||
unsigned i; /* loop counter */
|
||||
|
||||
if (b && data_element) {
|
||||
/* limit the amount of data that we accept */
|
||||
if (b->count < b->element_count) {
|
||||
offset = b->head + b->count;
|
||||
if (offset >= b->element_count)
|
||||
offset -= b->element_count;
|
||||
ring_data = b->data + offset * b->element_size;
|
||||
for (i = 0; i < b->element_size; i++) {
|
||||
ring_data[i] = data_element[i];
|
||||
}
|
||||
b->count++;
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* DESCRIPTION: Configures the ring buffer
|
||||
* RETURN: none
|
||||
* ALGORITHM: none
|
||||
* NOTES: none
|
||||
*****************************************************************************/
|
||||
void Ringbuf_Init(RING_BUFFER * b, /* ring buffer structure */
|
||||
char *data, /* data block or array of data */
|
||||
unsigned element_size, /* size of one element in the data block */
|
||||
unsigned element_count)
|
||||
{ /* number of elements in the data block */
|
||||
b->head = 0;
|
||||
b->count = 0;
|
||||
b->data = data;
|
||||
b->element_size = element_size;
|
||||
b->element_count = element_count;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ctest.h"
|
||||
|
||||
/* test the FIFO */
|
||||
#define RING_BUFFER_DATA_SIZE 5
|
||||
#define RING_BUFFER_COUNT 16
|
||||
void testRingBuf(Test * pTest)
|
||||
{
|
||||
RING_BUFFER test_buffer;
|
||||
char data_store[RING_BUFFER_DATA_SIZE * RING_BUFFER_COUNT];
|
||||
char data[RING_BUFFER_DATA_SIZE];
|
||||
char *test_data;
|
||||
unsigned index;
|
||||
unsigned data_index;
|
||||
unsigned count;
|
||||
unsigned dummy;
|
||||
bool status;
|
||||
|
||||
Ringbuf_Init(&test_buffer, data_store, RING_BUFFER_DATA_SIZE,
|
||||
RING_BUFFER_COUNT);
|
||||
ct_test(pTest, Ringbuf_Empty(&test_buffer));
|
||||
|
||||
for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; data_index++) {
|
||||
data[data_index] = data_index;
|
||||
}
|
||||
status = Ringbuf_Put(&test_buffer, data);
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, !Ringbuf_Empty(&test_buffer));
|
||||
|
||||
test_data = Ringbuf_Get_Front(&test_buffer);
|
||||
for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == data[data_index]);
|
||||
}
|
||||
ct_test(pTest, !Ringbuf_Empty(&test_buffer));
|
||||
|
||||
test_data = Ringbuf_Pop_Front(&test_buffer);
|
||||
for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE; data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == data[data_index]);
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Empty(&test_buffer));
|
||||
|
||||
/* fill to max */
|
||||
for (index = 0; index < RING_BUFFER_COUNT; index++) {
|
||||
for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE;
|
||||
data_index++) {
|
||||
data[data_index] = index;
|
||||
}
|
||||
status = Ringbuf_Put(&test_buffer, data);
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, !Ringbuf_Empty(&test_buffer));
|
||||
}
|
||||
/* verify actions on full buffer */
|
||||
for (index = 0; index < RING_BUFFER_COUNT; index++) {
|
||||
for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE;
|
||||
data_index++) {
|
||||
data[data_index] = index;
|
||||
}
|
||||
status = Ringbuf_Put(&test_buffer, data);
|
||||
ct_test(pTest, status == false);
|
||||
ct_test(pTest, !Ringbuf_Empty(&test_buffer));
|
||||
}
|
||||
|
||||
/* check buffer full */
|
||||
for (index = 0; index < RING_BUFFER_COUNT; index++) {
|
||||
test_data = Ringbuf_Get_Front(&test_buffer);
|
||||
for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE;
|
||||
data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == index);
|
||||
}
|
||||
|
||||
test_data = Ringbuf_Pop_Front(&test_buffer);
|
||||
for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE;
|
||||
data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == index);
|
||||
}
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Empty(&test_buffer));
|
||||
|
||||
/* test the ring around the buffer */
|
||||
for (index = 0; index < RING_BUFFER_COUNT; index++) {
|
||||
for (count = 1; count < 4; count++) {
|
||||
dummy = index * count;
|
||||
for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE;
|
||||
data_index++) {
|
||||
data[data_index] = dummy;
|
||||
}
|
||||
status = Ringbuf_Put(&test_buffer, data);
|
||||
ct_test(pTest, status == true);
|
||||
}
|
||||
|
||||
for (count = 1; count < 4; count++) {
|
||||
dummy = index * count;
|
||||
test_data = Ringbuf_Get_Front(&test_buffer);
|
||||
for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE;
|
||||
data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == dummy);
|
||||
}
|
||||
|
||||
test_data = Ringbuf_Pop_Front(&test_buffer);
|
||||
for (data_index = 0; data_index < RING_BUFFER_DATA_SIZE;
|
||||
data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == dummy);
|
||||
}
|
||||
}
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Empty(&test_buffer));
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_RINGBUF
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("ringbuf", NULL);
|
||||
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testRingBuf);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,341 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "rp.h"
|
||||
|
||||
/* encode service */
|
||||
int rp_encode_apdu(uint8_t * apdu,
|
||||
uint8_t invoke_id, BACNET_READ_PROPERTY_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_READ_PROPERTY; /* service choice */
|
||||
apdu_len = 4;
|
||||
len = encode_context_object_id(&apdu[apdu_len], 0,
|
||||
data->object_type, data->object_instance);
|
||||
apdu_len += len;
|
||||
len = encode_context_enumerated(&apdu[apdu_len], 1,
|
||||
data->object_property);
|
||||
apdu_len += len;
|
||||
/* optional array index */
|
||||
if (data->array_index != BACNET_ARRAY_ALL) {
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 2,
|
||||
data->array_index);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int rp_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_READ_PROPERTY_DATA * data)
|
||||
{
|
||||
unsigned len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
int type = 0; /* for decoding */
|
||||
int property = 0; /* for decoding */
|
||||
uint32_t array_value = 0; /* for decoding */
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && data) {
|
||||
/* Tag 0: Object ID */
|
||||
if (!decode_is_context_tag(&apdu[len++], 0))
|
||||
return -1;
|
||||
len += decode_object_id(&apdu[len], &type, &data->object_instance);
|
||||
data->object_type = (BACNET_OBJECT_TYPE)type;
|
||||
/* Tag 1: Property ID */
|
||||
len += decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
if (tag_number != 1)
|
||||
return -1;
|
||||
len += decode_enumerated(&apdu[len], len_value_type, &property);
|
||||
data->object_property = (BACNET_PROPERTY_ID)property;
|
||||
/* Tag 2: Optional Array Index */
|
||||
if (len < apdu_len) {
|
||||
len += decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
if (tag_number == 2) {
|
||||
len += decode_unsigned(&apdu[len], len_value_type,
|
||||
&array_value);
|
||||
data->array_index = array_value;
|
||||
} else
|
||||
data->array_index = BACNET_ARRAY_ALL;
|
||||
} else
|
||||
data->array_index = BACNET_ARRAY_ALL;
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
}
|
||||
|
||||
int rp_ack_encode_apdu(uint8_t * apdu,
|
||||
uint8_t invoke_id, BACNET_READ_PROPERTY_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_COMPLEX_ACK; /* complex ACK service */
|
||||
apdu[1] = invoke_id; /* original invoke id from request */
|
||||
apdu[2] = SERVICE_CONFIRMED_READ_PROPERTY; /* service choice */
|
||||
apdu_len = 3;
|
||||
/* service ack follows */
|
||||
apdu_len += encode_context_object_id(&apdu[apdu_len], 0,
|
||||
data->object_type, data->object_instance);
|
||||
apdu_len += encode_context_enumerated(&apdu[apdu_len], 1,
|
||||
data->object_property);
|
||||
/* context 2 array index is optional */
|
||||
if (data->array_index != BACNET_ARRAY_ALL) {
|
||||
apdu_len += encode_context_unsigned(&apdu[apdu_len], 2,
|
||||
data->array_index);
|
||||
}
|
||||
/* propertyValue */
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 3);
|
||||
for (len = 0; len < data->application_data_len; len++) {
|
||||
apdu[apdu_len++] = data->application_data[len];
|
||||
}
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 3);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int rp_ack_decode_service_request(uint8_t * apdu, int apdu_len, /* total length of the apdu */
|
||||
BACNET_READ_PROPERTY_DATA * data)
|
||||
{
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
int tag_len = 0; /* length of tag decode */
|
||||
int len = 0; /* total length of decodes */
|
||||
int object = 0, property = 0; /* for decoding */
|
||||
uint32_t array_value = 0; /* for decoding */
|
||||
|
||||
/* FIXME: check apdu_len against the len during decode */
|
||||
/* Tag 0: Object ID */
|
||||
if (!decode_is_context_tag(&apdu[0], 0))
|
||||
return -1;
|
||||
len = 1;
|
||||
len += decode_object_id(&apdu[len], &object, &data->object_instance);
|
||||
data->object_type = (BACNET_OBJECT_TYPE)object;
|
||||
/* Tag 1: Property ID */
|
||||
len += decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
if (tag_number != 1)
|
||||
return -1;
|
||||
len += decode_enumerated(&apdu[len], len_value_type, &property);
|
||||
data->object_property = (BACNET_PROPERTY_ID)property;
|
||||
/* Tag 2: Optional Array Index */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
if (tag_number == 2) {
|
||||
len += tag_len;
|
||||
len += decode_unsigned(&apdu[len], len_value_type, &array_value);
|
||||
data->array_index = array_value;
|
||||
} else
|
||||
data->array_index = BACNET_ARRAY_ALL;
|
||||
/* Tag 3: opening context tag */
|
||||
if (decode_is_opening_tag_number(&apdu[len], 3)) {
|
||||
/* a tag number of 3 is not extended so only one octet */
|
||||
len++;
|
||||
/* don't decode the application tag number or its data here */
|
||||
data->application_data = &apdu[len];
|
||||
data->application_data_len = apdu_len - len - 1 /*closing tag */ ;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
int rp_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id, BACNET_READ_PROPERTY_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_READ_PROPERTY)
|
||||
return -1;
|
||||
offset = 4;
|
||||
|
||||
if (apdu_len > offset) {
|
||||
len = rp_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int rp_ack_decode_apdu(uint8_t * apdu, int apdu_len, /* total length of the apdu */
|
||||
uint8_t * invoke_id, BACNET_READ_PROPERTY_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
int offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_COMPLEX_ACK)
|
||||
return -1;
|
||||
*invoke_id = apdu[1];
|
||||
if (apdu[2] != SERVICE_CONFIRMED_READ_PROPERTY)
|
||||
return -1;
|
||||
offset = 3;
|
||||
if (apdu_len > offset) {
|
||||
len = rp_ack_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void testReadPropertyAck(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
uint8_t apdu2[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 1;
|
||||
uint8_t test_invoke_id = 0;
|
||||
BACNET_READ_PROPERTY_DATA data;
|
||||
BACNET_READ_PROPERTY_DATA test_data;
|
||||
BACNET_OBJECT_TYPE object_type = OBJECT_DEVICE;
|
||||
uint32_t object_instance = 0;
|
||||
int object = 0;
|
||||
|
||||
data.object_type = OBJECT_DEVICE;
|
||||
data.object_instance = 1;
|
||||
data.object_property = PROP_OBJECT_IDENTIFIER;
|
||||
data.array_index = BACNET_ARRAY_ALL;
|
||||
|
||||
data.application_data_len = encode_bacnet_object_id(&apdu2[0],
|
||||
data.object_type, data.object_instance);
|
||||
data.application_data = &apdu2[0];
|
||||
|
||||
len = rp_ack_encode_apdu(&apdu[0], invoke_id, &data);
|
||||
ct_test(pTest, len != 0);
|
||||
ct_test(pTest, len != -1);
|
||||
apdu_len = len;
|
||||
len = rp_ack_decode_apdu(&apdu[0], apdu_len, /* total length of the apdu */
|
||||
&test_invoke_id, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
|
||||
ct_test(pTest, test_data.object_type == data.object_type);
|
||||
ct_test(pTest, test_data.object_instance == data.object_instance);
|
||||
ct_test(pTest, test_data.object_property == data.object_property);
|
||||
ct_test(pTest, test_data.array_index == data.array_index);
|
||||
ct_test(pTest,
|
||||
test_data.application_data_len == data.application_data_len);
|
||||
|
||||
/* since object property == object_id, decode the application data using
|
||||
the appropriate decode function */
|
||||
len = decode_object_id(test_data.application_data,
|
||||
&object, &object_instance);
|
||||
object_type = object;
|
||||
ct_test(pTest, object_type == data.object_type);
|
||||
ct_test(pTest, object_instance == data.object_instance);
|
||||
}
|
||||
|
||||
void testReadProperty(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 128;
|
||||
uint8_t test_invoke_id = 0;
|
||||
BACNET_READ_PROPERTY_DATA data;
|
||||
BACNET_READ_PROPERTY_DATA test_data;
|
||||
|
||||
data.object_type = OBJECT_DEVICE;
|
||||
data.object_instance = 1;
|
||||
data.object_property = PROP_OBJECT_IDENTIFIER;
|
||||
data.array_index = BACNET_ARRAY_ALL;
|
||||
len = rp_encode_apdu(&apdu[0], invoke_id, &data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = rp_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_data.object_type == data.object_type);
|
||||
ct_test(pTest, test_data.object_instance == data.object_instance);
|
||||
ct_test(pTest, test_data.object_property == data.object_property);
|
||||
ct_test(pTest, test_data.array_index == data.array_index);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_READ_PROPERTY
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet ReadProperty", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testReadProperty);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testReadPropertyAck);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_READ_PROPERTY */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,764 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacerror.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "bacapp.h"
|
||||
#include "rpm.h"
|
||||
|
||||
/* encode the initial portion of the service */
|
||||
int rpm_encode_apdu_init(uint8_t * apdu, uint8_t invoke_id)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE; /* service choice */
|
||||
apdu_len = 4;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int rpm_encode_apdu_object_begin(uint8_t * apdu,
|
||||
BACNET_OBJECT_TYPE object_type, uint32_t object_instance)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu_len = encode_context_object_id(&apdu[0], 0,
|
||||
object_type, object_instance);
|
||||
/* Tag 1: sequence of ReadAccessSpecification */
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 1);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int rpm_encode_apdu_object_property(uint8_t * apdu,
|
||||
BACNET_PROPERTY_ID object_property, int32_t array_index)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu_len = encode_context_enumerated(&apdu[0], 0, object_property);
|
||||
/* optional array index */
|
||||
if (array_index != BACNET_ARRAY_ALL)
|
||||
apdu_len += encode_context_unsigned(&apdu[apdu_len], 1,
|
||||
array_index);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int rpm_encode_apdu_object_end(uint8_t * apdu)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu_len = encode_closing_tag(&apdu[0], 1);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the object portion of the service request only */
|
||||
int rpm_decode_object_id(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_OBJECT_TYPE * object_type, uint32_t * object_instance)
|
||||
{
|
||||
unsigned len = 0;
|
||||
int type = 0; /* for decoding */
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu && apdu_len && object_type && object_instance) {
|
||||
/* Tag 0: Object ID */
|
||||
if (!decode_is_context_tag(&apdu[len++], 0))
|
||||
return -1;
|
||||
len += decode_object_id(&apdu[len], &type, object_instance);
|
||||
if (object_type)
|
||||
*object_type = (BACNET_OBJECT_TYPE)type;
|
||||
/* Tag 1: sequence of ReadAccessSpecification */
|
||||
if (!decode_is_opening_tag_number(&apdu[len], 1))
|
||||
return -1;
|
||||
len++; /* opening tag is only one octet */
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
}
|
||||
|
||||
int rpm_decode_object_end(uint8_t * apdu, unsigned apdu_len)
|
||||
{
|
||||
int len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu && apdu_len) {
|
||||
if (decode_is_closing_tag_number(apdu, 1))
|
||||
len = 1;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* decode the object property portion of the service request only */
|
||||
/* BACnetPropertyReference ::= SEQUENCE {
|
||||
propertyIdentifier [0] BACnetPropertyIdentifier,
|
||||
propertyArrayIndex [1] Unsigned OPTIONAL
|
||||
--used only with array datatype
|
||||
-- if omitted with an array the entire array is referenced
|
||||
}
|
||||
*/
|
||||
int rpm_decode_object_property(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_PROPERTY_ID * object_property, int32_t * array_index)
|
||||
{
|
||||
unsigned len = 0;
|
||||
unsigned option_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
int property = 0; /* for decoding */
|
||||
uint32_t array_value = 0; /* for decoding */
|
||||
|
||||
/* check for valid pointers */
|
||||
if (apdu && apdu_len && object_property && array_index) {
|
||||
/* Tag 0: propertyIdentifier */
|
||||
if (!decode_is_context_specific(&apdu[len]))
|
||||
return -1;
|
||||
len += decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
if (tag_number != 0)
|
||||
return -1;
|
||||
len += decode_enumerated(&apdu[len], len_value_type, &property);
|
||||
if (object_property)
|
||||
*object_property = (BACNET_PROPERTY_ID)property;
|
||||
/* Tag 1: Optional propertyArrayIndex */
|
||||
if ((len < apdu_len) &&
|
||||
decode_is_context_specific(&apdu[len]) &&
|
||||
(!decode_is_closing_tag(&apdu[len]))) {
|
||||
option_len =
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
if (tag_number == 1) {
|
||||
len += option_len;
|
||||
len += decode_unsigned(&apdu[len], len_value_type,
|
||||
&array_value);
|
||||
*array_index = array_value;
|
||||
} else
|
||||
*array_index = BACNET_ARRAY_ALL;
|
||||
} else
|
||||
*array_index = BACNET_ARRAY_ALL;
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
}
|
||||
|
||||
int rpm_ack_encode_apdu_init(uint8_t * apdu, uint8_t invoke_id)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_COMPLEX_ACK; /* complex ACK service */
|
||||
apdu[1] = invoke_id; /* original invoke id from request */
|
||||
apdu[2] = SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE; /* service choice */
|
||||
apdu_len = 3;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int rpm_ack_encode_apdu_object_begin(uint8_t * apdu,
|
||||
BACNET_OBJECT_TYPE object_type, uint32_t object_instance)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
/* Tag 0: objectIdentifier */
|
||||
apdu_len = encode_context_object_id(&apdu[0], 0,
|
||||
object_type, object_instance);
|
||||
/* Tag 1: listOfResults */
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 1);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int rpm_ack_encode_apdu_object_property(uint8_t * apdu,
|
||||
BACNET_PROPERTY_ID object_property, int32_t array_index)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
/* Tag 2: propertyIdentifier */
|
||||
apdu_len = encode_context_enumerated(&apdu[0], 2, object_property);
|
||||
/* Tag 3: optional propertyArrayIndex */
|
||||
if (array_index != BACNET_ARRAY_ALL)
|
||||
apdu_len += encode_context_unsigned(&apdu[apdu_len], 3,
|
||||
array_index);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int rpm_ack_encode_apdu_object_property_value(uint8_t * apdu,
|
||||
uint8_t * application_data, unsigned application_data_len)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
unsigned len = 0;
|
||||
|
||||
if (apdu) {
|
||||
/* Tag 4: propertyValue */
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 4);
|
||||
for (len = 0; len < application_data_len; len++) {
|
||||
apdu[apdu_len++] = application_data[len];
|
||||
}
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 4);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int rpm_ack_encode_apdu_object_property_error(uint8_t * apdu,
|
||||
BACNET_ERROR_CLASS error_class, BACNET_ERROR_CODE error_code)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
/* Tag 5: propertyAccessError */
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 5);
|
||||
apdu_len += encode_tagged_enumerated(&apdu[apdu_len], error_class);
|
||||
apdu_len += encode_tagged_enumerated(&apdu[apdu_len], error_code);
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 5);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int rpm_ack_encode_apdu_object_end(uint8_t * apdu)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu_len = encode_closing_tag(&apdu[0], 1);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the object portion of the service request only */
|
||||
int rpm_ack_decode_object_id(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_OBJECT_TYPE * object_type, uint32_t * object_instance)
|
||||
{
|
||||
unsigned len = 0;
|
||||
int type = 0; /* for decoding */
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu && apdu_len && object_type && object_instance) {
|
||||
/* Tag 0: objectIdentifier */
|
||||
if (!decode_is_context_tag(&apdu[len++], 0))
|
||||
return -1;
|
||||
len += decode_object_id(&apdu[len], &type, object_instance);
|
||||
if (object_type)
|
||||
*object_type = (BACNET_OBJECT_TYPE)type;
|
||||
/* Tag 1: listOfResults */
|
||||
if (!decode_is_opening_tag_number(&apdu[len], 1))
|
||||
return -1;
|
||||
len++; /* opening tag is only one octet */
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
}
|
||||
|
||||
/* is this the end of the list of this objects properties values? */
|
||||
int rpm_ack_decode_object_end(uint8_t * apdu, unsigned apdu_len)
|
||||
{
|
||||
int len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu && apdu_len) {
|
||||
if (decode_is_closing_tag_number(apdu, 1))
|
||||
len = 1;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int rpm_ack_decode_object_property(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_PROPERTY_ID * object_property, int32_t * array_index)
|
||||
{
|
||||
unsigned len = 0;
|
||||
unsigned tag_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
int property = 0; /* for decoding */
|
||||
uint32_t array_value = 0; /* for decoding */
|
||||
|
||||
/* check for valid pointers */
|
||||
if (apdu && apdu_len && object_property && array_index) {
|
||||
/* Tag 2: propertyIdentifier */
|
||||
if (!decode_is_context_specific(&apdu[len]))
|
||||
return -1;
|
||||
len += decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
if (tag_number != 2)
|
||||
return -1;
|
||||
len += decode_enumerated(&apdu[len], len_value_type, &property);
|
||||
if (object_property)
|
||||
*object_property = (BACNET_PROPERTY_ID)property;
|
||||
/* Tag 3: Optional propertyArrayIndex */
|
||||
if ((len < apdu_len) &&
|
||||
decode_is_context_specific(&apdu[len]) &&
|
||||
(!decode_is_closing_tag(&apdu[len]))) {
|
||||
tag_len = decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
if (tag_number == 3) {
|
||||
len += tag_len;
|
||||
len += decode_unsigned(&apdu[len], len_value_type,
|
||||
&array_value);
|
||||
*array_index = array_value;
|
||||
} else {
|
||||
*array_index = BACNET_ARRAY_ALL;
|
||||
}
|
||||
} else {
|
||||
*array_index = BACNET_ARRAY_ALL;
|
||||
}
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
int rpm_ack_decode_apdu(uint8_t * apdu, int apdu_len, /* total length of the apdu */
|
||||
uint8_t * invoke_id,
|
||||
uint8_t ** service_request, unsigned *service_request_len)
|
||||
{
|
||||
int offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_COMPLEX_ACK)
|
||||
return -1;
|
||||
*invoke_id = apdu[1];
|
||||
if (apdu[2] != SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE)
|
||||
return -1;
|
||||
offset = 3;
|
||||
if (apdu_len > offset) {
|
||||
if (service_request)
|
||||
*service_request = &apdu[offset];
|
||||
if (service_request_len)
|
||||
*service_request_len = apdu_len - offset;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
int rpm_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id,
|
||||
uint8_t ** service_request, unsigned *service_request_len)
|
||||
{
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_READ_PROPERTY_MULTIPLE)
|
||||
return -1;
|
||||
offset = 4;
|
||||
|
||||
if (apdu_len > offset) {
|
||||
if (service_request)
|
||||
*service_request = &apdu[offset];
|
||||
if (service_request_len)
|
||||
*service_request_len = apdu_len - offset;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
void testReadPropertyMultiple(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int test_len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 12;
|
||||
uint8_t test_invoke_id = 0;
|
||||
uint8_t *service_request = NULL;
|
||||
unsigned service_request_len = 0;
|
||||
BACNET_OBJECT_TYPE object_type = OBJECT_DEVICE;
|
||||
uint32_t object_instance = 0;
|
||||
BACNET_PROPERTY_ID object_property = PROP_OBJECT_IDENTIFIER;
|
||||
int32_t array_index = 0;
|
||||
|
||||
/* build the RPM - try to make it easy for the Application Layer development */
|
||||
/* IDEA: similar construction, but pass apdu, apdu_len pointer, size of apdu to
|
||||
let the called function handle the out of space problem that these get into
|
||||
by returning a boolean of success/failure.
|
||||
It almost needs to use the keylist library or something similar.
|
||||
Also check case of storing a backoff point (i.e. save enough room for object_end) */
|
||||
apdu_len = rpm_encode_apdu_init(&apdu[0], invoke_id);
|
||||
/* each object has a beginning and an end */
|
||||
apdu_len += rpm_encode_apdu_object_begin(&apdu[apdu_len],
|
||||
OBJECT_DEVICE, 123);
|
||||
/* then stuff as many properties into it as APDU length will allow */
|
||||
apdu_len += rpm_encode_apdu_object_property(&apdu[apdu_len],
|
||||
PROP_OBJECT_IDENTIFIER, BACNET_ARRAY_ALL);
|
||||
apdu_len += rpm_encode_apdu_object_property(&apdu[apdu_len],
|
||||
PROP_OBJECT_NAME, BACNET_ARRAY_ALL);
|
||||
apdu_len += rpm_encode_apdu_object_end(&apdu[apdu_len]);
|
||||
/* each object has a beginning and an end */
|
||||
apdu_len += rpm_encode_apdu_object_begin(&apdu[apdu_len],
|
||||
OBJECT_ANALOG_INPUT, 33);
|
||||
apdu_len += rpm_encode_apdu_object_property(&apdu[apdu_len],
|
||||
PROP_OBJECT_IDENTIFIER, BACNET_ARRAY_ALL);
|
||||
apdu_len += rpm_encode_apdu_object_property(&apdu[apdu_len],
|
||||
PROP_ALL, BACNET_ARRAY_ALL);
|
||||
apdu_len += rpm_encode_apdu_object_end(&apdu[apdu_len]);
|
||||
|
||||
ct_test(pTest, apdu_len != 0);
|
||||
|
||||
test_len = rpm_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &service_request, /* will point to the service request in the apdu */
|
||||
&service_request_len);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, service_request != NULL);
|
||||
ct_test(pTest, service_request_len > 0);
|
||||
|
||||
test_len = rpm_decode_object_id(service_request,
|
||||
service_request_len, &object_type, &object_instance);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, object_type == OBJECT_DEVICE);
|
||||
ct_test(pTest, object_instance == 123);
|
||||
len = test_len;
|
||||
/* decode the object property portion of the service request */
|
||||
test_len = rpm_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, object_property == PROP_OBJECT_IDENTIFIER);
|
||||
ct_test(pTest, array_index == BACNET_ARRAY_ALL);
|
||||
len += test_len;
|
||||
test_len = rpm_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, object_property == PROP_OBJECT_NAME);
|
||||
ct_test(pTest, array_index == BACNET_ARRAY_ALL);
|
||||
len += test_len;
|
||||
/* try again - we should fail */
|
||||
test_len = rpm_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, test_len == -1);
|
||||
/* is it the end of this object? */
|
||||
test_len = rpm_decode_object_end(&service_request[len],
|
||||
service_request_len - len);
|
||||
ct_test(pTest, test_len == 1);
|
||||
len += test_len;
|
||||
/* try to decode an object id */
|
||||
test_len = rpm_decode_object_id(&service_request[len],
|
||||
service_request_len - len, &object_type, &object_instance);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, object_type == OBJECT_ANALOG_INPUT);
|
||||
ct_test(pTest, object_instance == 33);
|
||||
len += test_len;
|
||||
/* decode the object property portion of the service request only */
|
||||
test_len = rpm_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, object_property == PROP_OBJECT_IDENTIFIER);
|
||||
ct_test(pTest, array_index == BACNET_ARRAY_ALL);
|
||||
len += test_len;
|
||||
test_len = rpm_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, object_property == PROP_ALL);
|
||||
ct_test(pTest, array_index == BACNET_ARRAY_ALL);
|
||||
len += test_len;
|
||||
test_len = rpm_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, test_len == -1);
|
||||
/* got an error -1, is it the end of this object? */
|
||||
test_len = rpm_decode_object_end(&service_request[len],
|
||||
service_request_len - len);
|
||||
ct_test(pTest, test_len == 1);
|
||||
len += test_len;
|
||||
ct_test(pTest, len == service_request_len);
|
||||
}
|
||||
|
||||
void testReadPropertyMultipleAck(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int test_len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 12;
|
||||
uint8_t test_invoke_id = 0;
|
||||
uint8_t *service_request = NULL;
|
||||
unsigned service_request_len = 0;
|
||||
BACNET_OBJECT_TYPE object_type = OBJECT_DEVICE;
|
||||
uint32_t object_instance = 0;
|
||||
BACNET_PROPERTY_ID object_property = PROP_OBJECT_IDENTIFIER;
|
||||
int32_t array_index = 0;
|
||||
BACNET_APPLICATION_DATA_VALUE application_data[4] = { {0} };
|
||||
BACNET_APPLICATION_DATA_VALUE test_application_data = { 0 };
|
||||
uint8_t application_data_buffer[MAX_APDU] = { 0 };
|
||||
int application_data_buffer_len = 0;
|
||||
BACNET_ERROR_CLASS error_class;
|
||||
BACNET_ERROR_CODE error_code;
|
||||
|
||||
/* build the RPM - try to make it easy for the
|
||||
Application Layer development */
|
||||
/* IDEA: similar construction, but pass apdu, apdu_len pointer,
|
||||
size of apdu to let the called function handle the out of
|
||||
space problem that these get into by returning a boolean
|
||||
of success/failure.
|
||||
It almost needs to use the keylist library or something similar.
|
||||
Also check case of storing a backoff point
|
||||
(i.e. save enough room for object_end) */
|
||||
apdu_len = rpm_ack_encode_apdu_init(&apdu[0], invoke_id);
|
||||
|
||||
/* object beginning */
|
||||
apdu_len += rpm_ack_encode_apdu_object_begin(&apdu[apdu_len],
|
||||
OBJECT_DEVICE, 123);
|
||||
/* reply property */
|
||||
apdu_len += rpm_ack_encode_apdu_object_property(&apdu[apdu_len],
|
||||
PROP_OBJECT_IDENTIFIER, BACNET_ARRAY_ALL);
|
||||
/* reply value */
|
||||
application_data[0].tag = BACNET_APPLICATION_TAG_OBJECT_ID;
|
||||
application_data[0].type.Object_Id.type = OBJECT_DEVICE;
|
||||
application_data[0].type.Object_Id.instance = 123;
|
||||
application_data_buffer_len =
|
||||
bacapp_encode_application_data(&application_data_buffer[0],
|
||||
&application_data[0]);
|
||||
apdu_len +=
|
||||
rpm_ack_encode_apdu_object_property_value(&apdu[apdu_len],
|
||||
&application_data_buffer[0], application_data_buffer_len);
|
||||
/* reply property */
|
||||
apdu_len += rpm_ack_encode_apdu_object_property(&apdu[apdu_len],
|
||||
PROP_OBJECT_TYPE, BACNET_ARRAY_ALL);
|
||||
/* reply value */
|
||||
application_data[1].tag = BACNET_APPLICATION_TAG_ENUMERATED;
|
||||
application_data[1].type.Enumerated = OBJECT_DEVICE;
|
||||
application_data_buffer_len =
|
||||
bacapp_encode_application_data(&application_data_buffer[0],
|
||||
&application_data[1]);
|
||||
apdu_len +=
|
||||
rpm_ack_encode_apdu_object_property_value(&apdu[apdu_len],
|
||||
&application_data_buffer[0], application_data_buffer_len);
|
||||
/* object end */
|
||||
apdu_len += rpm_ack_encode_apdu_object_end(&apdu[apdu_len]);
|
||||
|
||||
/* object beginning */
|
||||
apdu_len += rpm_ack_encode_apdu_object_begin(&apdu[apdu_len],
|
||||
OBJECT_ANALOG_INPUT, 33);
|
||||
/* reply property */
|
||||
apdu_len += rpm_ack_encode_apdu_object_property(&apdu[apdu_len],
|
||||
PROP_PRESENT_VALUE, BACNET_ARRAY_ALL);
|
||||
/* reply value */
|
||||
application_data[2].tag = BACNET_APPLICATION_TAG_REAL;
|
||||
application_data[2].type.Real = 0.0;
|
||||
application_data_buffer_len =
|
||||
bacapp_encode_application_data(&application_data_buffer[0],
|
||||
&application_data[2]);
|
||||
apdu_len +=
|
||||
rpm_ack_encode_apdu_object_property_value(&apdu[apdu_len],
|
||||
&application_data_buffer[0], application_data_buffer_len);
|
||||
/* reply property */
|
||||
apdu_len += rpm_ack_encode_apdu_object_property(&apdu[apdu_len],
|
||||
PROP_DEADBAND, BACNET_ARRAY_ALL);
|
||||
/* reply error */
|
||||
apdu_len += rpm_ack_encode_apdu_object_property_error(&apdu[apdu_len],
|
||||
ERROR_CLASS_PROPERTY, ERROR_CODE_UNKNOWN_PROPERTY);
|
||||
/* object end */
|
||||
apdu_len += rpm_ack_encode_apdu_object_end(&apdu[apdu_len]);
|
||||
ct_test(pTest, apdu_len != 0);
|
||||
|
||||
/****** decode the packet ******/
|
||||
test_len = rpm_ack_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &service_request, /* will point to the service request in the apdu */
|
||||
&service_request_len);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, service_request != NULL);
|
||||
ct_test(pTest, service_request_len > 0);
|
||||
/* the first part should be the first object id */
|
||||
test_len = rpm_ack_decode_object_id(service_request,
|
||||
service_request_len, &object_type, &object_instance);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, object_type == OBJECT_DEVICE);
|
||||
ct_test(pTest, object_instance == 123);
|
||||
len = test_len;
|
||||
/* extract the property */
|
||||
test_len = rpm_ack_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, object_property == PROP_OBJECT_IDENTIFIER);
|
||||
ct_test(pTest, array_index == BACNET_ARRAY_ALL);
|
||||
len += test_len;
|
||||
/* what is the result? An error or a value? */
|
||||
ct_test(pTest, decode_is_opening_tag_number(&service_request[len], 4));
|
||||
len++;
|
||||
/* decode the object property portion of the service request */
|
||||
/* note: if this was an array, there could have been
|
||||
more than one element to decode */
|
||||
test_len = bacapp_decode_application_data(&service_request[len],
|
||||
service_request_len - len, &test_application_data);
|
||||
ct_test(pTest, test_len > 0);
|
||||
ct_test(pTest, bacapp_same_value(&application_data[0],
|
||||
&test_application_data));
|
||||
len += test_len;
|
||||
ct_test(pTest, decode_is_closing_tag_number(&service_request[len], 4));
|
||||
len++;
|
||||
/* see if there is another property */
|
||||
test_len = rpm_ack_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, object_property == PROP_OBJECT_TYPE);
|
||||
ct_test(pTest, array_index == BACNET_ARRAY_ALL);
|
||||
len += test_len;
|
||||
/* what is the result value? */
|
||||
ct_test(pTest, decode_is_opening_tag_number(&service_request[len], 4));
|
||||
len++;
|
||||
/* decode the object property portion of the service request */
|
||||
test_len = bacapp_decode_application_data(&service_request[len],
|
||||
service_request_len - len, &test_application_data);
|
||||
ct_test(pTest, test_len > 0);
|
||||
ct_test(pTest, bacapp_same_value(&application_data[1],
|
||||
&test_application_data));
|
||||
len += test_len;
|
||||
ct_test(pTest, decode_is_closing_tag_number(&service_request[len], 4));
|
||||
len++;
|
||||
/* see if there is another property */
|
||||
/* this time we should fail */
|
||||
test_len = rpm_ack_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, test_len == -1);
|
||||
/* see if it is the end of this object */
|
||||
test_len = rpm_ack_decode_object_end(&service_request[len],
|
||||
service_request_len - len);
|
||||
ct_test(pTest, test_len == 1);
|
||||
len += test_len;
|
||||
/* try to decode another object id */
|
||||
test_len = rpm_ack_decode_object_id(&service_request[len],
|
||||
service_request_len - len, &object_type, &object_instance);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, object_type == OBJECT_ANALOG_INPUT);
|
||||
ct_test(pTest, object_instance == 33);
|
||||
len += test_len;
|
||||
/* decode the object property portion of the service request only */
|
||||
test_len = rpm_ack_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, object_property == PROP_PRESENT_VALUE);
|
||||
ct_test(pTest, array_index == BACNET_ARRAY_ALL);
|
||||
len += test_len;
|
||||
/* what is the result value? */
|
||||
ct_test(pTest, decode_is_opening_tag_number(&service_request[len], 4));
|
||||
len++;
|
||||
/* decode the object property portion of the service request */
|
||||
test_len = bacapp_decode_application_data(&service_request[len],
|
||||
service_request_len - len, &test_application_data);
|
||||
ct_test(pTest, test_len > 0);
|
||||
ct_test(pTest, bacapp_same_value(&application_data[2],
|
||||
&test_application_data));
|
||||
len += test_len;
|
||||
ct_test(pTest, decode_is_closing_tag_number(&service_request[len], 4));
|
||||
len++;
|
||||
/* see if there is another property */
|
||||
test_len = rpm_ack_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, test_len != -1);
|
||||
ct_test(pTest, object_property == PROP_DEADBAND);
|
||||
ct_test(pTest, array_index == BACNET_ARRAY_ALL);
|
||||
len += test_len;
|
||||
/* what is the result value? */
|
||||
ct_test(pTest, decode_is_opening_tag_number(&service_request[len], 5));
|
||||
len++;
|
||||
/* it was an error reply */
|
||||
test_len = bacerror_decode_error_class_and_code(&service_request[len],
|
||||
service_request_len - len, &error_class, &error_code);
|
||||
ct_test(pTest, test_len != 0);
|
||||
ct_test(pTest, error_class == ERROR_CLASS_PROPERTY);
|
||||
ct_test(pTest, error_code == ERROR_CODE_UNKNOWN_PROPERTY);
|
||||
len += test_len;
|
||||
ct_test(pTest, decode_is_closing_tag_number(&service_request[len], 5));
|
||||
len++;
|
||||
/* is there another property? */
|
||||
test_len = rpm_ack_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &object_property, &array_index);
|
||||
ct_test(pTest, test_len == -1);
|
||||
/* got an error -1, is it the end of this object? */
|
||||
test_len = rpm_ack_decode_object_end(&service_request[len],
|
||||
service_request_len - len);
|
||||
ct_test(pTest, test_len == 1);
|
||||
len += test_len;
|
||||
/* check for another object */
|
||||
test_len = rpm_ack_decode_object_id(&service_request[len],
|
||||
service_request_len - len, &object_type, &object_instance);
|
||||
ct_test(pTest, test_len == 0);
|
||||
ct_test(pTest, len == service_request_len);
|
||||
}
|
||||
|
||||
#ifdef TEST_READ_PROPERTY_MULTIPLE
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet ReadPropertyMultiple", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testReadPropertyMultiple);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testReadPropertyMultipleAck);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_READ_PROPERTY_MULTIPLE */
|
||||
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,209 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 by Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307
|
||||
USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
|
||||
/* Functional Description: Static buffer library for deeply
|
||||
embedded system. See the unit tests for usage examples. */
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include "sbuf.h"
|
||||
|
||||
void sbuf_init(STATIC_BUFFER * b, /* static buffer structure */
|
||||
char *data, /* actual size, in bytes, of the data block or array of data */
|
||||
unsigned size)
|
||||
{ /* number of bytes used */
|
||||
if (b) {
|
||||
b->data = data;
|
||||
b->size = size;
|
||||
b->count = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* returns true if count==0, false if count > 0 */
|
||||
bool sbuf_empty(STATIC_BUFFER const *b)
|
||||
{
|
||||
return (b ? (b->count == 0) : false);
|
||||
}
|
||||
|
||||
char *sbuf_data(STATIC_BUFFER const *b)
|
||||
{
|
||||
return (b ? b->data : NULL);
|
||||
}
|
||||
|
||||
unsigned sbuf_size(STATIC_BUFFER * b)
|
||||
{
|
||||
return (b ? b->size : 0);
|
||||
}
|
||||
|
||||
unsigned sbuf_count(STATIC_BUFFER * b)
|
||||
{
|
||||
return (b ? b->count : 0);
|
||||
}
|
||||
|
||||
/* returns true if successful, false if not enough room to append data */
|
||||
bool sbuf_put(STATIC_BUFFER * b, /* static buffer structure */
|
||||
unsigned offset, /* where to start */
|
||||
char *data, /* number of bytes used */
|
||||
unsigned data_size)
|
||||
{ /* how many to add */
|
||||
bool status = false; /* return value */
|
||||
|
||||
if (b && b->data) {
|
||||
if (((offset + data_size) < b->size)) {
|
||||
b->count = offset + data_size;
|
||||
while (data_size) {
|
||||
b->data[offset] = *data;
|
||||
offset++;
|
||||
data++;
|
||||
data_size--;
|
||||
}
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* returns true if successful, false if not enough room to append data */
|
||||
bool sbuf_append(STATIC_BUFFER * b, /* static buffer structure */
|
||||
char *data, /* number of bytes used */
|
||||
unsigned data_size)
|
||||
{ /* how many to add */
|
||||
unsigned count = 0;
|
||||
|
||||
if (b)
|
||||
count = b->count;
|
||||
|
||||
return sbuf_put(b, count, data, data_size);
|
||||
}
|
||||
|
||||
/* returns true if successful, false if not enough room to append data */
|
||||
bool sbuf_truncate(STATIC_BUFFER * b, /* static buffer structure */
|
||||
unsigned count)
|
||||
{ /* total number of bytes in use */
|
||||
bool status = false; /* return value */
|
||||
|
||||
if (b) {
|
||||
if (count < b->size) {
|
||||
b->count = count;
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ctest.h"
|
||||
|
||||
void testStaticBuffer(Test * pTest)
|
||||
{
|
||||
STATIC_BUFFER sbuffer;
|
||||
char *data1 = "Joshua";
|
||||
char *data2 = "Anna";
|
||||
char *data3 = "Christopher";
|
||||
char *data4 = "Mary";
|
||||
char data_buffer[480] = "";
|
||||
char test_data_buffer[480] = "";
|
||||
char *data;
|
||||
unsigned count;
|
||||
|
||||
sbuf_init(&sbuffer, NULL, 0);
|
||||
ct_test(pTest, sbuf_empty(&sbuffer) == true);
|
||||
ct_test(pTest, sbuf_data(&sbuffer) == NULL);
|
||||
ct_test(pTest, sbuf_size(&sbuffer) == 0);
|
||||
ct_test(pTest, sbuf_count(&sbuffer) == 0);
|
||||
ct_test(pTest, sbuf_append(&sbuffer, data1, strlen(data1)) == false);
|
||||
|
||||
sbuf_init(&sbuffer, data_buffer, sizeof(data_buffer));
|
||||
ct_test(pTest, sbuf_empty(&sbuffer) == true);
|
||||
ct_test(pTest, sbuf_data(&sbuffer) == data_buffer);
|
||||
ct_test(pTest, sbuf_size(&sbuffer) == sizeof(data_buffer));
|
||||
ct_test(pTest, sbuf_count(&sbuffer) == 0);
|
||||
|
||||
ct_test(pTest, sbuf_append(&sbuffer, data1, strlen(data1)) == true);
|
||||
ct_test(pTest, sbuf_append(&sbuffer, data2, strlen(data2)) == true);
|
||||
ct_test(pTest, sbuf_append(&sbuffer, data3, strlen(data3)) == true);
|
||||
ct_test(pTest, sbuf_append(&sbuffer, data4, strlen(data4)) == true);
|
||||
strcat(test_data_buffer, data1);
|
||||
strcat(test_data_buffer, data2);
|
||||
strcat(test_data_buffer, data3);
|
||||
strcat(test_data_buffer, data4);
|
||||
ct_test(pTest, sbuf_count(&sbuffer) == strlen(test_data_buffer));
|
||||
|
||||
data = sbuf_data(&sbuffer);
|
||||
count = sbuf_count(&sbuffer);
|
||||
ct_test(pTest, memcmp(data, test_data_buffer, count) == 0);
|
||||
ct_test(pTest, count == strlen(test_data_buffer));
|
||||
|
||||
ct_test(pTest, sbuf_truncate(&sbuffer, 0) == true);
|
||||
ct_test(pTest, sbuf_count(&sbuffer) == 0);
|
||||
ct_test(pTest, sbuf_size(&sbuffer) == sizeof(data_buffer));
|
||||
ct_test(pTest, sbuf_append(&sbuffer, data4, strlen(data4)) == true);
|
||||
data = sbuf_data(&sbuffer);
|
||||
count = sbuf_count(&sbuffer);
|
||||
ct_test(pTest, memcmp(data, data4, count) == 0);
|
||||
ct_test(pTest, count == strlen(data4));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_STATIC_BUFFER
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("static buffer", NULL);
|
||||
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testStaticBuffer);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_STATIC_BUFFER */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,214 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2006 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "bacapp.h"
|
||||
#include "timesync.h"
|
||||
|
||||
/* encode service */
|
||||
int timesync_encode_apdu_service(uint8_t * apdu,
|
||||
BACNET_UNCONFIRMED_SERVICE service,
|
||||
BACNET_DATE * my_date, BACNET_TIME * my_time)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu && my_date && my_time) {
|
||||
apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = service;
|
||||
apdu_len = 2;
|
||||
len = encode_tagged_date(&apdu[apdu_len], my_date);
|
||||
apdu_len += len;
|
||||
len = encode_tagged_time(&apdu[apdu_len], my_time);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int timesync_utc_encode_apdu(uint8_t * apdu,
|
||||
BACNET_DATE * my_date, BACNET_TIME * my_time)
|
||||
{
|
||||
return timesync_encode_apdu_service(apdu,
|
||||
SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, my_date, my_time);
|
||||
}
|
||||
|
||||
int timesync_encode_apdu(uint8_t * apdu,
|
||||
BACNET_DATE * my_date, BACNET_TIME * my_time)
|
||||
{
|
||||
return timesync_encode_apdu_service(apdu,
|
||||
SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, my_date, my_time);
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int timesync_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_DATE * my_date, BACNET_TIME * my_time)
|
||||
{
|
||||
int len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
|
||||
if (apdu_len && my_date && my_time) {
|
||||
/* date */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
if (tag_number == BACNET_APPLICATION_TAG_DATE) {
|
||||
len += decode_date(&apdu[len], my_date);
|
||||
} else
|
||||
return -1;
|
||||
/* time */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
if (tag_number == BACNET_APPLICATION_TAG_TIME) {
|
||||
len += decode_bacnet_time(&apdu[len], my_time);
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
int timesync_decode_apdu_service(uint8_t * apdu,
|
||||
BACNET_UNCONFIRMED_SERVICE service,
|
||||
unsigned apdu_len, BACNET_DATE * my_date, BACNET_TIME * my_time)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
if (apdu[1] != service)
|
||||
return -1;
|
||||
/* optional limits - must be used as a pair */
|
||||
if (apdu_len > 2) {
|
||||
len = timesync_decode_service_request(&apdu[2], apdu_len - 2,
|
||||
my_date, my_time);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int timesync_utc_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_DATE * my_date, BACNET_TIME * my_time)
|
||||
{
|
||||
return timesync_decode_apdu_service(apdu,
|
||||
SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION,
|
||||
apdu_len, my_date, my_time);
|
||||
}
|
||||
|
||||
int timesync_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_DATE * my_date, BACNET_TIME * my_time)
|
||||
{
|
||||
return timesync_decode_apdu_service(apdu,
|
||||
SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION,
|
||||
apdu_len, my_date, my_time);
|
||||
}
|
||||
|
||||
void testTimeSyncData(Test * pTest,
|
||||
BACNET_DATE * my_date, BACNET_TIME * my_time)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
BACNET_DATE test_date;
|
||||
BACNET_TIME test_time;
|
||||
|
||||
len = timesync_encode_apdu(&apdu[0], my_date, my_time);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
len = timesync_decode_apdu(&apdu[0], apdu_len, &test_date, &test_time);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, bacapp_same_time(my_time, &test_time));
|
||||
ct_test(pTest, bacapp_same_date(my_date, &test_date));
|
||||
|
||||
len = timesync_utc_encode_apdu(&apdu[0], my_date, my_time);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
len =
|
||||
timesync_utc_decode_apdu(&apdu[0], apdu_len, &test_date,
|
||||
&test_time);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, bacapp_same_time(my_time, &test_time));
|
||||
ct_test(pTest, bacapp_same_date(my_date, &test_date));
|
||||
}
|
||||
|
||||
void testTimeSync(Test * pTest)
|
||||
{
|
||||
BACNET_DATE bdate;
|
||||
BACNET_TIME btime;
|
||||
|
||||
bdate.year = 2006; /* AD */
|
||||
bdate.month = 4; /* 1=Jan */
|
||||
bdate.day = 11; /* 1..31 */
|
||||
bdate.wday = 1; /* 1=Monday */
|
||||
|
||||
btime.hour = 7;
|
||||
btime.min = 0;
|
||||
btime.sec = 3;
|
||||
btime.hundredths = 1;
|
||||
|
||||
testTimeSyncData(pTest, &bdate, &btime);
|
||||
}
|
||||
|
||||
#ifdef TEST_TIMESYNC
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Time-Sync", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testTimeSync);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_WHOIS */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,356 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
Corrections by Ferran Arumi, 2007, Barcelona, Spain
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "bits.h"
|
||||
#include "apdu.h"
|
||||
#include "bacdef.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacenum.h"
|
||||
#include "tsm.h"
|
||||
#include "config.h"
|
||||
#include "device.h"
|
||||
#include "datalink.h"
|
||||
#include "handlers.h"
|
||||
#include "address.h"
|
||||
#include "bacaddr.h"
|
||||
|
||||
#if (MAX_TSM_TRANSACTIONS)
|
||||
/* Transaction State Machine */
|
||||
/* Really only needed for segmented messages */
|
||||
/* and a little for sending confirmed messages */
|
||||
/* If we are only a server and only initiate broadcasts, */
|
||||
/* then we don't need a TSM layer. */
|
||||
|
||||
/* FIXME: not coded for segmentation */
|
||||
|
||||
/* declare space for the TSM transactions, and set it up in the init. */
|
||||
/* table rules: an Invoke ID = 0 is an unused spot in the table */
|
||||
static BACNET_TSM_DATA TSM_List[MAX_TSM_TRANSACTIONS];
|
||||
|
||||
/* returns MAX_TSM_TRANSACTIONS if not found */
|
||||
static uint8_t tsm_find_invokeID_index(uint8_t invokeID)
|
||||
{
|
||||
unsigned i = 0; /* counter */
|
||||
uint8_t index = MAX_TSM_TRANSACTIONS; /* return value */
|
||||
|
||||
for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) {
|
||||
if (TSM_List[i].InvokeID == invokeID) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
static uint8_t tsm_find_first_free_index(void)
|
||||
{
|
||||
unsigned i = 0; /* counter */
|
||||
uint8_t index = MAX_TSM_TRANSACTIONS; /* return value */
|
||||
|
||||
for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) {
|
||||
if (TSM_List[i].InvokeID == 0) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
bool tsm_transaction_available(void)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
unsigned i = 0; /* counter */
|
||||
|
||||
for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) {
|
||||
if (TSM_List[i].InvokeID == 0) {
|
||||
/* one is available! */
|
||||
status = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
uint8_t tsm_transaction_idle_count(void)
|
||||
{
|
||||
uint8_t count = 0; /* return value */
|
||||
unsigned i = 0; /* counter */
|
||||
|
||||
for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) {
|
||||
if ((TSM_List[i].InvokeID == 0) &&
|
||||
(TSM_List[i].state == TSM_STATE_IDLE)) {
|
||||
/* one is available! */
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* gets the next free invokeID,
|
||||
and reserves a spot in the table
|
||||
returns 0 if none are available */
|
||||
uint8_t tsm_next_free_invokeID(void)
|
||||
{
|
||||
static uint8_t current_invokeID = 1; /* incremented... */
|
||||
uint8_t index = 0;
|
||||
uint8_t invokeID = 0;
|
||||
bool found = false;
|
||||
|
||||
/* is there even space available? */
|
||||
if (tsm_transaction_available()) {
|
||||
while (!found) {
|
||||
index = tsm_find_invokeID_index(current_invokeID);
|
||||
if (index == MAX_TSM_TRANSACTIONS) {
|
||||
/* Not found, so this invokeID is not used */
|
||||
found = true;
|
||||
/* set this id into the table */
|
||||
index = tsm_find_first_free_index();
|
||||
if (index != MAX_TSM_TRANSACTIONS) {
|
||||
TSM_List[index].InvokeID = invokeID = current_invokeID;
|
||||
TSM_List[index].state = TSM_STATE_IDLE;
|
||||
TSM_List[index].RequestTimer = Device_APDU_Timeout();
|
||||
/* update for the next call or check */
|
||||
current_invokeID++;
|
||||
/* skip zero - we treat that internally as invalid or no free */
|
||||
if (current_invokeID == 0) {
|
||||
current_invokeID = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* found! This invokeID is already used */
|
||||
/* try next one */
|
||||
current_invokeID++;
|
||||
/* skip zero - we treat that internally as invalid or no free */
|
||||
if (current_invokeID == 0) {
|
||||
current_invokeID = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return invokeID;
|
||||
}
|
||||
|
||||
void tsm_set_confirmed_unsegmented_transaction(uint8_t invokeID,
|
||||
BACNET_ADDRESS * dest, BACNET_NPDU_DATA * ndpu_data,
|
||||
uint8_t * apdu, uint16_t apdu_len)
|
||||
{
|
||||
uint16_t j = 0;
|
||||
uint8_t index;
|
||||
|
||||
if (invokeID) {
|
||||
index = tsm_find_invokeID_index(invokeID);
|
||||
if (index < MAX_TSM_TRANSACTIONS) {
|
||||
/* assign the transaction */
|
||||
TSM_List[index].state = TSM_STATE_AWAIT_CONFIRMATION;
|
||||
TSM_List[index].RetryCount = Device_Number_Of_APDU_Retries();
|
||||
/* start the timer */
|
||||
TSM_List[index].RequestTimer = Device_APDU_Timeout();
|
||||
/* copy the data */
|
||||
for (j = 0; j < apdu_len; j++) {
|
||||
TSM_List[index].apdu[j] = apdu[j];
|
||||
}
|
||||
TSM_List[index].apdu_len = apdu_len;
|
||||
npdu_copy_data(&TSM_List[index].npdu_data, ndpu_data);
|
||||
bacnet_address_copy(&TSM_List[index].dest, dest);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* used to retrieve the transaction payload */
|
||||
/* if we wanted to find out what we sent (i.e. when we get an ack) */
|
||||
bool tsm_get_transaction_pdu(uint8_t invokeID,
|
||||
BACNET_ADDRESS * dest, BACNET_NPDU_DATA * ndpu_data,
|
||||
uint8_t * apdu, uint16_t * apdu_len)
|
||||
{
|
||||
uint16_t j = 0;
|
||||
uint8_t index;
|
||||
bool found = false;
|
||||
|
||||
if (invokeID) {
|
||||
index = tsm_find_invokeID_index(invokeID);
|
||||
/* how much checking is needed? state? dest match? just invokeID? */
|
||||
if (index < MAX_TSM_TRANSACTIONS) {
|
||||
/* FIXME: we may want to free the transaction so it doesn't timeout */
|
||||
/* retrieve the transaction */
|
||||
/* FIXME: bounds check the pdu_len? */
|
||||
*apdu_len = TSM_List[index].apdu_len;
|
||||
for (j = 0; j < *apdu_len; j++) {
|
||||
apdu[j] = TSM_List[index].apdu[j];
|
||||
}
|
||||
npdu_copy_data(ndpu_data, &TSM_List[index].npdu_data);
|
||||
bacnet_address_copy(dest, &TSM_List[index].dest);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/* called once a millisecond or slower */
|
||||
void tsm_timer_milliseconds(uint16_t milliseconds)
|
||||
{
|
||||
unsigned i = 0; /* counter */
|
||||
int bytes_sent = 0;
|
||||
|
||||
for (i = 0; i < MAX_TSM_TRANSACTIONS; i++) {
|
||||
if (TSM_List[i].state == TSM_STATE_AWAIT_CONFIRMATION) {
|
||||
if (TSM_List[i].RequestTimer > milliseconds)
|
||||
TSM_List[i].RequestTimer -= milliseconds;
|
||||
else
|
||||
TSM_List[i].RequestTimer = 0;
|
||||
/* timeout. retry? */
|
||||
if (TSM_List[i].RequestTimer == 0) {
|
||||
TSM_List[i].RetryCount--;
|
||||
TSM_List[i].RequestTimer = Device_APDU_Timeout();
|
||||
if (TSM_List[i].RetryCount) {
|
||||
bytes_sent = datalink_send_pdu(&TSM_List[i].dest,
|
||||
&TSM_List[i].npdu_data,
|
||||
&TSM_List[i].apdu[0], TSM_List[i].apdu_len);
|
||||
} else {
|
||||
/* note: the invoke id has not been cleared yet
|
||||
and this indicates a failed message:
|
||||
IDLE and a valid invoke id */
|
||||
TSM_List[i].state = TSM_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* frees the invokeID and sets its state to IDLE */
|
||||
void tsm_free_invoke_id(uint8_t invokeID)
|
||||
{
|
||||
uint8_t index;
|
||||
|
||||
index = tsm_find_invokeID_index(invokeID);
|
||||
if (index < MAX_TSM_TRANSACTIONS) {
|
||||
TSM_List[index].state = TSM_STATE_IDLE;
|
||||
TSM_List[index].InvokeID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* check if the invoke ID has been made free */
|
||||
bool tsm_invoke_id_free(uint8_t invokeID)
|
||||
{
|
||||
bool status = true;
|
||||
uint8_t index;
|
||||
|
||||
index = tsm_find_invokeID_index(invokeID);
|
||||
if (index < MAX_TSM_TRANSACTIONS)
|
||||
status = false;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* see if the invoke ID has failed get a confirmation */
|
||||
bool tsm_invoke_id_failed(uint8_t invokeID)
|
||||
{
|
||||
bool status = false;
|
||||
uint8_t index;
|
||||
|
||||
index = tsm_find_invokeID_index(invokeID);
|
||||
if (index < MAX_TSM_TRANSACTIONS) {
|
||||
/* a valid invoke ID and the state is IDLE is a
|
||||
message that failed to confirm */
|
||||
if (TSM_List[index].state == TSM_STATE_IDLE)
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
/* flag to send an I-Am */
|
||||
bool I_Am_Request = true;
|
||||
|
||||
/* dummy function stubs */
|
||||
int datalink_send_pdu(BACNET_ADDRESS * dest,
|
||||
BACNET_NPDU_DATA * npdu_data, uint8_t * pdu, unsigned pdu_len)
|
||||
{
|
||||
(void) dest;
|
||||
(void) npdu_data;
|
||||
(void) pdu;
|
||||
(void) pdu_len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* dummy function stubs */
|
||||
void datalink_get_broadcast_address(BACNET_ADDRESS * dest)
|
||||
{
|
||||
(void) dest;
|
||||
}
|
||||
|
||||
void testTSM(Test * pTest)
|
||||
{
|
||||
/* FIXME: add some unit testing... */
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_TSM
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet TSM", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testTSM);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_TSM */
|
||||
#endif /* TEST */
|
||||
#endif /* MAX_TSM_TRANSACTIONS */
|
||||
@@ -0,0 +1,36 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2007 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include "version.h"
|
||||
|
||||
char *BACnet_Version = BACNET_VERSION_TEXT;
|
||||
@@ -0,0 +1,248 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2006 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "whohas.h"
|
||||
|
||||
/* encode service - use -1 for limit for unlimited */
|
||||
|
||||
int whohas_encode_apdu(uint8_t * apdu, BACNET_WHO_HAS_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu && data) {
|
||||
apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = SERVICE_UNCONFIRMED_WHO_HAS; /* service choice */
|
||||
apdu_len = 2;
|
||||
/* optional limits - must be used as a pair */
|
||||
if ((data->low_limit >= 0)
|
||||
&& (data->low_limit <= BACNET_MAX_INSTANCE)
|
||||
&& (data->high_limit >= 0)
|
||||
&& (data->high_limit <= BACNET_MAX_INSTANCE)) {
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 0,
|
||||
data->low_limit);
|
||||
apdu_len += len;
|
||||
len = encode_context_unsigned(&apdu[apdu_len],
|
||||
1, data->high_limit);
|
||||
apdu_len += len;
|
||||
}
|
||||
if (data->object_name) {
|
||||
len = encode_context_character_string(&apdu[apdu_len],
|
||||
3, &data->object.name);
|
||||
apdu_len += len;
|
||||
} else {
|
||||
len = encode_context_object_id(&apdu[apdu_len],
|
||||
2,
|
||||
data->object.identifier.type,
|
||||
data->object.identifier.instance);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int whohas_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_WHO_HAS_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
uint32_t decoded_value = 0; /* for decoding */
|
||||
int decoded_type = 0; /* for decoding */
|
||||
|
||||
if (apdu_len && data) {
|
||||
/* optional limits - must be used as a pair */
|
||||
if (decode_is_context_tag(&apdu[len], 0)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
if (decoded_value <= BACNET_MAX_INSTANCE)
|
||||
data->low_limit = decoded_value;
|
||||
if (!decode_is_context_tag(&apdu[len], 1))
|
||||
return -1;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
if (decoded_value <= BACNET_MAX_INSTANCE)
|
||||
data->high_limit = decoded_value;
|
||||
} else {
|
||||
data->low_limit = -1;
|
||||
data->high_limit = -1;
|
||||
}
|
||||
/* object id */
|
||||
if (decode_is_context_tag(&apdu[len], 2)) {
|
||||
data->object_name = false;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_object_id(&apdu[len], &decoded_type,
|
||||
&data->object.identifier.instance);
|
||||
data->object.identifier.type = decoded_type;
|
||||
}
|
||||
/* object name */
|
||||
else if (decode_is_context_tag(&apdu[len], 3)) {
|
||||
data->object_name = true;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_character_string(&apdu[len], len_value,
|
||||
&data->object.name);
|
||||
}
|
||||
/* missing required parameters */
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
int whohas_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_WHO_HAS_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
if (apdu[1] != SERVICE_UNCONFIRMED_WHO_HAS)
|
||||
return -1;
|
||||
/* optional limits - must be used as a pair */
|
||||
if (apdu_len > 2) {
|
||||
len = whohas_decode_service_request(&apdu[2], apdu_len - 2, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void testWhoHasData(Test * pTest, BACNET_WHO_HAS_DATA * data)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
BACNET_WHO_HAS_DATA test_data;
|
||||
|
||||
len = whohas_encode_apdu(&apdu[0], data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = whohas_decode_apdu(&apdu[0], apdu_len, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_data.low_limit == data->low_limit);
|
||||
ct_test(pTest, test_data.high_limit == data->high_limit);
|
||||
ct_test(pTest, test_data.object_name == data->object_name);
|
||||
/* Object ID */
|
||||
if (data->object_name == false) {
|
||||
ct_test(pTest, test_data.object.identifier.type ==
|
||||
data->object.identifier.type);
|
||||
ct_test(pTest, test_data.object.identifier.instance ==
|
||||
data->object.identifier.instance);
|
||||
}
|
||||
/* Object Name */
|
||||
else {
|
||||
ct_test(pTest, characterstring_same(&test_data.object.name,
|
||||
&data->object.name));
|
||||
}
|
||||
}
|
||||
|
||||
void testWhoHas(Test * pTest)
|
||||
{
|
||||
BACNET_WHO_HAS_DATA data;
|
||||
|
||||
data.low_limit = -1;
|
||||
data.high_limit = -1;
|
||||
data.object_name = false;
|
||||
data.object.identifier.type = OBJECT_ANALOG_INPUT;
|
||||
data.object.identifier.instance = 1;
|
||||
testWhoHasData(pTest, &data);
|
||||
|
||||
for (data.low_limit = 0;
|
||||
data.low_limit <= BACNET_MAX_INSTANCE;
|
||||
data.low_limit += (BACNET_MAX_INSTANCE / 4)) {
|
||||
for (data.high_limit = 0;
|
||||
data.high_limit <= BACNET_MAX_INSTANCE;
|
||||
data.high_limit += (BACNET_MAX_INSTANCE / 4)) {
|
||||
data.object_name = false;
|
||||
for (data.object.identifier.type = OBJECT_ANALOG_INPUT;
|
||||
data.object.identifier.type <= MAX_BACNET_OBJECT_TYPE;
|
||||
data.object.identifier.type++) {
|
||||
for (data.object.identifier.instance = 1;
|
||||
data.object.identifier.instance <= BACNET_MAX_INSTANCE;
|
||||
data.object.identifier.instance <<= 1) {
|
||||
testWhoHasData(pTest, &data);
|
||||
}
|
||||
}
|
||||
data.object_name = true;
|
||||
characterstring_init_ansi(&data.object.name, "patricia");
|
||||
testWhoHasData(pTest, &data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TEST_WHOHAS
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Who-Has", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testWhoHas);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_WHOIS */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,182 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
|
||||
/* encode I-Am service - use -1 for limit if you want unlimited */
|
||||
int whois_encode_apdu(uint8_t * apdu,
|
||||
int32_t low_limit, int32_t high_limit)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = SERVICE_UNCONFIRMED_WHO_IS; /* service choice */
|
||||
apdu_len = 2;
|
||||
/* optional limits - must be used as a pair */
|
||||
if ((low_limit >= 0) && (low_limit <= BACNET_MAX_INSTANCE) &&
|
||||
(high_limit >= 0) && (high_limit <= BACNET_MAX_INSTANCE)) {
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 0, low_limit);
|
||||
apdu_len += len;
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 1, high_limit);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int whois_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, int32_t * pLow_limit, int32_t * pHigh_limit)
|
||||
{
|
||||
int len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
uint32_t decoded_value = 0;
|
||||
|
||||
/* optional limits - must be used as a pair */
|
||||
if (apdu_len) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
if (tag_number != 0)
|
||||
return -1;
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
if (decoded_value <= BACNET_MAX_INSTANCE) {
|
||||
if (pLow_limit)
|
||||
*pLow_limit = decoded_value;
|
||||
}
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
if (tag_number != 1)
|
||||
return -1;
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
if (decoded_value <= BACNET_MAX_INSTANCE) {
|
||||
if (pHigh_limit)
|
||||
*pHigh_limit = decoded_value;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
int whois_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len, int32_t * pLow_limit, int32_t * pHigh_limit)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
if (apdu[1] != SERVICE_UNCONFIRMED_WHO_IS)
|
||||
return -1;
|
||||
/* optional limits - must be used as a pair */
|
||||
if (apdu_len > 2) {
|
||||
len = whois_decode_service_request(&apdu[2],
|
||||
apdu_len - 2, pLow_limit, pHigh_limit);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void testWhoIs(Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
int32_t low_limit = -1;
|
||||
int32_t high_limit = -1;
|
||||
int32_t test_low_limit = -1;
|
||||
int32_t test_high_limit = -1;
|
||||
|
||||
len = whois_encode_apdu(&apdu[0], low_limit, high_limit);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len = whois_decode_apdu(&apdu[0],
|
||||
apdu_len, &test_low_limit, &test_high_limit);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_low_limit == low_limit);
|
||||
ct_test(pTest, test_high_limit == high_limit);
|
||||
|
||||
for (low_limit = 0;
|
||||
low_limit <= BACNET_MAX_INSTANCE;
|
||||
low_limit += (BACNET_MAX_INSTANCE / 4)) {
|
||||
for (high_limit = 0;
|
||||
high_limit <= BACNET_MAX_INSTANCE;
|
||||
high_limit += (BACNET_MAX_INSTANCE / 4)) {
|
||||
len = whois_encode_apdu(&apdu[0], low_limit, high_limit);
|
||||
apdu_len = len;
|
||||
ct_test(pTest, len != 0);
|
||||
len = whois_decode_apdu(&apdu[0],
|
||||
apdu_len, &test_low_limit, &test_high_limit);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_low_limit == low_limit);
|
||||
ct_test(pTest, test_high_limit == high_limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TEST_WHOIS
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Who-Is", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testWhoIs);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_WHOIS */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,372 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2005 Steve Karg
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to:
|
||||
The Free Software Foundation, Inc.
|
||||
59 Temple Place - Suite 330
|
||||
Boston, MA 02111-1307, USA.
|
||||
|
||||
As a special exception, if other files instantiate templates or
|
||||
use macros or inline functions from this file, or you compile
|
||||
this file and link it with other works to produce a work based
|
||||
on this file, this file does not by itself cause the resulting
|
||||
work to be covered by the GNU General Public License. However
|
||||
the source code for this file must still be made available in
|
||||
accordance with section (3) of the GNU General Public License.
|
||||
|
||||
This exception does not invalidate any other reasons why a work
|
||||
based on this file might be covered by the GNU General Public
|
||||
License.
|
||||
-------------------------------------------
|
||||
####COPYRIGHTEND####*/
|
||||
#include <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "device.h"
|
||||
#include "wp.h"
|
||||
|
||||
/* encode service */
|
||||
int wp_encode_apdu(uint8_t * apdu,
|
||||
uint8_t invoke_id, BACNET_WRITE_PROPERTY_DATA * data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
int len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] =
|
||||
encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted());
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_WRITE_PROPERTY; /* service choice */
|
||||
apdu_len = 4;
|
||||
len = encode_context_object_id(&apdu[apdu_len], 0,
|
||||
data->object_type, data->object_instance);
|
||||
apdu_len += len;
|
||||
len = encode_context_enumerated(&apdu[apdu_len], 1,
|
||||
data->object_property);
|
||||
apdu_len += len;
|
||||
/* optional array index; ALL is -1 which is assumed when missing */
|
||||
if (data->array_index != BACNET_ARRAY_ALL) {
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 2,
|
||||
data->array_index);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* propertyValue */
|
||||
len = encode_opening_tag(&apdu[apdu_len], 3);
|
||||
apdu_len += len;
|
||||
for (len = 0; len < data->application_data_len; len++) {
|
||||
apdu[apdu_len + len] = data->application_data[len];
|
||||
}
|
||||
apdu_len += data->application_data_len;
|
||||
len = encode_closing_tag(&apdu[apdu_len], 3);
|
||||
apdu_len += len;
|
||||
/* optional priority - 0 if not set, 1..16 if set */
|
||||
if (data->priority != BACNET_NO_PRIORITY) {
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 4,
|
||||
data->priority);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
/* FIXME: there could be various error messages returned
|
||||
using unique values less than zero */
|
||||
int wp_decode_service_request(uint8_t * apdu,
|
||||
unsigned apdu_len, BACNET_WRITE_PROPERTY_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
int tag_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
int type = 0; /* for decoding */
|
||||
int property = 0; /* for decoding */
|
||||
uint32_t unsigned_value = 0;
|
||||
int i = 0; /* loop counter */
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && data) {
|
||||
/* Tag 0: Object ID */
|
||||
if (!decode_is_context_tag(&apdu[len++], 0))
|
||||
return -1;
|
||||
len += decode_object_id(&apdu[len], &type, &data->object_instance);
|
||||
data->object_type = (BACNET_OBJECT_TYPE)type;
|
||||
/* Tag 1: Property ID */
|
||||
len += decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
if (tag_number != 1)
|
||||
return -1;
|
||||
len += decode_enumerated(&apdu[len], len_value_type, &property);
|
||||
data->object_property = (BACNET_PROPERTY_ID)property;
|
||||
/* Tag 2: Optional Array Index */
|
||||
/* note: decode without incrementing len so we can check for opening tag */
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
if (tag_number == 2) {
|
||||
len += tag_len;
|
||||
len += decode_unsigned(&apdu[len], len_value_type,
|
||||
&unsigned_value);
|
||||
data->array_index = unsigned_value;
|
||||
} else
|
||||
data->array_index = BACNET_ARRAY_ALL;
|
||||
/* Tag 3: opening context tag */
|
||||
if (!decode_is_opening_tag_number(&apdu[len], 3))
|
||||
return -1;
|
||||
/* determine the length of the data blob */
|
||||
data->application_data_len = bacapp_data_len(&apdu[len],
|
||||
apdu_len - len, (BACNET_PROPERTY_ID)property);
|
||||
/* a tag number of 3 is not extended so only one octet */
|
||||
len++;
|
||||
/* copy the data from the APDU */
|
||||
for (i = 0; i < data->application_data_len; i++) {
|
||||
data->application_data[i] = apdu[len + i];
|
||||
}
|
||||
/* add on the data length */
|
||||
len += data->application_data_len;
|
||||
if (!decode_is_closing_tag_number(&apdu[len], 3))
|
||||
return -2;
|
||||
/* a tag number of 3 is not extended so only one octet */
|
||||
len++;
|
||||
/* Tag 4: optional Priority - assumed MAX if not explicitly set */
|
||||
data->priority = BACNET_MAX_PRIORITY;
|
||||
if ((unsigned) len < apdu_len) {
|
||||
tag_len = decode_tag_number_and_value(&apdu[len],
|
||||
&tag_number, &len_value_type);
|
||||
if (tag_number == 4) {
|
||||
len += tag_len;
|
||||
len =
|
||||
decode_unsigned(&apdu[len], len_value_type,
|
||||
&unsigned_value);
|
||||
if ((unsigned_value >= BACNET_MIN_PRIORITY)
|
||||
&& (unsigned_value <= BACNET_MAX_PRIORITY)) {
|
||||
data->priority = (uint8_t) unsigned_value;
|
||||
} else
|
||||
return -5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
int wp_decode_apdu(uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id, BACNET_WRITE_PROPERTY_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_CONFIRMED_SERVICE_REQUEST)
|
||||
return -1;
|
||||
/* apdu[1] = encode_max_segs_max_apdu(0, Device_Max_APDU_Length_Accepted()); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_WRITE_PROPERTY)
|
||||
return -1;
|
||||
offset = 4;
|
||||
|
||||
if (apdu_len > offset) {
|
||||
len = wp_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void testWritePropertyTag(Test * pTest,
|
||||
BACNET_APPLICATION_DATA_VALUE * value)
|
||||
{
|
||||
BACNET_WRITE_PROPERTY_DATA data = { 0 };
|
||||
BACNET_WRITE_PROPERTY_DATA test_data = { 0 };
|
||||
BACNET_APPLICATION_DATA_VALUE test_value;
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 128;
|
||||
uint8_t test_invoke_id = 0;
|
||||
|
||||
data.application_data_len =
|
||||
bacapp_encode_application_data(&data.application_data[0], value);
|
||||
len = wp_encode_apdu(&apdu[0], invoke_id, &data);
|
||||
ct_test(pTest, len != 0);
|
||||
/* decode the data */
|
||||
apdu_len = len;
|
||||
len = wp_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_data.object_type == data.object_type);
|
||||
ct_test(pTest, test_data.object_instance == data.object_instance);
|
||||
ct_test(pTest, test_data.object_property == data.object_property);
|
||||
ct_test(pTest, test_data.array_index == data.array_index);
|
||||
/* decode the application value of the request */
|
||||
len = bacapp_decode_application_data(test_data.application_data,
|
||||
test_data.application_data_len, &test_value);
|
||||
ct_test(pTest, test_value.tag == value->tag);
|
||||
switch (test_value.tag) {
|
||||
case BACNET_APPLICATION_TAG_NULL:
|
||||
break;
|
||||
case BACNET_APPLICATION_TAG_BOOLEAN:
|
||||
ct_test(pTest, test_value.type.Boolean == value->type.Boolean);
|
||||
break;
|
||||
case BACNET_APPLICATION_TAG_UNSIGNED_INT:
|
||||
ct_test(pTest, test_value.type.Unsigned_Int ==
|
||||
value->type.Unsigned_Int);
|
||||
break;
|
||||
case BACNET_APPLICATION_TAG_SIGNED_INT:
|
||||
ct_test(pTest, test_value.type.Signed_Int ==
|
||||
value->type.Signed_Int);
|
||||
break;
|
||||
case BACNET_APPLICATION_TAG_REAL:
|
||||
ct_test(pTest, test_value.type.Real == value->type.Real);
|
||||
break;
|
||||
case BACNET_APPLICATION_TAG_ENUMERATED:
|
||||
ct_test(pTest, test_value.type.Enumerated ==
|
||||
value->type.Enumerated);
|
||||
break;
|
||||
case BACNET_APPLICATION_TAG_DATE:
|
||||
ct_test(pTest, test_value.type.Date.year == value->type.Date.year);
|
||||
ct_test(pTest, test_value.type.Date.month ==
|
||||
value->type.Date.month);
|
||||
ct_test(pTest, test_value.type.Date.day == value->type.Date.day);
|
||||
ct_test(pTest, test_value.type.Date.wday == value->type.Date.wday);
|
||||
break;
|
||||
case BACNET_APPLICATION_TAG_TIME:
|
||||
ct_test(pTest, test_value.type.Time.hour == value->type.Time.hour);
|
||||
ct_test(pTest, test_value.type.Time.min == value->type.Time.min);
|
||||
ct_test(pTest, test_value.type.Time.sec == value->type.Time.sec);
|
||||
ct_test(pTest, test_value.type.Time.hundredths ==
|
||||
value->type.Time.hundredths);
|
||||
break;
|
||||
case BACNET_APPLICATION_TAG_OBJECT_ID:
|
||||
ct_test(pTest, test_value.type.Object_Id.type ==
|
||||
value->type.Object_Id.type);
|
||||
ct_test(pTest, test_value.type.Object_Id.instance ==
|
||||
value->type.Object_Id.instance);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void testWriteProperty(Test * pTest)
|
||||
{
|
||||
BACNET_APPLICATION_DATA_VALUE value;
|
||||
|
||||
value.tag = BACNET_APPLICATION_TAG_NULL;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
|
||||
value.tag = BACNET_APPLICATION_TAG_BOOLEAN;
|
||||
value.type.Boolean = true;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Boolean = false;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
|
||||
value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT;
|
||||
value.type.Unsigned_Int = 0;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Unsigned_Int = 0xFFFF;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Unsigned_Int = 0xFFFFFFFF;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
|
||||
value.tag = BACNET_APPLICATION_TAG_SIGNED_INT;
|
||||
value.type.Signed_Int = 0;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Signed_Int = -1;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Signed_Int = 32768;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Signed_Int = -32768;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
|
||||
value.tag = BACNET_APPLICATION_TAG_REAL;
|
||||
value.type.Real = 0.0;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Real = -1.0;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Real = 1.0;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Real = 3.14159;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Real = -3.14159;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
|
||||
value.tag = BACNET_APPLICATION_TAG_ENUMERATED;
|
||||
value.type.Enumerated = 0;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Enumerated = 0xFFFF;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Enumerated = 0xFFFFFFFF;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
|
||||
value.tag = BACNET_APPLICATION_TAG_DATE;
|
||||
value.type.Date.year = 2005;
|
||||
value.type.Date.month = 5;
|
||||
value.type.Date.day = 22;
|
||||
value.type.Date.wday = 1;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
|
||||
value.tag = BACNET_APPLICATION_TAG_TIME;
|
||||
value.type.Time.hour = 23;
|
||||
value.type.Time.min = 59;
|
||||
value.type.Time.sec = 59;
|
||||
value.type.Time.hundredths = 12;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
|
||||
value.tag = BACNET_APPLICATION_TAG_OBJECT_ID;
|
||||
value.type.Object_Id.type = OBJECT_ANALOG_INPUT;
|
||||
value.type.Object_Id.instance = 0;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
value.type.Object_Id.type = OBJECT_LIFE_SAFETY_ZONE;
|
||||
value.type.Object_Id.instance = BACNET_MAX_INSTANCE;
|
||||
testWritePropertyTag(pTest, &value);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_WRITE_PROPERTY
|
||||
uint16_t Device_Max_APDU_Length_Accepted(void)
|
||||
{
|
||||
return MAX_APDU;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet WriteProperty", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testWriteProperty);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_WRITE_PROPERTY */
|
||||
#endif /* TEST */
|
||||
Reference in New Issue
Block a user