adjust root folder
This commit is contained in:
+255
@@ -0,0 +1,255 @@
|
||||
/*####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 "abort.h"
|
||||
|
||||
/** @file abort.c Abort Encoding/Decoding */
|
||||
/* Helper function to avoid needing additional entries in service data structures
|
||||
* when passing back abort status.
|
||||
* Convert from error code to abort code.
|
||||
* Anything not defined converts to ABORT_REASON_OTHER.
|
||||
* Will need reworking if it is required to return proprietary abort codes.
|
||||
*/
|
||||
BACNET_ABORT_REASON abort_convert_error_code(
|
||||
BACNET_ERROR_CODE error_code)
|
||||
{
|
||||
BACNET_ABORT_REASON abort_code = ABORT_REASON_OTHER;
|
||||
|
||||
switch (error_code) {
|
||||
case ERROR_CODE_ABORT_BUFFER_OVERFLOW:
|
||||
abort_code = ABORT_REASON_BUFFER_OVERFLOW;
|
||||
break;
|
||||
case ERROR_CODE_ABORT_INVALID_APDU_IN_THIS_STATE:
|
||||
abort_code = ABORT_REASON_INVALID_APDU_IN_THIS_STATE;
|
||||
break;
|
||||
case ERROR_CODE_ABORT_PREEMPTED_BY_HIGHER_PRIORITY_TASK:
|
||||
abort_code = ABORT_REASON_PREEMPTED_BY_HIGHER_PRIORITY_TASK;
|
||||
break;
|
||||
case ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED:
|
||||
abort_code = ABORT_REASON_SEGMENTATION_NOT_SUPPORTED;
|
||||
break;
|
||||
case ERROR_CODE_ABORT_PROPRIETARY:
|
||||
abort_code = ABORT_REASON_PROPRIETARY_FIRST;
|
||||
break;
|
||||
case ERROR_CODE_ABORT_OTHER:
|
||||
default:
|
||||
abort_code = ABORT_REASON_OTHER;
|
||||
break;
|
||||
}
|
||||
|
||||
return (abort_code);
|
||||
}
|
||||
|
||||
/* encode service */
|
||||
int abort_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
uint8_t abort_reason,
|
||||
bool server)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
if (server)
|
||||
apdu[0] = PDU_TYPE_ABORT | 1;
|
||||
else
|
||||
apdu[0] = PDU_TYPE_ABORT;
|
||||
apdu[1] = invoke_id;
|
||||
apdu[2] = abort_reason;
|
||||
apdu_len = 3;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
#if !BACNET_SVC_SERVER
|
||||
/* decode the service request only */
|
||||
int abort_decode_service_request(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id,
|
||||
uint8_t * abort_reason)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (apdu_len > 0) {
|
||||
if (invoke_id)
|
||||
*invoke_id = apdu[0];
|
||||
if (abort_reason)
|
||||
*abort_reason = apdu[1];
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
/* decode the whole APDU - mainly used for unit testing */
|
||||
int abort_decode_apdu(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id,
|
||||
uint8_t * abort_reason,
|
||||
bool * server)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (!apdu)
|
||||
return -1;
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu_len > 0) {
|
||||
if ((apdu[0] & 0xF0) != PDU_TYPE_ABORT)
|
||||
return -1;
|
||||
if (apdu[0] & 1)
|
||||
*server = true;
|
||||
else
|
||||
*server = false;
|
||||
if (apdu_len > 1) {
|
||||
len =
|
||||
abort_decode_service_request(&apdu[1], apdu_len - 1, invoke_id,
|
||||
abort_reason);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void testAbortAPDU(
|
||||
Test * pTest,
|
||||
uint8_t invoke_id,
|
||||
uint8_t abort_reason,
|
||||
bool server)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t test_invoke_id = 0;
|
||||
uint8_t test_abort_reason = 0;
|
||||
bool test_server = false;
|
||||
|
||||
len = abort_encode_apdu(&apdu[0], invoke_id, abort_reason, server);
|
||||
apdu_len = len;
|
||||
ct_test(pTest, len != 0);
|
||||
len =
|
||||
abort_decode_apdu(&apdu[0], apdu_len, &test_invoke_id,
|
||||
&test_abort_reason, &test_server);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, test_abort_reason == abort_reason);
|
||||
ct_test(pTest, test_server == server);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void testAbort(
|
||||
Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 0;
|
||||
uint8_t test_invoke_id = 0;
|
||||
uint8_t abort_reason = 0;
|
||||
uint8_t test_abort_reason = 0;
|
||||
bool server = false;
|
||||
bool test_server = false;
|
||||
|
||||
len = abort_encode_apdu(&apdu[0], invoke_id, abort_reason, server);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
len =
|
||||
abort_decode_apdu(&apdu[0], apdu_len, &test_invoke_id,
|
||||
&test_abort_reason, &test_server);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, test_abort_reason == abort_reason);
|
||||
ct_test(pTest, test_server == server);
|
||||
|
||||
/* change type to get negative response */
|
||||
apdu[0] = PDU_TYPE_REJECT;
|
||||
len =
|
||||
abort_decode_apdu(&apdu[0], apdu_len, &test_invoke_id,
|
||||
&test_abort_reason, &test_server);
|
||||
ct_test(pTest, len == -1);
|
||||
|
||||
/* test NULL APDU */
|
||||
len =
|
||||
abort_decode_apdu(NULL, apdu_len, &test_invoke_id, &test_abort_reason,
|
||||
&test_server);
|
||||
ct_test(pTest, len == -1);
|
||||
|
||||
/* force a zero length */
|
||||
len =
|
||||
abort_decode_apdu(&apdu[0], 0, &test_invoke_id, &test_abort_reason,
|
||||
&test_server);
|
||||
ct_test(pTest, len == 0);
|
||||
|
||||
/* check them all... */
|
||||
for (invoke_id = 0; invoke_id < 255; invoke_id++) {
|
||||
for (abort_reason = 0; abort_reason < 255; abort_reason++) {
|
||||
testAbortAPDU(pTest, invoke_id, abort_reason, false);
|
||||
testAbortAPDU(pTest, invoke_id, abort_reason, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TEST_ABORT
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Abort", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testAbort);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_ABORT */
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,185 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2015 Nikola Jelic <nikola.jelic@euroicc.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*********************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "access_rule.h"
|
||||
#include "bacdcode.h"
|
||||
|
||||
int bacapp_encode_access_rule(
|
||||
uint8_t * apdu,
|
||||
BACNET_ACCESS_RULE * rule)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_context_enumerated(&apdu[0], 0, rule->time_range_specifier);
|
||||
apdu_len += len;
|
||||
|
||||
if (rule->time_range_specifier == TIME_RANGE_SPECIFIER_SPECIFIED) {
|
||||
len =
|
||||
bacapp_encode_context_device_obj_property_ref(&apdu[apdu_len], 1,
|
||||
&rule->time_range);
|
||||
if (len > 0)
|
||||
apdu_len += len;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
len =
|
||||
encode_context_enumerated(&apdu[apdu_len], 2,
|
||||
rule->location_specifier);
|
||||
apdu_len += len;
|
||||
|
||||
if (rule->location_specifier == LOCATION_SPECIFIER_SPECIFIED) {
|
||||
len =
|
||||
bacapp_encode_context_device_obj_property_ref(&apdu[apdu_len], 3,
|
||||
&rule->location);
|
||||
if (len > 0)
|
||||
apdu_len += len;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
len = encode_context_boolean(&apdu[apdu_len], 4, rule->enable);
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_encode_context_access_rule(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_ACCESS_RULE * rule)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_opening_tag(&apdu[apdu_len], tag_number);
|
||||
apdu_len += len;
|
||||
|
||||
len = bacapp_encode_access_rule(&apdu[apdu_len], rule);
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_closing_tag(&apdu[apdu_len], tag_number);
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_decode_access_rule(
|
||||
uint8_t * apdu,
|
||||
BACNET_ACCESS_RULE * rule)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 0)) {
|
||||
len =
|
||||
decode_context_enumerated(&apdu[apdu_len], 0,
|
||||
&rule->time_range_specifier);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
if (rule->time_range_specifier == TIME_RANGE_SPECIFIER_SPECIFIED) {
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 1)) {
|
||||
len =
|
||||
bacapp_decode_context_device_obj_property_ref(&apdu[apdu_len],
|
||||
1, &rule->time_range);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 2)) {
|
||||
len =
|
||||
decode_context_enumerated(&apdu[apdu_len], 2,
|
||||
&rule->location_specifier);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
if (rule->location_specifier == LOCATION_SPECIFIER_SPECIFIED) {
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 3)) {
|
||||
len =
|
||||
bacapp_decode_context_device_obj_property_ref(&apdu[apdu_len],
|
||||
3, &rule->location);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 4)) {
|
||||
len = decode_context_boolean2(&apdu[apdu_len], 4, &rule->enable);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_decode_context_access_rule(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_ACCESS_RULE * rule)
|
||||
{
|
||||
int len = 0;
|
||||
int section_length;
|
||||
|
||||
if (decode_is_opening_tag_number(&apdu[len], tag_number)) {
|
||||
len++;
|
||||
section_length = bacapp_decode_access_rule(&apdu[len], rule);
|
||||
|
||||
if (section_length == -1) {
|
||||
len = -1;
|
||||
} else {
|
||||
len += section_length;
|
||||
if (decode_is_closing_tag_number(&apdu[len], tag_number)) {
|
||||
len++;
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
+1171
File diff suppressed because it is too large
Load Diff
+281
@@ -0,0 +1,281 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2009 John Minack
|
||||
|
||||
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 "alarm_ack.h"
|
||||
|
||||
/** @file alarm_ack.c Handles Event Notifications (ACKs) */
|
||||
|
||||
/***************************************************
|
||||
**
|
||||
** Creates an Unconfirmed Event Notification APDU
|
||||
**
|
||||
****************************************************/
|
||||
int alarm_ack_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_ALARM_ACK_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_ACKNOWLEDGE_ALARM; /* service choice */
|
||||
apdu_len = 4;
|
||||
|
||||
len = alarm_ack_encode_service_request(&apdu[apdu_len], data);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
|
||||
/***************************************************
|
||||
**
|
||||
** Encodes the service data part of Event Notification
|
||||
**
|
||||
****************************************************/
|
||||
int alarm_ack_encode_service_request(
|
||||
uint8_t * apdu,
|
||||
BACNET_ALARM_ACK_DATA * data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 0,
|
||||
data->ackProcessIdentifier);
|
||||
apdu_len += len;
|
||||
|
||||
len =
|
||||
encode_context_object_id(&apdu[apdu_len], 1,
|
||||
(int) data->eventObjectIdentifier.type,
|
||||
data->eventObjectIdentifier.instance);
|
||||
apdu_len += len;
|
||||
|
||||
len =
|
||||
encode_context_enumerated(&apdu[apdu_len], 2,
|
||||
data->eventStateAcked);
|
||||
apdu_len += len;
|
||||
|
||||
len =
|
||||
bacapp_encode_context_timestamp(&apdu[apdu_len], 3,
|
||||
&data->eventTimeStamp);
|
||||
apdu_len += len;
|
||||
|
||||
len =
|
||||
encode_context_character_string(&apdu[apdu_len], 4,
|
||||
&data->ackSource);
|
||||
apdu_len += len;
|
||||
|
||||
len =
|
||||
bacapp_encode_context_timestamp(&apdu[apdu_len], 5,
|
||||
&data->ackTimeStamp);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
|
||||
/***************************************************
|
||||
**
|
||||
** Decodes the service data part of Event Notification
|
||||
**
|
||||
****************************************************/
|
||||
int alarm_ack_decode_service_request(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_ALARM_ACK_DATA * data)
|
||||
{
|
||||
int len = 0;
|
||||
int section_len;
|
||||
uint32_t enumValue;
|
||||
|
||||
/* unused parameter */
|
||||
apdu_len = apdu_len;
|
||||
|
||||
if (-1 == (section_len =
|
||||
decode_context_unsigned(&apdu[len], 0,
|
||||
&data->ackProcessIdentifier))) {
|
||||
return -1;
|
||||
}
|
||||
len += section_len;
|
||||
|
||||
if (-1 == (section_len =
|
||||
decode_context_object_id(&apdu[len], 1,
|
||||
&data->eventObjectIdentifier.type,
|
||||
&data->eventObjectIdentifier.instance))) {
|
||||
return -1;
|
||||
}
|
||||
len += section_len;
|
||||
|
||||
if (-1 == (section_len =
|
||||
decode_context_enumerated(&apdu[len], 2, &enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
data->eventStateAcked = (BACNET_EVENT_STATE) enumValue;
|
||||
len += section_len;
|
||||
|
||||
if (-1 == (section_len =
|
||||
bacapp_decode_context_timestamp(&apdu[len], 3,
|
||||
&data->eventTimeStamp))) {
|
||||
return -1;
|
||||
}
|
||||
len += section_len;
|
||||
|
||||
if (-1 == (section_len =
|
||||
decode_context_character_string(&apdu[len], 4,
|
||||
&data->ackSource))) {
|
||||
return -1;
|
||||
}
|
||||
len += section_len;
|
||||
|
||||
if (-1 == (section_len =
|
||||
bacapp_decode_context_timestamp(&apdu[len], 5,
|
||||
&data->ackTimeStamp))) {
|
||||
return -1;
|
||||
}
|
||||
len += section_len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
|
||||
void testAlarmAck(
|
||||
Test * pTest)
|
||||
{
|
||||
BACNET_ALARM_ACK_DATA testAlarmAckIn;
|
||||
BACNET_ALARM_ACK_DATA testAlarmAckOut;
|
||||
|
||||
uint8_t buffer[MAX_APDU];
|
||||
int inLen;
|
||||
int outLen;
|
||||
|
||||
testAlarmAckIn.ackProcessIdentifier = 0x1234;
|
||||
characterstring_init_ansi(&testAlarmAckIn.ackSource, "This is a test");
|
||||
testAlarmAckIn.ackTimeStamp.tag = TIME_STAMP_SEQUENCE;
|
||||
testAlarmAckIn.ackTimeStamp.value.sequenceNum = 0x4331;
|
||||
testAlarmAckIn.eventObjectIdentifier.instance = 567;
|
||||
testAlarmAckIn.eventObjectIdentifier.type = OBJECT_DEVICE;
|
||||
testAlarmAckIn.eventTimeStamp.tag = TIME_STAMP_TIME;
|
||||
testAlarmAckIn.eventTimeStamp.value.time.hour = 10;
|
||||
testAlarmAckIn.eventTimeStamp.value.time.min = 11;
|
||||
testAlarmAckIn.eventTimeStamp.value.time.sec = 12;
|
||||
testAlarmAckIn.eventTimeStamp.value.time.hundredths = 14;
|
||||
testAlarmAckIn.eventStateAcked = EVENT_STATE_OFFNORMAL;
|
||||
|
||||
memset(&testAlarmAckOut, 0, sizeof(testAlarmAckOut));
|
||||
|
||||
|
||||
inLen = alarm_ack_encode_service_request(buffer, &testAlarmAckIn);
|
||||
outLen = alarm_ack_decode_service_request(buffer, inLen, &testAlarmAckOut);
|
||||
|
||||
ct_test(pTest, inLen == outLen);
|
||||
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.ackProcessIdentifier ==
|
||||
testAlarmAckOut.ackProcessIdentifier);
|
||||
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.ackTimeStamp.tag == testAlarmAckOut.ackTimeStamp.tag);
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.ackTimeStamp.value.sequenceNum ==
|
||||
testAlarmAckOut.ackTimeStamp.value.sequenceNum);
|
||||
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.ackProcessIdentifier ==
|
||||
testAlarmAckOut.ackProcessIdentifier);
|
||||
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.eventObjectIdentifier.instance ==
|
||||
testAlarmAckOut.eventObjectIdentifier.instance);
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.eventObjectIdentifier.type ==
|
||||
testAlarmAckOut.eventObjectIdentifier.type);
|
||||
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.eventTimeStamp.tag ==
|
||||
testAlarmAckOut.eventTimeStamp.tag);
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.eventTimeStamp.value.time.hour ==
|
||||
testAlarmAckOut.eventTimeStamp.value.time.hour);
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.eventTimeStamp.value.time.min ==
|
||||
testAlarmAckOut.eventTimeStamp.value.time.min);
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.eventTimeStamp.value.time.sec ==
|
||||
testAlarmAckOut.eventTimeStamp.value.time.sec);
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.eventTimeStamp.value.time.hundredths ==
|
||||
testAlarmAckOut.eventTimeStamp.value.time.hundredths);
|
||||
|
||||
ct_test(pTest,
|
||||
testAlarmAckIn.eventStateAcked == testAlarmAckOut.eventStateAcked);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#ifdef TEST_ALARM_ACK
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Alarm Ack", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testAlarmAck);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* TEST_ALARM_ACK */
|
||||
#endif /* TEST */
|
||||
+643
@@ -0,0 +1,643 @@
|
||||
/*####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"
|
||||
|
||||
/** @file apdu.c Handles APDU services */
|
||||
|
||||
extern int Routed_Device_Service_Approval(
|
||||
BACNET_CONFIRMED_SERVICE service,
|
||||
int service_argument,
|
||||
uint8_t * apdu_buff,
|
||||
uint8_t invoke_id);
|
||||
|
||||
|
||||
/* APDU Timeout in Milliseconds */
|
||||
static uint16_t Timeout_Milliseconds = 3000;
|
||||
/* Number of APDU Retries */
|
||||
static uint8_t Number_Of_Retries = 3;
|
||||
|
||||
/* 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_PROP_CONDITIONAL,
|
||||
SERVICE_SUPPORTED_READ_PROP_MULTIPLE,
|
||||
SERVICE_SUPPORTED_WRITE_PROPERTY,
|
||||
SERVICE_SUPPORTED_WRITE_PROP_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];
|
||||
|
||||
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) {
|
||||
found = true;
|
||||
if (Confirmed_Function[i] != NULL) {
|
||||
#if BAC_ROUTING
|
||||
/* Check to see if the current Device supports this service. */
|
||||
int len =
|
||||
Routed_Device_Service_Approval(service_supported, 0,
|
||||
NULL, 0);
|
||||
if (len > 0)
|
||||
break; /* Not supported - return false */
|
||||
#endif
|
||||
|
||||
status = 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;
|
||||
}
|
||||
|
||||
/** Function to translate a SERVICE_SUPPORTED_ enum to its SERVICE_CONFIRMED_
|
||||
* or SERVICE_UNCONFIRMED_ index.
|
||||
* Useful with the bactext_confirmed_service_name() functions.
|
||||
*
|
||||
* @param service_supported [in] The SERVICE_SUPPORTED_ enum value to convert.
|
||||
* @param index [out] The SERVICE_CONFIRMED_ or SERVICE_UNCONFIRMED_ index,
|
||||
* if found.
|
||||
* @param bIsConfirmed [out] True if index is a SERVICE_CONFIRMED_ type.
|
||||
* @return True if a match was found and index and bIsConfirmed are valid.
|
||||
*/
|
||||
bool apdu_service_supported_to_index(
|
||||
BACNET_SERVICES_SUPPORTED service_supported,
|
||||
size_t * index,
|
||||
bool * bIsConfirmed)
|
||||
{
|
||||
int i = 0;
|
||||
bool found = false;
|
||||
|
||||
*bIsConfirmed = 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) {
|
||||
found = true;
|
||||
*index = (size_t) i;
|
||||
*bIsConfirmed = 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) {
|
||||
found = true;
|
||||
*index = (size_t) i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
/* Confirmed ACK Function Handlers */
|
||||
static confirmed_ack_function
|
||||
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_PROP_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] =
|
||||
(confirmed_ack_function) 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_PROP_CONDITIONAL:
|
||||
case SERVICE_CONFIRMED_READ_PROP_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] = 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] & BIT(3)) ? true : false;
|
||||
service_data->more_follows = (apdu[0] & BIT(2)) ? true : false;
|
||||
service_data->segmented_response_accepted =
|
||||
(apdu[0] & BIT(1)) ? 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;
|
||||
}
|
||||
|
||||
uint16_t apdu_timeout(
|
||||
void)
|
||||
{
|
||||
return Timeout_Milliseconds;
|
||||
}
|
||||
|
||||
void apdu_timeout_set(
|
||||
uint16_t milliseconds)
|
||||
{
|
||||
Timeout_Milliseconds = milliseconds;
|
||||
}
|
||||
|
||||
uint8_t apdu_retries(
|
||||
void)
|
||||
{
|
||||
return Number_Of_Retries;
|
||||
}
|
||||
|
||||
void apdu_retries_set(
|
||||
uint8_t value)
|
||||
{
|
||||
Number_Of_Retries = value;
|
||||
}
|
||||
|
||||
|
||||
/* When network communications are completely disabled,
|
||||
only DeviceCommunicationControl and ReinitializeDevice APDUs
|
||||
shall be processed and no messages shall be initiated.
|
||||
When the initiation of communications is disabled,
|
||||
all APDUs shall be processed and responses returned as
|
||||
required... */
|
||||
static bool apdu_confirmed_dcc_disabled(
|
||||
uint8_t service_choice)
|
||||
{
|
||||
bool status = false;
|
||||
|
||||
if (dcc_communication_disabled()) {
|
||||
switch (service_choice) {
|
||||
case SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL:
|
||||
case SERVICE_CONFIRMED_REINITIALIZE_DEVICE:
|
||||
break;
|
||||
default:
|
||||
status = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* When network communications are completely disabled,
|
||||
only DeviceCommunicationControl and ReinitializeDevice APDUs
|
||||
shall be processed and no messages shall be initiated. */
|
||||
/* If the request is valid and the 'Enable/Disable' parameter is
|
||||
DISABLE_INITIATION, the responding BACnet-user shall
|
||||
discontinue the initiation of messages except for I-Am
|
||||
requests issued in accordance with the Who-Is service procedure.*/
|
||||
static bool apdu_unconfirmed_dcc_disabled(
|
||||
uint8_t service_choice)
|
||||
{
|
||||
bool status = false;
|
||||
|
||||
if (dcc_communication_disabled()) {
|
||||
/* there are no Unconfirmed messages that
|
||||
can be processed in this state */
|
||||
status = true;
|
||||
} else if (dcc_communication_initiation_disabled()) {
|
||||
/* WhoIs will be processed and I-Am initiated as response. */
|
||||
switch (service_choice) {
|
||||
case SERVICE_UNCONFIRMED_WHO_IS:
|
||||
break;
|
||||
default:
|
||||
status = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/** Process the APDU header and invoke the appropriate service handler
|
||||
* to manage the received request.
|
||||
* Almost all requests and ACKs invoke this function.
|
||||
* @ingroup MISCHNDLR
|
||||
*
|
||||
* @param src [in] The BACNET_ADDRESS of the message's source.
|
||||
* @param apdu [in] The apdu portion of the request, to be processed.
|
||||
* @param apdu_len [in] The total (remaining) length of the apdu.
|
||||
*/
|
||||
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;
|
||||
int len = 0; /* counts where we are in PDU */
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
uint32_t error_code = 0;
|
||||
uint32_t error_class = 0;
|
||||
uint8_t reason = 0;
|
||||
bool server = false;
|
||||
|
||||
if (apdu) {
|
||||
/* PDU Type */
|
||||
switch (apdu[0] & 0xF0) {
|
||||
case PDU_TYPE_CONFIRMED_SERVICE_REQUEST:
|
||||
(void)apdu_decode_confirmed_service_request(&apdu[0],
|
||||
apdu_len, &service_data, &service_choice, &service_request,
|
||||
&service_request_len);
|
||||
if (apdu_confirmed_dcc_disabled(service_choice)) {
|
||||
/* When network communications are completely disabled,
|
||||
only DeviceCommunicationControl and ReinitializeDevice APDUs
|
||||
shall be processed and no messages shall be initiated. */
|
||||
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:
|
||||
service_choice = apdu[1];
|
||||
service_request = &apdu[2];
|
||||
service_request_len = apdu_len - 2;
|
||||
if (apdu_unconfirmed_dcc_disabled(service_choice)) {
|
||||
/* When network communications are disabled,
|
||||
only DeviceCommunicationControl and ReinitializeDevice APDUs
|
||||
shall be processed and no messages shall be initiated.
|
||||
If communications have been initiation disabled, then
|
||||
WhoIs may be processed. */
|
||||
break;
|
||||
}
|
||||
if (service_choice < MAX_BACNET_UNCONFIRMED_SERVICE) {
|
||||
if (Unconfirmed_Function[service_choice])
|
||||
Unconfirmed_Function[service_choice] (service_request,
|
||||
service_request_len, src);
|
||||
}
|
||||
break;
|
||||
case PDU_TYPE_SIMPLE_ACK:
|
||||
invoke_id = apdu[1];
|
||||
service_choice = apdu[2];
|
||||
switch (service_choice) {
|
||||
case SERVICE_CONFIRMED_ACKNOWLEDGE_ALARM:
|
||||
case SERVICE_CONFIRMED_COV_NOTIFICATION:
|
||||
case SERVICE_CONFIRMED_EVENT_NOTIFICATION:
|
||||
case SERVICE_CONFIRMED_SUBSCRIBE_COV:
|
||||
case SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY:
|
||||
case SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION:
|
||||
/* Object Access Services */
|
||||
case SERVICE_CONFIRMED_ADD_LIST_ELEMENT:
|
||||
case SERVICE_CONFIRMED_REMOVE_LIST_ELEMENT:
|
||||
case SERVICE_CONFIRMED_DELETE_OBJECT:
|
||||
case SERVICE_CONFIRMED_WRITE_PROPERTY:
|
||||
case SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE:
|
||||
/* Remote Device Management Services */
|
||||
case SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL:
|
||||
case SERVICE_CONFIRMED_REINITIALIZE_DEVICE:
|
||||
case SERVICE_CONFIRMED_TEXT_MESSAGE:
|
||||
/* Virtual Terminal Services */
|
||||
case SERVICE_CONFIRMED_VT_CLOSE:
|
||||
/* Security Services */
|
||||
case SERVICE_CONFIRMED_REQUEST_KEY:
|
||||
if (Confirmed_ACK_Function[service_choice] != NULL) {
|
||||
((confirmed_simple_ack_function)
|
||||
Confirmed_ACK_Function[service_choice]) (src,
|
||||
invoke_id);
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case PDU_TYPE_COMPLEX_ACK:
|
||||
service_ack_data.segmented_message =
|
||||
(apdu[0] & BIT(3)) ? true : false;
|
||||
service_ack_data.more_follows =
|
||||
(apdu[0] & BIT(2)) ? true : false;
|
||||
invoke_id = service_ack_data.invoke_id = apdu[1];
|
||||
len = 2;
|
||||
if (service_ack_data.segmented_message) {
|
||||
service_ack_data.sequence_number = apdu[len++];
|
||||
service_ack_data.proposed_window_number = apdu[len++];
|
||||
}
|
||||
service_choice = apdu[len++];
|
||||
service_request = &apdu[len];
|
||||
service_request_len = apdu_len - (uint16_t) len;
|
||||
switch (service_choice) {
|
||||
case SERVICE_CONFIRMED_GET_ALARM_SUMMARY:
|
||||
case SERVICE_CONFIRMED_GET_ENROLLMENT_SUMMARY:
|
||||
case SERVICE_CONFIRMED_GET_EVENT_INFORMATION:
|
||||
/* File Access Services */
|
||||
case SERVICE_CONFIRMED_ATOMIC_READ_FILE:
|
||||
case SERVICE_CONFIRMED_ATOMIC_WRITE_FILE:
|
||||
/* Object Access Services */
|
||||
case SERVICE_CONFIRMED_CREATE_OBJECT:
|
||||
case SERVICE_CONFIRMED_READ_PROPERTY:
|
||||
case SERVICE_CONFIRMED_READ_PROP_CONDITIONAL:
|
||||
case SERVICE_CONFIRMED_READ_PROP_MULTIPLE:
|
||||
case SERVICE_CONFIRMED_READ_RANGE:
|
||||
case SERVICE_CONFIRMED_PRIVATE_TRANSFER:
|
||||
/* Virtual Terminal Services */
|
||||
case SERVICE_CONFIRMED_VT_OPEN:
|
||||
case SERVICE_CONFIRMED_VT_DATA:
|
||||
/* Security Services */
|
||||
case SERVICE_CONFIRMED_AUTHENTICATE:
|
||||
if (Confirmed_ACK_Function[service_choice] != NULL) {
|
||||
(Confirmed_ACK_Function[service_choice])
|
||||
(service_request, service_request_len, src,
|
||||
&service_ack_data);
|
||||
}
|
||||
tsm_free_invoke_id(invoke_id);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
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;
|
||||
|
||||
/* FIXME: Currently special case for C_P_T but there are others which may
|
||||
need consideration such as ChangeList-Error, CreateObject-Error,
|
||||
WritePropertyMultiple-Error and VTClose_Error but they may be left as
|
||||
is for now until support for these services is added */
|
||||
|
||||
if (service_choice == SERVICE_CONFIRMED_PRIVATE_TRANSFER) { /* skip over opening tag 0 */
|
||||
if (decode_is_opening_tag_number(&apdu[len], 0)) {
|
||||
len++; /* a tag number of 0 is not extended so only one octet */
|
||||
}
|
||||
}
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
/* FIXME: we could validate that the tag is enumerated... */
|
||||
len += decode_enumerated(&apdu[len], len_value, &error_class);
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
/* FIXME: we could validate that the tag is enumerated... */
|
||||
len += decode_enumerated(&apdu[len], len_value, &error_code);
|
||||
|
||||
if (service_choice == SERVICE_CONFIRMED_PRIVATE_TRANSFER) { /* skip over closing tag 0 */
|
||||
if (decode_is_closing_tag_number(&apdu[len], 0)) {
|
||||
len++; /* a tag number of 0 is not extended so only one octet */
|
||||
}
|
||||
}
|
||||
if (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,554 @@
|
||||
/*####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 "arf.h"
|
||||
|
||||
/** @file arf.c 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, MAX_APDU);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_ATOMIC_READ_FILE; /* service choice */
|
||||
apdu_len = 4;
|
||||
apdu_len +=
|
||||
encode_application_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_application_signed(&apdu[apdu_len],
|
||||
data->type.stream.fileStartPosition);
|
||||
apdu_len +=
|
||||
encode_application_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_application_signed(&apdu[apdu_len],
|
||||
data->type.record.fileStartRecord);
|
||||
apdu_len +=
|
||||
encode_application_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;
|
||||
uint16_t 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 = (BACNET_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, MAX_APDU); */
|
||||
*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 */
|
||||
uint32_t i = 0;
|
||||
|
||||
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_application_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_application_signed(&apdu[apdu_len],
|
||||
data->type.stream.fileStartPosition);
|
||||
apdu_len +=
|
||||
encode_application_octet_string(&apdu[apdu_len],
|
||||
&data->fileData[0]);
|
||||
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_application_signed(&apdu[apdu_len],
|
||||
data->type.record.fileStartRecord);
|
||||
apdu_len +=
|
||||
encode_application_unsigned(&apdu[apdu_len],
|
||||
data->type.record.RecordCount);
|
||||
for (i = 0; i < data->type.record.RecordCount; i++) {
|
||||
apdu_len +=
|
||||
encode_application_octet_string(&apdu[apdu_len],
|
||||
&data->fileData[i]);
|
||||
}
|
||||
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;
|
||||
int decoded_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
uint32_t i = 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;
|
||||
}
|
||||
decoded_len =
|
||||
decode_octet_string(&apdu[len], len_value_type,
|
||||
&data->fileData[0]);
|
||||
if ((uint32_t)decoded_len != len_value_type) {
|
||||
return -1;
|
||||
}
|
||||
len += decoded_len;
|
||||
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);
|
||||
for (i = 0; i < data->type.record.RecordCount; i++) {
|
||||
/* 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;
|
||||
}
|
||||
decoded_len =
|
||||
decode_octet_string(&apdu[len], len_value_type,
|
||||
&data->fileData[i]);
|
||||
if ((uint32_t)decoded_len != len_value_type) {
|
||||
return -1;
|
||||
}
|
||||
len += decoded_len;
|
||||
}
|
||||
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;
|
||||
unsigned int i = 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);
|
||||
ct_test(pTest,
|
||||
octetstring_length(&test_data.fileData[0]) ==
|
||||
octetstring_length(&data->fileData[0]));
|
||||
ct_test(pTest, memcmp(octetstring_value(&test_data.fileData[0]),
|
||||
octetstring_value(&data->fileData[0]),
|
||||
octetstring_length(&test_data.fileData[0])) == 0);
|
||||
} 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);
|
||||
for (i = 0; i < data->type.record.RecordCount; i++) {
|
||||
ct_test(pTest,
|
||||
octetstring_length(&test_data.fileData[i]) ==
|
||||
octetstring_length(&data->fileData[i]));
|
||||
ct_test(pTest, memcmp(octetstring_value(&test_data.fileData[i]),
|
||||
octetstring_value(&data->fileData[i]),
|
||||
octetstring_length(&test_data.fileData[i])) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testAtomicReadFileAck(
|
||||
Test * pTest)
|
||||
{
|
||||
BACNET_ATOMIC_READ_FILE_DATA data = { 0 };
|
||||
uint8_t test_octet_string[32] = "Joshua-Mary-Anna-Christopher";
|
||||
unsigned int i = 0;
|
||||
|
||||
data.endOfFile = true;
|
||||
data.access = FILE_STREAM_ACCESS;
|
||||
data.type.stream.fileStartPosition = 0;
|
||||
octetstring_init(&data.fileData[0], 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 = BACNET_READ_FILE_RECORD_COUNT;
|
||||
for (i = 0; i < data.type.record.RecordCount; i++) {
|
||||
octetstring_init(&data.fileData[i], 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
|
||||
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,131 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2015 Nikola Jelic <nikola.jelic@euroicc.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*********************************************************************/
|
||||
|
||||
#include "assigned_access_rights.h"
|
||||
#include "bacdcode.h"
|
||||
|
||||
|
||||
int bacapp_encode_assigned_access_rights(
|
||||
uint8_t * apdu,
|
||||
BACNET_ASSIGNED_ACCESS_RIGHTS * aar)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len =
|
||||
bacapp_encode_context_device_obj_ref(&apdu[apdu_len], 0,
|
||||
&aar->assigned_access_rights);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_context_boolean(&apdu[apdu_len], 1, aar->enable);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_encode_context_assigned_access_rights(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag,
|
||||
BACNET_ASSIGNED_ACCESS_RIGHTS * aar)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_opening_tag(&apdu[apdu_len], tag);
|
||||
apdu_len += len;
|
||||
|
||||
len = bacapp_encode_assigned_access_rights(&apdu[apdu_len], aar);
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_closing_tag(&apdu[apdu_len], tag);
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
|
||||
}
|
||||
|
||||
int bacapp_decode_assigned_access_rights(
|
||||
uint8_t * apdu,
|
||||
BACNET_ASSIGNED_ACCESS_RIGHTS * aar)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 0)) {
|
||||
len =
|
||||
bacapp_decode_context_device_obj_ref(&apdu[apdu_len], 0,
|
||||
&aar->assigned_access_rights);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 1)) {
|
||||
len = decode_context_boolean2(&apdu[apdu_len], 1, &aar->enable);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_decode_context_assigned_access_rights(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag,
|
||||
BACNET_ASSIGNED_ACCESS_RIGHTS * aar)
|
||||
{
|
||||
int len = 0;
|
||||
int section_length;
|
||||
|
||||
if (decode_is_opening_tag_number(&apdu[len], tag)) {
|
||||
len++;
|
||||
section_length = bacapp_decode_assigned_access_rights(&apdu[len], aar);
|
||||
|
||||
if (section_length == -1) {
|
||||
len = -1;
|
||||
} else {
|
||||
len += section_length;
|
||||
if (decode_is_closing_tag_number(&apdu[len], tag)) {
|
||||
len++;
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2015 Nikola Jelic <nikola.jelic@euroicc.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*********************************************************************/
|
||||
|
||||
#include "authentication_factor.h"
|
||||
#include "bacdcode.h"
|
||||
|
||||
|
||||
int bacapp_encode_authentication_factor(
|
||||
uint8_t * apdu,
|
||||
BACNET_AUTHENTICATION_FACTOR * af)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_context_enumerated(&apdu[apdu_len], 0, af->format_type);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 1, af->format_class);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_context_octet_string(&apdu[apdu_len], 2, &af->value);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_encode_context_authentication_factor(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag,
|
||||
BACNET_AUTHENTICATION_FACTOR * af)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_opening_tag(&apdu[apdu_len], tag);
|
||||
apdu_len += len;
|
||||
|
||||
len = bacapp_encode_authentication_factor(&apdu[apdu_len], af);
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_closing_tag(&apdu[apdu_len], tag);
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
|
||||
}
|
||||
|
||||
int bacapp_decode_authentication_factor(
|
||||
uint8_t * apdu,
|
||||
BACNET_AUTHENTICATION_FACTOR * af)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 0)) {
|
||||
len = decode_context_enumerated(&apdu[apdu_len], 0, &af->format_type);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 1)) {
|
||||
len = decode_context_unsigned(&apdu[apdu_len], 1, &af->format_class);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 2)) {
|
||||
len = decode_context_octet_string(&apdu[apdu_len], 2, &af->value);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_decode_context_authentication_factor(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag,
|
||||
BACNET_AUTHENTICATION_FACTOR * af)
|
||||
{
|
||||
int len = 0;
|
||||
int section_length;
|
||||
|
||||
if (decode_is_opening_tag_number(&apdu[len], tag)) {
|
||||
len++;
|
||||
section_length = bacapp_decode_authentication_factor(&apdu[len], af);
|
||||
|
||||
if (section_length == -1) {
|
||||
len = -1;
|
||||
} else {
|
||||
len += section_length;
|
||||
if (decode_is_closing_tag_number(&apdu[len], tag)) {
|
||||
len++;
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2015 Nikola Jelic <nikola.jelic@euroicc.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*********************************************************************/
|
||||
|
||||
#include "bacdcode.h"
|
||||
#include "authentication_factor_format.h"
|
||||
|
||||
|
||||
int bacapp_encode_authentication_factor_format(
|
||||
uint8_t * apdu,
|
||||
BACNET_AUTHENTICATION_FACTOR_FORMAT * aff)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_context_enumerated(&apdu[apdu_len], 0, aff->format_type);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
|
||||
if (aff->format_type == AUTHENTICATION_FACTOR_CUSTOM) {
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 1, aff->vendor_id);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 2, aff->vendor_format);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
}
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_encode_context_authentication_factor_format(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag,
|
||||
BACNET_AUTHENTICATION_FACTOR_FORMAT * aff)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_opening_tag(&apdu[apdu_len], tag);
|
||||
apdu_len += len;
|
||||
|
||||
len = bacapp_encode_authentication_factor_format(&apdu[apdu_len], aff);
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_closing_tag(&apdu[apdu_len], tag);
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
|
||||
}
|
||||
|
||||
int bacapp_decode_authentication_factor_format(
|
||||
uint8_t * apdu,
|
||||
BACNET_AUTHENTICATION_FACTOR_FORMAT * aff)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 0)) {
|
||||
len =
|
||||
decode_context_enumerated(&apdu[apdu_len], 0,
|
||||
&aff->format_type);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 1)) {
|
||||
len = decode_context_unsigned(&apdu[apdu_len], 1, &aff->vendor_id);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
if ((aff->format_type != AUTHENTICATION_FACTOR_CUSTOM)
|
||||
&& (aff->vendor_id != 0))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 2)) {
|
||||
len = decode_context_unsigned(&apdu[apdu_len], 2, &aff->vendor_format);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
if ((aff->format_type != AUTHENTICATION_FACTOR_CUSTOM)
|
||||
&& (aff->vendor_format != 0))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_decode_context_authentication_factor_format(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag,
|
||||
BACNET_AUTHENTICATION_FACTOR_FORMAT * aff)
|
||||
{
|
||||
int len = 0;
|
||||
int section_length;
|
||||
|
||||
if (decode_is_opening_tag_number(&apdu[len], tag)) {
|
||||
len++;
|
||||
section_length =
|
||||
bacapp_decode_authentication_factor_format(&apdu[len], aff);
|
||||
|
||||
if (section_length == -1) {
|
||||
len = -1;
|
||||
} else {
|
||||
len += section_length;
|
||||
if (decode_is_closing_tag_number(&apdu[len], tag)) {
|
||||
len++;
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
return len;
|
||||
|
||||
}
|
||||
@@ -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 <stdint.h>
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "awf.h"
|
||||
|
||||
/** @file awf.c 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 */
|
||||
uint32_t i = 0;
|
||||
|
||||
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_ATOMIC_WRITE_FILE; /* service choice */
|
||||
apdu_len = 4;
|
||||
apdu_len +=
|
||||
encode_application_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_application_signed(&apdu[apdu_len],
|
||||
data->type.stream.fileStartPosition);
|
||||
apdu_len +=
|
||||
encode_application_octet_string(&apdu[apdu_len],
|
||||
&data->fileData[0]);
|
||||
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_application_signed(&apdu[apdu_len],
|
||||
data->type.record.fileStartRecord);
|
||||
apdu_len +=
|
||||
encode_application_unsigned(&apdu[apdu_len],
|
||||
data->type.record.returnedRecordCount);
|
||||
for (i = 0; i < data->type.record.returnedRecordCount; i++) {
|
||||
apdu_len +=
|
||||
encode_application_octet_string(&apdu[apdu_len],
|
||||
&data->fileData[i]);
|
||||
}
|
||||
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;
|
||||
int decoded_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
int32_t signed_value = 0;
|
||||
uint32_t unsigned_value = 0;
|
||||
uint16_t type = 0; /* for decoding */
|
||||
uint32_t i = 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_OBJECT_ID)
|
||||
return -1;
|
||||
len += decode_object_id(&apdu[len], &type, &data->object_instance);
|
||||
data->object_type = (BACNET_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;
|
||||
decoded_len =
|
||||
decode_octet_string(&apdu[len], len_value_type,
|
||||
&data->fileData[0]);
|
||||
if ((uint32_t)decoded_len != len_value_type) {
|
||||
return -1;
|
||||
}
|
||||
len += decoded_len;
|
||||
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 */
|
||||
for (i = 0; i < data->type.record.returnedRecordCount; i++) {
|
||||
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;
|
||||
decoded_len =
|
||||
decode_octet_string(&apdu[len], len_value_type,
|
||||
&data->fileData[i]);
|
||||
if ((uint32_t)decoded_len != len_value_type) {
|
||||
return -1;
|
||||
}
|
||||
len += decoded_len;
|
||||
}
|
||||
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, MAX_APDU); */
|
||||
*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_ack_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[0]) ==
|
||||
octetstring_length(&data->fileData[0]));
|
||||
ct_test(pTest, memcmp(octetstring_value(&test_data.fileData[0]),
|
||||
octetstring_value(&data->fileData[0]),
|
||||
octetstring_length(&test_data.fileData[0])) == 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[0], 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 = 1;
|
||||
octetstring_init(&data.fileData[0], 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
|
||||
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,97 @@
|
||||
/*####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"
|
||||
#include "bacaddr.h"
|
||||
|
||||
/** @file bacaddr.c BACnet Address structure utilities */
|
||||
|
||||
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)
|
||||
{
|
||||
uint8_t i = 0; /* loop counter */
|
||||
uint8_t max_len = 0; /* used for dynamic max */
|
||||
|
||||
if (dest == src) /* same ? */
|
||||
return true;
|
||||
|
||||
if (dest->net != src->net)
|
||||
return false;
|
||||
|
||||
if (dest->len != src->len)
|
||||
return 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])
|
||||
return false;
|
||||
}
|
||||
if (dest->net == 0) {
|
||||
if (dest->mac_len != src->mac_len)
|
||||
return 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])
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
+2296
File diff suppressed because it is too large
Load Diff
+3195
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,457 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2008 John Minack
|
||||
|
||||
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 "bacdcode.h"
|
||||
#include "npdu.h"
|
||||
#include "timestamp.h"
|
||||
#include "bacdevobjpropref.h"
|
||||
|
||||
/** @file bacdevobjpropref.c BACnet Application Device Object (Property) Reference */
|
||||
|
||||
int bacapp_encode_context_device_obj_property_ref(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE * value)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_opening_tag(&apdu[apdu_len], tag_number);
|
||||
apdu_len += len;
|
||||
|
||||
len = bacapp_encode_device_obj_property_ref(&apdu[apdu_len], value);
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_closing_tag(&apdu[apdu_len], tag_number);
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* BACnetDeviceObjectPropertyReference ::= SEQUENCE {
|
||||
object-identifier [0] BACnetObjectIdentifier,
|
||||
property-identifier [1] BACnetPropertyIdentifier,
|
||||
property-array-index [2] Unsigned OPTIONAL,
|
||||
-- used only with array datatype
|
||||
-- if omitted with an array then
|
||||
-- the entire array is referenced
|
||||
device-identifier [3] BACnetObjectIdentifier OPTIONAL
|
||||
}
|
||||
*/
|
||||
int bacapp_encode_device_obj_property_ref(
|
||||
uint8_t * apdu,
|
||||
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE * value)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
/* object-identifier [0] BACnetObjectIdentifier */
|
||||
len =
|
||||
encode_context_object_id(&apdu[apdu_len], 0,
|
||||
(int) value->objectIdentifier.type, value->objectIdentifier.instance);
|
||||
apdu_len += len;
|
||||
/* property-identifier [1] BACnetPropertyIdentifier */
|
||||
len =
|
||||
encode_context_enumerated(&apdu[apdu_len], 1,
|
||||
value->propertyIdentifier);
|
||||
apdu_len += len;
|
||||
/* property-array-index [2] Unsigned OPTIONAL */
|
||||
/* Check if needed before inserting */
|
||||
if (value->arrayIndex != BACNET_ARRAY_ALL) {
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 2, value->arrayIndex);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* device-identifier [3] BACnetObjectIdentifier OPTIONAL */
|
||||
/* Likewise, device id is optional so see if needed
|
||||
* (set type to BACNET_NO_DEV_TYPE or something other than OBJECT_DEVICE to
|
||||
* omit */
|
||||
if (value->deviceIdentifier.type == OBJECT_DEVICE) {
|
||||
len =
|
||||
encode_context_object_id(&apdu[apdu_len], 3,
|
||||
(int) value->deviceIdentifier.type,
|
||||
value->deviceIdentifier.instance);
|
||||
apdu_len += len;
|
||||
}
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* BACnetDeviceObjectPropertyReference ::= SEQUENCE {
|
||||
object-identifier [0] BACnetObjectIdentifier,
|
||||
property-identifier [1] BACnetPropertyIdentifier,
|
||||
property-array-index [2] Unsigned OPTIONAL,
|
||||
-- used only with array datatype
|
||||
-- if omitted with an array then
|
||||
-- the entire array is referenced
|
||||
device-identifier [3] BACnetObjectIdentifier OPTIONAL
|
||||
}
|
||||
*/
|
||||
int bacapp_decode_device_obj_property_ref(
|
||||
uint8_t * apdu,
|
||||
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE * value)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
uint32_t enumValue;
|
||||
|
||||
/* object-identifier [0] BACnetObjectIdentifier */
|
||||
if (-1 == (len =
|
||||
decode_context_object_id(&apdu[apdu_len], 0,
|
||||
&value->objectIdentifier.type,
|
||||
&value->objectIdentifier.instance))) {
|
||||
return -1;
|
||||
}
|
||||
apdu_len += len;
|
||||
/* property-identifier [1] BACnetPropertyIdentifier */
|
||||
if (-1 == (len =
|
||||
decode_context_enumerated(&apdu[apdu_len], 1, &enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->propertyIdentifier = (BACNET_PROPERTY_ID) enumValue;
|
||||
apdu_len += len;
|
||||
/* property-array-index [2] Unsigned OPTIONAL */
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 2) &&
|
||||
!decode_is_closing_tag(&apdu[apdu_len])) {
|
||||
if (-1 == (len =
|
||||
decode_context_unsigned(&apdu[apdu_len], 2,
|
||||
&value->arrayIndex))) {
|
||||
return -1;
|
||||
}
|
||||
apdu_len += len;
|
||||
} else {
|
||||
value->arrayIndex = BACNET_ARRAY_ALL;
|
||||
}
|
||||
/* device-identifier [3] BACnetObjectIdentifier OPTIONAL */
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 3) &&
|
||||
!decode_is_closing_tag(&apdu[apdu_len])) {
|
||||
if (-1 == (len =
|
||||
decode_context_object_id(&apdu[apdu_len], 3,
|
||||
&value->deviceIdentifier.type,
|
||||
&value->deviceIdentifier.instance))) {
|
||||
return -1;
|
||||
}
|
||||
apdu_len += len;
|
||||
} else {
|
||||
value->deviceIdentifier.type = BACNET_NO_DEV_TYPE;
|
||||
value->deviceIdentifier.instance = BACNET_NO_DEV_ID;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_decode_context_device_obj_property_ref(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE * value)
|
||||
{
|
||||
int len = 0;
|
||||
int section_length;
|
||||
|
||||
if (decode_is_opening_tag_number(&apdu[len], tag_number)) {
|
||||
len++;
|
||||
section_length =
|
||||
bacapp_decode_device_obj_property_ref(&apdu[len], value);
|
||||
|
||||
if (section_length == -1) {
|
||||
len = -1;
|
||||
} else {
|
||||
len += section_length;
|
||||
if (decode_is_closing_tag_number(&apdu[len], tag_number)) {
|
||||
len++;
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
|
||||
/* BACnetDeviceObjectReference ::= SEQUENCE {
|
||||
device-identifier [0] BACnetObjectIdentifier OPTIONAL,
|
||||
object-identifier [1] BACnetObjectIdentifier
|
||||
}*/
|
||||
int bacapp_encode_context_device_obj_ref(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_DEVICE_OBJECT_REFERENCE * value)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_opening_tag(&apdu[apdu_len], tag_number);
|
||||
apdu_len += len;
|
||||
|
||||
len = bacapp_encode_device_obj_ref(&apdu[apdu_len], value);
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_closing_tag(&apdu[apdu_len], tag_number);
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* BACnetDeviceObjectReference ::= SEQUENCE {
|
||||
device-identifier [0] BACnetObjectIdentifier OPTIONAL,
|
||||
object-identifier [1] BACnetObjectIdentifier
|
||||
}*/
|
||||
int bacapp_encode_device_obj_ref(
|
||||
uint8_t * apdu,
|
||||
BACNET_DEVICE_OBJECT_REFERENCE * value)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
/* device-identifier [0] BACnetObjectIdentifier OPTIONAL */
|
||||
/* Device id is optional so see if needed
|
||||
* (set type to BACNET_NO_DEV_TYPE or something other than OBJECT_DEVICE to
|
||||
* omit */
|
||||
if (value->deviceIdentifier.type == OBJECT_DEVICE) {
|
||||
len =
|
||||
encode_context_object_id(&apdu[apdu_len], 0,
|
||||
(int) value->deviceIdentifier.type,
|
||||
value->deviceIdentifier.instance);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* object-identifier [1] BACnetObjectIdentifier */
|
||||
len =
|
||||
encode_context_object_id(&apdu[apdu_len], 1,
|
||||
(int) value->objectIdentifier.type, value->objectIdentifier.instance);
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* BACnetDeviceObjectReference ::= SEQUENCE {
|
||||
device-identifier [0] BACnetObjectIdentifier OPTIONAL,
|
||||
object-identifier [1] BACnetObjectIdentifier
|
||||
}*/
|
||||
int bacapp_decode_device_obj_ref(
|
||||
uint8_t * apdu,
|
||||
BACNET_DEVICE_OBJECT_REFERENCE * value)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
/* device-identifier [0] BACnetObjectIdentifier OPTIONAL */
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 0) &&
|
||||
!decode_is_closing_tag(&apdu[apdu_len])) {
|
||||
if (-1 == (len =
|
||||
decode_context_object_id(&apdu[apdu_len], 0,
|
||||
&value->deviceIdentifier.type,
|
||||
&value->deviceIdentifier.instance))) {
|
||||
return -1;
|
||||
}
|
||||
apdu_len += len;
|
||||
} else {
|
||||
value->deviceIdentifier.type = BACNET_NO_DEV_TYPE;
|
||||
value->deviceIdentifier.instance = BACNET_NO_DEV_ID;
|
||||
}
|
||||
/* object-identifier [1] BACnetObjectIdentifier */
|
||||
if (-1 == (len =
|
||||
decode_context_object_id(&apdu[apdu_len], 1,
|
||||
&value->objectIdentifier.type,
|
||||
&value->objectIdentifier.instance))) {
|
||||
return -1;
|
||||
}
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_decode_context_device_obj_ref(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_DEVICE_OBJECT_REFERENCE * value)
|
||||
{
|
||||
int len = 0;
|
||||
int section_length;
|
||||
|
||||
if (decode_is_opening_tag_number(&apdu[len], tag_number)) {
|
||||
len++;
|
||||
section_length = bacapp_decode_device_obj_ref(&apdu[len], value);
|
||||
|
||||
if (section_length == -1) {
|
||||
len = -1;
|
||||
} else {
|
||||
len += section_length;
|
||||
if (decode_is_closing_tag_number(&apdu[len], tag_number)) {
|
||||
len++;
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
static void testDevObjPropRef(
|
||||
Test * pTest,
|
||||
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE *inData)
|
||||
{
|
||||
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE outData;
|
||||
uint8_t buffer[MAX_APDU] = {0};
|
||||
int inLen = 0;
|
||||
int outLen = 0;
|
||||
|
||||
/* encode */
|
||||
inLen = bacapp_encode_device_obj_property_ref(buffer, inData);
|
||||
/* add a closing tag at the end of the buffer to verify proper handling
|
||||
when that is encountered in real packets */
|
||||
encode_closing_tag(&buffer[inLen], 3);
|
||||
/* decode */
|
||||
outLen = bacapp_decode_device_obj_property_ref(buffer, &outData);
|
||||
ct_test(pTest, outLen == inLen);
|
||||
ct_test(pTest,
|
||||
inData->objectIdentifier.instance == outData.objectIdentifier.instance);
|
||||
ct_test(pTest,
|
||||
inData->objectIdentifier.type == outData.objectIdentifier.type);
|
||||
ct_test(pTest, inData->propertyIdentifier == outData.propertyIdentifier);
|
||||
if (inData->arrayIndex != BACNET_ARRAY_ALL) {
|
||||
ct_test(pTest, inData->arrayIndex == outData.arrayIndex);
|
||||
} else {
|
||||
ct_test(pTest, outData.arrayIndex == BACNET_ARRAY_ALL);
|
||||
}
|
||||
if (inData->deviceIdentifier.type == OBJECT_DEVICE) {
|
||||
ct_test(pTest,
|
||||
inData->deviceIdentifier.instance ==
|
||||
outData.deviceIdentifier.instance);
|
||||
ct_test(pTest,
|
||||
inData->deviceIdentifier.type == outData.deviceIdentifier.type);
|
||||
} else {
|
||||
ct_test(pTest,outData.deviceIdentifier.instance == BACNET_NO_DEV_ID);
|
||||
ct_test(pTest,outData.deviceIdentifier.type == BACNET_NO_DEV_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
static void testDevIdPropRef(
|
||||
Test * pTest)
|
||||
{
|
||||
BACNET_DEVICE_OBJECT_PROPERTY_REFERENCE inData;
|
||||
|
||||
/* everything encoded */
|
||||
inData.objectIdentifier.instance = 0x1234;
|
||||
inData.objectIdentifier.type = 15;
|
||||
inData.propertyIdentifier = 25;
|
||||
inData.arrayIndex = 0x5678;
|
||||
inData.deviceIdentifier.instance = 0x4343;
|
||||
inData.deviceIdentifier.type = OBJECT_DEVICE;
|
||||
testDevObjPropRef(pTest, &inData);
|
||||
/* optional array */
|
||||
inData.objectIdentifier.instance = 0x1234;
|
||||
inData.objectIdentifier.type = 15;
|
||||
inData.propertyIdentifier = 25;
|
||||
inData.arrayIndex = BACNET_ARRAY_ALL;
|
||||
inData.deviceIdentifier.instance = 0x4343;
|
||||
inData.deviceIdentifier.type = OBJECT_DEVICE;
|
||||
testDevObjPropRef(pTest, &inData);
|
||||
/* optional device ID */
|
||||
inData.objectIdentifier.instance = 0x1234;
|
||||
inData.objectIdentifier.type = 15;
|
||||
inData.propertyIdentifier = 25;
|
||||
inData.arrayIndex = 1;
|
||||
inData.deviceIdentifier.instance = 0;
|
||||
inData.deviceIdentifier.type = BACNET_NO_DEV_TYPE;
|
||||
testDevObjPropRef(pTest, &inData);
|
||||
/* optional array + optional device ID */
|
||||
inData.objectIdentifier.instance = 0x1234;
|
||||
inData.objectIdentifier.type = 15;
|
||||
inData.propertyIdentifier = 25;
|
||||
inData.arrayIndex = BACNET_ARRAY_ALL;
|
||||
inData.deviceIdentifier.instance = 0;
|
||||
inData.deviceIdentifier.type = BACNET_NO_DEV_TYPE;
|
||||
testDevObjPropRef(pTest, &inData);
|
||||
}
|
||||
|
||||
static void testDevIdRef(
|
||||
Test * pTest)
|
||||
{
|
||||
BACNET_DEVICE_OBJECT_REFERENCE inData;
|
||||
BACNET_DEVICE_OBJECT_REFERENCE outData;
|
||||
uint8_t buffer[MAX_APDU];
|
||||
int inLen;
|
||||
int outLen;
|
||||
|
||||
inData.deviceIdentifier.instance = 0x4343;
|
||||
inData.deviceIdentifier.type = OBJECT_DEVICE;
|
||||
inLen = bacapp_encode_device_obj_ref(buffer, &inData);
|
||||
outLen = bacapp_decode_device_obj_ref(buffer, &outData);
|
||||
ct_test(pTest, outLen == inLen);
|
||||
ct_test(pTest,
|
||||
inData.deviceIdentifier.instance ==
|
||||
outData.deviceIdentifier.instance);
|
||||
ct_test(pTest,
|
||||
inData.deviceIdentifier.type == outData.deviceIdentifier.type);
|
||||
}
|
||||
|
||||
void testBACnetDeviceObjectPropertyReference(
|
||||
Test * pTest)
|
||||
{
|
||||
bool rc;
|
||||
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testDevIdPropRef);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testDevIdRef);
|
||||
assert(rc);
|
||||
}
|
||||
|
||||
#ifdef TEST_DEV_ID_PROP_REF
|
||||
#include <assert.h>
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
|
||||
pTest = ct_create("BACnet Prop Ref", NULL);
|
||||
testBACnetDeviceObjectPropertyReference(pTest);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* TEST_DEV_ID_PROP_REF */
|
||||
#endif /* TEST */
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
/*####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 "bacerror.h"
|
||||
|
||||
/** @file bacerror.c Encode/Decode BACnet Errors */
|
||||
|
||||
/* 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_application_enumerated(&apdu[apdu_len], error_class);
|
||||
apdu_len += encode_application_enumerated(&apdu[apdu_len], error_code);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
#if !BACNET_SVC_SERVER
|
||||
/* 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;
|
||||
uint32_t 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
#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 < ERROR_CLASS_PROPRIETARY_FIRST;
|
||||
error_class++) {
|
||||
for (error_code = 0; error_code < ERROR_CODE_PROPRIETARY_FIRST;
|
||||
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 = ERROR_CLASS_PROPRIETARY_LAST;
|
||||
error_code = ERROR_CODE_PROPRIETARY_LAST;
|
||||
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 */
|
||||
+440
@@ -0,0 +1,440 @@
|
||||
/*####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>
|
||||
#include "config.h"
|
||||
#include "bacint.h"
|
||||
|
||||
/** @file bacint.c Encode/Decode Integer Types */
|
||||
|
||||
int encode_unsigned16(
|
||||
uint8_t * apdu,
|
||||
uint16_t value)
|
||||
{
|
||||
apdu[0] = (uint8_t) ((value & 0xff00) >> 8);
|
||||
apdu[1] = (uint8_t) (value & 0x00ff);
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int decode_unsigned16(
|
||||
uint8_t * apdu,
|
||||
uint16_t * value)
|
||||
{
|
||||
if (value) {
|
||||
*value = (uint16_t) ((((uint16_t) apdu[0]) << 8) & 0xff00);
|
||||
*value |= ((uint16_t) (((uint16_t) apdu[1]) & 0x00ff));
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int encode_unsigned24(
|
||||
uint8_t * apdu,
|
||||
uint32_t value)
|
||||
{
|
||||
apdu[0] = (uint8_t) ((value & 0xff0000) >> 16);
|
||||
apdu[1] = (uint8_t) ((value & 0x00ff00) >> 8);
|
||||
apdu[2] = (uint8_t) (value & 0x0000ff);
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
int decode_unsigned24(
|
||||
uint8_t * apdu,
|
||||
uint32_t * value)
|
||||
{
|
||||
if (value) {
|
||||
*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));
|
||||
}
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
int encode_unsigned32(
|
||||
uint8_t * apdu,
|
||||
uint32_t value)
|
||||
{
|
||||
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);
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
int decode_unsigned32(
|
||||
uint8_t * apdu,
|
||||
uint32_t * value)
|
||||
{
|
||||
if (value) {
|
||||
*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));
|
||||
}
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
#ifdef UINT64_MAX
|
||||
/**
|
||||
* @brief Encode a 64-bit unsigned value into buffer
|
||||
* @param buffer - pointer to bytes for storing encoding
|
||||
* @param value - 16-bit value to encode
|
||||
* @return Returns the number of bytes encoded
|
||||
*/
|
||||
int encode_unsigned64(
|
||||
uint8_t * buffer,
|
||||
uint64_t value)
|
||||
{
|
||||
buffer[0] = (uint8_t) ((value & 0xff00000000000000) >> 56);
|
||||
buffer[1] = (uint8_t) ((value & 0x00ff000000000000) >> 48);
|
||||
buffer[2] = (uint8_t) ((value & 0x0000ff0000000000) >> 40);
|
||||
buffer[3] = (uint8_t) ((value & 0x000000ff00000000) >> 32);
|
||||
buffer[4] = (uint8_t) ((value & 0x00000000ff000000) >> 24);
|
||||
buffer[5] = (uint8_t) ((value & 0x0000000000ff0000) >> 16);
|
||||
buffer[6] = (uint8_t) ((value & 0x000000000000ff00) >> 8);
|
||||
buffer[7] = (uint8_t) (value & 0x00000000000000ff);
|
||||
|
||||
return 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Decode a 64-bit unsigned value from a buffer
|
||||
* @param buffer - pointer to bytes for used for decoding
|
||||
* @param value - pointer to 64-bit value to store the decoded value
|
||||
* @return Returns the number of bytes decoded
|
||||
*/
|
||||
int decode_unsigned64(
|
||||
uint8_t * buffer,
|
||||
uint64_t * value)
|
||||
{
|
||||
if (value) {
|
||||
*value = ((uint64_t) ((((uint64_t) buffer[0]) << 56) & 0xff00000000000000));
|
||||
*value |= ((uint64_t) ((((uint64_t) buffer[1]) << 48) & 0x00ff000000000000));
|
||||
*value |= ((uint64_t) ((((uint64_t) buffer[2]) << 40) & 0x0000ff0000000000));
|
||||
*value |= ((uint64_t) ((((uint64_t) buffer[3]) << 32) & 0x000000ff00000000));
|
||||
*value |= ((uint64_t) ((((uint64_t) buffer[4]) << 24) & 0x00000000ff000000));
|
||||
*value |= ((uint64_t) ((((uint64_t) buffer[5]) << 16) & 0x0000000000ff0000));
|
||||
*value |= ((uint64_t) ((((uint64_t) buffer[6]) << 8) & 0x000000000000ff00));
|
||||
*value |= ((uint64_t) (((uint64_t) buffer[7]) & 0x00000000000000ff));
|
||||
}
|
||||
|
||||
return 8;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if BACNET_USE_SIGNED
|
||||
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) {
|
||||
/* negative - bit 7 is set */
|
||||
if (apdu[0] & 0x80)
|
||||
*value = 0xFFFFFF00;
|
||||
else
|
||||
*value = 0;
|
||||
*value |= ((int32_t) (((int32_t) apdu[0]) & 0x000000ff));
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int encode_signed16(
|
||||
uint8_t * apdu,
|
||||
int16_t value)
|
||||
{
|
||||
apdu[0] = (uint8_t) ((value & 0xff00) >> 8);
|
||||
apdu[1] = (uint8_t) (value & 0x00ff);
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int decode_signed16(
|
||||
uint8_t * apdu,
|
||||
int32_t * value)
|
||||
{
|
||||
if (value) {
|
||||
/* 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));
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int encode_signed24(
|
||||
uint8_t * apdu,
|
||||
int32_t value)
|
||||
{
|
||||
apdu[0] = (uint8_t) ((value & 0xff0000) >> 16);
|
||||
apdu[1] = (uint8_t) ((value & 0x00ff00) >> 8);
|
||||
apdu[2] = (uint8_t) (value & 0x0000ff);
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
int decode_signed24(
|
||||
uint8_t * apdu,
|
||||
int32_t * value)
|
||||
{
|
||||
if (value) {
|
||||
/* 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));
|
||||
}
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
int encode_signed32(
|
||||
uint8_t * apdu,
|
||||
int32_t value)
|
||||
{
|
||||
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);
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
int decode_signed32(
|
||||
uint8_t * apdu,
|
||||
int32_t * value)
|
||||
{
|
||||
if (value) {
|
||||
*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));
|
||||
}
|
||||
|
||||
return 4;
|
||||
}
|
||||
#endif
|
||||
/* end of decoding_encoding.c */
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "ctest.h"
|
||||
|
||||
static 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;
|
||||
}
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
}
|
||||
|
||||
static 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);
|
||||
}
|
||||
}
|
||||
|
||||
static 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);
|
||||
}
|
||||
}
|
||||
|
||||
void testBACnetIntegers(
|
||||
Test * pTest)
|
||||
{
|
||||
bool rc;
|
||||
|
||||
assert(pTest);
|
||||
/* 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);
|
||||
}
|
||||
|
||||
#ifdef TEST_BACINT
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
|
||||
pTest = ct_create("BACint", NULL);
|
||||
testBACnetIntegers(pTest);
|
||||
/* configure output */
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_BACINT */
|
||||
#endif /* TEST */
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*####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>
|
||||
#if PRINT_ENABLED
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
#include "bacprop.h"
|
||||
|
||||
/** @file bacprop.c Lookup BACnet Property Tags */
|
||||
|
||||
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:
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "Unsupported object type");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2008 John Minack
|
||||
|
||||
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 "bacdcode.h"
|
||||
#include "npdu.h"
|
||||
#include "timestamp.h"
|
||||
#include "bacpropstates.h"
|
||||
|
||||
/** @file bacpropstates.c Encode/Decode BACnet Application Property States */
|
||||
|
||||
int bacapp_decode_property_state(
|
||||
uint8_t * apdu,
|
||||
BACNET_PROPERTY_STATE * value)
|
||||
{
|
||||
int len = 0;
|
||||
uint32_t len_value_type;
|
||||
int section_length;
|
||||
uint32_t enumValue;
|
||||
uint8_t tagnum;
|
||||
|
||||
section_length =
|
||||
decode_tag_number_and_value(&apdu[len], &tagnum, &len_value_type);
|
||||
|
||||
if (-1 == section_length) {
|
||||
return -1;
|
||||
}
|
||||
value->tag = tagnum;
|
||||
len += section_length;
|
||||
switch (value->tag) {
|
||||
case BOOLEAN_VALUE:
|
||||
value->state.booleanValue = decode_boolean(len_value_type);
|
||||
break;
|
||||
|
||||
case BINARY_VALUE:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.binaryValue = (BACNET_BINARY_PV) enumValue;
|
||||
break;
|
||||
|
||||
case EVENT_TYPE:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.eventType = (BACNET_EVENT_TYPE) enumValue;
|
||||
break;
|
||||
|
||||
case POLARITY:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.polarity = (BACNET_POLARITY) enumValue;
|
||||
break;
|
||||
|
||||
case PROGRAM_CHANGE:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.programChange = (BACNET_PROGRAM_REQUEST) enumValue;
|
||||
break;
|
||||
|
||||
case PROGRAM_STATE:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.programState = (BACNET_PROGRAM_STATE) enumValue;
|
||||
break;
|
||||
|
||||
case REASON_FOR_HALT:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.programError = (BACNET_PROGRAM_ERROR) enumValue;
|
||||
break;
|
||||
|
||||
case RELIABILITY:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.reliability = (BACNET_RELIABILITY) enumValue;
|
||||
break;
|
||||
|
||||
case STATE:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.state = (BACNET_EVENT_STATE) enumValue;
|
||||
break;
|
||||
|
||||
case SYSTEM_STATUS:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.systemStatus = (BACNET_DEVICE_STATUS) enumValue;
|
||||
break;
|
||||
|
||||
case UNITS:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.units = (BACNET_ENGINEERING_UNITS) enumValue;
|
||||
break;
|
||||
|
||||
case UNSIGNED_VALUE:
|
||||
if (-1 == (section_length =
|
||||
decode_unsigned(&apdu[len], len_value_type,
|
||||
&value->state.unsignedValue))) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
|
||||
case LIFE_SAFETY_MODE:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.lifeSafetyMode = (BACNET_LIFE_SAFETY_MODE) enumValue;
|
||||
break;
|
||||
|
||||
case LIFE_SAFETY_STATE:
|
||||
if (-1 == (section_length =
|
||||
decode_enumerated(&apdu[len], len_value_type,
|
||||
&enumValue))) {
|
||||
return -1;
|
||||
}
|
||||
value->state.lifeSafetyState =
|
||||
(BACNET_LIFE_SAFETY_STATE) enumValue;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
len += section_length;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bacapp_decode_context_property_state(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_PROPERTY_STATE * value)
|
||||
{
|
||||
int len = 0;
|
||||
int section_length;
|
||||
|
||||
if (decode_is_opening_tag_number(&apdu[len], tag_number)) {
|
||||
len++;
|
||||
section_length = bacapp_decode_property_state(&apdu[len], value);
|
||||
|
||||
if (section_length == -1) {
|
||||
len = -1;
|
||||
} else {
|
||||
len += section_length;
|
||||
if (decode_is_closing_tag_number(&apdu[len], tag_number)) {
|
||||
len++;
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int bacapp_encode_property_state(
|
||||
uint8_t * apdu,
|
||||
BACNET_PROPERTY_STATE * value)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
if (value && apdu) {
|
||||
switch (value->tag) {
|
||||
case BOOLEAN_VALUE:
|
||||
len =
|
||||
encode_context_boolean(&apdu[0], 0,
|
||||
value->state.booleanValue);
|
||||
break;
|
||||
|
||||
case BINARY_VALUE:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 1,
|
||||
value->state.binaryValue);
|
||||
break;
|
||||
|
||||
case EVENT_TYPE:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 2,
|
||||
value->state.eventType);
|
||||
break;
|
||||
|
||||
case POLARITY:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 3,
|
||||
value->state.polarity);
|
||||
break;
|
||||
|
||||
case PROGRAM_CHANGE:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 4,
|
||||
value->state.programChange);
|
||||
break;
|
||||
|
||||
case PROGRAM_STATE:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 5,
|
||||
value->state.programState);
|
||||
break;
|
||||
|
||||
case REASON_FOR_HALT:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 6,
|
||||
value->state.programError);
|
||||
break;
|
||||
|
||||
case RELIABILITY:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 7,
|
||||
value->state.reliability);
|
||||
break;
|
||||
|
||||
case STATE:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 8, value->state.state);
|
||||
break;
|
||||
|
||||
case SYSTEM_STATUS:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 9,
|
||||
value->state.systemStatus);
|
||||
break;
|
||||
|
||||
case UNITS:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 10,
|
||||
value->state.units);
|
||||
break;
|
||||
|
||||
case UNSIGNED_VALUE:
|
||||
len =
|
||||
encode_context_unsigned(&apdu[0], 11,
|
||||
value->state.unsignedValue);
|
||||
break;
|
||||
|
||||
case LIFE_SAFETY_MODE:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 12,
|
||||
value->state.lifeSafetyMode);
|
||||
break;
|
||||
|
||||
case LIFE_SAFETY_STATE:
|
||||
len =
|
||||
encode_context_enumerated(&apdu[0], 13,
|
||||
value->state.lifeSafetyState);
|
||||
break;
|
||||
|
||||
default:
|
||||
/* FIXME: assert(0); - return a negative len? */
|
||||
break;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <string.h> /* for memset */
|
||||
|
||||
void testPropStates(
|
||||
Test * pTest)
|
||||
{
|
||||
BACNET_PROPERTY_STATE inData;
|
||||
BACNET_PROPERTY_STATE outData;
|
||||
uint8_t appMsg[MAX_APDU];
|
||||
int inLen;
|
||||
int outLen;
|
||||
|
||||
inData.tag = BOOLEAN_VALUE;
|
||||
inData.state.booleanValue = true;
|
||||
|
||||
inLen = bacapp_encode_property_state(appMsg, &inData);
|
||||
|
||||
memset(&outData, 0, sizeof(outData));
|
||||
|
||||
outLen = bacapp_decode_property_state(appMsg, &outData);
|
||||
|
||||
ct_test(pTest, outLen == inLen);
|
||||
ct_test(pTest, inData.tag == outData.tag);
|
||||
ct_test(pTest, inData.state.booleanValue == outData.state.booleanValue);
|
||||
|
||||
/****************
|
||||
*****************
|
||||
****************/
|
||||
inData.tag = BINARY_VALUE;
|
||||
inData.state.binaryValue = BINARY_ACTIVE;
|
||||
|
||||
inLen = bacapp_encode_property_state(appMsg, &inData);
|
||||
|
||||
memset(&outData, 0, sizeof(outData));
|
||||
|
||||
outLen = bacapp_decode_property_state(appMsg, &outData);
|
||||
|
||||
ct_test(pTest, outLen == inLen);
|
||||
ct_test(pTest, inData.tag == outData.tag);
|
||||
ct_test(pTest, inData.state.binaryValue == outData.state.binaryValue);
|
||||
|
||||
/****************
|
||||
*****************
|
||||
****************/
|
||||
inData.tag = EVENT_TYPE;
|
||||
inData.state.eventType = EVENT_BUFFER_READY;
|
||||
|
||||
inLen = bacapp_encode_property_state(appMsg, &inData);
|
||||
|
||||
memset(&outData, 0, sizeof(outData));
|
||||
|
||||
outLen = bacapp_decode_property_state(appMsg, &outData);
|
||||
|
||||
ct_test(pTest, outLen == inLen);
|
||||
ct_test(pTest, inData.tag == outData.tag);
|
||||
ct_test(pTest, inData.state.eventType == outData.state.eventType);
|
||||
|
||||
/****************
|
||||
*****************
|
||||
****************/
|
||||
inData.tag = POLARITY;
|
||||
inData.state.polarity = POLARITY_REVERSE;
|
||||
|
||||
inLen = bacapp_encode_property_state(appMsg, &inData);
|
||||
|
||||
memset(&outData, 0, sizeof(outData));
|
||||
|
||||
outLen = bacapp_decode_property_state(appMsg, &outData);
|
||||
|
||||
ct_test(pTest, outLen == inLen);
|
||||
ct_test(pTest, inData.tag == outData.tag);
|
||||
ct_test(pTest, inData.state.polarity == outData.state.polarity);
|
||||
|
||||
/****************
|
||||
*****************
|
||||
****************/
|
||||
inData.tag = PROGRAM_CHANGE;
|
||||
inData.state.programChange = PROGRAM_REQUEST_RESTART;
|
||||
|
||||
inLen = bacapp_encode_property_state(appMsg, &inData);
|
||||
|
||||
memset(&outData, 0, sizeof(outData));
|
||||
|
||||
outLen = bacapp_decode_property_state(appMsg, &outData);
|
||||
|
||||
ct_test(pTest, outLen == inLen);
|
||||
ct_test(pTest, inData.tag == outData.tag);
|
||||
ct_test(pTest, inData.state.programChange == outData.state.programChange);
|
||||
|
||||
/****************
|
||||
*****************
|
||||
****************/
|
||||
inData.tag = UNSIGNED_VALUE;
|
||||
inData.state.unsignedValue = 0xdeadbeef;
|
||||
|
||||
inLen = bacapp_encode_property_state(appMsg, &inData);
|
||||
|
||||
memset(&outData, 0, sizeof(outData));
|
||||
|
||||
outLen = bacapp_decode_property_state(appMsg, &outData);
|
||||
|
||||
ct_test(pTest, outLen == inLen);
|
||||
ct_test(pTest, inData.tag == outData.tag);
|
||||
ct_test(pTest, inData.state.unsignedValue == outData.state.unsignedValue);
|
||||
|
||||
}
|
||||
|
||||
#ifdef TEST_PROP_STATES
|
||||
#include <assert.h>
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Event", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testPropStates);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* TEST_PROP_STATES */
|
||||
#endif /* TEST */
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2004-2008 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 <string.h>
|
||||
|
||||
#include "bacdef.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacenum.h"
|
||||
#include "bits.h"
|
||||
#include "bacstr.h"
|
||||
#include "bacint.h"
|
||||
#include "bacreal.h"
|
||||
|
||||
/** @file bacreal.c Encode/Decode Floating Point (Real) Types */
|
||||
|
||||
/* NOTE: byte order plays a role in decoding multibyte values */
|
||||
/* http://www.unixpapa.com/incnote/byteorder.html */
|
||||
#ifndef BIG_ENDIAN
|
||||
#error Define BIG_ENDIAN=0 or BIG_ENDIAN=1 for BACnet Stack in compiler settings
|
||||
#endif
|
||||
|
||||
/* from clause 20.2.6 Encoding of a Real Number Value */
|
||||
/* returns the number of apdu bytes consumed */
|
||||
int decode_real(
|
||||
uint8_t * apdu,
|
||||
float *real_value)
|
||||
{
|
||||
union {
|
||||
uint8_t byte[4];
|
||||
float real_value;
|
||||
} my_data;
|
||||
|
||||
/* NOTE: assumes the compiler stores float as IEEE-754 float */
|
||||
#if BIG_ENDIAN
|
||||
my_data.byte[0] = apdu[0];
|
||||
my_data.byte[1] = apdu[1];
|
||||
my_data.byte[2] = apdu[2];
|
||||
my_data.byte[3] = apdu[3];
|
||||
#else
|
||||
my_data.byte[0] = apdu[3];
|
||||
my_data.byte[1] = apdu[2];
|
||||
my_data.byte[2] = apdu[1];
|
||||
my_data.byte[3] = apdu[0];
|
||||
#endif
|
||||
|
||||
*real_value = my_data.real_value;
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
int decode_real_safe(
|
||||
uint8_t * apdu,
|
||||
uint32_t len_value,
|
||||
float *real_value)
|
||||
{
|
||||
if (len_value != 4) {
|
||||
*real_value = 0.0f;
|
||||
return (int) len_value;
|
||||
} else {
|
||||
return decode_real(apdu, real_value);
|
||||
}
|
||||
}
|
||||
|
||||
int decode_context_real(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
float *real_value)
|
||||
{
|
||||
uint32_t len_value;
|
||||
int len = 0;
|
||||
|
||||
if (decode_is_context_tag(&apdu[len], tag_number)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number, &len_value);
|
||||
len += decode_real(&apdu[len], real_value);
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/* from clause 20.2.6 Encoding of a Real Number Value */
|
||||
/* returns the number of apdu bytes consumed */
|
||||
int encode_bacnet_real(
|
||||
float value,
|
||||
uint8_t * apdu)
|
||||
{
|
||||
union {
|
||||
uint8_t byte[4];
|
||||
float real_value;
|
||||
} my_data;
|
||||
|
||||
/* NOTE: assumes the compiler stores float as IEEE-754 float */
|
||||
my_data.real_value = value;
|
||||
#if BIG_ENDIAN
|
||||
apdu[0] = my_data.byte[0];
|
||||
apdu[1] = my_data.byte[1];
|
||||
apdu[2] = my_data.byte[2];
|
||||
apdu[3] = my_data.byte[3];
|
||||
#else
|
||||
apdu[0] = my_data.byte[3];
|
||||
apdu[1] = my_data.byte[2];
|
||||
apdu[2] = my_data.byte[1];
|
||||
apdu[3] = my_data.byte[0];
|
||||
#endif
|
||||
|
||||
return 4;
|
||||
}
|
||||
|
||||
#if BACNET_USE_DOUBLE
|
||||
|
||||
/* from clause 20.2.7 Encoding of a Double Precision Real Number Value */
|
||||
/* returns the number of apdu bytes consumed */
|
||||
int decode_double(
|
||||
uint8_t * apdu,
|
||||
double *double_value)
|
||||
{
|
||||
union {
|
||||
uint8_t byte[8];
|
||||
double double_value;
|
||||
} my_data;
|
||||
|
||||
/* NOTE: assumes the compiler stores float as IEEE-754 float */
|
||||
#if BIG_ENDIAN
|
||||
my_data.byte[0] = apdu[0];
|
||||
my_data.byte[1] = apdu[1];
|
||||
my_data.byte[2] = apdu[2];
|
||||
my_data.byte[3] = apdu[3];
|
||||
my_data.byte[4] = apdu[4];
|
||||
my_data.byte[5] = apdu[5];
|
||||
my_data.byte[6] = apdu[6];
|
||||
my_data.byte[7] = apdu[7];
|
||||
#else
|
||||
my_data.byte[0] = apdu[7];
|
||||
my_data.byte[1] = apdu[6];
|
||||
my_data.byte[2] = apdu[5];
|
||||
my_data.byte[3] = apdu[4];
|
||||
my_data.byte[4] = apdu[3];
|
||||
my_data.byte[5] = apdu[2];
|
||||
my_data.byte[6] = apdu[1];
|
||||
my_data.byte[7] = apdu[0];
|
||||
#endif
|
||||
|
||||
*double_value = my_data.double_value;
|
||||
|
||||
return 8;
|
||||
}
|
||||
|
||||
int decode_double_safe(
|
||||
uint8_t * apdu,
|
||||
uint32_t len_value,
|
||||
double *double_value)
|
||||
{
|
||||
if (len_value != 8) {
|
||||
*double_value = 0.0;
|
||||
return (int) len_value;
|
||||
} else {
|
||||
return decode_double(apdu, double_value);
|
||||
}
|
||||
}
|
||||
|
||||
/* from clause 20.2.7 Encoding of a Double Precision Real Number Value */
|
||||
/* returns the number of apdu bytes consumed */
|
||||
int encode_bacnet_double(
|
||||
double value,
|
||||
uint8_t * apdu)
|
||||
{
|
||||
union {
|
||||
uint8_t byte[8];
|
||||
double double_value;
|
||||
} my_data;
|
||||
|
||||
/* NOTE: assumes the compiler stores float as IEEE-754 float */
|
||||
my_data.double_value = value;
|
||||
#if BIG_ENDIAN
|
||||
apdu[0] = my_data.byte[0];
|
||||
apdu[1] = my_data.byte[1];
|
||||
apdu[2] = my_data.byte[2];
|
||||
apdu[3] = my_data.byte[3];
|
||||
apdu[4] = my_data.byte[4];
|
||||
apdu[5] = my_data.byte[5];
|
||||
apdu[6] = my_data.byte[6];
|
||||
apdu[7] = my_data.byte[7];
|
||||
#else
|
||||
apdu[0] = my_data.byte[7];
|
||||
apdu[1] = my_data.byte[6];
|
||||
apdu[2] = my_data.byte[5];
|
||||
apdu[3] = my_data.byte[4];
|
||||
apdu[4] = my_data.byte[3];
|
||||
apdu[5] = my_data.byte[2];
|
||||
apdu[6] = my_data.byte[1];
|
||||
apdu[7] = my_data.byte[0];
|
||||
#endif
|
||||
|
||||
return 8;
|
||||
}
|
||||
|
||||
int decode_context_double(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
double *double_value)
|
||||
{
|
||||
uint32_t len_value;
|
||||
int len = 0;
|
||||
|
||||
if (decode_is_context_tag(&apdu[len], tag_number)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number, &len_value);
|
||||
len += decode_double(&apdu[len], double_value);
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* end of decoding_encoding.c */
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testBACreal(
|
||||
Test * pTest)
|
||||
{
|
||||
float real_value = 3.14159F, test_real_value = 0.0;
|
||||
uint8_t apdu[MAX_APDU] = { 0 };
|
||||
int len = 0, test_len = 0;
|
||||
|
||||
len = encode_bacnet_real(real_value, &apdu[0]);
|
||||
ct_test(pTest, len == 4);
|
||||
test_len = decode_real(&apdu[0], &test_real_value);
|
||||
ct_test(pTest, test_len == len);
|
||||
ct_test(pTest, test_real_value == real_value);
|
||||
}
|
||||
|
||||
void testBACdouble(
|
||||
Test * pTest)
|
||||
{
|
||||
double double_value = 3.1415927, test_double_value = 0.0;
|
||||
uint8_t apdu[MAX_APDU] = { 0 };
|
||||
int len = 0, test_len = 0;
|
||||
|
||||
len = encode_bacnet_double(double_value, &apdu[0]);
|
||||
ct_test(pTest, len == 8);
|
||||
test_len = decode_double(&apdu[0], &test_double_value);
|
||||
ct_test(pTest, test_len == len);
|
||||
ct_test(pTest, test_double_value == double_value);
|
||||
}
|
||||
|
||||
#ifdef TEST_BACNET_REAL
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACreal", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testBACreal);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testBACdouble);
|
||||
assert(rc);
|
||||
|
||||
/* configure output */
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_BACNET_REAL */
|
||||
#endif /* TEST */
|
||||
+834
@@ -0,0 +1,834 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2015 Nikola Jelic <nikola.jelic@euroicc.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*********************************************************************/
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "bacdcode.h"
|
||||
#include "bacsec.h"
|
||||
|
||||
BACNET_KEY_IDENTIFIER_ALGORITHM key_algorithm(uint16_t id)
|
||||
{
|
||||
return (BACNET_KEY_IDENTIFIER_ALGORITHM) ((id >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
BACNET_KEY_IDENTIFIER_KEY_NUMBER key_number(uint16_t id)
|
||||
{
|
||||
return (BACNET_KEY_IDENTIFIER_KEY_NUMBER) (id & 0xFF);
|
||||
}
|
||||
|
||||
int encode_security_wrapper(int bytes_before,
|
||||
uint8_t * apdu,
|
||||
BACNET_SECURITY_WRAPPER * wrapper)
|
||||
{
|
||||
int curr = 0;
|
||||
int enc_begin = 0;
|
||||
BACNET_KEY_ENTRY key;
|
||||
BACNET_SECURITY_RESPONSE_CODE res = SEC_RESP_SUCCESS;
|
||||
|
||||
apdu[curr] = 0;
|
||||
/* control byte */
|
||||
if (wrapper->payload_net_or_bvll_flag) {
|
||||
apdu[curr] |= 1 << 7;
|
||||
}
|
||||
/* encryption flag will be set after signature calculation */
|
||||
/* bit 5 is reserved and shall be 0 */
|
||||
if (wrapper->authentication_flag) {
|
||||
apdu[curr] |= 1 << 4;
|
||||
}
|
||||
if (wrapper->do_not_unwrap_flag) {
|
||||
apdu[curr] |= 1 << 3;
|
||||
}
|
||||
if (wrapper->do_not_decrypt_flag) {
|
||||
apdu[curr] |= 1 << 2;
|
||||
}
|
||||
if (wrapper->non_trusted_source_flag) {
|
||||
apdu[curr] |= 1 << 1;
|
||||
}
|
||||
if (wrapper->secured_by_router_flag) {
|
||||
apdu[curr] |= 1;
|
||||
}
|
||||
curr++;
|
||||
/* basic integrity checks */
|
||||
if (wrapper->do_not_decrypt_flag && !wrapper->do_not_unwrap_flag) {
|
||||
return -SEC_RESP_MALFORMED_MESSAGE;
|
||||
}
|
||||
if (!wrapper->encrypted_flag && wrapper->do_not_decrypt_flag) {
|
||||
return -SEC_RESP_MALFORMED_MESSAGE;
|
||||
}
|
||||
/* key */
|
||||
apdu[curr++] = wrapper->key_revision;
|
||||
curr += encode_unsigned16(&apdu[curr], wrapper->key_identifier);
|
||||
/* find appropriate key */
|
||||
key.key_identifier = wrapper->key_identifier;
|
||||
res = bacnet_find_key(wrapper->key_revision, &key);
|
||||
if (res != SEC_RESP_SUCCESS) {
|
||||
return -res;
|
||||
}
|
||||
/* source device instance */
|
||||
curr += encode_unsigned24(&apdu[curr], wrapper->source_device_instance);
|
||||
/* message id */
|
||||
curr += encode_unsigned32(&apdu[curr], wrapper->message_id);
|
||||
/* timestamp */
|
||||
curr += encode_unsigned32(&apdu[curr], wrapper->timestamp);
|
||||
/* begin encryption starting from destination device instance */
|
||||
enc_begin = curr;
|
||||
/* destination device instance */
|
||||
curr +=
|
||||
encode_unsigned24(&apdu[curr], wrapper->destination_device_instance);
|
||||
/* dst address */
|
||||
curr += encode_unsigned16(&apdu[curr], wrapper->dnet);
|
||||
apdu[curr++] = wrapper->dlen;
|
||||
memcpy(&apdu[curr], wrapper->dadr, wrapper->dlen);
|
||||
curr += wrapper->dlen;
|
||||
/* src address */
|
||||
curr += encode_unsigned16(&apdu[curr], wrapper->snet);
|
||||
apdu[curr++] = wrapper->slen;
|
||||
memcpy(&apdu[curr], wrapper->sadr, wrapper->slen);
|
||||
curr += wrapper->slen;
|
||||
/* authentication */
|
||||
if (wrapper->authentication_flag) {
|
||||
apdu[curr++] = wrapper->authentication_mechanism;
|
||||
/* authentication data */
|
||||
curr += encode_unsigned16(&apdu[curr], wrapper->user_id);
|
||||
apdu[curr++] = wrapper->user_role;
|
||||
if ((wrapper->authentication_mechanism >= 1) &&
|
||||
(wrapper->authentication_mechanism <= 199)) {
|
||||
curr +=
|
||||
encode_unsigned16(&apdu[curr],
|
||||
wrapper->authentication_data_length + 5);
|
||||
memcpy(&apdu[curr], wrapper->authentication_data,
|
||||
wrapper->authentication_data_length);
|
||||
curr += wrapper->authentication_data_length;
|
||||
} else if (wrapper->authentication_mechanism >= 200) {
|
||||
curr +=
|
||||
encode_unsigned16(&apdu[curr],
|
||||
wrapper->authentication_data_length + 7);
|
||||
curr += encode_unsigned16(&apdu[curr], wrapper->vendor_id);
|
||||
memcpy(&apdu[curr], wrapper->authentication_data,
|
||||
wrapper->authentication_data_length);
|
||||
curr += wrapper->authentication_data_length;
|
||||
}
|
||||
}
|
||||
memcpy(&apdu[curr], wrapper->service_data, wrapper->service_data_len);
|
||||
curr += wrapper->service_data_len;
|
||||
/* signature calculation */
|
||||
key_sign_msg(&key, &apdu[-bytes_before], (uint32_t)(bytes_before + curr),
|
||||
wrapper->signature);
|
||||
/* padding and encryption */
|
||||
if (wrapper->encrypted_flag) {
|
||||
/* set encryption flag, signing is done */
|
||||
apdu[0] |= 1 << 6;
|
||||
/* handle padding */
|
||||
key_set_padding(&key, curr - enc_begin, &wrapper->padding_len,
|
||||
wrapper->padding);
|
||||
if (wrapper->padding_len > 2) {
|
||||
memcpy(&apdu[curr], wrapper->padding, wrapper->padding_len - 2);
|
||||
curr += wrapper->padding_len - 2;
|
||||
}
|
||||
curr += encode_unsigned16(&apdu[curr], wrapper->padding_len);
|
||||
/* encryption */
|
||||
key_encrypt_msg(&key, &apdu[enc_begin], (uint32_t)(curr - enc_begin),
|
||||
wrapper->signature);
|
||||
}
|
||||
memcpy(&apdu[curr], wrapper->signature, SIGNATURE_LEN);
|
||||
curr += SIGNATURE_LEN;
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
int encode_challenge_request(uint8_t * apdu,
|
||||
BACNET_CHALLENGE_REQUEST * bc_req)
|
||||
{
|
||||
int curr = 0;
|
||||
|
||||
apdu[curr++] = bc_req->message_challenge;
|
||||
curr += encode_unsigned32(&apdu[curr], bc_req->orig_message_id);
|
||||
curr += encode_unsigned32(&apdu[curr], bc_req->orig_timestamp);
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
int encode_security_payload(uint8_t * apdu,
|
||||
BACNET_SECURITY_PAYLOAD * payload)
|
||||
{
|
||||
encode_unsigned16(&apdu[0], payload->payload_length);
|
||||
memcpy(&apdu[2], payload->payload, payload->payload_length);
|
||||
|
||||
return (int)(2 + payload->payload_length);
|
||||
}
|
||||
|
||||
int encode_security_response(uint8_t * apdu,
|
||||
BACNET_SECURITY_RESPONSE * resp)
|
||||
{
|
||||
int curr = 0;
|
||||
int i;
|
||||
|
||||
apdu[curr++] = resp->response_code;
|
||||
curr += encode_unsigned32(&apdu[curr], resp->orig_message_id);
|
||||
curr += encode_unsigned32(&apdu[curr], resp->orig_timestamp);
|
||||
switch ((BACNET_SECURITY_RESPONSE_CODE) resp->response_code) {
|
||||
case SEC_RESP_BAD_TIMESTAMP:
|
||||
curr +=
|
||||
encode_unsigned32(&apdu[curr],
|
||||
resp->response.bad_timestamp.expected_timestamp);
|
||||
break;
|
||||
case SEC_RESP_CANNOT_USE_KEY:
|
||||
curr +=
|
||||
encode_unsigned16(&apdu[curr],
|
||||
resp->response.cannot_use_key.key);
|
||||
break;
|
||||
case SEC_RESP_INCORRECT_KEY:
|
||||
apdu[curr++] = resp->response.incorrect_key.number_of_keys;
|
||||
for (i = 0; i < (int)resp->response.incorrect_key.number_of_keys; i++) {
|
||||
curr +=
|
||||
encode_unsigned16(&apdu[curr],
|
||||
resp->response.incorrect_key.keys[i]);
|
||||
}
|
||||
break;
|
||||
case SEC_RESP_UNKNOWN_AUTHENTICATION_TYPE:
|
||||
apdu[curr++] =
|
||||
resp->response.unknown_authentication_type.
|
||||
original_authentication_type;
|
||||
curr +=
|
||||
encode_unsigned16(&apdu[curr],
|
||||
resp->response.unknown_authentication_type.vendor_id);
|
||||
break;
|
||||
case SEC_RESP_UNKNOWN_KEY:
|
||||
curr +=
|
||||
encode_unsigned16(&apdu[curr],
|
||||
resp->response.unknown_key.original_key);
|
||||
break;
|
||||
case SEC_RESP_UNKNOWN_KEY_REVISION:
|
||||
apdu[curr++] =
|
||||
resp->response.unknown_key_revision.original_key_revision;
|
||||
break;
|
||||
case SEC_RESP_TOO_MANY_KEYS:
|
||||
apdu[curr++] = resp->response.too_many_keys.max_num_of_keys;
|
||||
break;
|
||||
case SEC_RESP_INVALID_KEY_DATA:
|
||||
curr +=
|
||||
encode_unsigned16(&apdu[curr],
|
||||
resp->response.invalid_key_data.key);
|
||||
break;
|
||||
case SEC_RESP_SUCCESS:
|
||||
case SEC_RESP_ACCESS_DENIED:
|
||||
case SEC_RESP_BAD_DESTINATION_ADDRESS:
|
||||
case SEC_RESP_BAD_DESTINATION_DEVICE_ID:
|
||||
case SEC_RESP_BAD_SIGNATURE:
|
||||
case SEC_RESP_BAD_SOURCE_ADDRESS:
|
||||
case SEC_RESP_CANNOT_VERIFY_MESSAGE_ID:
|
||||
case SEC_RESP_CORRECT_KEY_REVISION:
|
||||
case SEC_RESP_DESTINATION_DEVICE_ID_REQUIRED:
|
||||
case SEC_RESP_DUPLICATE_MESSAGE:
|
||||
case SEC_RESP_ENCRYPTION_NOT_CONFIGURED:
|
||||
case SEC_RESP_ENCRYPTION_REQUIRED:
|
||||
case SEC_RESP_KEY_UPDATE_IN_PROGRESS:
|
||||
case SEC_RESP_MALFORMED_MESSAGE:
|
||||
case SEC_RESP_NOT_KEY_SERVER:
|
||||
case SEC_RESP_SECURITY_NOT_CONFIGURED:
|
||||
case SEC_RESP_SOURCE_SECURITY_REQUIRED:
|
||||
case SEC_RESP_UNKNOWN_SOURCE_MESSAGE:
|
||||
break;
|
||||
default:
|
||||
return -1; /* unknown message type */
|
||||
}
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
int encode_request_key_update(uint8_t * apdu,
|
||||
BACNET_REQUEST_KEY_UPDATE * req)
|
||||
{
|
||||
int curr = 0;
|
||||
|
||||
apdu[curr++] = req->set_1_key_revision;
|
||||
curr += encode_unsigned32(&apdu[curr], req->set_1_activation_time);
|
||||
curr += encode_unsigned32(&apdu[curr], req->set_1_expiration_time);
|
||||
apdu[curr++] = req->set_2_key_revision;
|
||||
curr += encode_unsigned32(&apdu[curr], req->set_2_activation_time);
|
||||
curr += encode_unsigned32(&apdu[curr], req->set_2_expiration_time);
|
||||
apdu[curr++] = req->distribution_key_revision;
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
int encode_key_entry(uint8_t * apdu,
|
||||
BACNET_KEY_ENTRY * entry)
|
||||
{
|
||||
int curr = 0;
|
||||
|
||||
curr += encode_unsigned16(&apdu[curr], entry->key_identifier);
|
||||
apdu[curr++] = entry->key_len;
|
||||
memcpy(&apdu[curr], entry->key, entry->key_len);
|
||||
curr += entry->key_len;
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
int encode_update_key_set(uint8_t * apdu,
|
||||
BACNET_UPDATE_KEY_SET * key_set)
|
||||
{
|
||||
int curr = 0;
|
||||
int i, res;
|
||||
apdu[curr] = 0;
|
||||
|
||||
if (key_set->remove) {
|
||||
apdu[curr] |= 1;
|
||||
}
|
||||
if (key_set->more) {
|
||||
apdu[curr] |= 1 << 1;
|
||||
}
|
||||
if (key_set->set_clr[1]) {
|
||||
apdu[curr] |= 1 << 2;
|
||||
}
|
||||
if (key_set->set_ck[1]) {
|
||||
apdu[curr] |= 1 << 3;
|
||||
}
|
||||
if (key_set->set_rae[1]) {
|
||||
apdu[curr] |= 1 << 4;
|
||||
}
|
||||
if (key_set->set_clr[0]) {
|
||||
apdu[curr] |= 1 << 5;
|
||||
}
|
||||
if (key_set->set_ck[0]) {
|
||||
apdu[curr] |= 1 << 6;
|
||||
}
|
||||
if (key_set->set_rae[0]) {
|
||||
apdu[curr] |= 1 << 7;
|
||||
}
|
||||
curr++;
|
||||
if (key_set->set_rae[0]) {
|
||||
apdu[curr++] = key_set->set_key_revision[0];
|
||||
curr +=
|
||||
encode_unsigned32(&apdu[curr], key_set->set_activation_time[0]);
|
||||
curr +=
|
||||
encode_unsigned32(&apdu[curr], key_set->set_expiration_time[0]);
|
||||
}
|
||||
if (key_set->set_ck[0]) {
|
||||
apdu[curr++] = key_set->set_key_count[0];
|
||||
if (key_set->set_key_count[0] > MAX_UPDATE_KEY_COUNT) {
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < (int)key_set->set_key_count[0]; i++) {
|
||||
res = encode_key_entry(&apdu[curr], &key_set->set_keys[0][i]);
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
curr += res;
|
||||
}
|
||||
}
|
||||
if (key_set->set_rae[1]) {
|
||||
apdu[curr++] = key_set->set_key_revision[1];
|
||||
curr +=
|
||||
encode_unsigned32(&apdu[curr], key_set->set_activation_time[1]);
|
||||
curr +=
|
||||
encode_unsigned32(&apdu[curr], key_set->set_expiration_time[1]);
|
||||
}
|
||||
if (key_set->set_ck[1]) {
|
||||
apdu[curr++] = key_set->set_key_count[1];
|
||||
if (key_set->set_key_count[1] > MAX_UPDATE_KEY_COUNT) {
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < (int)key_set->set_key_count[1]; i++) {
|
||||
res = encode_key_entry(&apdu[curr], &key_set->set_keys[1][i]);
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
curr += res;
|
||||
}
|
||||
}
|
||||
return curr;
|
||||
}
|
||||
|
||||
int encode_update_distribution_key(uint8_t * apdu,
|
||||
BACNET_UPDATE_DISTRIBUTION_KEY * dist_key)
|
||||
{
|
||||
int curr = 0;
|
||||
int res;
|
||||
|
||||
apdu[curr++] = dist_key->key_revision;
|
||||
res = encode_key_entry(&apdu[curr], &dist_key->key);
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return curr + res;
|
||||
}
|
||||
|
||||
int encode_request_master_key(uint8_t * apdu,
|
||||
BACNET_REQUEST_MASTER_KEY * req_master_key)
|
||||
{
|
||||
int curr = 0;
|
||||
|
||||
apdu[curr++] = req_master_key->no_supported_algorithms;
|
||||
memcpy(&apdu[curr], req_master_key->es_algorithms,
|
||||
req_master_key->no_supported_algorithms);
|
||||
|
||||
return (int)(curr + req_master_key->no_supported_algorithms);
|
||||
}
|
||||
|
||||
int encode_set_master_key(uint8_t * apdu,
|
||||
BACNET_SET_MASTER_KEY * set_master_key)
|
||||
{
|
||||
return encode_key_entry(apdu, &set_master_key->key);
|
||||
}
|
||||
|
||||
int decode_security_wrapper_safe(int bytes_before,
|
||||
uint8_t * apdu,
|
||||
uint32_t apdu_len_remaining,
|
||||
BACNET_SECURITY_WRAPPER * wrapper)
|
||||
{
|
||||
int curr = 0;
|
||||
int enc_begin = 0;
|
||||
int real_len = (int)(apdu_len_remaining - SIGNATURE_LEN);
|
||||
BACNET_KEY_ENTRY key;
|
||||
BACNET_SECURITY_RESPONSE_CODE res = SEC_RESP_SUCCESS;
|
||||
|
||||
if (apdu_len_remaining < 40) {
|
||||
return -SEC_RESP_MALFORMED_MESSAGE;
|
||||
}
|
||||
wrapper->payload_net_or_bvll_flag = ((apdu[curr] & (1 << 7)) != 0);
|
||||
wrapper->encrypted_flag = ((apdu[curr] & (1 << 6)) != 0);
|
||||
wrapper->authentication_flag = ((apdu[curr] & (1 << 4)) != 0);
|
||||
wrapper->do_not_unwrap_flag = ((apdu[curr] & (1 << 3)) != 0);
|
||||
wrapper->do_not_decrypt_flag = ((apdu[curr] & (1 << 2)) != 0);
|
||||
wrapper->non_trusted_source_flag = ((apdu[curr] & (1 << 1)) != 0);
|
||||
wrapper->secured_by_router_flag = ((apdu[curr] & 1) != 0);
|
||||
/* basic integrity checks */
|
||||
if (wrapper->do_not_decrypt_flag && !wrapper->do_not_unwrap_flag) {
|
||||
return -SEC_RESP_MALFORMED_MESSAGE;
|
||||
}
|
||||
if (!wrapper->encrypted_flag && wrapper->do_not_decrypt_flag) {
|
||||
return -SEC_RESP_MALFORMED_MESSAGE;
|
||||
}
|
||||
/* remove encryption flag for signature validation */
|
||||
apdu[curr] &= ~((uint8_t) (1 << 6));
|
||||
curr++;
|
||||
/* key */
|
||||
wrapper->key_revision = apdu[curr++];
|
||||
curr += decode_unsigned16(&apdu[curr], &wrapper->key_identifier);
|
||||
/* find appropriate key */
|
||||
key.key_identifier = wrapper->key_identifier;
|
||||
res = bacnet_find_key(wrapper->key_revision, &key);
|
||||
if (res != SEC_RESP_SUCCESS) {
|
||||
return -res;
|
||||
}
|
||||
/* source device instance */
|
||||
curr += decode_unsigned24(&apdu[curr], &wrapper->source_device_instance);
|
||||
/* message id */
|
||||
curr += decode_unsigned32(&apdu[curr], &wrapper->message_id);
|
||||
/* timestamp */
|
||||
curr += decode_unsigned32(&apdu[curr], &wrapper->timestamp);
|
||||
/* begin decryption starting from destination device instance */
|
||||
enc_begin = curr;
|
||||
/* read signature */
|
||||
memcpy(wrapper->signature, &apdu[real_len], SIGNATURE_LEN);
|
||||
if (wrapper->encrypted_flag) {
|
||||
if (!key_decrypt_msg(&key, &apdu[enc_begin],
|
||||
(uint32_t)(real_len - enc_begin), wrapper->signature)) {
|
||||
return -SEC_RESP_MALFORMED_MESSAGE;
|
||||
}
|
||||
curr += decode_unsigned16(&apdu[real_len - 2], &wrapper->padding_len);
|
||||
real_len -= wrapper->padding_len;
|
||||
memcpy(wrapper->padding, &apdu[wrapper->padding_len],
|
||||
wrapper->padding_len - 2);
|
||||
}
|
||||
/* destination device instance */
|
||||
curr +=
|
||||
decode_unsigned24(&apdu[curr], &wrapper->destination_device_instance);
|
||||
/* dst address */
|
||||
curr += decode_unsigned16(&apdu[curr], &wrapper->dnet);
|
||||
wrapper->dlen = apdu[curr++];
|
||||
memcpy(wrapper->dadr, &apdu[curr], wrapper->dlen);
|
||||
curr += wrapper->dlen;
|
||||
/* src address */
|
||||
curr += decode_unsigned16(&apdu[curr], &wrapper->snet);
|
||||
wrapper->slen = apdu[curr++];
|
||||
memcpy(wrapper->sadr, &apdu[curr], wrapper->slen);
|
||||
curr += wrapper->slen;
|
||||
/* authentication */
|
||||
if (wrapper->authentication_flag) {
|
||||
wrapper->authentication_mechanism = apdu[curr++];
|
||||
/* authentication data */
|
||||
curr += decode_unsigned16(&apdu[curr], &wrapper->user_id);
|
||||
wrapper->user_role = apdu[curr++];
|
||||
if ((wrapper->authentication_mechanism >= 1) &&
|
||||
(wrapper->authentication_mechanism <= 199)) {
|
||||
curr +=
|
||||
decode_unsigned16(&apdu[curr],
|
||||
&wrapper->authentication_data_length);
|
||||
wrapper->authentication_data_length -= 5;
|
||||
memcpy(wrapper->authentication_data, &apdu[curr],
|
||||
wrapper->authentication_data_length);
|
||||
curr += wrapper->authentication_data_length;
|
||||
} else if (wrapper->authentication_mechanism >= 200) {
|
||||
curr +=
|
||||
decode_unsigned16(&apdu[curr],
|
||||
&wrapper->authentication_data_length);
|
||||
wrapper->authentication_data_length -= 7;
|
||||
curr += decode_unsigned16(&apdu[curr], &wrapper->vendor_id);
|
||||
memcpy(wrapper->authentication_data, &apdu[curr],
|
||||
wrapper->authentication_data_length);
|
||||
curr += wrapper->authentication_data_length;
|
||||
}
|
||||
}
|
||||
wrapper->service_data_len = (uint16_t)(real_len - curr);
|
||||
memcpy(wrapper->service_data, &apdu[curr], wrapper->service_data_len);
|
||||
curr += wrapper->service_data_len;
|
||||
if (!key_verify_sign_msg(&key, &apdu[-bytes_before],
|
||||
(uint32_t)(bytes_before + real_len), wrapper->signature)) {
|
||||
return -SEC_RESP_BAD_SIGNATURE;
|
||||
}
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
int decode_challenge_request_safe(uint8_t * apdu,
|
||||
uint32_t apdu_len_remaining,
|
||||
BACNET_CHALLENGE_REQUEST * bc_req)
|
||||
{
|
||||
int curr = 0;
|
||||
|
||||
if (apdu_len_remaining < 9) {
|
||||
return -1;
|
||||
}
|
||||
bc_req->message_challenge = apdu[curr++];
|
||||
curr += decode_unsigned32(&apdu[curr], &bc_req->orig_message_id);
|
||||
curr += decode_unsigned32(&apdu[curr], &bc_req->orig_timestamp);
|
||||
|
||||
return curr; /* always 9! */
|
||||
}
|
||||
|
||||
int decode_security_payload_safe(uint8_t * apdu,
|
||||
uint32_t apdu_len_remaining,
|
||||
BACNET_SECURITY_PAYLOAD * payload)
|
||||
{
|
||||
if (apdu_len_remaining < 2) {
|
||||
return -1;
|
||||
}
|
||||
decode_unsigned16(&apdu[0], &payload->payload_length);
|
||||
if (apdu_len_remaining - 2 < payload->payload_length) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(payload->payload, &apdu[2], payload->payload_length);
|
||||
return (int)(2 + payload->payload_length);
|
||||
}
|
||||
|
||||
int decode_security_response_safe(uint8_t * apdu,
|
||||
uint32_t apdu_len_remaining,
|
||||
BACNET_SECURITY_RESPONSE * resp)
|
||||
{
|
||||
int curr = 0;
|
||||
int i;
|
||||
|
||||
if (apdu_len_remaining < 9) {
|
||||
return -1;
|
||||
}
|
||||
resp->response_code = apdu[curr++];
|
||||
curr += decode_unsigned32(&apdu[curr], &resp->orig_message_id);
|
||||
curr += decode_unsigned32(&apdu[curr], &resp->orig_timestamp);
|
||||
switch ((BACNET_SECURITY_RESPONSE_CODE) resp->response_code) {
|
||||
case SEC_RESP_BAD_TIMESTAMP:
|
||||
if (apdu_len_remaining < 13) {
|
||||
return -1;
|
||||
}
|
||||
curr +=
|
||||
decode_unsigned32(&apdu[curr],
|
||||
&resp->response.bad_timestamp.expected_timestamp);
|
||||
break;
|
||||
case SEC_RESP_CANNOT_USE_KEY:
|
||||
if (apdu_len_remaining < 11) {
|
||||
return -1;
|
||||
}
|
||||
curr +=
|
||||
decode_unsigned16(&apdu[curr],
|
||||
&resp->response.cannot_use_key.key);
|
||||
break;
|
||||
case SEC_RESP_INCORRECT_KEY:
|
||||
if (apdu_len_remaining < 10) {
|
||||
return -1;
|
||||
}
|
||||
resp->response.incorrect_key.number_of_keys = apdu[curr++];
|
||||
if (apdu_len_remaining - 10 <
|
||||
resp->response.incorrect_key.number_of_keys * 2) {
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < (int)resp->response.incorrect_key.number_of_keys; i++) {
|
||||
curr +=
|
||||
decode_unsigned16(&apdu[curr],
|
||||
&resp->response.incorrect_key.keys[i]);
|
||||
}
|
||||
break;
|
||||
case SEC_RESP_UNKNOWN_AUTHENTICATION_TYPE:
|
||||
if (apdu_len_remaining < 12) {
|
||||
return -1;
|
||||
}
|
||||
resp->response.unknown_authentication_type.
|
||||
original_authentication_type = apdu[curr++];
|
||||
curr +=
|
||||
decode_unsigned16(&apdu[curr],
|
||||
&resp->response.unknown_authentication_type.vendor_id);
|
||||
if (resp->response.unknown_authentication_type.
|
||||
original_authentication_type < 200 &&
|
||||
resp->response.unknown_authentication_type.vendor_id != 0) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case SEC_RESP_UNKNOWN_KEY:
|
||||
if (apdu_len_remaining < 11) {
|
||||
return -1;
|
||||
}
|
||||
curr +=
|
||||
decode_unsigned16(&apdu[curr],
|
||||
&resp->response.unknown_key.original_key);
|
||||
break;
|
||||
case SEC_RESP_UNKNOWN_KEY_REVISION:
|
||||
if (apdu_len_remaining < 10) {
|
||||
return -1;
|
||||
}
|
||||
resp->response.unknown_key_revision.original_key_revision =
|
||||
apdu[curr++];
|
||||
break;
|
||||
case SEC_RESP_TOO_MANY_KEYS:
|
||||
if (apdu_len_remaining < 10) {
|
||||
return -1;
|
||||
}
|
||||
resp->response.too_many_keys.max_num_of_keys = apdu[curr++];
|
||||
break;
|
||||
case SEC_RESP_INVALID_KEY_DATA:
|
||||
if (apdu_len_remaining < 11) {
|
||||
return -1;
|
||||
}
|
||||
curr +=
|
||||
decode_unsigned16(&apdu[curr],
|
||||
&resp->response.invalid_key_data.key);
|
||||
break;
|
||||
case SEC_RESP_SUCCESS:
|
||||
case SEC_RESP_ACCESS_DENIED:
|
||||
case SEC_RESP_BAD_DESTINATION_ADDRESS:
|
||||
case SEC_RESP_BAD_DESTINATION_DEVICE_ID:
|
||||
case SEC_RESP_BAD_SIGNATURE:
|
||||
case SEC_RESP_BAD_SOURCE_ADDRESS:
|
||||
case SEC_RESP_CANNOT_VERIFY_MESSAGE_ID:
|
||||
case SEC_RESP_CORRECT_KEY_REVISION:
|
||||
case SEC_RESP_DESTINATION_DEVICE_ID_REQUIRED:
|
||||
case SEC_RESP_DUPLICATE_MESSAGE:
|
||||
case SEC_RESP_ENCRYPTION_NOT_CONFIGURED:
|
||||
case SEC_RESP_ENCRYPTION_REQUIRED:
|
||||
case SEC_RESP_KEY_UPDATE_IN_PROGRESS:
|
||||
case SEC_RESP_MALFORMED_MESSAGE:
|
||||
case SEC_RESP_NOT_KEY_SERVER:
|
||||
case SEC_RESP_SECURITY_NOT_CONFIGURED:
|
||||
case SEC_RESP_SOURCE_SECURITY_REQUIRED:
|
||||
case SEC_RESP_UNKNOWN_SOURCE_MESSAGE:
|
||||
break;
|
||||
default:
|
||||
return -1; /* unknown message type */
|
||||
}
|
||||
return curr;
|
||||
}
|
||||
|
||||
int decode_request_key_update_safe(uint8_t * apdu,
|
||||
uint32_t apdu_len_remaining,
|
||||
BACNET_REQUEST_KEY_UPDATE * req)
|
||||
{
|
||||
int curr = 0;
|
||||
|
||||
if (apdu_len_remaining < 19) {
|
||||
return -1;
|
||||
}
|
||||
req->set_1_key_revision = apdu[curr++];
|
||||
curr += decode_unsigned32(&apdu[curr], &req->set_1_activation_time);
|
||||
curr += decode_unsigned32(&apdu[curr], &req->set_1_expiration_time);
|
||||
req->set_2_key_revision = apdu[curr++];
|
||||
curr += decode_unsigned32(&apdu[curr], &req->set_2_activation_time);
|
||||
curr += decode_unsigned32(&apdu[curr], &req->set_2_expiration_time);
|
||||
req->distribution_key_revision = apdu[curr++];
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
int decode_key_entry_safe(uint8_t * apdu,
|
||||
uint32_t apdu_len_remaining,
|
||||
BACNET_KEY_ENTRY * entry)
|
||||
{
|
||||
int curr = 0;
|
||||
|
||||
if (apdu_len_remaining < 3) {
|
||||
return -1;
|
||||
}
|
||||
curr += decode_unsigned16(&apdu[curr], &entry->key_identifier);
|
||||
entry->key_len = apdu[curr++];
|
||||
if (apdu_len_remaining - 3 < entry->key_len ||
|
||||
entry->key_len > MAX_KEY_LEN) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(entry->key, &apdu[curr], entry->key_len);
|
||||
curr += entry->key_len;
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
int decode_update_key_set_safe(uint8_t * apdu,
|
||||
uint32_t apdu_len_remaining,
|
||||
BACNET_UPDATE_KEY_SET * key_set)
|
||||
{
|
||||
int curr = 0;
|
||||
int i, res;
|
||||
|
||||
if (apdu_len_remaining < 1) {
|
||||
return -1;
|
||||
}
|
||||
if (apdu[curr] & 1) {
|
||||
key_set->remove = true;
|
||||
}
|
||||
if ((apdu[curr] >> 1) & 1) {
|
||||
key_set->more = true;
|
||||
}
|
||||
if ((apdu[curr] >> 2) & 1) {
|
||||
key_set->set_clr[1] = true;
|
||||
}
|
||||
if ((apdu[curr] >> 3) & 1) {
|
||||
key_set->set_ck[1] = true;
|
||||
}
|
||||
if ((apdu[curr] >> 4) & 1) {
|
||||
key_set->set_rae[1] = true;
|
||||
}
|
||||
if ((apdu[curr] >> 5) & 1) {
|
||||
key_set->set_clr[0] = true;
|
||||
}
|
||||
if ((apdu[curr] >> 6) & 1) {
|
||||
key_set->set_ck[0] = true;
|
||||
}
|
||||
if ((apdu[curr] >> 7) & 1) {
|
||||
key_set->set_rae[0] = true;
|
||||
}
|
||||
curr++;
|
||||
if (key_set->set_rae[0]) {
|
||||
if (apdu_len_remaining - curr < 9) {
|
||||
return -1;
|
||||
}
|
||||
key_set->set_key_revision[0] = apdu[curr++];
|
||||
curr +=
|
||||
decode_unsigned32(&apdu[curr], &key_set->set_activation_time[0]);
|
||||
curr +=
|
||||
decode_unsigned32(&apdu[curr], &key_set->set_expiration_time[0]);
|
||||
}
|
||||
if (key_set->set_ck[0]) {
|
||||
if (apdu_len_remaining - curr < 1) {
|
||||
return -1;
|
||||
}
|
||||
key_set->set_key_count[0] = apdu[curr++];
|
||||
if (key_set->set_key_count[0] > MAX_UPDATE_KEY_COUNT) {
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < (int)key_set->set_key_count[0]; i++) {
|
||||
res =
|
||||
decode_key_entry_safe(apdu + curr, apdu_len_remaining - curr,
|
||||
&key_set->set_keys[0][i]);
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
curr += res;
|
||||
}
|
||||
}
|
||||
if (key_set->set_rae[1]) {
|
||||
if (apdu_len_remaining - curr < 9) {
|
||||
return -1;
|
||||
}
|
||||
key_set->set_key_revision[1] = apdu[curr++];
|
||||
curr +=
|
||||
decode_unsigned32(&apdu[curr], &key_set->set_activation_time[1]);
|
||||
curr +=
|
||||
decode_unsigned32(&apdu[curr], &key_set->set_expiration_time[1]);
|
||||
}
|
||||
if (key_set->set_ck[1]) {
|
||||
if (apdu_len_remaining - curr < 1) {
|
||||
return -1;
|
||||
}
|
||||
key_set->set_key_count[1] = apdu[curr++];
|
||||
if (key_set->set_key_count[1] > MAX_UPDATE_KEY_COUNT) {
|
||||
return -1;
|
||||
}
|
||||
for (i = 0; i < (int)key_set->set_key_count[1]; i++) {
|
||||
res =
|
||||
decode_key_entry_safe(apdu + curr, apdu_len_remaining - curr,
|
||||
&key_set->set_keys[1][i]);
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
curr += res;
|
||||
}
|
||||
}
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
int decode_update_distribution_key_safe(uint8_t * apdu,
|
||||
uint32_t apdu_len_remaining,
|
||||
BACNET_UPDATE_DISTRIBUTION_KEY * dist_key)
|
||||
{
|
||||
int curr = 0;
|
||||
int res;
|
||||
if (apdu_len_remaining < 1) {
|
||||
return -1;
|
||||
}
|
||||
dist_key->key_revision = apdu[curr++];
|
||||
res =
|
||||
decode_key_entry_safe(&apdu[curr], apdu_len_remaining - curr,
|
||||
&dist_key->key);
|
||||
if (res < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return curr + res;
|
||||
}
|
||||
|
||||
int decode_request_master_key_safe(uint8_t * apdu,
|
||||
uint32_t apdu_len_remaining,
|
||||
BACNET_REQUEST_MASTER_KEY * req_master_key)
|
||||
{
|
||||
uint32_t curr = 0;
|
||||
|
||||
if (apdu_len_remaining < 1) {
|
||||
return -1;
|
||||
}
|
||||
req_master_key->no_supported_algorithms = apdu[curr++];
|
||||
if (apdu_len_remaining < curr + req_master_key->no_supported_algorithms) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(req_master_key->es_algorithms, &apdu[curr],
|
||||
req_master_key->no_supported_algorithms);
|
||||
|
||||
return (int)(curr + req_master_key->no_supported_algorithms);
|
||||
}
|
||||
|
||||
int decode_set_master_key_safe(uint8_t * apdu,
|
||||
uint32_t apdu_len_remaining,
|
||||
BACNET_SET_MASTER_KEY * set_master_key)
|
||||
{
|
||||
return decode_key_entry_safe(apdu, apdu_len_remaining,
|
||||
&set_master_key->key);
|
||||
}
|
||||
+1122
File diff suppressed because it is too large
Load Diff
+2569
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2015 Nikola Jelic
|
||||
|
||||
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 "bacdcode.h"
|
||||
#include "bactimevalue.h"
|
||||
|
||||
int bacapp_encode_time_value(uint8_t * apdu,
|
||||
BACNET_TIME_VALUE * value)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_application_time(&apdu[apdu_len], &value->Time);
|
||||
apdu_len += len;
|
||||
|
||||
len = bacapp_encode_application_data(&apdu[apdu_len], &value->Value);
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_encode_context_time_value(uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_TIME_VALUE * value)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_opening_tag(&apdu[apdu_len], tag_number);
|
||||
apdu_len += len;
|
||||
|
||||
len = bacapp_encode_time_value(&apdu[apdu_len], value);
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_closing_tag(&apdu[apdu_len], tag_number);
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_decode_time_value(uint8_t * apdu,
|
||||
BACNET_TIME_VALUE * value)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = decode_application_time(&apdu[apdu_len], &value->Time);
|
||||
if (len <= 0)
|
||||
return -1;
|
||||
apdu_len += len;
|
||||
|
||||
len = bacapp_decode_application_data(&apdu[apdu_len], 2048, &value->Value);
|
||||
if (len <= 0)
|
||||
return -1;
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_decode_context_time_value(uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_TIME_VALUE * value)
|
||||
{
|
||||
int len = 0;
|
||||
int section_length;
|
||||
|
||||
if (decode_is_opening_tag_number(&apdu[len], tag_number))
|
||||
len++;
|
||||
else
|
||||
return -1;
|
||||
|
||||
section_length = bacapp_decode_time_value(&apdu[len], value);
|
||||
if (section_length > 0)
|
||||
len += section_length;
|
||||
else
|
||||
return -1;
|
||||
|
||||
if (decode_is_closing_tag_number(&apdu[len], tag_number))
|
||||
len++;
|
||||
else
|
||||
return -1;
|
||||
|
||||
return len;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/* Derived from "Unix Incompatibility Notes: Byte Order" by Jan Wolter */
|
||||
/* http://unixpapa.com/incnote/byteorder.html */
|
||||
|
||||
/** @file bigend.c Determination of Endianess */
|
||||
|
||||
#include "bigend.h"
|
||||
|
||||
/* 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 */
|
||||
|
||||
/* Note: Endianness doesn't apply to all variable manipulation.
|
||||
If you use bitwise or bitshift operations on integers,
|
||||
you can avoid having to check for endianness. */
|
||||
|
||||
/* The names are derived from Jonathon Swift's book Gulliver's Travels,
|
||||
where they describe Lilliputian political parties who disagree
|
||||
vehemently over which end to start eating an egg from.
|
||||
This terminology was popularized for byte order by a less than
|
||||
completely serious paper authored by Danny Cohen which appeared
|
||||
on April 1, 1980 and was entitled "On Holy Wars and a Plea for Peace" */
|
||||
|
||||
/* function to return true on Big-Endian architectures */
|
||||
/* (based on Harbison & Steele) */
|
||||
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,399 @@
|
||||
/*####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 "bvlc.h"
|
||||
#include "net.h" /* custom per port */
|
||||
#if PRINT_ENABLED
|
||||
#include <stdio.h> /* for standard i/o, like printing */
|
||||
#endif
|
||||
|
||||
/** @file bip.c Configuration and Operations for BACnet/IP */
|
||||
|
||||
static int BIP_Socket = -1;
|
||||
/* port to use - stored in network byte order */
|
||||
static uint16_t BIP_Port = 0; /* this will force initialization in demos */
|
||||
/* IP Address - stored in network byte order */
|
||||
static struct in_addr BIP_Address;
|
||||
/* Broadcast Address - stored in network byte order */
|
||||
static struct in_addr BIP_Broadcast_Address;
|
||||
|
||||
/** Setter for the BACnet/IP socket handle.
|
||||
*
|
||||
* @param sock_fd [in] Handle for the BACnet/IP socket.
|
||||
*/
|
||||
void bip_set_socket(
|
||||
int sock_fd)
|
||||
{
|
||||
BIP_Socket = sock_fd;
|
||||
}
|
||||
|
||||
/** Getter for the BACnet/IP socket handle.
|
||||
*
|
||||
* @return The handle to the BACnet/IP socket.
|
||||
*/
|
||||
int bip_socket(
|
||||
void)
|
||||
{
|
||||
return BIP_Socket;
|
||||
}
|
||||
|
||||
bool bip_valid(
|
||||
void)
|
||||
{
|
||||
return (BIP_Socket != -1);
|
||||
}
|
||||
|
||||
void bip_set_addr(
|
||||
uint32_t net_address)
|
||||
{ /* in network byte order */
|
||||
BIP_Address.s_addr = net_address;
|
||||
}
|
||||
|
||||
/* returns network byte order */
|
||||
uint32_t bip_get_addr(
|
||||
void)
|
||||
{
|
||||
return BIP_Address.s_addr;
|
||||
}
|
||||
|
||||
void bip_set_broadcast_addr(
|
||||
uint32_t net_address)
|
||||
{ /* in network byte order */
|
||||
BIP_Broadcast_Address.s_addr = net_address;
|
||||
}
|
||||
|
||||
/* returns network byte order */
|
||||
uint32_t bip_get_broadcast_addr(
|
||||
void)
|
||||
{
|
||||
return BIP_Broadcast_Address.s_addr;
|
||||
}
|
||||
|
||||
|
||||
void bip_set_port(
|
||||
uint16_t port)
|
||||
{ /* in network byte order */
|
||||
BIP_Port = port;
|
||||
}
|
||||
|
||||
/* returns network byte order */
|
||||
uint16_t bip_get_port(
|
||||
void)
|
||||
{
|
||||
return BIP_Port;
|
||||
}
|
||||
|
||||
static int bip_decode_bip_address(
|
||||
BACNET_ADDRESS * bac_addr,
|
||||
struct in_addr *address, /* in network format */
|
||||
uint16_t * port)
|
||||
{ /* in network format */
|
||||
int len = 0;
|
||||
|
||||
if (bac_addr) {
|
||||
memcpy(&address->s_addr, &bac_addr->mac[0], 4);
|
||||
memcpy(port, &bac_addr->mac[4], 2);
|
||||
len = 6;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/** Function to send a packet out the BACnet/IP socket (Annex J).
|
||||
* @ingroup DLBIP
|
||||
*
|
||||
* @param dest [in] Destination address (may encode an IP address and port #).
|
||||
* @param npdu_data [in] The NPDU header (Network) information (not used).
|
||||
* @param pdu [in] Buffer of data to be sent - may be null (why?).
|
||||
* @param pdu_len [in] Number of bytes in the pdu buffer.
|
||||
* @return 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;
|
||||
/* addr and port in host format */
|
||||
struct in_addr address;
|
||||
uint16_t port = 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) || (dest->mac_len == 0)) {
|
||||
/* broadcast */
|
||||
address.s_addr = BIP_Broadcast_Address.s_addr;
|
||||
port = BIP_Port;
|
||||
mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU;
|
||||
} else if ((dest->net > 0) && (dest->len == 0)) {
|
||||
/* network specific broadcast */
|
||||
if (dest->mac_len == 6) {
|
||||
bip_decode_bip_address(dest, &address, &port);
|
||||
} else {
|
||||
address.s_addr = BIP_Broadcast_Address.s_addr;
|
||||
port = BIP_Port;
|
||||
}
|
||||
mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU;
|
||||
} else if (dest->mac_len == 6) {
|
||||
bip_decode_bip_address(dest, &address, &port);
|
||||
mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU;
|
||||
} else {
|
||||
/* invalid address */
|
||||
return -1;
|
||||
}
|
||||
bip_dest.sin_addr.s_addr = address.s_addr;
|
||||
bip_dest.sin_port = port;
|
||||
memset(&(bip_dest.sin_zero), '\0', 8);
|
||||
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;
|
||||
}
|
||||
|
||||
/** Implementation of the receive() function for BACnet/IP; receives one
|
||||
* packet, verifies its BVLC header, and removes the BVLC header from
|
||||
* the PDU data before returning.
|
||||
*
|
||||
* @param src [out] Source of the packet - who should receive any response.
|
||||
* @param pdu [out] A buffer to hold the PDU portion of the received packet,
|
||||
* after the BVLC portion has been stripped off.
|
||||
* @param max_pdu [in] Size of the pdu[] buffer.
|
||||
* @param timeout [in] The number of milliseconds to wait for a packet.
|
||||
* @return The number of octets (remaining) 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)
|
||||
{
|
||||
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 = { 0 };
|
||||
socklen_t sin_len = sizeof(sin);
|
||||
uint16_t i = 0;
|
||||
int function = 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(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 (bvlc_for_non_bbmd(&sin, pdu, received_bytes) > 0) {
|
||||
/* Handled, usually with a NACK. */
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "BIP: BVLC discarded!\n");
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
function = bvlc_get_function_code(); /* aka, pdu[1] */
|
||||
if ((function == BVLC_ORIGINAL_UNICAST_NPDU) ||
|
||||
(function == BVLC_ORIGINAL_BROADCAST_NPDU)) {
|
||||
/* ignore messages from me */
|
||||
if ((sin.sin_addr.s_addr == BIP_Address.s_addr) &&
|
||||
(sin.sin_port == BIP_Port)) {
|
||||
pdu_len = 0;
|
||||
#if 0
|
||||
fprintf(stderr, "BIP: src is me. Discarded!\n");
|
||||
#endif
|
||||
} else {
|
||||
/* data in src->mac[] is in network format */
|
||||
src->mac_len = 6;
|
||||
memcpy(&src->mac[0], &sin.sin_addr.s_addr, 4);
|
||||
memcpy(&src->mac[4], &sin.sin_port, 2);
|
||||
/* 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) {
|
||||
#if 0
|
||||
fprintf(stderr, "BIP: NPDU[%hu]:", pdu_len);
|
||||
#endif
|
||||
/* shift the buffer to return a valid PDU */
|
||||
for (i = 0; i < pdu_len; i++) {
|
||||
pdu[i] = pdu[4 + i];
|
||||
#if 0
|
||||
fprintf(stderr, "%02X ", pdu[i]);
|
||||
#endif
|
||||
}
|
||||
#if 0
|
||||
fprintf(stderr, "\n");
|
||||
#endif
|
||||
}
|
||||
/* ignore packets that are too large */
|
||||
/* clients should check my max-apdu first */
|
||||
else {
|
||||
pdu_len = 0;
|
||||
#if PRINT_ENABLED
|
||||
fprintf(stderr, "BIP: PDU too large. Discarded!.\n");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} else if (function == BVLC_FORWARDED_NPDU) {
|
||||
memcpy(&sin.sin_addr.s_addr, &pdu[4], 4);
|
||||
memcpy(&sin.sin_port, &pdu[8], 2);
|
||||
if ((sin.sin_addr.s_addr == BIP_Address.s_addr) &&
|
||||
(sin.sin_port == BIP_Port)) {
|
||||
/* ignore messages from me */
|
||||
pdu_len = 0;
|
||||
} else {
|
||||
/* data in src->mac[] is in network format */
|
||||
src->mac_len = 6;
|
||||
memcpy(&src->mac[0], &sin.sin_addr.s_addr, 4);
|
||||
memcpy(&src->mac[4], &sin.sin_port, 2);
|
||||
/* 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 -= 10;
|
||||
if (pdu_len < max_pdu) {
|
||||
/* shift the buffer to return a valid PDU */
|
||||
for (i = 0; i < pdu_len; i++) {
|
||||
pdu[i] = pdu[4 + 6 + i];
|
||||
}
|
||||
} else {
|
||||
/* ignore packets that are too large */
|
||||
/* clients should check my max-apdu first */
|
||||
pdu_len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pdu_len;
|
||||
}
|
||||
|
||||
void bip_get_my_address(
|
||||
BACNET_ADDRESS * my_address)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (my_address) {
|
||||
my_address->mac_len = 6;
|
||||
memcpy(&my_address->mac[0], &BIP_Address.s_addr, 4);
|
||||
memcpy(&my_address->mac[4], &BIP_Port, 2);
|
||||
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;
|
||||
memcpy(&dest->mac[0], &BIP_Broadcast_Address.s_addr, 4);
|
||||
memcpy(&dest->mac[4], &BIP_Port, 2);
|
||||
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;
|
||||
}
|
||||
+1924
File diff suppressed because it is too large
Load Diff
+2337
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,302 @@
|
||||
/*####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 "crc.h"
|
||||
|
||||
/** @file crc.c Calculate CRCs */
|
||||
|
||||
#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,132 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2015 Nikola Jelic <nikola.jelic@euroicc.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*********************************************************************/
|
||||
|
||||
#include "credential_authentication_factor.h"
|
||||
#include "bacdcode.h"
|
||||
|
||||
|
||||
int bacapp_encode_credential_authentication_factor(
|
||||
uint8_t * apdu,
|
||||
BACNET_CREDENTIAL_AUTHENTICATION_FACTOR * caf)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_context_enumerated(&apdu[apdu_len], 0, caf->disable);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
|
||||
len =
|
||||
bacapp_encode_context_authentication_factor(&apdu[apdu_len], 1,
|
||||
&caf->authentication_factor);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_encode_context_credential_authentication_factor(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag,
|
||||
BACNET_CREDENTIAL_AUTHENTICATION_FACTOR * caf)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
len = encode_opening_tag(&apdu[apdu_len], tag);
|
||||
apdu_len += len;
|
||||
|
||||
len = bacapp_encode_credential_authentication_factor(&apdu[apdu_len], caf);
|
||||
apdu_len += len;
|
||||
|
||||
len = encode_closing_tag(&apdu[apdu_len], tag);
|
||||
apdu_len += len;
|
||||
|
||||
return apdu_len;
|
||||
|
||||
}
|
||||
|
||||
int bacapp_decode_credential_authentication_factor(
|
||||
uint8_t * apdu,
|
||||
BACNET_CREDENTIAL_AUTHENTICATION_FACTOR * caf)
|
||||
{
|
||||
int len;
|
||||
int apdu_len = 0;
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 0)) {
|
||||
len = decode_context_enumerated(&apdu[apdu_len], 0, &caf->disable);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 1)) {
|
||||
len =
|
||||
bacapp_decode_context_authentication_factor(&apdu[apdu_len], 1,
|
||||
&caf->authentication_factor);
|
||||
if (len < 0)
|
||||
return -1;
|
||||
else
|
||||
apdu_len += len;
|
||||
} else
|
||||
return -1;
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_decode_context_credential_authentication_factor(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag,
|
||||
BACNET_CREDENTIAL_AUTHENTICATION_FACTOR * caf)
|
||||
{
|
||||
int len = 0;
|
||||
int section_length;
|
||||
|
||||
if (decode_is_opening_tag_number(&apdu[len], tag)) {
|
||||
len++;
|
||||
section_length =
|
||||
bacapp_decode_credential_authentication_factor(&apdu[len], caf);
|
||||
|
||||
if (section_length == -1) {
|
||||
len = -1;
|
||||
} else {
|
||||
len += section_length;
|
||||
if (decode_is_closing_tag_number(&apdu[len], tag)) {
|
||||
len++;
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
len = -1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
/*####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####*/
|
||||
/** @file datalink.c Optional run-time assignment of datalink transport */
|
||||
#include "datalink.h"
|
||||
|
||||
#if defined(BACDL_ALL) || defined FOR_DOXYGEN
|
||||
#include "ethernet.h"
|
||||
#include "bip.h"
|
||||
#include "bip6.h"
|
||||
#include "bvlc.h"
|
||||
#include "arcnet.h"
|
||||
#include "dlmstp.h"
|
||||
#include <string.h>
|
||||
/* Function pointers - point to your datalink */
|
||||
|
||||
/** Function template to Initialize the DataLink services at the given interface.
|
||||
* @ingroup DLTemplates
|
||||
*
|
||||
* @note For Linux, ifname is eth0, ath0, arc0, ttyS0, and others.
|
||||
For Windows, ifname is the COM port or dotted ip address of the interface.
|
||||
|
||||
* @param ifname [in] The named interface to use for the network layer.
|
||||
* @return True if the interface is successfully initialized,
|
||||
* else False if the initialization fails.
|
||||
*/
|
||||
bool(*datalink_init) (char *ifname);
|
||||
|
||||
/** Function template to send a packet via the DataLink.
|
||||
* @ingroup DLTemplates
|
||||
*
|
||||
* @param dest [in] Destination address.
|
||||
* @param npdu_data [in] The NPDU header (Network) information.
|
||||
* @param pdu [in] Buffer of data to be sent - may be null.
|
||||
* @param pdu_len [in] Number of bytes in the pdu buffer.
|
||||
* @return Number of bytes sent on success, negative number on failure.
|
||||
*/
|
||||
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);
|
||||
|
||||
/** Function template to close the DataLink services and perform any cleanup.
|
||||
* @ingroup DLTemplates
|
||||
*/
|
||||
void (
|
||||
*datalink_cleanup) (
|
||||
void);
|
||||
|
||||
void (
|
||||
*datalink_get_broadcast_address) (
|
||||
BACNET_ADDRESS * dest);
|
||||
|
||||
void (
|
||||
*datalink_get_my_address) (
|
||||
BACNET_ADDRESS * my_address);
|
||||
|
||||
void datalink_set(
|
||||
char *datalink_string)
|
||||
{
|
||||
if (strcasecmp("bip", datalink_string) == 0) {
|
||||
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;
|
||||
} else if (strcasecmp("bvlc", datalink_string) == 0) {
|
||||
datalink_init = bip_init;
|
||||
datalink_send_pdu = bvlc_send_pdu;
|
||||
datalink_receive = bvlc_receive;
|
||||
datalink_cleanup = bip_cleanup;
|
||||
datalink_get_broadcast_address = bip_get_broadcast_address;
|
||||
datalink_get_my_address = bip_get_my_address;
|
||||
} else if (strcasecmp("bip6", datalink_string) == 0) {
|
||||
datalink_init = bip6_init;
|
||||
datalink_send_pdu = bip6_send_pdu;
|
||||
datalink_receive = bip6_receive;
|
||||
datalink_cleanup = bip6_cleanup;
|
||||
datalink_get_broadcast_address = bip6_get_broadcast_address;
|
||||
datalink_get_my_address = bip6_get_my_address;
|
||||
} else if (strcasecmp("bvlc6", datalink_string) == 0) {
|
||||
datalink_init = bip6_init;
|
||||
datalink_send_pdu = bvlc6_send_pdu;
|
||||
datalink_receive = bvlc6_receive;
|
||||
datalink_cleanup = bip6_cleanup;
|
||||
datalink_get_broadcast_address = bip6_get_broadcast_address;
|
||||
datalink_get_my_address = bip6_get_my_address;
|
||||
} else if (strcasecmp("ethernet", datalink_string) == 0) {
|
||||
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;
|
||||
} else if (strcasecmp("arcnet", datalink_string) == 0) {
|
||||
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;
|
||||
} else if (strcasecmp("mstp", datalink_string) == 0) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(BACDL_NONE)
|
||||
int datalink_send_pdu(
|
||||
BACNET_ADDRESS * dest,
|
||||
BACNET_NPDU_DATA * npdu_data,
|
||||
uint8_t * pdu,
|
||||
unsigned pdu_len)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t datalink_receive(
|
||||
BACNET_ADDRESS * src,
|
||||
uint8_t * pdu,
|
||||
uint16_t max_pdu,
|
||||
unsigned timeout)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void datalink_cleanup(
|
||||
void)
|
||||
{
|
||||
}
|
||||
|
||||
void datalink_get_broadcast_address(
|
||||
BACNET_ADDRESS * dest)
|
||||
{
|
||||
}
|
||||
|
||||
void datalink_get_my_address(
|
||||
BACNET_ADDRESS * my_address)
|
||||
{
|
||||
}
|
||||
|
||||
void datalink_set_interface(
|
||||
char *ifname)
|
||||
{
|
||||
}
|
||||
|
||||
void datalink_set(
|
||||
char *datalink_string)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
+1514
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,339 @@
|
||||
/*####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"
|
||||
|
||||
/** @file dcc.c Enable/Disable Device Communication Control (DCC) */
|
||||
|
||||
/* 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. */
|
||||
/* infinite time duration is defined as 0 */
|
||||
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);
|
||||
}
|
||||
|
||||
/* note: 0 indicates either expired, or infinite duration */
|
||||
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;
|
||||
}
|
||||
|
||||
#if BACNET_SVC_DCC_A
|
||||
/* 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 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;
|
||||
uint32_t value32 = 0;
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len) {
|
||||
/* Tag 0: timeDuration, in minutes --optional--
|
||||
* But if not included, take it as indefinite,
|
||||
* which we return as "very large" */
|
||||
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) {
|
||||
/* zero indicates infinite duration and
|
||||
results in no timeout */
|
||||
*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, &value32);
|
||||
if (enable_disable) {
|
||||
*enable_disable = (BACNET_COMMUNICATION_ENABLE_DISABLE) value32;
|
||||
}
|
||||
/* 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, MAX_APDU); */
|
||||
*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 */
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2008 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 <stdio.h> /* Standard I/O */
|
||||
#include <stdlib.h> /* Standard Library */
|
||||
#include <stdarg.h>
|
||||
#include "debug.h"
|
||||
|
||||
/** @file debug.c Debug print function */
|
||||
|
||||
#if DEBUG_ENABLED
|
||||
void debug_printf(
|
||||
const char *format,
|
||||
...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
vfprintf(stdout, format, ap);
|
||||
va_end(ap);
|
||||
fflush(stdout);
|
||||
|
||||
return;
|
||||
}
|
||||
#else
|
||||
void debug_printf(
|
||||
const char *format,
|
||||
...)
|
||||
{
|
||||
format = format;
|
||||
}
|
||||
#endif
|
||||
+1522
File diff suppressed because it is too large
Load Diff
Executable
+502
@@ -0,0 +1,502 @@
|
||||
/**
|
||||
* @file
|
||||
* @author Steve Karg
|
||||
* @date 2004
|
||||
* @brief Generic interrupt safe FIFO library for deeply embedded system.
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Generic interrupt safe FIFO library for deeply embedded system
|
||||
* This library only uses a byte sized chunk for a data element.
|
||||
* It uses a data store whose size is a power of 2 (8, 16, 32, 64, ...)
|
||||
* and doesn't waste any data bytes. It has very low overhead, and
|
||||
* utilizes modulo for indexing the data in the data store.
|
||||
*
|
||||
* To use this library, first declare a data store, sized for a power of 2:
|
||||
* {@code
|
||||
* static volatile uint8_t data_store[64];
|
||||
* }
|
||||
*
|
||||
* Then declare the FIFO tracking structure:
|
||||
* {@code
|
||||
* static FIFO_BUFFER queue;
|
||||
* }
|
||||
*
|
||||
* Initialize the queue with the data store:
|
||||
* {@code
|
||||
* FIFO_Init(&queue, data_store, sizeof(data_store));
|
||||
* }
|
||||
*
|
||||
* Then begin to use the FIFO queue by giving it data, retreiving data,
|
||||
* and checking the FIFO queue to see if it is empty or full:
|
||||
* {@code
|
||||
* uint8_t in_data = 0;
|
||||
* uint8_t out_data = 0;
|
||||
* uint8_t add_data[5] = {0};
|
||||
* uint8_t pull_data[5] = {0};
|
||||
* unsigned count = 0;
|
||||
* bool status = false;
|
||||
*
|
||||
* status = FIFO_Put(&queue, in_data);
|
||||
* if (!FIFO_Empty(&queue)) {
|
||||
* out_data = FIFO_Get(&queue);
|
||||
* }
|
||||
* if (FIFO_Available(&queue, sizeof(add_data))) {
|
||||
* status = FIFO_Add(&queue, add_data, sizeof(add_data));
|
||||
* }
|
||||
* count = FIFO_Count(&queue);
|
||||
* if (count == sizeof(add_data)) {
|
||||
* count = FIFO_Pull(&queue, &pull_data[0], sizeof(pull_data));
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* Normally the FIFO is used by a producer, such as in interrupt service
|
||||
* routine, which places data into the queue using FIFO_Put(), and a consumer,
|
||||
* such as a main loop handler, which pulls data from the queue by first
|
||||
* checking the queue for data using FIFO_Empty(), and then pulling data from
|
||||
* the queue using FIFO_Get().
|
||||
*
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "fifo.h"
|
||||
|
||||
/**
|
||||
* Returns the number of bytes in the FIFO
|
||||
*
|
||||
* @param b - pointer to FIFO_BUFFER structure
|
||||
*
|
||||
* @return Number of bytes in the FIFO
|
||||
*/
|
||||
unsigned FIFO_Count(
|
||||
FIFO_BUFFER const *b)
|
||||
{
|
||||
unsigned head, tail; /* used to avoid volatile decision */
|
||||
|
||||
if (b) {
|
||||
head = b->head;
|
||||
tail = b->tail;
|
||||
return head - tail;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full status of the FIFO
|
||||
*
|
||||
* @param b - pointer to FIFO_BUFFER structure
|
||||
*
|
||||
* @return true if the FIFO is full, false if it is not.
|
||||
*/
|
||||
bool FIFO_Full(
|
||||
FIFO_BUFFER const *b)
|
||||
{
|
||||
return (b ? (FIFO_Count(b) == b->buffer_len) : true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests to see if space is available in the FIFO
|
||||
*
|
||||
* @param b - pointer to FIFO_BUFFER structure
|
||||
* @param count [in] - number of bytes tested for availability
|
||||
*
|
||||
* @return true if the number of bytes sought is available
|
||||
*/
|
||||
bool FIFO_Available(
|
||||
FIFO_BUFFER const *b,
|
||||
unsigned count)
|
||||
{
|
||||
return (b ? (count <= (b->buffer_len - FIFO_Count(b))) : false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the empty status of the FIFO
|
||||
*
|
||||
* @param b - pointer to FIFO_BUFFER structure
|
||||
* @return true if the FIFO is empty, false if it is not.
|
||||
*/
|
||||
bool FIFO_Empty(
|
||||
FIFO_BUFFER const *b)
|
||||
{
|
||||
return (b ? (FIFO_Count(b) == 0) : true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Peeks at the data from the front of the FIFO without removing it.
|
||||
* Use FIFO_Empty() or FIFO_Available() function to see if there is
|
||||
* data to retrieve since this function doesn't return a flag indicating
|
||||
* success or failure.
|
||||
*
|
||||
* @param b - pointer to FIFO_BUFFER structure
|
||||
*
|
||||
* @return byte of data, or zero if nothing in the list
|
||||
*/
|
||||
uint8_t FIFO_Peek(
|
||||
FIFO_BUFFER const *b)
|
||||
{
|
||||
unsigned index;
|
||||
|
||||
if (b) {
|
||||
index = b->tail % b->buffer_len;
|
||||
return (b->buffer[index]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a byte from the front of the FIFO, and removes it.
|
||||
* Use FIFO_Empty() or FIFO_Available() function to see if there is
|
||||
* data to retrieve since this function doesn't return a flag indicating
|
||||
* success or failure.
|
||||
*
|
||||
* @param b - pointer to FIFO_BUFFER structure
|
||||
*
|
||||
* @return the data
|
||||
*/
|
||||
uint8_t FIFO_Get(
|
||||
FIFO_BUFFER * b)
|
||||
{
|
||||
uint8_t data_byte = 0;
|
||||
unsigned index;
|
||||
|
||||
if (!FIFO_Empty(b)) {
|
||||
index = b->tail % b->buffer_len;
|
||||
data_byte = b->buffer[index];
|
||||
b->tail++;
|
||||
}
|
||||
return data_byte;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls one or more bytes from the front of the FIFO, and removes them
|
||||
* from the FIFO. If less bytes are available, only the available bytes
|
||||
* are retrieved.
|
||||
*
|
||||
* @param b - pointer to FIFO_BUFFER structure
|
||||
* @param buffer [out] - buffer to hold the pulled bytes
|
||||
* @param length [in] - number of bytes to pull from the FIFO
|
||||
*
|
||||
* @return the number of bytes actually pulled from the FIFO
|
||||
*/
|
||||
unsigned FIFO_Pull(
|
||||
FIFO_BUFFER * b,
|
||||
uint8_t * buffer,
|
||||
unsigned length)
|
||||
{
|
||||
unsigned count;
|
||||
uint8_t data_byte;
|
||||
unsigned index;
|
||||
|
||||
count = FIFO_Count(b);
|
||||
if (count > length) {
|
||||
/* adjust to limit the number of bytes pulled */
|
||||
count = length;
|
||||
}
|
||||
if (length > count) {
|
||||
/* adjust the return value */
|
||||
length = count;
|
||||
}
|
||||
while (count) {
|
||||
index = b->tail % b->buffer_len;
|
||||
data_byte = b->buffer[index];
|
||||
b->tail++;
|
||||
if (buffer) {
|
||||
*buffer = data_byte;
|
||||
buffer++;
|
||||
}
|
||||
count--;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a byte of data to the FIFO
|
||||
*
|
||||
* @param b - pointer to FIFO_BUFFER structure
|
||||
* @param data_byte [in] - data to put into the FIFO
|
||||
*
|
||||
* @return true on successful add, false if not added
|
||||
*/
|
||||
bool FIFO_Put(
|
||||
FIFO_BUFFER * b,
|
||||
uint8_t data_byte)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
unsigned index;
|
||||
|
||||
if (b) {
|
||||
/* limit the buffer to prevent overwriting */
|
||||
if (!FIFO_Full(b)) {
|
||||
index = b->head % b->buffer_len;
|
||||
b->buffer[index] = data_byte;
|
||||
b->head++;
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more bytes of data to the FIFO
|
||||
*
|
||||
* @param b - pointer to FIFO_BUFFER structure
|
||||
* @param buffer [out] - data bytes to add to the FIFO
|
||||
* @param count [in] - number of bytes to add to the FIFO
|
||||
*
|
||||
* @return true if space available and added, false if not added
|
||||
*/
|
||||
bool FIFO_Add(
|
||||
FIFO_BUFFER * b,
|
||||
uint8_t * buffer,
|
||||
unsigned count)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
unsigned index;
|
||||
|
||||
/* limit the buffer to prevent overwriting */
|
||||
if (FIFO_Available(b, count) && buffer) {
|
||||
while (count) {
|
||||
index = b->head % b->buffer_len;
|
||||
b->buffer[index] = *buffer;
|
||||
b->head++;
|
||||
buffer++;
|
||||
count--;
|
||||
}
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes any data in the FIFO buffer
|
||||
*
|
||||
* @param b - pointer to FIFO_BUFFER structure
|
||||
*
|
||||
* @return none
|
||||
*/
|
||||
void FIFO_Flush(
|
||||
FIFO_BUFFER * b)
|
||||
{
|
||||
unsigned head; /* used to avoid volatile decision */
|
||||
|
||||
if (b) {
|
||||
head = b->head;
|
||||
b->tail = head;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the FIFO buffer with a data store
|
||||
*
|
||||
* @param b - pointer to FIFO_BUFFER structure
|
||||
* @param buffer [in] - data bytes used to store bytes used by the FIFO
|
||||
* @param buffer_len [in] - size of the buffer in bytes - must be power of 2.
|
||||
*
|
||||
* @return none
|
||||
*/
|
||||
void FIFO_Init(
|
||||
FIFO_BUFFER * b,
|
||||
volatile uint8_t * buffer,
|
||||
unsigned buffer_len)
|
||||
{
|
||||
if (b && buffer && buffer_len) {
|
||||
b->head = 0;
|
||||
b->tail = 0;
|
||||
b->buffer = buffer;
|
||||
b->buffer_len = buffer_len;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "ctest.h"
|
||||
|
||||
/**
|
||||
* Unit Test for the FIFO buffer
|
||||
*
|
||||
* @param pTest - test tracking pointer
|
||||
*/
|
||||
void testFIFOBuffer(
|
||||
Test * pTest)
|
||||
{
|
||||
/* FIFO data structure */
|
||||
FIFO_BUFFER test_buffer = { 0 };
|
||||
/* FIFO data store. Note: size must be a power of two! */
|
||||
volatile uint8_t data_store[64] = { 0 };
|
||||
uint8_t add_data[40] = { "RoseSteveLouPatRachelJessicaDaniAmyHerb" };
|
||||
uint8_t test_add_data[40] = { 0 };
|
||||
uint8_t test_data = 0;
|
||||
unsigned index = 0;
|
||||
unsigned count = 0;
|
||||
bool status = 0;
|
||||
|
||||
FIFO_Init(&test_buffer, data_store, sizeof(data_store));
|
||||
ct_test(pTest, FIFO_Empty(&test_buffer));
|
||||
|
||||
/* load the buffer */
|
||||
for (test_data = 0; test_data < sizeof(data_store); test_data++) {
|
||||
ct_test(pTest, !FIFO_Full(&test_buffer));
|
||||
ct_test(pTest, FIFO_Available(&test_buffer, 1));
|
||||
status = FIFO_Put(&test_buffer, test_data);
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, !FIFO_Empty(&test_buffer));
|
||||
}
|
||||
/* not able to put any more */
|
||||
ct_test(pTest, FIFO_Full(&test_buffer));
|
||||
ct_test(pTest, !FIFO_Available(&test_buffer, 1));
|
||||
status = FIFO_Put(&test_buffer, 42);
|
||||
ct_test(pTest, status == false);
|
||||
/* unload the buffer */
|
||||
for (index = 0; index < sizeof(data_store); index++) {
|
||||
ct_test(pTest, !FIFO_Empty(&test_buffer));
|
||||
test_data = FIFO_Peek(&test_buffer);
|
||||
ct_test(pTest, test_data == index);
|
||||
test_data = FIFO_Get(&test_buffer);
|
||||
ct_test(pTest, test_data == index);
|
||||
ct_test(pTest, FIFO_Available(&test_buffer, 1));
|
||||
ct_test(pTest, !FIFO_Full(&test_buffer));
|
||||
}
|
||||
ct_test(pTest, FIFO_Empty(&test_buffer));
|
||||
test_data = FIFO_Get(&test_buffer);
|
||||
ct_test(pTest, test_data == 0);
|
||||
test_data = FIFO_Peek(&test_buffer);
|
||||
ct_test(pTest, test_data == 0);
|
||||
ct_test(pTest, FIFO_Empty(&test_buffer));
|
||||
/* test the ring around the buffer */
|
||||
for (index = 0; index < sizeof(data_store); index++) {
|
||||
ct_test(pTest, FIFO_Empty(&test_buffer));
|
||||
ct_test(pTest, FIFO_Available(&test_buffer, 4));
|
||||
for (count = 1; count < 4; count++) {
|
||||
test_data = count;
|
||||
status = FIFO_Put(&test_buffer, test_data);
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, !FIFO_Empty(&test_buffer));
|
||||
}
|
||||
for (count = 1; count < 4; count++) {
|
||||
ct_test(pTest, !FIFO_Empty(&test_buffer));
|
||||
test_data = FIFO_Peek(&test_buffer);
|
||||
ct_test(pTest, test_data == count);
|
||||
test_data = FIFO_Get(&test_buffer);
|
||||
ct_test(pTest, test_data == count);
|
||||
}
|
||||
}
|
||||
ct_test(pTest, FIFO_Empty(&test_buffer));
|
||||
/* test Add */
|
||||
ct_test(pTest, FIFO_Available(&test_buffer, sizeof(add_data)));
|
||||
status = FIFO_Add(&test_buffer, add_data, sizeof(add_data));
|
||||
ct_test(pTest, status == true);
|
||||
count = FIFO_Count(&test_buffer);
|
||||
ct_test(pTest, count == sizeof(add_data));
|
||||
ct_test(pTest, !FIFO_Empty(&test_buffer));
|
||||
for (index = 0; index < sizeof(add_data); index++) {
|
||||
/* unload the buffer */
|
||||
ct_test(pTest, !FIFO_Empty(&test_buffer));
|
||||
test_data = FIFO_Peek(&test_buffer);
|
||||
ct_test(pTest, test_data == add_data[index]);
|
||||
test_data = FIFO_Get(&test_buffer);
|
||||
ct_test(pTest, test_data == add_data[index]);
|
||||
}
|
||||
ct_test(pTest, FIFO_Empty(&test_buffer));
|
||||
/* test Pull */
|
||||
ct_test(pTest, FIFO_Available(&test_buffer, sizeof(add_data)));
|
||||
status = FIFO_Add(&test_buffer, add_data, sizeof(add_data));
|
||||
ct_test(pTest, status == true);
|
||||
count = FIFO_Count(&test_buffer);
|
||||
ct_test(pTest, count == sizeof(add_data));
|
||||
ct_test(pTest, !FIFO_Empty(&test_buffer));
|
||||
count = FIFO_Pull(&test_buffer, &test_add_data[0], sizeof(test_add_data));
|
||||
ct_test(pTest, FIFO_Empty(&test_buffer));
|
||||
ct_test(pTest, count == sizeof(test_add_data));
|
||||
for (index = 0; index < sizeof(add_data); index++) {
|
||||
ct_test(pTest, test_add_data[index] == add_data[index]);
|
||||
}
|
||||
ct_test(pTest, FIFO_Available(&test_buffer, sizeof(add_data)));
|
||||
status = FIFO_Add(&test_buffer, test_add_data, sizeof(add_data));
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, !FIFO_Empty(&test_buffer));
|
||||
for (index = 0; index < sizeof(add_data); index++) {
|
||||
count = FIFO_Pull(&test_buffer, &test_add_data[0], 1);
|
||||
ct_test(pTest, count == 1);
|
||||
ct_test(pTest, test_add_data[0] == add_data[index]);
|
||||
}
|
||||
ct_test(pTest, FIFO_Empty(&test_buffer));
|
||||
/* test flush */
|
||||
status = FIFO_Add(&test_buffer, test_add_data, sizeof(test_add_data));
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, !FIFO_Empty(&test_buffer));
|
||||
FIFO_Flush(&test_buffer);
|
||||
ct_test(pTest, FIFO_Empty(&test_buffer));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_FIFO_BUFFER
|
||||
/**
|
||||
* Main program entry for Unit Test
|
||||
*
|
||||
* @return returns 0 on success, and non-zero on fail.
|
||||
*/
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("FIFO Buffer", NULL);
|
||||
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testFIFOBuffer);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*####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>
|
||||
#include "filename.h"
|
||||
|
||||
/** @file filename.c Function for filename manipulation */
|
||||
|
||||
char *filename_remove_path(
|
||||
const char *filename_in)
|
||||
{
|
||||
char *filename_out = (char *) filename_in;
|
||||
|
||||
/* 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++;
|
||||
} else {
|
||||
/* no slash in filename */
|
||||
filename_out = (char *) filename_in;
|
||||
}
|
||||
}
|
||||
|
||||
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 *data5 = "bin\\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);
|
||||
filename = filename_remove_path(data5);
|
||||
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 */
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* @file
|
||||
* @author Krzysztof Malorny <malornykrzysztof@gmail.com>
|
||||
* @date 2011
|
||||
* @brief GetAlarmSummary service encoding and decoding
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* The GetAlarmSummary service is used by a client BACnet-user
|
||||
* to obtain a summary of "active alarms." The term "active alarm" refers
|
||||
* to BACnet standard objects that have an Event_State property whose value
|
||||
* is not equal to NORMAL and a Notify_Type property whose value is ALARM.
|
||||
* The GetEnrollmentSummary service provides a more sophisticated approach
|
||||
* with various kinds of filters
|
||||
*/
|
||||
#include <assert.h>
|
||||
|
||||
#include "bacdcode.h"
|
||||
#include "get_alarm_sum.h"
|
||||
#include "npdu.h"
|
||||
|
||||
/* encode service */
|
||||
int get_alarm_summary_encode_apdu(
|
||||
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_GET_ALARM_SUMMARY;
|
||||
apdu_len = 4;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/** Helper function encode the beginning of a GetAlarmSummary ACK.
|
||||
*
|
||||
* @param apdu - buffer where to put encoding
|
||||
* @param invoke_id - unique sequence number sent with the message
|
||||
*
|
||||
* @return number of bytes encoded
|
||||
*/
|
||||
int get_alarm_summary_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_GET_ALARM_SUMMARY;
|
||||
apdu_len = 3;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/** Helper function encode the data portion of a GetAlarmSummary ACK.
|
||||
*
|
||||
* @param apdu - buffer where to put encoding
|
||||
* @param max_apdu - number of bytes available in the buffer for encoding
|
||||
* @param get_alarm_data - BACNET_GET_ALARM_SUMMARY_DATA type with data
|
||||
*
|
||||
* @return number of bytes encoded, or BACNET_STATUS_ERROR if an error.
|
||||
*/
|
||||
int get_alarm_summary_ack_encode_apdu_data(
|
||||
uint8_t * apdu,
|
||||
size_t max_apdu,
|
||||
BACNET_GET_ALARM_SUMMARY_DATA * get_alarm_data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (!apdu) {
|
||||
apdu_len = BACNET_STATUS_ERROR;
|
||||
} else if (max_apdu >= 10) {
|
||||
/* tag 0 - Object Identifier */
|
||||
apdu_len +=
|
||||
encode_application_object_id(&apdu[apdu_len],
|
||||
(int) get_alarm_data->objectIdentifier.type,
|
||||
get_alarm_data->objectIdentifier.instance);
|
||||
/* tag 1 - Alarm State */
|
||||
apdu_len +=
|
||||
encode_application_enumerated(&apdu[apdu_len],
|
||||
get_alarm_data->alarmState);
|
||||
/* tag 2 - Acknowledged Transitions */
|
||||
apdu_len +=
|
||||
encode_application_bitstring(&apdu[apdu_len],
|
||||
&get_alarm_data->acknowledgedTransitions);
|
||||
} else {
|
||||
apdu_len = BACNET_STATUS_ABORT;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/** Helper function to decode the data portion of a GetAlarmSummary ACK.
|
||||
*
|
||||
* @param apdu - buffer where to put encoding
|
||||
* @param max_apdu - number of bytes available in the buffer for encoding
|
||||
* @param get_alarm_data - BACNET_GET_ALARM_SUMMARY_DATA type for data
|
||||
*
|
||||
* @return number of bytes decoded, or BACNET_STATUS_ERROR if an error.
|
||||
*/
|
||||
int get_alarm_summary_ack_decode_apdu_data(
|
||||
uint8_t * apdu,
|
||||
size_t max_apdu,
|
||||
BACNET_GET_ALARM_SUMMARY_DATA * get_alarm_data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
BACNET_APPLICATION_DATA_VALUE value;
|
||||
|
||||
if (!apdu) {
|
||||
apdu_len = BACNET_STATUS_ERROR;
|
||||
} else if (max_apdu >= 10) {
|
||||
/* tag 0 - Object Identifier */
|
||||
apdu_len +=
|
||||
bacapp_decode_application_data(&apdu[apdu_len],
|
||||
(unsigned int)(max_apdu - apdu_len), &value);
|
||||
if (value.tag == BACNET_APPLICATION_TAG_OBJECT_ID) {
|
||||
get_alarm_data->objectIdentifier = value.type.Object_Id;
|
||||
} else {
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
/* tag 1 - Alarm State */
|
||||
apdu_len +=
|
||||
bacapp_decode_application_data(&apdu[apdu_len],
|
||||
(unsigned int)(max_apdu - apdu_len), &value);
|
||||
if (value.tag == BACNET_APPLICATION_TAG_ENUMERATED) {
|
||||
get_alarm_data->alarmState =
|
||||
(BACNET_EVENT_STATE) value.type.Enumerated;
|
||||
} else {
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
/* tag 2 - Acknowledged Transitions */
|
||||
apdu_len +=
|
||||
bacapp_decode_application_data(&apdu[apdu_len],
|
||||
(unsigned int)(max_apdu - apdu_len), &value);
|
||||
if (value.tag == BACNET_APPLICATION_TAG_BIT_STRING) {
|
||||
get_alarm_data->acknowledgedTransitions = value.type.Bit_String;
|
||||
} else {
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
+515
@@ -0,0 +1,515 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2009 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 "getevent.h"
|
||||
|
||||
/** @file getevent.c Encode/Decode GetEvent services */
|
||||
|
||||
/* encode service */
|
||||
int getevent_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_OBJECT_ID * lastReceivedObjectIdentifier)
|
||||
{
|
||||
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_GET_EVENT_INFORMATION;
|
||||
apdu_len = 4;
|
||||
/* encode optional parameter */
|
||||
if (lastReceivedObjectIdentifier) {
|
||||
len =
|
||||
encode_context_object_id(&apdu[apdu_len], 0,
|
||||
(int) lastReceivedObjectIdentifier->type,
|
||||
lastReceivedObjectIdentifier->instance);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int getevent_decode_service_request(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_OBJECT_ID * lastReceivedObjectIdentifier)
|
||||
{
|
||||
unsigned len = 0;
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && lastReceivedObjectIdentifier) {
|
||||
/* Tag 0: Object ID - optional */
|
||||
if (!decode_is_context_tag(&apdu[len++], 0))
|
||||
return -1;
|
||||
len +=
|
||||
decode_object_id(&apdu[len], &lastReceivedObjectIdentifier->type,
|
||||
&lastReceivedObjectIdentifier->instance);
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
}
|
||||
|
||||
int getevent_ack_encode_apdu_init(
|
||||
uint8_t * apdu,
|
||||
size_t max_apdu,
|
||||
uint8_t invoke_id)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu && (max_apdu >= 4)) {
|
||||
apdu[0] = PDU_TYPE_COMPLEX_ACK; /* complex ACK service */
|
||||
apdu[1] = invoke_id; /* original invoke id from request */
|
||||
apdu[2] = SERVICE_CONFIRMED_GET_EVENT_INFORMATION;
|
||||
apdu_len = 3;
|
||||
/* service ack follows */
|
||||
/* Tag 0: listOfEventSummaries */
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 0);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int getevent_ack_encode_apdu_data(
|
||||
uint8_t * apdu,
|
||||
size_t max_apdu,
|
||||
BACNET_GET_EVENT_INFORMATION_DATA * get_event_data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
BACNET_GET_EVENT_INFORMATION_DATA *event_data;
|
||||
unsigned i = 0; /* counter */
|
||||
|
||||
/* unused parameter */
|
||||
max_apdu = max_apdu;
|
||||
if (apdu) {
|
||||
event_data = get_event_data;
|
||||
while (event_data) {
|
||||
/* Tag 0: objectIdentifier */
|
||||
apdu_len +=
|
||||
encode_context_object_id(&apdu[apdu_len], 0,
|
||||
(int) event_data->objectIdentifier.type,
|
||||
event_data->objectIdentifier.instance);
|
||||
/* Tag 1: eventState */
|
||||
apdu_len +=
|
||||
encode_context_enumerated(&apdu[apdu_len], 1,
|
||||
event_data->eventState);
|
||||
/* Tag 2: acknowledgedTransitions */
|
||||
apdu_len +=
|
||||
encode_context_bitstring(&apdu[apdu_len], 2,
|
||||
&event_data->acknowledgedTransitions);
|
||||
/* Tag 3: eventTimeStamps */
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 3);
|
||||
for (i = 0; i < 3; i++) {
|
||||
apdu_len +=
|
||||
bacapp_encode_timestamp(&apdu[apdu_len],
|
||||
&event_data->eventTimeStamps[i]);
|
||||
}
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 3);
|
||||
/* Tag 4: notifyType */
|
||||
apdu_len +=
|
||||
encode_context_enumerated(&apdu[apdu_len], 4,
|
||||
event_data->notifyType);
|
||||
/* Tag 5: eventEnable */
|
||||
apdu_len +=
|
||||
encode_context_bitstring(&apdu[apdu_len], 5,
|
||||
&event_data->eventEnable);
|
||||
/* Tag 6: eventPriorities */
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 6);
|
||||
for (i = 0; i < 3; i++) {
|
||||
apdu_len +=
|
||||
encode_application_unsigned(&apdu[apdu_len],
|
||||
event_data->eventPriorities[i]);
|
||||
}
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 6);
|
||||
event_data = event_data->next;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int getevent_ack_encode_apdu_end(
|
||||
uint8_t * apdu,
|
||||
size_t max_apdu,
|
||||
bool moreEvents)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
/* unused parameter */
|
||||
max_apdu = max_apdu;
|
||||
if (apdu) {
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 0);
|
||||
apdu_len += encode_context_boolean(&apdu[apdu_len], 1, moreEvents);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int getevent_ack_decode_service_request(
|
||||
uint8_t * apdu,
|
||||
int apdu_len, /* total length of the apdu */
|
||||
BACNET_GET_EVENT_INFORMATION_DATA * get_event_data,
|
||||
bool * moreEvents)
|
||||
{
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
int len = 0; /* total length of decodes */
|
||||
uint32_t enum_value = 0; /* for decoding */
|
||||
BACNET_GET_EVENT_INFORMATION_DATA *event_data;
|
||||
unsigned i = 0; /* counter */
|
||||
|
||||
/* FIXME: check apdu_len against the len during decode */
|
||||
event_data = get_event_data;
|
||||
if (apdu && apdu_len && event_data && moreEvents) {
|
||||
if (!decode_is_opening_tag_number(&apdu[len], 0)) {
|
||||
return -1;
|
||||
}
|
||||
len++;
|
||||
while (event_data) {
|
||||
/* Tag 0: objectIdentifier */
|
||||
if (decode_is_context_tag(&apdu[len], 0)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_object_id(&apdu[len],
|
||||
&event_data->objectIdentifier.type,
|
||||
&event_data->objectIdentifier.instance);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
/* Tag 1: eventState */
|
||||
if (decode_is_context_tag(&apdu[len], 1)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_enumerated(&apdu[len], len_value, &enum_value);
|
||||
event_data->eventState = enum_value;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
/* Tag 2: acknowledgedTransitions */
|
||||
if (decode_is_context_tag(&apdu[len], 2)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_bitstring(&apdu[len], len_value,
|
||||
&event_data->acknowledgedTransitions);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
/* Tag 3: eventTimeStamps */
|
||||
if (decode_is_opening_tag_number(&apdu[len], 3)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
for (i = 0; i < 3; i++) {
|
||||
len +=
|
||||
bacapp_decode_timestamp(&apdu[len],
|
||||
&event_data->eventTimeStamps[i]);
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
if (decode_is_closing_tag_number(&apdu[len], 3)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
/* Tag 4: notifyType */
|
||||
if (decode_is_context_tag(&apdu[len], 4)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len += decode_enumerated(&apdu[len], len_value, &enum_value);
|
||||
event_data->notifyType = enum_value;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
/* Tag 5: eventEnable */
|
||||
if (decode_is_context_tag(&apdu[len], 5)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_bitstring(&apdu[len], len_value,
|
||||
&event_data->eventEnable);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
/* Tag 6: eventPriorities */
|
||||
if (decode_is_opening_tag_number(&apdu[len], 6)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
for (i = 0; i < 3; i++) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
len +=
|
||||
decode_unsigned(&apdu[len], len_value,
|
||||
&event_data->eventPriorities[i]);
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
if (decode_is_closing_tag_number(&apdu[len], 6)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
if (decode_is_closing_tag_number(&apdu[len], 0)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
event_data->next = NULL;
|
||||
}
|
||||
event_data = event_data->next;
|
||||
}
|
||||
if (decode_is_context_tag(&apdu[len], 1)) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
if (len_value == 1)
|
||||
*moreEvents = decode_context_boolean(&apdu[len++]);
|
||||
else
|
||||
*moreEvents = false;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
int getevent_decode_apdu(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id,
|
||||
BACNET_OBJECT_ID * lastReceivedObjectIdentifier)
|
||||
{
|
||||
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, MAX_APDU); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_GET_EVENT_INFORMATION)
|
||||
return -1;
|
||||
offset = 4;
|
||||
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
getevent_decode_service_request(&apdu[offset], apdu_len - offset,
|
||||
lastReceivedObjectIdentifier);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int getevent_ack_decode_apdu(
|
||||
uint8_t * apdu,
|
||||
int apdu_len, /* total length of the apdu */
|
||||
uint8_t * invoke_id,
|
||||
BACNET_GET_EVENT_INFORMATION_DATA * get_event_data,
|
||||
bool * moreEvents)
|
||||
{
|
||||
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_GET_EVENT_INFORMATION)
|
||||
return -1;
|
||||
offset = 3;
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
getevent_ack_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, get_event_data, moreEvents);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void testGetEventInformationAck(
|
||||
Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 1;
|
||||
uint8_t test_invoke_id = 0;
|
||||
BACNET_GET_EVENT_INFORMATION_DATA event_data;
|
||||
BACNET_GET_EVENT_INFORMATION_DATA test_event_data;
|
||||
bool moreEvents = false;
|
||||
bool test_moreEvents = false;
|
||||
unsigned i = 0;
|
||||
|
||||
event_data.objectIdentifier.type = OBJECT_BINARY_INPUT;
|
||||
event_data.objectIdentifier.instance = 1;
|
||||
event_data.eventState = EVENT_STATE_NORMAL;
|
||||
bitstring_init(&event_data.acknowledgedTransitions);
|
||||
bitstring_set_bit(&event_data.acknowledgedTransitions,
|
||||
TRANSITION_TO_OFFNORMAL, false);
|
||||
bitstring_set_bit(&event_data.acknowledgedTransitions, TRANSITION_TO_FAULT,
|
||||
false);
|
||||
bitstring_set_bit(&event_data.acknowledgedTransitions,
|
||||
TRANSITION_TO_NORMAL, false);
|
||||
for (i = 0; i < 3; i++) {
|
||||
event_data.eventTimeStamps[i].tag = TIME_STAMP_SEQUENCE;
|
||||
event_data.eventTimeStamps[i].value.sequenceNum = 0;
|
||||
}
|
||||
event_data.notifyType = NOTIFY_ALARM;
|
||||
bitstring_init(&event_data.eventEnable);
|
||||
bitstring_set_bit(&event_data.eventEnable, TRANSITION_TO_OFFNORMAL, true);
|
||||
bitstring_set_bit(&event_data.eventEnable, TRANSITION_TO_FAULT, true);
|
||||
bitstring_set_bit(&event_data.eventEnable, TRANSITION_TO_NORMAL, true);
|
||||
for (i = 0; i < 3; i++) {
|
||||
event_data.eventPriorities[i] = 1;
|
||||
}
|
||||
event_data.next = NULL;
|
||||
|
||||
len = getevent_ack_encode_apdu_init(&apdu[0], sizeof(apdu), invoke_id);
|
||||
ct_test(pTest, len != 0);
|
||||
ct_test(pTest, len != -1);
|
||||
apdu_len = len;
|
||||
len =
|
||||
getevent_ack_encode_apdu_data(&apdu[apdu_len], sizeof(apdu) - apdu_len,
|
||||
&event_data);
|
||||
ct_test(pTest, len != 0);
|
||||
ct_test(pTest, len != -1);
|
||||
apdu_len += len;
|
||||
len =
|
||||
getevent_ack_encode_apdu_end(&apdu[apdu_len], sizeof(apdu) - apdu_len,
|
||||
moreEvents);
|
||||
ct_test(pTest, len != 0);
|
||||
ct_test(pTest, len != -1);
|
||||
apdu_len += len;
|
||||
len = getevent_ack_decode_apdu(&apdu[0], apdu_len, /* total length of the apdu */
|
||||
&test_invoke_id, &test_event_data, &test_moreEvents);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
|
||||
ct_test(pTest,
|
||||
event_data.objectIdentifier.type ==
|
||||
test_event_data.objectIdentifier.type);
|
||||
ct_test(pTest,
|
||||
event_data.objectIdentifier.instance ==
|
||||
test_event_data.objectIdentifier.instance);
|
||||
|
||||
ct_test(pTest, event_data.eventState == test_event_data.eventState);
|
||||
}
|
||||
|
||||
void testGetEventInformation(
|
||||
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_OBJECT_ID lastReceivedObjectIdentifier;
|
||||
BACNET_OBJECT_ID test_lastReceivedObjectIdentifier;
|
||||
|
||||
lastReceivedObjectIdentifier.type = OBJECT_BINARY_INPUT;
|
||||
lastReceivedObjectIdentifier.instance = 12345;
|
||||
len =
|
||||
getevent_encode_apdu(&apdu[0], invoke_id,
|
||||
&lastReceivedObjectIdentifier);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
|
||||
len =
|
||||
getevent_decode_apdu(&apdu[0], apdu_len, &test_invoke_id,
|
||||
&test_lastReceivedObjectIdentifier);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest,
|
||||
test_lastReceivedObjectIdentifier.type ==
|
||||
lastReceivedObjectIdentifier.type);
|
||||
ct_test(pTest,
|
||||
test_lastReceivedObjectIdentifier.instance ==
|
||||
lastReceivedObjectIdentifier.instance);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_GET_EVENT_INFORMATION
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet GetEventInformation", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testGetEventInformation);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testGetEventInformationAck);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,221 @@
|
||||
/*####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 "bacdcode.h"
|
||||
#include "address.h"
|
||||
#include "iam.h"
|
||||
|
||||
/** @file iam.c Encode/Decode I-Am service */
|
||||
|
||||
/* 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_application_object_id(&apdu[apdu_len], OBJECT_DEVICE,
|
||||
device_id);
|
||||
apdu_len += len;
|
||||
len = encode_application_unsigned(&apdu[apdu_len], max_apdu);
|
||||
apdu_len += len;
|
||||
len =
|
||||
encode_application_enumerated(&apdu[apdu_len],
|
||||
(uint32_t) segmentation);
|
||||
apdu_len += len;
|
||||
len = encode_application_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 */
|
||||
uint16_t 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;
|
||||
|
||||
/* 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 = (unsigned) 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_value);
|
||||
apdu_len += len;
|
||||
if (decoded_value >= MAX_BACNET_SEGMENTATION)
|
||||
return -1;
|
||||
if (pSegmentation)
|
||||
*pSegmentation = (int) decoded_value;
|
||||
/* 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;
|
||||
}
|
||||
|
||||
#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
|
||||
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 */
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
/*####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"
|
||||
|
||||
/** @file ihave.c Encode/Decode I-Have service */
|
||||
|
||||
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_application_object_id(&apdu[apdu_len],
|
||||
(int) data->device_id.type, data->device_id.instance);
|
||||
apdu_len += len;
|
||||
/* objectIdentifier */
|
||||
len =
|
||||
encode_application_object_id(&apdu[apdu_len],
|
||||
(int) data->object_id.type, data->object_id.instance);
|
||||
apdu_len += len;
|
||||
/* objectName */
|
||||
len =
|
||||
encode_application_character_string(&apdu[apdu_len],
|
||||
&data->object_name);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
#if BACNET_SVC_I_HAVE_A
|
||||
|
||||
/* 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;
|
||||
uint16_t 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
#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 */
|
||||
+267
@@ -0,0 +1,267 @@
|
||||
/*####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"
|
||||
|
||||
/** @file indtext.c Maps text strings and indices of type INDTEXT_DATA */
|
||||
|
||||
#if !defined(__BORLANDC__) && !defined(_MSC_VER)
|
||||
#include <ctype.h>
|
||||
int stricmp(
|
||||
const char *s1,
|
||||
const char *s2)
|
||||
{
|
||||
unsigned char c1, c2;
|
||||
|
||||
do {
|
||||
c1 = (unsigned char) *s1;
|
||||
c2 = (unsigned char) *s2;
|
||||
c1 = (unsigned char) tolower(c1);
|
||||
c2 = (unsigned char) tolower(c2);
|
||||
s1++;
|
||||
s2++;
|
||||
} while ((c1 == c2) && (c1 != '\0'));
|
||||
|
||||
return (int) c1 - c2;
|
||||
}
|
||||
#endif
|
||||
#if defined(_MSC_VER)
|
||||
#define stricmp _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 (stricmp(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,
|
||||
unsigned index,
|
||||
unsigned 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,121 @@
|
||||
/*####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"
|
||||
|
||||
/** @file key.c Tests (only) of key encoding/decoding. */
|
||||
|
||||
#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
|
||||
+737
@@ -0,0 +1,737 @@
|
||||
/*####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####*/
|
||||
|
||||
/** @file keylist.c 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 index from the node specified by key */
|
||||
int Keylist_Index(
|
||||
OS_Keylist list,
|
||||
KEY key)
|
||||
{
|
||||
int index = -1; /* used to look up the index of node */
|
||||
|
||||
if (list && list->array && list->count) {
|
||||
if (!FindIndex(list, key, &index)) {
|
||||
index = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
/* returns the data specified by index */
|
||||
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 FIFO */
|
||||
static 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 */
|
||||
static 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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
}
|
||||
|
||||
static 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 */
|
||||
static 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;
|
||||
}
|
||||
|
||||
/* test access of a lot of entries */
|
||||
void testKeyList(
|
||||
Test * pTest)
|
||||
{
|
||||
bool rc;
|
||||
|
||||
/* 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, testKeyListDataIndex);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testKeyListLarge);
|
||||
assert(rc);
|
||||
}
|
||||
|
||||
#ifdef TEST_KEYLIST
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
|
||||
pTest = ct_create("keylist", NULL);
|
||||
testKeyList(pTest);
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_KEYLIST */
|
||||
#endif /* TEST */
|
||||
+385
@@ -0,0 +1,385 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2013 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 "lighting.h"
|
||||
#include "bacdcode.h"
|
||||
|
||||
/** @file lighting.c Manipulate BACnet lighting command values */
|
||||
|
||||
|
||||
/**
|
||||
* Encodes into bytes from the lighting-command structure
|
||||
*
|
||||
* @param apdu - buffer to hold the bytes
|
||||
* @param value - lighting command value to encode
|
||||
*
|
||||
* @return number of bytes encoded, or 0 if unable to encode.
|
||||
*/
|
||||
int lighting_command_encode(
|
||||
uint8_t * apdu,
|
||||
BACNET_LIGHTING_COMMAND * data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
int len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
len = encode_context_enumerated(&apdu[apdu_len], 0,
|
||||
data->operation);
|
||||
apdu_len += len;
|
||||
/* optional target-level */
|
||||
if (data->use_target_level) {
|
||||
len = encode_context_real(&apdu[apdu_len], 1,
|
||||
data->target_level);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* optional ramp-rate */
|
||||
if (data->use_ramp_rate) {
|
||||
len = encode_context_real(&apdu[apdu_len], 2,
|
||||
data->ramp_rate);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* optional step increment */
|
||||
if (data->use_step_increment) {
|
||||
len = encode_context_real(&apdu[apdu_len], 3,
|
||||
data->step_increment);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* optional fade time */
|
||||
if (data->use_fade_time) {
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 4,
|
||||
data->fade_time);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* optional priority */
|
||||
if (data->use_priority) {
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 5,
|
||||
data->priority);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes into bytes from the lighting-command structure
|
||||
* a context tagged chunk (opening and closing tag)
|
||||
*
|
||||
* @param apdu - buffer to hold the bytes
|
||||
* @param tag_number - tag number to encode this chunk
|
||||
* @param value - lighting command value to encode
|
||||
*
|
||||
* @return number of bytes encoded, or 0 if unable to encode.
|
||||
*/
|
||||
int lighting_command_encode_context(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_LIGHTING_COMMAND * value)
|
||||
{
|
||||
int apdu_len = 0;
|
||||
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], tag_number);
|
||||
apdu_len += lighting_command_encode(&apdu[apdu_len], value);
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], tag_number);
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes from bytes into the lighting-command structure
|
||||
*
|
||||
* @param apdu - buffer to hold the bytes
|
||||
* @param apdu_max_len - number of bytes in the buffer to decode
|
||||
* @param value - lighting command value to place the decoded values
|
||||
*
|
||||
* @return number of bytes encoded
|
||||
*/
|
||||
int lighting_command_decode(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_max_len,
|
||||
BACNET_LIGHTING_COMMAND * data)
|
||||
{
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
uint32_t unsigned_value = 0;
|
||||
float real_value = 0.0;
|
||||
|
||||
apdu_max_len = apdu_max_len;
|
||||
/* check for value pointers */
|
||||
if (apdu_max_len && data) {
|
||||
/* Tag 0: operation */
|
||||
if (!decode_is_context_tag(&apdu[apdu_len], 0))
|
||||
return BACNET_STATUS_ERROR;
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value_type);
|
||||
apdu_len += len;
|
||||
len =
|
||||
decode_enumerated(&apdu[apdu_len], len_value_type, &unsigned_value);
|
||||
if (len > 0) {
|
||||
data->operation = unsigned_value;
|
||||
}
|
||||
apdu_len += len;
|
||||
/* Tag 1: target-level - OPTIONAL */
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 1)) {
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value_type);
|
||||
apdu_len += len;
|
||||
len = decode_real(&apdu[apdu_len], &real_value);
|
||||
data->target_level = real_value;
|
||||
apdu_len += len;
|
||||
data->use_target_level = true;
|
||||
} else {
|
||||
data->use_target_level = false;
|
||||
}
|
||||
/* Tag 2: ramp-rate - OPTIONAL */
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 2)) {
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value_type);
|
||||
apdu_len += len;
|
||||
len = decode_real(&apdu[apdu_len], &real_value);
|
||||
data->ramp_rate = real_value;
|
||||
data->use_ramp_rate = true;
|
||||
} else {
|
||||
data->use_ramp_rate = false;
|
||||
}
|
||||
/* Tag 3: step-increment - OPTIONAL */
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 3)) {
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value_type);
|
||||
apdu_len += len;
|
||||
len = decode_real(&apdu[apdu_len], &real_value);
|
||||
data->step_increment = real_value;
|
||||
data->use_step_increment = true;
|
||||
} else {
|
||||
data->use_step_increment = false;
|
||||
}
|
||||
/* Tag 4: fade-time - OPTIONAL */
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 4)) {
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value_type);
|
||||
apdu_len += len;
|
||||
len = decode_unsigned(&apdu[apdu_len], len_value_type,
|
||||
&unsigned_value);
|
||||
data->fade_time = unsigned_value;
|
||||
data->use_fade_time = true;
|
||||
} else {
|
||||
data->use_fade_time = false;
|
||||
}
|
||||
/* Tag 5: priority - OPTIONAL */
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 4)) {
|
||||
len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value_type);
|
||||
apdu_len += len;
|
||||
len = decode_unsigned(&apdu[apdu_len], len_value_type,
|
||||
&unsigned_value);
|
||||
data->priority = unsigned_value;
|
||||
data->use_priority = true;
|
||||
} else {
|
||||
data->use_priority = false;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies one lighting-command structure to another
|
||||
*
|
||||
* @param dst - lighting command value target
|
||||
* @param src - lighting command value source
|
||||
*
|
||||
* @return true if copy succeeded
|
||||
*/
|
||||
bool lighting_command_copy(
|
||||
BACNET_LIGHTING_COMMAND * dst,
|
||||
BACNET_LIGHTING_COMMAND * src)
|
||||
{
|
||||
bool status = false;
|
||||
|
||||
if (dst && src) {
|
||||
dst->operation = src->operation;
|
||||
dst->use_target_level = src->use_target_level;
|
||||
dst->use_ramp_rate = src->use_ramp_rate;
|
||||
dst->use_step_increment = src->use_step_increment;
|
||||
dst->use_fade_time = src->use_fade_time;
|
||||
dst->use_priority = src->use_priority;
|
||||
dst->target_level = src->target_level;
|
||||
dst->ramp_rate = src->ramp_rate;
|
||||
dst->step_increment = src->step_increment;
|
||||
dst->fade_time = src->fade_time;
|
||||
dst->priority = src->priority;
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare one lighting-command structure to another
|
||||
*
|
||||
* @param dst - lighting command value target
|
||||
* @param src - lighting command value source
|
||||
*
|
||||
* @return true if lighting-commands are the same for values in-use
|
||||
*/
|
||||
bool lighting_command_same(
|
||||
BACNET_LIGHTING_COMMAND * dst,
|
||||
BACNET_LIGHTING_COMMAND * src)
|
||||
{
|
||||
bool status = false;
|
||||
|
||||
if (dst && src) {
|
||||
if ((dst->operation == src->operation) &&
|
||||
(dst->use_target_level == src->use_target_level) &&
|
||||
(dst->use_ramp_rate == src->use_ramp_rate) &&
|
||||
(dst->use_step_increment == src->use_step_increment) &&
|
||||
(dst->use_fade_time == src->use_fade_time) &&
|
||||
(dst->use_priority == src->use_priority)) {
|
||||
status = true;
|
||||
if ((dst->use_target_level) &&
|
||||
(dst->target_level != src->target_level)) {
|
||||
status = false;
|
||||
}
|
||||
if ((dst->use_ramp_rate) &&
|
||||
(dst->ramp_rate != src->ramp_rate)) {
|
||||
status = false;
|
||||
}
|
||||
if ((dst->use_step_increment) &&
|
||||
(dst->step_increment != src->step_increment)) {
|
||||
status = false;
|
||||
}
|
||||
if ((dst->use_fade_time) &&
|
||||
(dst->fade_time != src->fade_time)) {
|
||||
status = false;
|
||||
}
|
||||
if ((dst->use_priority) &&
|
||||
(dst->priority != src->priority)) {
|
||||
status = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testBACnetLightingCommand(
|
||||
Test * pTest,
|
||||
BACNET_LIGHTING_COMMAND *data)
|
||||
{
|
||||
bool status = false;
|
||||
BACNET_LIGHTING_COMMAND test_data;
|
||||
int len, apdu_len;
|
||||
uint8_t apdu[MAX_APDU] = {0};
|
||||
|
||||
status = lighting_command_copy(&test_data, NULL);
|
||||
ct_test(pTest, status == false);
|
||||
status = lighting_command_copy(NULL, data);
|
||||
ct_test(pTest, status == false);
|
||||
status = lighting_command_copy(&test_data, data);
|
||||
ct_test(pTest, status == true);
|
||||
status = lighting_command_same(&test_data, data);
|
||||
ct_test(pTest, status == true);
|
||||
len = lighting_command_encode(apdu, data);
|
||||
apdu_len = lighting_command_decode(apdu, sizeof(apdu), &test_data);
|
||||
ct_test(pTest, len > 0);
|
||||
ct_test(pTest, apdu_len > 0);
|
||||
status = lighting_command_same(&test_data, data);
|
||||
}
|
||||
|
||||
void testBACnetLightingCommandAll(
|
||||
Test * pTest)
|
||||
{
|
||||
BACNET_LIGHTING_COMMAND data;
|
||||
|
||||
data.operation = BACNET_LIGHTS_NONE;
|
||||
data.use_target_level = false;
|
||||
data.use_ramp_rate = false;
|
||||
data.use_step_increment = false;
|
||||
data.use_fade_time = false;
|
||||
data.use_priority = false;
|
||||
data.target_level = 0.0;
|
||||
data.ramp_rate = 100.0;
|
||||
data.step_increment = 1.0;
|
||||
data.fade_time = 100;
|
||||
data.priority = 1;
|
||||
testBACnetLightingCommand(pTest, &data);
|
||||
data.operation = BACNET_LIGHTS_STOP;
|
||||
data.use_target_level = true;
|
||||
data.use_ramp_rate = true;
|
||||
data.use_step_increment = true;
|
||||
data.use_fade_time = true;
|
||||
data.use_priority = true;
|
||||
testBACnetLightingCommand(pTest, &data);
|
||||
}
|
||||
|
||||
#ifdef TEST_LIGHTING_COMMAND
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Lighting Command", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testBACnetLightingCommandAll);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif /* TEST */
|
||||
@@ -0,0 +1,194 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2008 John Minack
|
||||
|
||||
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 "lso.h"
|
||||
#include "bacdcode.h"
|
||||
#include "apdu.h"
|
||||
|
||||
/** @file lso.c BACnet Life Safety Operation encode/decode */
|
||||
|
||||
int lso_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_LSO_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_CONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_LIFE_SAFETY_OPERATION;
|
||||
apdu_len = 4;
|
||||
/* tag 0 - requestingProcessId */
|
||||
len = encode_context_unsigned(&apdu[apdu_len], 0, data->processId);
|
||||
apdu_len += len;
|
||||
/* tag 1 - requestingSource */
|
||||
len =
|
||||
encode_context_character_string(&apdu[apdu_len], 1,
|
||||
&data->requestingSrc);
|
||||
apdu_len += len;
|
||||
/*
|
||||
Operation
|
||||
*/
|
||||
len = encode_context_enumerated(&apdu[apdu_len], 2, data->operation);
|
||||
apdu_len += len;
|
||||
/*
|
||||
Object ID
|
||||
*/
|
||||
|
||||
len =
|
||||
encode_context_object_id(&apdu[apdu_len], 3,
|
||||
(int) data->targetObject.type, data->targetObject.instance);
|
||||
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int lso_decode_service_request(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_LSO_DATA * data)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
int section_length = 0; /* length returned from decoding */
|
||||
uint32_t operation = 0; /* handles decoded value */
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && data) {
|
||||
/* Tag 0: Object ID */
|
||||
|
||||
if ((section_length =
|
||||
decode_context_unsigned(&apdu[len], 0,
|
||||
&data->processId)) == -1) {
|
||||
return -1;
|
||||
}
|
||||
len += section_length;
|
||||
|
||||
if ((section_length =
|
||||
decode_context_character_string(&apdu[len], 1,
|
||||
&data->requestingSrc)) == -1) {
|
||||
return -1;
|
||||
}
|
||||
len += section_length;
|
||||
|
||||
if ((section_length =
|
||||
decode_context_enumerated(&apdu[len], 2, &operation)) == -1) {
|
||||
return -1;
|
||||
}
|
||||
data->operation = (BACNET_LIFE_SAFETY_OPERATION) operation;
|
||||
len += section_length;
|
||||
|
||||
/*
|
||||
** This is an optional parameter, so dont fail if it doesnt exist
|
||||
*/
|
||||
if (decode_is_context_tag(&apdu[len], 3)) {
|
||||
if ((section_length =
|
||||
decode_context_object_id(&apdu[len], 3,
|
||||
&data->targetObject.type,
|
||||
&data->targetObject.instance)) == -1) {
|
||||
return -1;
|
||||
}
|
||||
len += section_length;
|
||||
} else {
|
||||
data->targetObject.type = 0;
|
||||
data->targetObject.instance = 0;
|
||||
}
|
||||
return len;
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
#include "bacapp.h"
|
||||
|
||||
void testLSO(
|
||||
Test * pTest)
|
||||
{
|
||||
uint8_t apdu[1000];
|
||||
int len;
|
||||
|
||||
BACNET_LSO_DATA data;
|
||||
BACNET_LSO_DATA rxdata;
|
||||
|
||||
memset(&rxdata, 0, sizeof(rxdata));
|
||||
|
||||
|
||||
characterstring_init_ansi(&data.requestingSrc, "foobar");
|
||||
data.operation = LIFE_SAFETY_OP_RESET;
|
||||
data.processId = 0x1234;
|
||||
data.targetObject.instance = 0x1000;
|
||||
data.targetObject.type = OBJECT_BINARY_INPUT;
|
||||
|
||||
len = lso_encode_apdu(apdu, 100, &data);
|
||||
|
||||
lso_decode_service_request(&apdu[4], len, &rxdata);
|
||||
|
||||
ct_test(pTest, data.operation == rxdata.operation);
|
||||
ct_test(pTest, data.processId == rxdata.processId);
|
||||
ct_test(pTest, data.targetObject.instance == rxdata.targetObject.instance);
|
||||
ct_test(pTest, data.targetObject.type == rxdata.targetObject.type);
|
||||
ct_test(pTest, memcmp(data.requestingSrc.value, rxdata.requestingSrc.value,
|
||||
rxdata.requestingSrc.length) == 0);
|
||||
}
|
||||
|
||||
#ifdef TEST_LSO
|
||||
int main(
|
||||
int argc,
|
||||
char *argv[])
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Life Safety Operation", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testLSO);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* TEST_COV */
|
||||
#endif /* TEST */
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* @file
|
||||
* @author Steve Karg
|
||||
* @date 2008
|
||||
* @section LICENSE
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Memory copy functions for deeply embedded system. The functions
|
||||
* are used with a buffer, the buffer offset, the sizeof the buffer,
|
||||
* and the number of bytes to copy to the buffer.
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include "memcopy.h"
|
||||
#include <string.h>
|
||||
|
||||
/**
|
||||
* Tests to see if the number of bytes is available from an offset
|
||||
* given the maximum sizeof a buffer.
|
||||
*
|
||||
* @param offset - offset into a buffer
|
||||
* @param max - maximum sizeof a buffer
|
||||
* @param len - number of bytes to add to the buffer starting at offset.
|
||||
*
|
||||
* @return True if there is enough space to copy len bytes.
|
||||
*/
|
||||
bool memcopylen(
|
||||
size_t offset,
|
||||
size_t max,
|
||||
size_t len)
|
||||
{
|
||||
return ((offset + len) <= max);
|
||||
}
|
||||
|
||||
#if defined (MEMCOPY_SIMPLE)
|
||||
/**
|
||||
* Copy len bytes from src to offset of dest if there is enough space
|
||||
* in a buffer of max bytes.
|
||||
*
|
||||
* @param dest - pointer to the destination buffer
|
||||
* @param src - pointer to the source buffer
|
||||
* @param offset - offset into the destination buffer
|
||||
* @param max - maximum sizeof the destination buffer
|
||||
* @param len - number of bytes to add to the destination buffer
|
||||
* starting at offset.
|
||||
*
|
||||
* @return returns zero if there is not enough space, or returns
|
||||
* the number of bytes copied.
|
||||
*/
|
||||
size_t memcopy(
|
||||
void *dest,
|
||||
void *src,
|
||||
size_t offset,
|
||||
size_t len,
|
||||
size_t max)
|
||||
{
|
||||
size_t i;
|
||||
size_t copy_len = 0;
|
||||
char *s1, *s2;
|
||||
|
||||
s1 = dest;
|
||||
s2 = src;
|
||||
if (memcopylen(offset, max, len)) {
|
||||
for (i = 0; i < len; i++) {
|
||||
s1[offset + i] = s2[i];
|
||||
copy_len++;
|
||||
}
|
||||
}
|
||||
|
||||
return copy_len;
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* Copy len bytes from src to offset of dest if there is enough space
|
||||
* in a buffer of max bytes.
|
||||
*
|
||||
* @param dest - pointer to the destination buffer
|
||||
* @param src - pointer to the source buffer
|
||||
* @param offset - offset into the destination buffer
|
||||
* @param max - maximum sizeof the destination buffer
|
||||
* @param len - number of bytes to add to the destination buffer
|
||||
* starting at offset.
|
||||
*
|
||||
* @return returns zero if there is not enough space, or returns
|
||||
* the number of bytes copied.
|
||||
*/
|
||||
size_t memcopy(
|
||||
void *dest,
|
||||
void *src,
|
||||
size_t offset, /* where in dest to put the data */
|
||||
size_t len, /* amount of data to copy */
|
||||
size_t max)
|
||||
{ /* total size of destination */
|
||||
if (memcopylen(offset, max, len)) {
|
||||
memcpy(&((char *) dest)[offset], src, len);
|
||||
return (len);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ctest.h"
|
||||
|
||||
void test_memcopy(
|
||||
Test * pTest)
|
||||
{
|
||||
char *data1 = "Joshua";
|
||||
char *data2 = "Anna";
|
||||
char buffer[480] = "";
|
||||
char big_buffer[480] = "";
|
||||
size_t len = 0;
|
||||
|
||||
len = memcopy(&buffer[0], &data1[0], 0, sizeof(data1), sizeof(buffer));
|
||||
ct_test(pTest, len == sizeof(data1));
|
||||
ct_test(pTest, memcmp(&buffer[0], &data1[0], len) == 0);
|
||||
len = memcopy(&buffer[0], &data2[0], len, sizeof(data2), sizeof(buffer));
|
||||
ct_test(pTest, len == sizeof(data2));
|
||||
len =
|
||||
memcopy(&buffer[0], &big_buffer[0], 1, sizeof(big_buffer),
|
||||
sizeof(buffer));
|
||||
ct_test(pTest, len == 0);
|
||||
}
|
||||
|
||||
#ifdef TEST_MEM_COPY
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("Memory Copy", NULL);
|
||||
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, test_memcopy);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#endif /* TEST */
|
||||
+1738
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,93 @@
|
||||
/*####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 <stdio.h>
|
||||
#include "mstp.h"
|
||||
#include "indtext.h"
|
||||
#include "bacenum.h"
|
||||
#include "mstptext.h"
|
||||
|
||||
/** @file mstptext.c Text mapping functions for BACnet MS/TP */
|
||||
|
||||
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_DATA, "DATA"},
|
||||
{0, NULL}
|
||||
};
|
||||
|
||||
const char *mstptext_receive_state(
|
||||
unsigned 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(
|
||||
unsigned 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(
|
||||
unsigned index)
|
||||
{
|
||||
return indtext_by_index_split_default(mstp_frame_type_text, index,
|
||||
FRAME_TYPE_PROPRIETARY_MIN, "UNKNOWN", "PROPRIETARY");
|
||||
}
|
||||
+614
@@ -0,0 +1,614 @@
|
||||
/*####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"
|
||||
|
||||
/** @file npdu.c Encode/Decode NPDUs - Network Protocol Data Units */
|
||||
|
||||
/** Copy the npdu_data structure information from src to dest.
|
||||
* @param dest [out] The 'to' structure
|
||||
* @param src [in] The 'from' structure
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
||||
|
||||
/** Encode the NPDU portion of a message to be sent, based on the npdu_data
|
||||
* and associated data.
|
||||
* If this is to be a Network Layer Control Message, there are probably
|
||||
* more bytes which will need to be encoded following the ones encoded here.
|
||||
* The Network Layer Protocol Control Information byte is described
|
||||
* in section 6.2.2 of the BACnet standard.
|
||||
* @param npdu [out] Buffer which will hold the encoded NPDU header bytes.
|
||||
* The size isn't given, but it must be at least 2 bytes
|
||||
* for the simplest case, and should always be at least 24
|
||||
* bytes to accommodate the maximal case (all fields loaded).
|
||||
* @param dest [in] The routing destination information if the message must
|
||||
* be routed to reach its destination.
|
||||
* If dest->net and dest->len are 0, there is no
|
||||
* routing destination information.
|
||||
* @param src [in] The routing source information if the message was routed
|
||||
* from another BACnet network.
|
||||
* If src->net and src->len are 0, there is no
|
||||
* routing source information.
|
||||
* This src describes the original source of the message when
|
||||
* it had to be routed to reach this BACnet Device.
|
||||
* @param npdu_data [in] The structure which describes how the NCPI and other
|
||||
* NPDU bytes should be encoded.
|
||||
* @return On success, returns the number of bytes which were encoded into the
|
||||
* NPDU section.
|
||||
* If 0 or negative, there were problems with the data or encoding.
|
||||
*/
|
||||
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 */
|
||||
uint8_t 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 && src->len)
|
||||
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 && src->len) { /* Only insert if valid */
|
||||
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] = npdu_data->hop_count;
|
||||
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.
|
||||
*/
|
||||
|
||||
/** Initialize an npdu_data structure to good defaults.
|
||||
* The name is a misnomer, as it doesn't do any actual encoding here.
|
||||
* @see npdu_encode_npdu_network if you need to set a network layer msg.
|
||||
*
|
||||
* @param npdu_data [out] Returns a filled-out structure with information
|
||||
* provided by the other arguments and good defaults.
|
||||
* @param data_expecting_reply [in] True if message should have a reply.
|
||||
* @param priority [in] One of the 4 priorities defined in section 6.2.2,
|
||||
* like B'11' = Life Safety message
|
||||
*/
|
||||
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 = HOP_COUNT_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
/** Decode the NPDU portion of a received message, particularly the NCPI byte.
|
||||
* The Network Layer Protocol Control Information byte is described
|
||||
* in section 6.2.2 of the BACnet standard.
|
||||
* @param npdu [in] Buffer holding the received NPDU header bytes (must be at least 2)
|
||||
* @param dest [out] Returned with routing destination information if the NPDU
|
||||
* has any and if this points to non-null storage for it.
|
||||
* If dest->net and dest->len are 0 on return, there is no
|
||||
* routing destination information.
|
||||
* @param src [out] Returned with routing source information if the NPDU
|
||||
* has any and if this points to non-null storage for it.
|
||||
* If src->net and src->len are 0 on return, there is no
|
||||
* routing source information.
|
||||
* This src describes the original source of the message when
|
||||
* it had to be routed to reach this BACnet Device.
|
||||
* @param npdu_data [out] Returns a filled-out structure with information
|
||||
* decoded from the NCPI and other NPDU bytes.
|
||||
* @return On success, returns the number of bytes which were decoded from the
|
||||
* NPDU section; if this is a network layer message, there may be more
|
||||
* bytes left in the NPDU; if not a network msg, the APDU follows.
|
||||
* If 0 or negative, there were problems with the data or arguments.
|
||||
*/
|
||||
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 */
|
||||
uint8_t i = 0; /* counter */
|
||||
uint16_t src_net = 0;
|
||||
uint16_t dest_net = 0;
|
||||
uint8_t slen = 0;
|
||||
uint8_t dlen = 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 */
|
||||
dlen = npdu[len++];
|
||||
if (dest) {
|
||||
dest->net = dest_net;
|
||||
dest->len = dlen;
|
||||
}
|
||||
if (dlen) {
|
||||
if (dlen > MAX_MAC_LEN) {
|
||||
/* address is too large could be a malformed message */
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < dlen; 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 */
|
||||
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 */
|
||||
slen = npdu[len++];
|
||||
if (src) {
|
||||
src->net = src_net;
|
||||
src->len = slen;
|
||||
}
|
||||
if (slen) {
|
||||
if (slen > MAX_MAC_LEN) {
|
||||
/* address is too large could be a malformed message */
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < slen; i++) {
|
||||
mac_octet = npdu[len++];
|
||||
if (src)
|
||||
src->adr[i] = mac_octet;
|
||||
}
|
||||
}
|
||||
} else if (src) {
|
||||
/* Clear the net number, with one exception: if the receive()
|
||||
* function set it to BACNET_BROADCAST_NETWORK, (eg, for
|
||||
* BVLC_ORIGINAL_BROADCAST_NPDU) then don't stomp on that.
|
||||
*/
|
||||
if (src->net != BACNET_BROADCAST_NETWORK)
|
||||
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 {
|
||||
/* Since npdu_data->network_layer_message is false,
|
||||
* it doesn't much matter what we set here; this is safe: */
|
||||
npdu_data->network_message_type = NETWORK_MESSAGE_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#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 */
|
||||
+1416
File diff suppressed because it is too large
Load Diff
+696
@@ -0,0 +1,696 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2009 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 "ptransfer.h"
|
||||
|
||||
/** @file ptransfer.c Encode/Decode Private Transfer data */
|
||||
|
||||
/* encode service */
|
||||
static int pt_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint16_t max_apdu,
|
||||
BACNET_PRIVATE_TRANSFER_DATA * private_data)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
/*
|
||||
Unconfirmed/ConfirmedPrivateTransfer-Request ::= SEQUENCE {
|
||||
vendorID [0] Unsigned,
|
||||
serviceNumber [1] Unsigned,
|
||||
serviceParameters [2] ABSTRACT-SYNTAX.&Type OPTIONAL
|
||||
}
|
||||
*/
|
||||
/* unused parameter */
|
||||
max_apdu = max_apdu;
|
||||
if (apdu) {
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 0,
|
||||
private_data->vendorID);
|
||||
apdu_len += len;
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 1,
|
||||
private_data->serviceNumber);
|
||||
apdu_len += len;
|
||||
len = encode_opening_tag(&apdu[apdu_len], 2);
|
||||
apdu_len += len;
|
||||
for (len = 0; len < private_data->serviceParametersLen; len++) {
|
||||
apdu[apdu_len] = private_data->serviceParameters[len];
|
||||
apdu_len++;
|
||||
}
|
||||
len = encode_closing_tag(&apdu[apdu_len], 2);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int ptransfer_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_PRIVATE_TRANSFER_DATA * private_data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
int len = 0;
|
||||
|
||||
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_PRIVATE_TRANSFER;
|
||||
apdu_len = 4;
|
||||
len =
|
||||
pt_encode_apdu(&apdu[apdu_len], (uint16_t) (MAX_APDU - apdu_len),
|
||||
private_data);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int uptransfer_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
BACNET_PRIVATE_TRANSFER_DATA * private_data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
int len = 0;
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST;
|
||||
apdu[1] = SERVICE_UNCONFIRMED_PRIVATE_TRANSFER;
|
||||
apdu_len = 2;
|
||||
len =
|
||||
pt_encode_apdu(&apdu[apdu_len], (uint16_t) (MAX_APDU - apdu_len),
|
||||
private_data);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int ptransfer_decode_service_request(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_PRIVATE_TRANSFER_DATA * private_data)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
int decode_len = 0; /* return value */
|
||||
uint32_t unsigned_value = 0;
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && private_data) {
|
||||
/* Tag 0: vendorID */
|
||||
decode_len = decode_context_unsigned(&apdu[len], 0, &unsigned_value);
|
||||
if (decode_len < 0) {
|
||||
return -1;
|
||||
}
|
||||
len = decode_len;
|
||||
private_data->vendorID = (uint16_t) unsigned_value;
|
||||
/* Tag 1: serviceNumber */
|
||||
decode_len = decode_context_unsigned(&apdu[len], 1, &unsigned_value);
|
||||
if (decode_len < 0) {
|
||||
return -1;
|
||||
}
|
||||
len += decode_len;
|
||||
private_data->serviceNumber = unsigned_value;
|
||||
/* Tag 2: serviceParameters */
|
||||
if (decode_is_opening_tag_number(&apdu[len], 2)) {
|
||||
/* a tag number of 2 is not extended so only one octet */
|
||||
len++;
|
||||
/* don't decode the serviceParameters here */
|
||||
private_data->serviceParameters = &apdu[len];
|
||||
private_data->serviceParametersLen =
|
||||
(int) apdu_len - len - 1 /*closing tag */ ;
|
||||
/* len includes the data and the closing tag */
|
||||
len = (int) apdu_len;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int ptransfer_error_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_ERROR_CLASS error_class,
|
||||
BACNET_ERROR_CODE error_code,
|
||||
BACNET_PRIVATE_TRANSFER_DATA * private_data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
int len = 0; /* length of the part of the encoding */
|
||||
|
||||
if (apdu) {
|
||||
apdu[0] = PDU_TYPE_ERROR;
|
||||
apdu[1] = invoke_id;
|
||||
apdu[2] = SERVICE_CONFIRMED_PRIVATE_TRANSFER;
|
||||
apdu_len = 3;
|
||||
/* service parameters */
|
||||
/*
|
||||
ConfirmedPrivateTransfer-Error ::= SEQUENCE {
|
||||
errorType [0] Error,
|
||||
vendorID [1] Unsigned,
|
||||
serviceNumber [2] Unsigned,
|
||||
errorParameters [3] ABSTRACT-SYNTAX.&Type OPTIONAL
|
||||
}
|
||||
*/
|
||||
len = encode_opening_tag(&apdu[apdu_len], 0);
|
||||
apdu_len += len;
|
||||
len = encode_application_enumerated(&apdu[apdu_len], error_class);
|
||||
apdu_len += len;
|
||||
len = encode_application_enumerated(&apdu[apdu_len], error_code);
|
||||
apdu_len += len;
|
||||
len = encode_closing_tag(&apdu[apdu_len], 0);
|
||||
apdu_len += len;
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 1,
|
||||
private_data->vendorID);
|
||||
apdu_len += len;
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 2,
|
||||
private_data->serviceNumber);
|
||||
apdu_len += len;
|
||||
len = encode_opening_tag(&apdu[apdu_len], 3);
|
||||
apdu_len += len;
|
||||
for (len = 0; len < private_data->serviceParametersLen; len++) {
|
||||
apdu[apdu_len] = private_data->serviceParameters[len];
|
||||
apdu_len++;
|
||||
}
|
||||
len = encode_closing_tag(&apdu[apdu_len], 3);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* decode the service request only */
|
||||
int ptransfer_error_decode_service_request(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_ERROR_CLASS * error_class,
|
||||
BACNET_ERROR_CODE * error_code,
|
||||
BACNET_PRIVATE_TRANSFER_DATA * private_data)
|
||||
{
|
||||
int len = 0; /* return value */
|
||||
int decode_len = 0; /* return value */
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
uint32_t unsigned_value = 0;
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && private_data) {
|
||||
/* Tag 0: Error */
|
||||
if (decode_is_opening_tag_number(&apdu[len], 0)) {
|
||||
/* a tag number of 0 is not extended so only one octet */
|
||||
len++;
|
||||
/* error class */
|
||||
decode_len =
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
len += decode_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_ENUMERATED) {
|
||||
return 0;
|
||||
}
|
||||
decode_len =
|
||||
decode_enumerated(&apdu[len], len_value_type, &unsigned_value);
|
||||
len += decode_len;
|
||||
if (error_class) {
|
||||
*error_class = (BACNET_ERROR_CLASS) unsigned_value;
|
||||
}
|
||||
/* error code */
|
||||
decode_len =
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
len += decode_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_ENUMERATED) {
|
||||
return 0;
|
||||
}
|
||||
decode_len =
|
||||
decode_enumerated(&apdu[len], len_value_type, &unsigned_value);
|
||||
len += decode_len;
|
||||
if (error_code) {
|
||||
*error_code = (BACNET_ERROR_CODE) unsigned_value;
|
||||
}
|
||||
if (decode_is_closing_tag_number(&apdu[len], 0)) {
|
||||
/* a tag number of 0 is not extended so only one octet */
|
||||
len++;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
/* Tag 1: vendorID */
|
||||
decode_len = decode_context_unsigned(&apdu[len], 1, &unsigned_value);
|
||||
if (decode_len < 0) {
|
||||
return -1;
|
||||
}
|
||||
len += decode_len;
|
||||
private_data->vendorID = (uint16_t) unsigned_value;
|
||||
/* Tag 2: serviceNumber */
|
||||
decode_len = decode_context_unsigned(&apdu[len], 2, &unsigned_value);
|
||||
if (decode_len < 0) {
|
||||
return -1;
|
||||
}
|
||||
len += decode_len;
|
||||
private_data->serviceNumber = unsigned_value;
|
||||
/* Tag 3: serviceParameters */
|
||||
if (decode_is_opening_tag_number(&apdu[len], 3)) {
|
||||
/* a tag number of 2 is not extended so only one octet */
|
||||
len++;
|
||||
/* don't decode the serviceParameters here */
|
||||
private_data->serviceParameters = &apdu[len];
|
||||
private_data->serviceParametersLen =
|
||||
(int) apdu_len - len - 1 /*closing tag */ ;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
/* we could check for a closing tag of 3 */
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int ptransfer_ack_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_PRIVATE_TRANSFER_DATA * private_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_PRIVATE_TRANSFER; /* service choice */
|
||||
apdu_len = 3;
|
||||
/* service ack follows */
|
||||
/*
|
||||
ConfirmedPrivateTransfer-ACK ::= SEQUENCE {
|
||||
vendorID [0] Unsigned,
|
||||
serviceNumber [1] Unsigned,
|
||||
resultBlock [2] ABSTRACT-SYNTAX.&Type OPTIONAL
|
||||
}
|
||||
*/
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 0,
|
||||
private_data->vendorID);
|
||||
apdu_len += len;
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 1,
|
||||
private_data->serviceNumber);
|
||||
apdu_len += len;
|
||||
len = encode_opening_tag(&apdu[apdu_len], 2);
|
||||
apdu_len += len;
|
||||
for (len = 0; len < private_data->serviceParametersLen; len++) {
|
||||
apdu[apdu_len] = private_data->serviceParameters[len];
|
||||
apdu_len++;
|
||||
}
|
||||
len = encode_closing_tag(&apdu[apdu_len], 2);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* ptransfer_ack_decode_service_request() is the same as
|
||||
ptransfer_decode_service_request */
|
||||
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
#include "bacapp.h"
|
||||
|
||||
int ptransfer_decode_apdu(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
uint8_t * invoke_id,
|
||||
BACNET_PRIVATE_TRANSFER_DATA * private_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, MAX_APDU); */
|
||||
/* invoke id - filled in by net layer */
|
||||
*invoke_id = apdu[2];
|
||||
if (apdu[3] != SERVICE_CONFIRMED_PRIVATE_TRANSFER)
|
||||
return -1;
|
||||
offset = 4;
|
||||
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
ptransfer_decode_service_request(&apdu[offset], apdu_len - offset,
|
||||
private_data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int uptransfer_decode_apdu(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_PRIVATE_TRANSFER_DATA * private_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 -1;
|
||||
}
|
||||
if (apdu[1] != SERVICE_UNCONFIRMED_PRIVATE_TRANSFER) {
|
||||
return -1;
|
||||
}
|
||||
offset = 2;
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
ptransfer_decode_service_request(&apdu[offset], apdu_len - offset,
|
||||
private_data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int ptransfer_ack_decode_apdu(
|
||||
uint8_t * apdu,
|
||||
int apdu_len, /* total length of the apdu */
|
||||
uint8_t * invoke_id,
|
||||
BACNET_PRIVATE_TRANSFER_DATA * private_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_PRIVATE_TRANSFER)
|
||||
return -1;
|
||||
offset = 3;
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
ptransfer_decode_service_request(&apdu[offset], apdu_len - offset,
|
||||
private_data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int ptransfer_error_decode_apdu(
|
||||
uint8_t * apdu,
|
||||
int apdu_len, /* total length of the apdu */
|
||||
uint8_t * invoke_id,
|
||||
BACNET_ERROR_CLASS * error_class,
|
||||
BACNET_ERROR_CODE * error_code,
|
||||
BACNET_PRIVATE_TRANSFER_DATA * private_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_ERROR)
|
||||
return -1;
|
||||
*invoke_id = apdu[1];
|
||||
if (apdu[2] != SERVICE_CONFIRMED_PRIVATE_TRANSFER)
|
||||
return -1;
|
||||
offset = 3;
|
||||
if (apdu_len > offset) {
|
||||
len =
|
||||
ptransfer_error_decode_service_request(&apdu[offset],
|
||||
apdu_len - offset, error_class, error_code, private_data);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void test_Private_Transfer_Ack(
|
||||
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_PRIVATE_TRANSFER_DATA private_data;
|
||||
BACNET_PRIVATE_TRANSFER_DATA test_data;
|
||||
uint8_t test_value[480] = { 0 };
|
||||
int private_data_len = 0;
|
||||
char private_data_chunk[33] = { "00112233445566778899AABBCCDDEEFF" };
|
||||
BACNET_APPLICATION_DATA_VALUE data_value;
|
||||
BACNET_APPLICATION_DATA_VALUE test_data_value;
|
||||
bool status = false;
|
||||
|
||||
private_data.vendorID = BACNET_VENDOR_ID;
|
||||
private_data.serviceNumber = 1;
|
||||
|
||||
status =
|
||||
bacapp_parse_application_data(BACNET_APPLICATION_TAG_OCTET_STRING,
|
||||
&private_data_chunk[0], &data_value);
|
||||
ct_test(pTest, status == true);
|
||||
private_data_len =
|
||||
bacapp_encode_application_data(&test_value[0], &data_value);
|
||||
|
||||
private_data.serviceParameters = &test_value[0];
|
||||
private_data.serviceParametersLen = private_data_len;
|
||||
|
||||
|
||||
len = ptransfer_ack_encode_apdu(&apdu[0], invoke_id, &private_data);
|
||||
ct_test(pTest, len != 0);
|
||||
ct_test(pTest, len != -1);
|
||||
apdu_len = len;
|
||||
len =
|
||||
ptransfer_ack_decode_apdu(&apdu[0], apdu_len, &test_invoke_id,
|
||||
&test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, test_data.vendorID == private_data.vendorID);
|
||||
ct_test(pTest, test_data.serviceNumber == private_data.serviceNumber);
|
||||
ct_test(pTest,
|
||||
test_data.serviceParametersLen == private_data.serviceParametersLen);
|
||||
len =
|
||||
bacapp_decode_application_data(test_data.serviceParameters,
|
||||
test_data.serviceParametersLen, &test_data_value);
|
||||
ct_test(pTest, bacapp_same_value(&data_value, &test_data_value) == true);
|
||||
}
|
||||
|
||||
void test_Private_Transfer_Error(
|
||||
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_ERROR_CLASS error_class = ERROR_CLASS_RESOURCES;
|
||||
BACNET_ERROR_CODE error_code = ERROR_CODE_OPERATIONAL_PROBLEM;
|
||||
BACNET_ERROR_CLASS test_error_class = 0;
|
||||
BACNET_ERROR_CODE test_error_code = 0;
|
||||
BACNET_PRIVATE_TRANSFER_DATA private_data;
|
||||
BACNET_PRIVATE_TRANSFER_DATA test_data;
|
||||
uint8_t test_value[480] = { 0 };
|
||||
int private_data_len = 0;
|
||||
char private_data_chunk[33] = { "00112233445566778899AABBCCDDEEFF" };
|
||||
BACNET_APPLICATION_DATA_VALUE data_value;
|
||||
BACNET_APPLICATION_DATA_VALUE test_data_value;
|
||||
bool status = false;
|
||||
|
||||
private_data.vendorID = BACNET_VENDOR_ID;
|
||||
private_data.serviceNumber = 1;
|
||||
|
||||
status =
|
||||
bacapp_parse_application_data(BACNET_APPLICATION_TAG_OCTET_STRING,
|
||||
&private_data_chunk[0], &data_value);
|
||||
ct_test(pTest, status == true);
|
||||
private_data_len =
|
||||
bacapp_encode_application_data(&test_value[0], &data_value);
|
||||
private_data.serviceParameters = &test_value[0];
|
||||
private_data.serviceParametersLen = private_data_len;
|
||||
|
||||
len =
|
||||
ptransfer_error_encode_apdu(&apdu[0], invoke_id, error_class,
|
||||
error_code, &private_data);
|
||||
ct_test(pTest, len != 0);
|
||||
ct_test(pTest, len != -1);
|
||||
apdu_len = len;
|
||||
len =
|
||||
ptransfer_error_decode_apdu(&apdu[0], apdu_len, &test_invoke_id,
|
||||
&test_error_class, &test_error_code, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_invoke_id == invoke_id);
|
||||
ct_test(pTest, test_data.vendorID == private_data.vendorID);
|
||||
ct_test(pTest, test_data.serviceNumber == private_data.serviceNumber);
|
||||
ct_test(pTest, test_error_class == error_class);
|
||||
ct_test(pTest, test_error_code == error_code);
|
||||
ct_test(pTest,
|
||||
test_data.serviceParametersLen == private_data.serviceParametersLen);
|
||||
len =
|
||||
bacapp_decode_application_data(test_data.serviceParameters,
|
||||
test_data.serviceParametersLen, &test_data_value);
|
||||
ct_test(pTest, bacapp_same_value(&data_value, &test_data_value) == true);
|
||||
}
|
||||
|
||||
void test_Private_Transfer_Request(
|
||||
Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
uint8_t test_value[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
uint8_t invoke_id = 128;
|
||||
uint8_t test_invoke_id = 0;
|
||||
int private_data_len = 0;
|
||||
char private_data_chunk[33] = { "00112233445566778899AABBCCDDEEFF" };
|
||||
BACNET_APPLICATION_DATA_VALUE data_value = { 0 };
|
||||
BACNET_APPLICATION_DATA_VALUE test_data_value = { 0 };
|
||||
BACNET_PRIVATE_TRANSFER_DATA private_data = { 0 };
|
||||
BACNET_PRIVATE_TRANSFER_DATA test_data = { 0 };
|
||||
bool status = false;
|
||||
|
||||
private_data.vendorID = BACNET_VENDOR_ID;
|
||||
private_data.serviceNumber = 1;
|
||||
|
||||
status =
|
||||
bacapp_parse_application_data(BACNET_APPLICATION_TAG_OCTET_STRING,
|
||||
&private_data_chunk[0], &data_value);
|
||||
ct_test(pTest, status == true);
|
||||
private_data_len =
|
||||
bacapp_encode_application_data(&test_value[0], &data_value);
|
||||
private_data.serviceParameters = &test_value[0];
|
||||
private_data.serviceParametersLen = private_data_len;
|
||||
|
||||
len = ptransfer_encode_apdu(&apdu[0], invoke_id, &private_data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
len =
|
||||
ptransfer_decode_apdu(&apdu[0], apdu_len, &test_invoke_id, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_data.vendorID == private_data.vendorID);
|
||||
ct_test(pTest, test_data.serviceNumber == private_data.serviceNumber);
|
||||
ct_test(pTest,
|
||||
test_data.serviceParametersLen == private_data.serviceParametersLen);
|
||||
len =
|
||||
bacapp_decode_application_data(test_data.serviceParameters,
|
||||
test_data.serviceParametersLen, &test_data_value);
|
||||
ct_test(pTest, bacapp_same_value(&data_value, &test_data_value) == true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void test_Unconfirmed_Private_Transfer_Request(
|
||||
Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
uint8_t test_value[480] = { 0 };
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
int private_data_len = 0;
|
||||
char private_data_chunk[32] = { "I Love You, Patricia!" };
|
||||
BACNET_APPLICATION_DATA_VALUE data_value;
|
||||
BACNET_APPLICATION_DATA_VALUE test_data_value;
|
||||
BACNET_PRIVATE_TRANSFER_DATA private_data;
|
||||
BACNET_PRIVATE_TRANSFER_DATA test_data;
|
||||
bool status = false;
|
||||
|
||||
private_data.vendorID = BACNET_VENDOR_ID;
|
||||
private_data.serviceNumber = 1;
|
||||
|
||||
status =
|
||||
bacapp_parse_application_data(BACNET_APPLICATION_TAG_CHARACTER_STRING,
|
||||
&private_data_chunk[0], &data_value);
|
||||
ct_test(pTest, status == true);
|
||||
private_data_len =
|
||||
bacapp_encode_application_data(&test_value[0], &data_value);
|
||||
private_data.serviceParameters = &test_value[0];
|
||||
private_data.serviceParametersLen = private_data_len;
|
||||
|
||||
len = uptransfer_encode_apdu(&apdu[0], &private_data);
|
||||
ct_test(pTest, len != 0);
|
||||
apdu_len = len;
|
||||
len = uptransfer_decode_apdu(&apdu[0], apdu_len, &test_data);
|
||||
ct_test(pTest, len != -1);
|
||||
ct_test(pTest, test_data.vendorID == private_data.vendorID);
|
||||
ct_test(pTest, test_data.serviceNumber == private_data.serviceNumber);
|
||||
ct_test(pTest,
|
||||
test_data.serviceParametersLen == private_data.serviceParametersLen);
|
||||
len =
|
||||
bacapp_decode_application_data(test_data.serviceParameters,
|
||||
test_data.serviceParametersLen, &test_data_value);
|
||||
ct_test(pTest, bacapp_same_value(&data_value, &test_data_value) == true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef TEST_PRIVATE_TRANSFER
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet PrivateTransfer", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, test_Private_Transfer_Request);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, test_Private_Transfer_Ack);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, test_Private_Transfer_Error);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, test_Unconfirmed_Private_Transfer_Request);
|
||||
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,196 @@
|
||||
/*####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"
|
||||
|
||||
/** @file rd.c Encode/Decode Reinitialize Device APDUs */
|
||||
#if BACNET_SVC_RD_A
|
||||
/* 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 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;
|
||||
uint32_t 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, MAX_APDU); */
|
||||
*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 */
|
||||
+455
@@ -0,0 +1,455 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2009 Peter Mc Shane
|
||||
|
||||
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 "readrange.h"
|
||||
|
||||
/** @file readrange.c Encode/Decode ReadRange requests */
|
||||
|
||||
/*
|
||||
* ReadRange-Request ::= SEQUENCE {
|
||||
* objectIdentifier [0] BACnetObjectIdentifier,
|
||||
* propertyIdentifier [1] BACnetPropertyIdentifier,
|
||||
* propertyArrayIndex [2] Unsigned OPTIONAL, -- used only with array datatype
|
||||
* range CHOICE {
|
||||
* byPosition [3] SEQUENCE {
|
||||
* referenceIndex Unsigned,
|
||||
* count INTEGER
|
||||
* },
|
||||
* -- context tag 4 is deprecated
|
||||
* -- context tag 5 is deprecated
|
||||
* bySequenceNumber [6] SEQUENCE {
|
||||
* referenceIndex Unsigned,
|
||||
* count INTEGER
|
||||
* },
|
||||
* byTime [7] SEQUENCE {
|
||||
* referenceTime BACnetDateTime,
|
||||
* count INTEGER
|
||||
* }
|
||||
* } OPTIONAL
|
||||
* }
|
||||
*/
|
||||
|
||||
/*****************************************************************************
|
||||
* Build a ReadRange request packet. *
|
||||
*****************************************************************************/
|
||||
|
||||
int rr_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_READ_RANGE_DATA * rrdata)
|
||||
{
|
||||
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_RANGE; /* service choice */
|
||||
apdu_len = 4;
|
||||
|
||||
apdu_len +=
|
||||
encode_context_object_id(&apdu[apdu_len], 0, rrdata->object_type,
|
||||
rrdata->object_instance);
|
||||
apdu_len +=
|
||||
encode_context_enumerated(&apdu[apdu_len], 1,
|
||||
rrdata->object_property);
|
||||
|
||||
/* optional array index */
|
||||
|
||||
if (rrdata->array_index != BACNET_ARRAY_ALL) {
|
||||
apdu_len +=
|
||||
encode_context_unsigned(&apdu[apdu_len], 2,
|
||||
rrdata->array_index);
|
||||
}
|
||||
|
||||
/* Build the appropriate (optional) range parameter based on the request type */
|
||||
|
||||
switch (rrdata->RequestType) {
|
||||
case RR_BY_POSITION:
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 3);
|
||||
apdu_len +=
|
||||
encode_application_unsigned(&apdu[apdu_len],
|
||||
rrdata->Range.RefIndex);
|
||||
apdu_len +=
|
||||
encode_application_signed(&apdu[apdu_len], rrdata->Count);
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 3);
|
||||
break;
|
||||
|
||||
case RR_BY_SEQUENCE:
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 6);
|
||||
apdu_len +=
|
||||
encode_application_unsigned(&apdu[apdu_len],
|
||||
rrdata->Range.RefSeqNum);
|
||||
apdu_len +=
|
||||
encode_application_signed(&apdu[apdu_len], rrdata->Count);
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 6);
|
||||
break;
|
||||
|
||||
case RR_BY_TIME:
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 7);
|
||||
apdu_len +=
|
||||
encode_application_date(&apdu[apdu_len],
|
||||
&rrdata->Range.RefTime.date);
|
||||
apdu_len +=
|
||||
encode_application_time(&apdu[apdu_len],
|
||||
&rrdata->Range.RefTime.time);
|
||||
apdu_len +=
|
||||
encode_application_signed(&apdu[apdu_len], rrdata->Count);
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 7);
|
||||
break;
|
||||
|
||||
case RR_READ_ALL: /* to attempt a read of the whole array or list, omit the range parameter */
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Decode the received ReadRange request *
|
||||
*****************************************************************************/
|
||||
|
||||
int rr_decode_service_request(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_READ_RANGE_DATA * rrdata)
|
||||
{
|
||||
unsigned len = 0;
|
||||
unsigned TagLen = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
uint16_t type = 0; /* for decoding */
|
||||
uint32_t UnsignedTemp;
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && rrdata) {
|
||||
/* Tag 0: Object ID */
|
||||
if (!decode_is_context_tag(&apdu[len++], 0))
|
||||
return -1;
|
||||
len += decode_object_id(&apdu[len], &type, &rrdata->object_instance);
|
||||
rrdata->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, &UnsignedTemp);
|
||||
rrdata->object_property = (BACNET_PROPERTY_ID) UnsignedTemp;
|
||||
rrdata->Overhead = RR_OVERHEAD; /* Start with the fixed overhead */
|
||||
|
||||
/* Tag 2: Optional Array Index - set to ALL if not present */
|
||||
rrdata->array_index = BACNET_ARRAY_ALL; /* Assuming this is the most common outcome... */
|
||||
if (len < apdu_len) {
|
||||
TagLen =
|
||||
(unsigned) decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
if (tag_number == 2) {
|
||||
len += TagLen;
|
||||
len +=
|
||||
decode_unsigned(&apdu[len], len_value_type, &UnsignedTemp);
|
||||
rrdata->array_index = UnsignedTemp;
|
||||
rrdata->Overhead += RR_INDEX_OVERHEAD; /* Allow for this in the response */
|
||||
}
|
||||
}
|
||||
/* And/or optional range selection- Tags 3, 6 and 7 */
|
||||
rrdata->RequestType = RR_READ_ALL; /* Assume the worst to cut out explicit checking later */
|
||||
if (len < apdu_len) {
|
||||
/*
|
||||
* Note: We pick up the opening tag and then decode the parameter types we recognise.
|
||||
* We deal with the count and the closing tag in each case statement even though it
|
||||
* might appear that we could do them after the switch statement as common elements.
|
||||
* This is so that if we receive a tag we don't recognise, we don't try to decode it
|
||||
* blindly and make a mess of it.
|
||||
*/
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
switch (tag_number) {
|
||||
case 3: /* ReadRange by position */
|
||||
rrdata->RequestType = RR_BY_POSITION;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
len +=
|
||||
decode_unsigned(&apdu[len], len_value_type,
|
||||
&rrdata->Range.RefIndex);
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
len +=
|
||||
decode_signed(&apdu[len], len_value_type,
|
||||
&rrdata->Count);
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
break;
|
||||
|
||||
case 6: /* ReadRange by sequence number */
|
||||
rrdata->RequestType = RR_BY_SEQUENCE;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
len +=
|
||||
decode_unsigned(&apdu[len], len_value_type,
|
||||
&rrdata->Range.RefSeqNum);
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
len +=
|
||||
decode_signed(&apdu[len], len_value_type,
|
||||
&rrdata->Count);
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
rrdata->Overhead += RR_1ST_SEQ_OVERHEAD; /* Allow for this in the response */
|
||||
break;
|
||||
|
||||
case 7: /* ReadRange by time stamp */
|
||||
rrdata->RequestType = RR_BY_TIME;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
len +=
|
||||
decode_date(&apdu[len], &rrdata->Range.RefTime.date);
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
len +=
|
||||
decode_bacnet_time(&apdu[len],
|
||||
&rrdata->Range.RefTime.time);
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
len +=
|
||||
decode_signed(&apdu[len], len_value_type,
|
||||
&rrdata->Count);
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
rrdata->Overhead += RR_1ST_SEQ_OVERHEAD; /* Allow for this in the response */
|
||||
break;
|
||||
|
||||
default: /* If we don't recognise the tag then we do nothing here and try to return
|
||||
* all elements of the array */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
}
|
||||
|
||||
/*
|
||||
* ReadRange-ACK ::= SEQUENCE {
|
||||
* objectIdentifier [0] BACnetObjectIdentifier,
|
||||
* propertyIdentifier [1] BACnetPropertyIdentifier,
|
||||
* propertyArrayIndex [2] Unsigned OPTIONAL , -- used only with array datatype
|
||||
* resultFlags [3] BACnetResultFlags,
|
||||
* itemCount [4] Unsigned,
|
||||
* itemData [5] SEQUENCE OF ABSTRACT-SYNTAX.&TYPE,
|
||||
* firstSequenceNumber [6] Unsigned32 OPTIONAL -- used only if 'Item Count' > 0 and the request was either of
|
||||
* -- type 'By Sequence Number' or 'By Time'
|
||||
* }
|
||||
*/
|
||||
|
||||
/*****************************************************************************
|
||||
* Build a ReadRange response packet *
|
||||
*****************************************************************************/
|
||||
|
||||
int rr_ack_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_READ_RANGE_DATA * rrdata)
|
||||
{
|
||||
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_RANGE; /* service choice */
|
||||
apdu_len = 3;
|
||||
/* service ack follows */
|
||||
apdu_len +=
|
||||
encode_context_object_id(&apdu[apdu_len], 0, rrdata->object_type,
|
||||
rrdata->object_instance);
|
||||
apdu_len +=
|
||||
encode_context_enumerated(&apdu[apdu_len], 1,
|
||||
rrdata->object_property);
|
||||
/* context 2 array index is optional */
|
||||
if (rrdata->array_index != BACNET_ARRAY_ALL) {
|
||||
apdu_len +=
|
||||
encode_context_unsigned(&apdu[apdu_len], 2,
|
||||
rrdata->array_index);
|
||||
}
|
||||
/* Context 3 BACnet Result Flags */
|
||||
apdu_len +=
|
||||
encode_context_bitstring(&apdu[apdu_len], 3, &rrdata->ResultFlags);
|
||||
/* Context 4 Item Count */
|
||||
apdu_len +=
|
||||
encode_context_unsigned(&apdu[apdu_len], 4, rrdata->ItemCount);
|
||||
/* Context 5 Property list - reading the standard it looks like an empty list still
|
||||
* requires an opening and closing tag as the tagged parameter is not optional
|
||||
*/
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 5);
|
||||
if (rrdata->ItemCount != 0) {
|
||||
for (len = 0; len < rrdata->application_data_len; len++) {
|
||||
apdu[apdu_len++] = rrdata->application_data[len];
|
||||
}
|
||||
}
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 5);
|
||||
|
||||
if ((rrdata->ItemCount != 0) && (rrdata->RequestType != RR_BY_POSITION)
|
||||
&& (rrdata->RequestType != RR_READ_ALL)) {
|
||||
/* Context 6 Sequence number of first item */
|
||||
apdu_len +=
|
||||
encode_context_unsigned(&apdu[apdu_len], 6,
|
||||
rrdata->FirstSequence);
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Decode the received ReadRange response *
|
||||
*****************************************************************************/
|
||||
|
||||
int rr_ack_decode_service_request(
|
||||
uint8_t * apdu,
|
||||
int apdu_len, /* total length of the apdu */
|
||||
BACNET_READ_RANGE_DATA * rrdata)
|
||||
{
|
||||
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 start_len;
|
||||
uint16_t object = 0; /* object type */
|
||||
uint32_t 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, &rrdata->object_instance);
|
||||
rrdata->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);
|
||||
rrdata->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);
|
||||
rrdata->array_index = array_value;
|
||||
} else
|
||||
rrdata->array_index = BACNET_ARRAY_ALL;
|
||||
|
||||
/* Tag 3: Result Flags */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number, &len_value_type);
|
||||
if (tag_number != 3)
|
||||
return -1;
|
||||
|
||||
len += decode_bitstring(&apdu[len], len_value_type, &rrdata->ResultFlags);
|
||||
|
||||
/* Tag 4: Item count */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number, &len_value_type);
|
||||
if (tag_number != 4)
|
||||
return -1;
|
||||
|
||||
len += decode_unsigned(&apdu[len], len_value_type, &rrdata->ItemCount);
|
||||
|
||||
if (decode_is_opening_tag_number(&apdu[len], 5)) {
|
||||
len++; /* a tag number of 5 is not extended so only one octet */
|
||||
/* Setup the start position and length of the data returned from the request
|
||||
* don't decode the application tag number or its data here */
|
||||
rrdata->application_data = &apdu[len];
|
||||
start_len = len;
|
||||
while (len < apdu_len) {
|
||||
if (IS_CONTEXT_SPECIFIC(apdu[len]) &&
|
||||
(decode_is_closing_tag_number(&apdu[len], 5))) {
|
||||
rrdata->application_data_len = len - start_len;
|
||||
len++; /* Step over single byte closing tag */
|
||||
break;
|
||||
} else {
|
||||
/* Don't care about tag number, just skipping over anyway */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], NULL,
|
||||
&len_value_type);
|
||||
len += len_value_type; /* Skip over data value as well */
|
||||
if (len >= apdu_len) /* APDU is exhausted so we have failed to find closing tag */
|
||||
return (-1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
if (len < apdu_len) { /* Still something left to look at? */
|
||||
/* Tag 6: Item count */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
if (tag_number != 6)
|
||||
return -1;
|
||||
|
||||
len +=
|
||||
decode_unsigned(&apdu[len], len_value_type,
|
||||
&rrdata->FirstSequence);
|
||||
}
|
||||
|
||||
len = apdu_len; /* There should be nothing left to see here */
|
||||
return len;
|
||||
}
|
||||
|
||||
/* FIXME: Currently does not have test framework */
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
/*####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 "reject.h"
|
||||
|
||||
/** @file reject.c Encode/Decode Reject APDUs */
|
||||
|
||||
/* Helper function to avoid needing additional entries in service data structures
|
||||
* when passing back reject status.
|
||||
* Convert from error code to reject code.
|
||||
* Anything not defined gets converted to REJECT_REASON_OTHER.
|
||||
* Will need reworking if it is required to return proprietary reject codes.
|
||||
*/
|
||||
|
||||
BACNET_REJECT_REASON reject_convert_error_code(
|
||||
BACNET_ERROR_CODE error_code)
|
||||
{
|
||||
BACNET_REJECT_REASON reject_code = REJECT_REASON_OTHER;
|
||||
|
||||
switch (error_code) {
|
||||
case ERROR_CODE_REJECT_BUFFER_OVERFLOW:
|
||||
reject_code = REJECT_REASON_BUFFER_OVERFLOW;
|
||||
break;
|
||||
case ERROR_CODE_REJECT_INCONSISTENT_PARAMETERS:
|
||||
reject_code = REJECT_REASON_INCONSISTENT_PARAMETERS;
|
||||
break;
|
||||
case ERROR_CODE_REJECT_INVALID_PARAMETER_DATA_TYPE:
|
||||
reject_code = REJECT_REASON_INVALID_PARAMETER_DATA_TYPE;
|
||||
break;
|
||||
case ERROR_CODE_REJECT_INVALID_TAG:
|
||||
reject_code = REJECT_REASON_INVALID_TAG;
|
||||
break;
|
||||
case ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER:
|
||||
reject_code = REJECT_REASON_MISSING_REQUIRED_PARAMETER;
|
||||
break;
|
||||
case ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE:
|
||||
reject_code = REJECT_REASON_PARAMETER_OUT_OF_RANGE;
|
||||
break;
|
||||
case ERROR_CODE_REJECT_TOO_MANY_ARGUMENTS:
|
||||
reject_code = REJECT_REASON_TOO_MANY_ARGUMENTS;
|
||||
break;
|
||||
case ERROR_CODE_REJECT_UNDEFINED_ENUMERATION:
|
||||
reject_code = REJECT_REASON_UNDEFINED_ENUMERATION;
|
||||
break;
|
||||
case ERROR_CODE_REJECT_UNRECOGNIZED_SERVICE:
|
||||
reject_code = REJECT_REASON_UNRECOGNIZED_SERVICE;
|
||||
break;
|
||||
case ERROR_CODE_REJECT_PROPRIETARY:
|
||||
reject_code = REJECT_REASON_PROPRIETARY_FIRST;
|
||||
break;
|
||||
case ERROR_CODE_REJECT_OTHER:
|
||||
default:
|
||||
reject_code = REJECT_REASON_OTHER;
|
||||
break;
|
||||
}
|
||||
|
||||
return (reject_code);
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
#if !BACNET_SVC_SERVER
|
||||
/* 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
#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 */
|
||||
+832
@@ -0,0 +1,832 @@
|
||||
/**
|
||||
* @file
|
||||
* @author Steve Karg
|
||||
* @date 2004
|
||||
* @brief Generic ring buffer library for deeply embedded system.
|
||||
*
|
||||
* @section LICENSE
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @section DESCRIPTION
|
||||
*
|
||||
* Generic ring buffer library for deeply embedded system.
|
||||
* It uses a data store whose size is a power of 2 (8, 16, 32, 64, ...)
|
||||
* and doesn't waste any data bytes. It has very low overhead, and
|
||||
* utilizes modulo for indexing the data in the data store.
|
||||
* It uses separate variables for consumer and producer so it can
|
||||
* be used in multithreaded environment.
|
||||
*
|
||||
* See the unit tests for usage examples.
|
||||
*
|
||||
*/
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "ringbuf.h"
|
||||
|
||||
/**
|
||||
* Returns the number of elements in the ring buffer
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @return Number of elements in the ring buffer
|
||||
*/
|
||||
unsigned Ringbuf_Count(RING_BUFFER const *b)
|
||||
{
|
||||
unsigned head, tail; /* used to avoid volatile decision */
|
||||
|
||||
if (b) {
|
||||
head = b->head;
|
||||
tail = b->tail;
|
||||
return head - tail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the empty/full status of the ring buffer
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @return true if the ring buffer is full, false if it is not.
|
||||
*/
|
||||
bool Ringbuf_Full(RING_BUFFER const *b)
|
||||
{
|
||||
return (b ? (Ringbuf_Count(b) == b->element_count) : true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the empty/full status of the ring buffer
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @return true if the ring buffer is empty, false if it is not.
|
||||
*/
|
||||
bool Ringbuf_Empty(RING_BUFFER const *b)
|
||||
{
|
||||
return (b ? (Ringbuf_Count(b) == 0) : true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the depth tracking in the ring buffer
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
*/
|
||||
static void Ringbuf_Depth_Update(RING_BUFFER *b)
|
||||
{
|
||||
unsigned count;
|
||||
|
||||
if (b) {
|
||||
count = Ringbuf_Count(b);
|
||||
if (count > b->depth) {
|
||||
b->depth = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the depth tracking in the ring buffer
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @return largest number of items that have been in the ring buffer
|
||||
*/
|
||||
unsigned Ringbuf_Depth(RING_BUFFER const *b)
|
||||
{
|
||||
unsigned depth = 0;
|
||||
|
||||
if (b) {
|
||||
depth = b->depth;
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the depth tracking in the ring buffer
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @return largest number of items that have been in the ring buffer
|
||||
*/
|
||||
unsigned Ringbuf_Depth_Reset(RING_BUFFER *b)
|
||||
{
|
||||
unsigned depth = 0;
|
||||
|
||||
if (b) {
|
||||
depth = b->depth;
|
||||
b->depth = 0;
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the capacity of the ring buffer (number of possible elements)
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @return largest number of items that have been in the ring buffer
|
||||
*/
|
||||
unsigned Ringbuf_Size(RING_BUFFER const *b)
|
||||
{
|
||||
unsigned count = 0;
|
||||
|
||||
if (b) {
|
||||
count = b->element_count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at the data from the head of the list without removing it
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @return pointer to the data, or NULL if nothing in the list
|
||||
*/
|
||||
volatile uint8_t *Ringbuf_Peek(RING_BUFFER const *b)
|
||||
{
|
||||
volatile uint8_t *data_element = NULL; /* return value */
|
||||
|
||||
if (!Ringbuf_Empty(b)) {
|
||||
data_element = b->buffer;
|
||||
data_element += ((b->tail % b->element_count) * b->element_size);
|
||||
}
|
||||
|
||||
return data_element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks at the data from the next element of the list without removing it
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @param data_element - find the next element from this one
|
||||
* @return pointer to the data, or NULL if nothing in the list
|
||||
*/
|
||||
volatile uint8_t *Ringbuf_Peek_Next(RING_BUFFER const *b,
|
||||
uint8_t * data_element)
|
||||
{
|
||||
unsigned index; /* list index */
|
||||
volatile uint8_t *this_element;
|
||||
volatile uint8_t *next_element = NULL; /* return value */
|
||||
if (!Ringbuf_Empty(b) && data_element != NULL) {
|
||||
/* Use (b->head-1) here to avoid walking off end of ring */
|
||||
for (index = b->tail; index < b->head-1; index++) {
|
||||
/* Find the specified data_element */
|
||||
this_element = b->buffer +
|
||||
((index % b->element_count) * b->element_size);
|
||||
if (data_element == this_element) {
|
||||
/* Found the current element, get the next one on the list */
|
||||
next_element = b->buffer +
|
||||
(((index+1) % b->element_count) * b->element_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next_element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the data from the front of the list, and removes it
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @param data_element - element of data that is loaded with data from ring
|
||||
* @return true if data was copied, false if list is empty
|
||||
*/
|
||||
bool Ringbuf_Pop(RING_BUFFER * b,
|
||||
uint8_t * data_element)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
volatile uint8_t *ring_data = NULL; /* used to help point ring data */
|
||||
unsigned i; /* loop counter */
|
||||
|
||||
if (!Ringbuf_Empty(b)) {
|
||||
ring_data = b->buffer;
|
||||
ring_data += ((b->tail % b->element_count) * b->element_size);
|
||||
if (data_element) {
|
||||
for (i = 0; i < b->element_size; i++) {
|
||||
data_element[i] = ring_data[i];
|
||||
}
|
||||
}
|
||||
b->tail++;
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the data from the specified element, and removes it and moves other
|
||||
* elements up the list
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @param this_element - element to find
|
||||
* @param data_element - element of data that is loaded with data from ring
|
||||
* @return true if data was copied, false if list is empty
|
||||
*/
|
||||
bool Ringbuf_Pop_Element(RING_BUFFER * b,
|
||||
uint8_t * this_element,
|
||||
uint8_t * data_element)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
volatile uint8_t *ring_data = NULL; /* used to help point ring data */
|
||||
volatile uint8_t *prev_data;
|
||||
unsigned index; /* list index */
|
||||
unsigned this_index = b->head; /* index of element to remove */
|
||||
unsigned i; /* loop counter */
|
||||
if (!Ringbuf_Empty(b) && this_element != NULL) {
|
||||
for (index = b->tail; index < b->head; index++) {
|
||||
/* Find the specified data_element */
|
||||
ring_data = b->buffer +
|
||||
((index % b->element_count) * b->element_size);
|
||||
if (this_element == ring_data) {
|
||||
/* Found the specified element, copy the data if required */
|
||||
this_index = index;
|
||||
if (data_element) {
|
||||
for (i = 0; i < b->element_size; i++) {
|
||||
data_element[i] = ring_data[i];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this_index < b->head) {
|
||||
/* Found a match, move elements up the list to fill the gap */
|
||||
for ( index = this_index; index > b->tail; index--) {
|
||||
/* Get pointers to current and previous data_elements */
|
||||
ring_data = b->buffer +
|
||||
((index % b->element_count) * b->element_size);
|
||||
prev_data = b->buffer +
|
||||
(((index-1) % b->element_count) * b->element_size);
|
||||
for (i = 0; i < b->element_size; i++) {
|
||||
ring_data[i] = prev_data[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
b->tail++;
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element of data to the ring buffer
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @param data_element - one element that is copied to the ring buffer
|
||||
* @return true on succesful add, false if not added
|
||||
*/
|
||||
bool Ringbuf_Put(RING_BUFFER * b,
|
||||
uint8_t * data_element)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
volatile uint8_t *ring_data = NULL; /* used to help point ring data */
|
||||
unsigned i; /* loop counter */
|
||||
|
||||
if (b && data_element) {
|
||||
/* limit the amount of elements that we accept */
|
||||
if (!Ringbuf_Full(b)) {
|
||||
ring_data = b->buffer;
|
||||
ring_data += ((b->head % b->element_count) * b->element_size);
|
||||
for (i = 0; i < b->element_size; i++) {
|
||||
ring_data[i] = data_element[i];
|
||||
}
|
||||
b->head++;
|
||||
Ringbuf_Depth_Update(b);
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an element of data to the front of the ring buffer
|
||||
*
|
||||
* Note that this function moves the tail on add instead of head,
|
||||
* so this function cannot be used if you are keeping producer and
|
||||
* consumer as separate processes (i.e. interrupt handlers)
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @param data_element - one element to copy to the front of the ring
|
||||
* @return true on succesful add, false if not added
|
||||
*/
|
||||
bool Ringbuf_Put_Front(RING_BUFFER * b,
|
||||
uint8_t * data_element)
|
||||
{
|
||||
bool status = false; /* return value */
|
||||
volatile uint8_t *ring_data = NULL; /* used to help point ring data */
|
||||
unsigned i = 0; /* loop counter */
|
||||
|
||||
if (b && data_element) {
|
||||
/* limit the amount of elements that we accept */
|
||||
if (!Ringbuf_Full(b)) {
|
||||
b->tail--;
|
||||
ring_data = b->buffer;
|
||||
ring_data += ((b->tail % b->element_count) * b->element_size);
|
||||
/* copy the data to the ring data element */
|
||||
for (i = 0; i < b->element_size; i++) {
|
||||
ring_data[i] = data_element[i];
|
||||
}
|
||||
Ringbuf_Depth_Update(b);
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pointer to the next free data element of the buffer
|
||||
* without adding it to the ring.
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @return pointer to the next data element, or NULL if the list is full
|
||||
*/
|
||||
volatile uint8_t *Ringbuf_Data_Peek(RING_BUFFER * b)
|
||||
{
|
||||
volatile uint8_t *ring_data = NULL; /* used to help point ring data */
|
||||
|
||||
if (b) {
|
||||
/* limit the amount of elements that we accept */
|
||||
if (!Ringbuf_Full(b)) {
|
||||
ring_data = b->buffer;
|
||||
ring_data += ((b->head % b->element_count) * b->element_size);
|
||||
}
|
||||
}
|
||||
|
||||
return ring_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the previously peeked element of data to the end of the
|
||||
* ring buffer.
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @param data_element - pointer to the peeked data element
|
||||
* @return true if the buffer has space and the data element points to the
|
||||
* same memory previously peeked.
|
||||
*/
|
||||
bool Ringbuf_Data_Put(RING_BUFFER * b, volatile uint8_t *data_element)
|
||||
{
|
||||
bool status = false;
|
||||
volatile uint8_t *ring_data = NULL; /* used to help point ring data */
|
||||
|
||||
if (b) {
|
||||
/* limit the amount of elements that we accept */
|
||||
if (!Ringbuf_Full(b)) {
|
||||
ring_data = b->buffer;
|
||||
ring_data += ((b->head % b->element_count) * b->element_size);
|
||||
if (ring_data == data_element) {
|
||||
/* same chunk of memory - okay to signal the head */
|
||||
b->head++;
|
||||
Ringbuf_Depth_Update(b);
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the parameter is a power of two.
|
||||
*
|
||||
* @param x unsigned integer value to be tested
|
||||
*
|
||||
* @return true if the parameter is a power of 2
|
||||
*/
|
||||
static bool isPowerOfTwo (unsigned int x)
|
||||
{
|
||||
/* First x in the below expression is for the case when x is 0 */
|
||||
return x && (!(x&(x-1)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the ring buffer data buffer. Note that the element_count
|
||||
* parameter must be a power of two.
|
||||
*
|
||||
* @param b - pointer to RING_BUFFER structure
|
||||
* @param buffer - pointer to a data buffer that is used to store the ring data
|
||||
* @param element_size - size of one element in the data block
|
||||
* @param element_count - number elements in the data block
|
||||
*
|
||||
* @return true if ring buffer was initialized
|
||||
*/
|
||||
bool Ringbuf_Init(RING_BUFFER * b,
|
||||
volatile uint8_t * buffer,
|
||||
unsigned element_size,
|
||||
unsigned element_count)
|
||||
{
|
||||
bool status = false;
|
||||
|
||||
if (b && isPowerOfTwo(element_count)) {
|
||||
b->head = 0;
|
||||
b->tail = 0;
|
||||
b->buffer = buffer;
|
||||
b->element_size = element_size;
|
||||
b->element_count = element_count;
|
||||
/* tuning diagnostics */
|
||||
b->depth = 0;
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include "ctest.h"
|
||||
|
||||
/**
|
||||
* Unit Test for the ring buffer
|
||||
*
|
||||
* @param pTest - test tracking pointer
|
||||
* @param test_buffer - pointer to RING_BUFFER structure
|
||||
* @param data_element - one data element
|
||||
* @param element_size - size of one data element
|
||||
* @param element_count - number of data elements in the store
|
||||
*/
|
||||
static void testRingAroundBuffer(Test * pTest,
|
||||
RING_BUFFER * test_buffer,
|
||||
uint8_t * data_element,
|
||||
unsigned element_size,
|
||||
unsigned element_count)
|
||||
{
|
||||
volatile uint8_t *test_data;
|
||||
unsigned index;
|
||||
unsigned data_index;
|
||||
unsigned count;
|
||||
uint8_t value;
|
||||
bool status;
|
||||
|
||||
ct_test(pTest, Ringbuf_Empty(test_buffer));
|
||||
/* test the ring around the buffer */
|
||||
for (index = 0; index < element_count; index++) {
|
||||
for (count = 1; count < 4; count++) {
|
||||
value = (index * count)%255;
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
data_element[data_index] = value;
|
||||
}
|
||||
status = Ringbuf_Put(test_buffer, data_element);
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, Ringbuf_Count(test_buffer) == count);
|
||||
}
|
||||
for (count = 1; count < 4; count++) {
|
||||
value = (index * count)%255;
|
||||
test_data = Ringbuf_Peek(test_buffer);
|
||||
ct_test(pTest, test_data);
|
||||
if (test_data) {
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == value);
|
||||
}
|
||||
}
|
||||
status = Ringbuf_Pop(test_buffer, NULL);
|
||||
ct_test(pTest, status == true);
|
||||
}
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Empty(test_buffer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit Test for the ring buffer
|
||||
*
|
||||
* @param pTest - test tracking pointer
|
||||
* @param data_store - buffer to store elements
|
||||
* @param data_element - one data element
|
||||
* @param element_size - size of one data element
|
||||
* @param element_count - number of data elements in the store
|
||||
*/
|
||||
static bool testRingBuf(Test * pTest,
|
||||
uint8_t * data_store,
|
||||
uint8_t * data_element,
|
||||
unsigned element_size,
|
||||
unsigned element_count)
|
||||
{
|
||||
RING_BUFFER test_buffer;
|
||||
volatile uint8_t *test_data;
|
||||
unsigned index;
|
||||
unsigned data_index;
|
||||
bool status;
|
||||
|
||||
status = Ringbuf_Init(&test_buffer, data_store,
|
||||
element_size, element_count);
|
||||
if (!status) {
|
||||
return false;
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Empty(&test_buffer));
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == 0);
|
||||
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
data_element[data_index] = data_index;
|
||||
}
|
||||
status = Ringbuf_Put(&test_buffer, data_element);
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, !Ringbuf_Empty(&test_buffer));
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == 1);
|
||||
|
||||
test_data = Ringbuf_Peek(&test_buffer);
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == data_element[data_index]);
|
||||
}
|
||||
ct_test(pTest, !Ringbuf_Empty(&test_buffer));
|
||||
(void) Ringbuf_Pop(&test_buffer, NULL);
|
||||
ct_test(pTest, Ringbuf_Empty(&test_buffer));
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == 1);
|
||||
|
||||
/* fill to max */
|
||||
for (index = 0; index < element_count; index++) {
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
data_element[data_index] = index;
|
||||
}
|
||||
status = Ringbuf_Put(&test_buffer, data_element);
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, !Ringbuf_Empty(&test_buffer));
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == (index+1));
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == element_count);
|
||||
/* verify actions on full buffer */
|
||||
for (index = 0; index < element_count; index++) {
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
data_element[data_index] = index;
|
||||
}
|
||||
status = Ringbuf_Put(&test_buffer, data_element);
|
||||
ct_test(pTest, status == false);
|
||||
ct_test(pTest, !Ringbuf_Empty(&test_buffer));
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == element_count);
|
||||
}
|
||||
|
||||
/* check buffer full */
|
||||
for (index = 0; index < element_count; index++) {
|
||||
test_data = Ringbuf_Peek(&test_buffer);
|
||||
ct_test(pTest, test_data);
|
||||
if (test_data) {
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == index);
|
||||
}
|
||||
}
|
||||
(void) Ringbuf_Pop(&test_buffer, NULL);
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Empty(&test_buffer));
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == element_count);
|
||||
Ringbuf_Depth_Reset(&test_buffer);
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == 0);
|
||||
|
||||
testRingAroundBuffer(pTest, &test_buffer, data_element, element_size,
|
||||
element_count);
|
||||
|
||||
/* adjust the internal index of Ringbuf to test unsigned wrapping */
|
||||
test_buffer.head = UINT_MAX - 1;
|
||||
test_buffer.tail = UINT_MAX - 1;
|
||||
|
||||
testRingAroundBuffer(pTest, &test_buffer, data_element, element_size,
|
||||
element_count);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit Test for the ring buffer with 16 data elements
|
||||
*
|
||||
* @param pTest - test tracking pointer
|
||||
*/
|
||||
void testRingBufSizeSmall(Test * pTest)
|
||||
{
|
||||
bool status;
|
||||
uint8_t data_element[5];
|
||||
uint8_t data_store[sizeof(data_element) * NEXT_POWER_OF_2(16)];
|
||||
|
||||
status = testRingBuf(pTest, data_store, data_element,
|
||||
sizeof(data_element),
|
||||
sizeof(data_store) / sizeof(data_element));
|
||||
ct_test(pTest, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit Test for the ring buffer with 32 data elements
|
||||
*
|
||||
* @param pTest - test tracking pointer
|
||||
*/
|
||||
void testRingBufSizeLarge(Test * pTest)
|
||||
{
|
||||
bool status;
|
||||
uint8_t data_element[16];
|
||||
uint8_t data_store[sizeof(data_element) * NEXT_POWER_OF_2(99)];
|
||||
|
||||
status = testRingBuf(pTest, data_store, data_element,
|
||||
sizeof(data_element),
|
||||
sizeof(data_store) / sizeof(data_element));
|
||||
ct_test(pTest, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit Test for the ring buffer with 32 data elements
|
||||
*
|
||||
* @param pTest - test tracking pointer
|
||||
*/
|
||||
void testRingBufSizeInvalid(Test * pTest)
|
||||
{
|
||||
bool status;
|
||||
uint8_t data_element[16];
|
||||
uint8_t data_store[sizeof(data_element) * 99];
|
||||
|
||||
status = testRingBuf(pTest, data_store, data_element,
|
||||
sizeof(data_element),
|
||||
sizeof(data_store) / sizeof(data_element));
|
||||
ct_test(pTest, status==false);
|
||||
}
|
||||
|
||||
void testRingBufPowerOfTwo(Test * pTest)
|
||||
{
|
||||
ct_test(pTest, NEXT_POWER_OF_2(3)==4);
|
||||
ct_test(pTest, NEXT_POWER_OF_2(100)==128);
|
||||
ct_test(pTest, NEXT_POWER_OF_2(127)==128);
|
||||
ct_test(pTest, NEXT_POWER_OF_2(128)==128);
|
||||
ct_test(pTest, NEXT_POWER_OF_2(129)==256);
|
||||
ct_test(pTest, NEXT_POWER_OF_2(300)==512);
|
||||
ct_test(pTest, NEXT_POWER_OF_2(500)==512);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit Test for the ring buffer peek/pop next element
|
||||
*
|
||||
* @param pTest - test tracking pointer
|
||||
* @param data_store - buffer to store elements
|
||||
* @param data_element - one data element
|
||||
* @param element_size - size of one data element
|
||||
* @param element_count - number of data elements in the store
|
||||
*/
|
||||
static bool testRingBufNextElement(Test * pTest,
|
||||
uint8_t * data_store,
|
||||
uint8_t * data_element,
|
||||
unsigned element_size,
|
||||
unsigned element_count)
|
||||
{
|
||||
RING_BUFFER test_buffer;
|
||||
volatile uint8_t *test_data;
|
||||
unsigned index;
|
||||
unsigned data_index;
|
||||
bool status;
|
||||
status = Ringbuf_Init(&test_buffer, data_store,
|
||||
element_size, element_count);
|
||||
if (!status) {
|
||||
return false;
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Empty(&test_buffer));
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == 0);
|
||||
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
data_element[data_index] = data_index;
|
||||
}
|
||||
status = Ringbuf_Put(&test_buffer, data_element);
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, !Ringbuf_Empty(&test_buffer));
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == 1);
|
||||
|
||||
test_data = Ringbuf_Peek(&test_buffer);
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == data_element[data_index]);
|
||||
}
|
||||
ct_test(pTest, !Ringbuf_Empty(&test_buffer));
|
||||
(void) Ringbuf_Pop(&test_buffer, NULL);
|
||||
ct_test(pTest, Ringbuf_Empty(&test_buffer));
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == 1);
|
||||
|
||||
/* fill to max */
|
||||
for (index = 0; index < element_count; index++) {
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
data_element[data_index] = index;
|
||||
}
|
||||
status = Ringbuf_Put(&test_buffer, data_element);
|
||||
ct_test(pTest, status == true);
|
||||
ct_test(pTest, !Ringbuf_Empty(&test_buffer));
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == (index+1));
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Depth(&test_buffer) == element_count);
|
||||
ct_test(pTest, Ringbuf_Count(&test_buffer) == element_count);
|
||||
|
||||
/* Walk through ring buffer */
|
||||
test_data = Ringbuf_Peek(&test_buffer);
|
||||
ct_test(pTest, test_data);
|
||||
for (index = 1; index < element_count; index++) {
|
||||
test_data = Ringbuf_Peek_Next(&test_buffer, (uint8_t *)test_data);
|
||||
ct_test(pTest, test_data);
|
||||
if (test_data) {
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == index);
|
||||
}
|
||||
}
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Count(&test_buffer) == element_count);
|
||||
/* Try to walk off end of buffer - should return NULL */
|
||||
test_data = Ringbuf_Peek_Next(&test_buffer, (uint8_t *)test_data);
|
||||
ct_test(pTest, (test_data == NULL));
|
||||
|
||||
/* Walk through ring buffer and pop alternate elements */
|
||||
test_data = Ringbuf_Peek(&test_buffer);
|
||||
ct_test(pTest, test_data);
|
||||
for (index = 1; index < element_count/2; index++) {
|
||||
test_data = Ringbuf_Peek_Next(&test_buffer, (uint8_t *)test_data);
|
||||
ct_test(pTest, test_data);
|
||||
(void) Ringbuf_Pop_Element(&test_buffer, (uint8_t *)test_data, NULL);
|
||||
test_data = Ringbuf_Peek_Next(&test_buffer, (uint8_t *)test_data);
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Count(&test_buffer) == element_count/2+1);
|
||||
|
||||
/* Walk through ring buffer and check data */
|
||||
test_data = Ringbuf_Peek(&test_buffer);
|
||||
ct_test(pTest, test_data);
|
||||
for (index = 0; index < element_count/2; index++) {
|
||||
if (test_data) {
|
||||
for (data_index = 0; data_index < element_size; data_index++) {
|
||||
ct_test(pTest, test_data[data_index] == index*2);
|
||||
}
|
||||
}
|
||||
test_data = Ringbuf_Peek_Next(&test_buffer, (uint8_t *)test_data);
|
||||
ct_test(pTest, test_data);
|
||||
}
|
||||
ct_test(pTest, Ringbuf_Count(&test_buffer) == element_count/2+1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit Test for the ring buffer with 16 data elements
|
||||
*
|
||||
* @param pTest - test tracking pointer
|
||||
*/
|
||||
void testRingBufNextElementSizeSmall(Test * pTest)
|
||||
{
|
||||
bool status;
|
||||
uint8_t data_element[5];
|
||||
uint8_t data_store[sizeof(data_element) * NEXT_POWER_OF_2(16)];
|
||||
|
||||
status = testRingBufNextElement(pTest, data_store, data_element,
|
||||
sizeof(data_element),
|
||||
sizeof(data_store) / sizeof(data_element));
|
||||
ct_test(pTest, status);
|
||||
}
|
||||
|
||||
#ifdef TEST_RING_BUFFER
|
||||
/**
|
||||
* Main program entry for Unit Test
|
||||
*
|
||||
* @return returns 0 on success, and non-zero on fail.
|
||||
*/
|
||||
int main(void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("Ring Buffer", NULL);
|
||||
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testRingBufPowerOfTwo);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testRingBufSizeSmall);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testRingBufSizeLarge);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testRingBufSizeInvalid);
|
||||
assert(rc);
|
||||
rc = ct_addTestFunction(pTest, testRingBufNextElementSizeSmall);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,455 @@
|
||||
/*####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"
|
||||
|
||||
/** @file rp.c Encode/Decode Read Property and RP ACKs */
|
||||
|
||||
#if BACNET_SVC_RP_A
|
||||
/* encode service */
|
||||
int rp_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_READ_PROPERTY_DATA * rpdata)
|
||||
{
|
||||
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;
|
||||
if (rpdata->object_type <= BACNET_MAX_OBJECT) {
|
||||
/* check bounds so that we could create malformed
|
||||
messages for testing */
|
||||
len =
|
||||
encode_context_object_id(&apdu[apdu_len], 0,
|
||||
rpdata->object_type, rpdata->object_instance);
|
||||
apdu_len += len;
|
||||
}
|
||||
if (rpdata->object_property <= MAX_BACNET_PROPERTY_ID) {
|
||||
/* check bounds so that we could create malformed
|
||||
messages for testing */
|
||||
len =
|
||||
encode_context_enumerated(&apdu[apdu_len], 1,
|
||||
rpdata->object_property);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* optional array index */
|
||||
if (rpdata->array_index != BACNET_ARRAY_ALL) {
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 2,
|
||||
rpdata->array_index);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* decode the service request only */
|
||||
int rp_decode_service_request(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_READ_PROPERTY_DATA * rpdata)
|
||||
{
|
||||
unsigned len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
uint16_t type = 0; /* for decoding */
|
||||
uint32_t property = 0; /* for decoding */
|
||||
uint32_t array_value = 0; /* for decoding */
|
||||
|
||||
/* check for value pointers */
|
||||
if (rpdata) {
|
||||
/* Must have at least 2 tags, an object id and a property identifier
|
||||
* of at least 1 byte in length to have any chance of parsing */
|
||||
if (apdu_len < 7) {
|
||||
rpdata->error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
|
||||
/* Tag 0: Object ID */
|
||||
if (!decode_is_context_tag(&apdu[len++], 0)) {
|
||||
rpdata->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
len += decode_object_id(&apdu[len], &type, &rpdata->object_instance);
|
||||
rpdata->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) {
|
||||
rpdata->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
len += decode_enumerated(&apdu[len], len_value_type, &property);
|
||||
rpdata->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 < apdu_len)) {
|
||||
len +=
|
||||
decode_unsigned(&apdu[len], len_value_type, &array_value);
|
||||
rpdata->array_index = array_value;
|
||||
} else {
|
||||
rpdata->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
} else
|
||||
rpdata->array_index = BACNET_ARRAY_ALL;
|
||||
}
|
||||
|
||||
if (len < apdu_len) {
|
||||
/* If something left over now, we have an invalid request */
|
||||
if (rpdata) {
|
||||
rpdata->error_code = ERROR_CODE_REJECT_TOO_MANY_ARGUMENTS;
|
||||
}
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
}
|
||||
|
||||
/* alternate method to encode the ack without extra buffer */
|
||||
int rp_ack_encode_apdu_init(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_READ_PROPERTY_DATA * rpdata)
|
||||
{
|
||||
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 */
|
||||
len =
|
||||
encode_context_object_id(&apdu[apdu_len], 0, rpdata->object_type,
|
||||
rpdata->object_instance);
|
||||
apdu_len += len;
|
||||
len =
|
||||
encode_context_enumerated(&apdu[apdu_len], 1,
|
||||
rpdata->object_property);
|
||||
apdu_len += len;
|
||||
/* context 2 array index is optional */
|
||||
if (rpdata->array_index != BACNET_ARRAY_ALL) {
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 2,
|
||||
rpdata->array_index);
|
||||
apdu_len += len;
|
||||
}
|
||||
len = encode_opening_tag(&apdu[apdu_len], 3);
|
||||
apdu_len += len;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/* note: encode the application tagged data yourself */
|
||||
int rp_ack_encode_apdu_object_property_end(
|
||||
uint8_t * apdu)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
apdu_len = encode_closing_tag(&apdu[0], 3);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int rp_ack_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_READ_PROPERTY_DATA * rpdata)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
|
||||
if (apdu) {
|
||||
/* Do the initial encoding */
|
||||
apdu_len = rp_ack_encode_apdu_init(apdu, invoke_id, rpdata);
|
||||
/* propertyValue */
|
||||
for (len = 0; len < rpdata->application_data_len; len++) {
|
||||
apdu[apdu_len++] = rpdata->application_data[len];
|
||||
}
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 3);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
|
||||
#if BACNET_SVC_RP_A
|
||||
/** Decode the ReadProperty reply and store the result for one Property in a
|
||||
* BACNET_READ_PROPERTY_DATA structure.
|
||||
* This leaves the value(s) in the application_data buffer to be decoded later;
|
||||
* the application_data field points into the apdu buffer (is not allocated).
|
||||
*
|
||||
* @param apdu [in] The apdu portion of the ACK reply.
|
||||
* @param apdu_len [in] The total length of the apdu.
|
||||
* @param rpdata [out] The structure holding the partially decoded result.
|
||||
* @return Number of decoded bytes (could be less than apdu_len),
|
||||
* or -1 on decoding error.
|
||||
*/
|
||||
int rp_ack_decode_service_request(
|
||||
uint8_t * apdu,
|
||||
int apdu_len, /* total length of the apdu */
|
||||
BACNET_READ_PROPERTY_DATA * rpdata)
|
||||
{
|
||||
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 */
|
||||
uint16_t object = 0; /* object type */
|
||||
uint32_t 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, &rpdata->object_instance);
|
||||
rpdata->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);
|
||||
rpdata->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);
|
||||
rpdata->array_index = array_value;
|
||||
} else
|
||||
rpdata->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 */
|
||||
rpdata->application_data = &apdu[len];
|
||||
rpdata->application_data_len = apdu_len - len - 1 /*closing tag */ ;
|
||||
/* len includes the data and the closing tag */
|
||||
len = apdu_len;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
|
||||
#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 * rpdata)
|
||||
{
|
||||
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, MAX_APDU); */
|
||||
*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,
|
||||
rpdata);
|
||||
}
|
||||
|
||||
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 * rpdata)
|
||||
{
|
||||
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,
|
||||
rpdata);
|
||||
}
|
||||
|
||||
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 rpdata;
|
||||
BACNET_READ_PROPERTY_DATA test_data;
|
||||
BACNET_OBJECT_TYPE object_type = OBJECT_DEVICE;
|
||||
uint32_t object_instance = 0;
|
||||
uint16_t object = 0;
|
||||
|
||||
rpdata.object_type = OBJECT_DEVICE;
|
||||
rpdata.object_instance = 1;
|
||||
rpdata.object_property = PROP_OBJECT_IDENTIFIER;
|
||||
rpdata.array_index = BACNET_ARRAY_ALL;
|
||||
|
||||
rpdata.application_data_len =
|
||||
encode_bacnet_object_id(&apdu2[0], rpdata.object_type,
|
||||
rpdata.object_instance);
|
||||
rpdata.application_data = &apdu2[0];
|
||||
|
||||
len = rp_ack_encode_apdu(&apdu[0], invoke_id, &rpdata);
|
||||
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 == rpdata.object_type);
|
||||
ct_test(pTest, test_data.object_instance == rpdata.object_instance);
|
||||
ct_test(pTest, test_data.object_property == rpdata.object_property);
|
||||
ct_test(pTest, test_data.array_index == rpdata.array_index);
|
||||
ct_test(pTest,
|
||||
test_data.application_data_len == rpdata.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 == rpdata.object_type);
|
||||
ct_test(pTest, object_instance == rpdata.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 rpdata;
|
||||
BACNET_READ_PROPERTY_DATA test_data;
|
||||
|
||||
rpdata.object_type = OBJECT_DEVICE;
|
||||
rpdata.object_instance = 1;
|
||||
rpdata.object_property = PROP_OBJECT_IDENTIFIER;
|
||||
rpdata.array_index = BACNET_ARRAY_ALL;
|
||||
len = rp_encode_apdu(&apdu[0], invoke_id, &rpdata);
|
||||
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 == rpdata.object_type);
|
||||
ct_test(pTest, test_data.object_instance == rpdata.object_instance);
|
||||
ct_test(pTest, test_data.object_property == rpdata.object_property);
|
||||
ct_test(pTest, test_data.array_index == rpdata.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,968 @@
|
||||
/*####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 "memcopy.h"
|
||||
#include "rpm.h"
|
||||
|
||||
/** @file rpm.c Encode/Decode Read Property Multiple and RPM ACKs */
|
||||
|
||||
#if BACNET_SVC_RPM_A
|
||||
/* 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_PROP_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,
|
||||
uint32_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;
|
||||
}
|
||||
|
||||
/** Encode an RPM request, to be sent.
|
||||
*
|
||||
* @param apdu [in,out] Buffer to hold encoded bytes.
|
||||
* @param max_apdu [in] Length of apdu buffer.
|
||||
* @param invoke_id [in] The Invoke ID to use for this message.
|
||||
* @param read_access_data [in] The RPM data to be requested.
|
||||
* @return Length of encoded bytes, or 0 on failure.
|
||||
*/
|
||||
int rpm_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
size_t max_apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_READ_ACCESS_DATA * read_access_data)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
int len = 0; /* length of the data */
|
||||
BACNET_READ_ACCESS_DATA *rpm_object; /* current object */
|
||||
uint8_t apdu_temp[16]; /* temp for data before copy */
|
||||
BACNET_PROPERTY_REFERENCE *rpm_property; /* current property */
|
||||
|
||||
len = rpm_encode_apdu_init(&apdu_temp[0], invoke_id);
|
||||
len =
|
||||
(int) memcopy(&apdu[0], &apdu_temp[0], (size_t) apdu_len, (size_t) len,
|
||||
(size_t) max_apdu);
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
apdu_len += len;
|
||||
rpm_object = read_access_data;
|
||||
while (rpm_object) {
|
||||
len =
|
||||
encode_context_object_id(&apdu_temp[0], 0, rpm_object->object_type,
|
||||
rpm_object->object_instance);
|
||||
len =
|
||||
(int) memcopy(&apdu[0], &apdu_temp[0], (size_t) apdu_len,
|
||||
(size_t) len, (size_t) max_apdu);
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
apdu_len += len;
|
||||
/* Tag 1: sequence of ReadAccessSpecification */
|
||||
len = encode_opening_tag(&apdu_temp[0], 1);
|
||||
len =
|
||||
(int) memcopy(&apdu[0], &apdu_temp[0], (size_t) apdu_len,
|
||||
(size_t) len, (size_t) max_apdu);
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
apdu_len += len;
|
||||
rpm_property = rpm_object->listOfProperties;
|
||||
while (rpm_property) {
|
||||
/* stuff as many properties into it as APDU length will allow */
|
||||
len =
|
||||
encode_context_enumerated(&apdu_temp[0], 0,
|
||||
rpm_property->propertyIdentifier);
|
||||
len =
|
||||
(int) memcopy(&apdu[0], &apdu_temp[0], (size_t) apdu_len,
|
||||
(size_t) len, (size_t) max_apdu);
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
apdu_len += len;
|
||||
/* optional array index */
|
||||
if (rpm_property->propertyArrayIndex != BACNET_ARRAY_ALL) {
|
||||
len =
|
||||
encode_context_unsigned(&apdu_temp[0], 1,
|
||||
rpm_property->propertyArrayIndex);
|
||||
len =
|
||||
(int) memcopy(&apdu[0], &apdu_temp[0], (size_t) apdu_len,
|
||||
(size_t) len, (size_t) max_apdu);
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
apdu_len += len;
|
||||
}
|
||||
rpm_property = rpm_property->next;
|
||||
}
|
||||
len = encode_closing_tag(&apdu_temp[0], 1);
|
||||
len =
|
||||
(int) memcopy(&apdu[0], &apdu_temp[0], (size_t) apdu_len,
|
||||
(size_t) len, (size_t) max_apdu);
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
apdu_len += len;
|
||||
rpm_object = rpm_object->next;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/* decode the object portion of the service request only. Bails out if
|
||||
* tags are wrong or missing/incomplete
|
||||
*/
|
||||
int rpm_decode_object_id(
|
||||
uint8_t * apdu,
|
||||
unsigned apdu_len,
|
||||
BACNET_RPM_DATA * rpmdata)
|
||||
{
|
||||
unsigned len = 0;
|
||||
uint16_t type = 0; /* for decoding */
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu && apdu_len && rpmdata) {
|
||||
if (apdu_len < 5) { /* Must be at least 2 tags and an object id */
|
||||
rpmdata->error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
/* Tag 0: Object ID */
|
||||
if (!decode_is_context_tag(&apdu[len++], 0)) {
|
||||
rpmdata->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
len += decode_object_id(&apdu[len], &type, &rpmdata->object_instance);
|
||||
rpmdata->object_type = (BACNET_OBJECT_TYPE) type;
|
||||
/* Tag 1: sequence of ReadAccessSpecification */
|
||||
if (!decode_is_opening_tag_number(&apdu[len], 1)) {
|
||||
rpmdata->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
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) == true)
|
||||
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_RPM_DATA * rpmdata)
|
||||
{
|
||||
unsigned len = 0;
|
||||
unsigned option_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
uint32_t property = 0; /* for decoding */
|
||||
uint32_t array_value = 0; /* for decoding */
|
||||
|
||||
/* check for valid pointers */
|
||||
if (apdu && apdu_len && rpmdata) {
|
||||
/* Tag 0: propertyIdentifier */
|
||||
if (!IS_CONTEXT_SPECIFIC(apdu[len])) {
|
||||
rpmdata->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
if (tag_number != 0) {
|
||||
rpmdata->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
/* Should be at least the unsigned value + 1 tag left */
|
||||
if ((len + len_value_type) >= apdu_len) {
|
||||
rpmdata->error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
len += decode_enumerated(&apdu[len], len_value_type, &property);
|
||||
rpmdata->object_property = (BACNET_PROPERTY_ID) property;
|
||||
/* Assume most probable outcome */
|
||||
rpmdata->array_index = BACNET_ARRAY_ALL;
|
||||
/* Tag 1: Optional propertyArrayIndex */
|
||||
if (IS_CONTEXT_SPECIFIC(apdu[len]) && !IS_CLOSING_TAG(apdu[len])) {
|
||||
option_len =
|
||||
(unsigned) decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value_type);
|
||||
if (tag_number == 1) {
|
||||
len += option_len;
|
||||
/* Should be at least the unsigned array index + 1 tag left */
|
||||
if ((len + len_value_type) >= apdu_len) {
|
||||
rpmdata->error_code =
|
||||
ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
len +=
|
||||
decode_unsigned(&apdu[len], len_value_type, &array_value);
|
||||
rpmdata->array_index = array_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_PROP_MULTIPLE; /* service choice */
|
||||
apdu_len = 3;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int rpm_ack_encode_apdu_object_begin(
|
||||
uint8_t * apdu,
|
||||
BACNET_RPM_DATA * rpmdata)
|
||||
{
|
||||
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, rpmdata->object_type,
|
||||
rpmdata->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,
|
||||
uint32_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);
|
||||
if (application_data == &apdu[apdu_len]) { /* Is Data already in place? */
|
||||
apdu_len += application_data_len; /* Yes, step over data */
|
||||
} else { /* No, copy data in */
|
||||
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_application_enumerated(&apdu[apdu_len], error_class);
|
||||
apdu_len += encode_application_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;
|
||||
}
|
||||
|
||||
#if BACNET_SVC_RPM_A
|
||||
|
||||
/* 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;
|
||||
uint16_t 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,
|
||||
uint32_t * array_index)
|
||||
{
|
||||
unsigned len = 0;
|
||||
unsigned tag_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
uint32_t 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 (!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) && IS_CONTEXT_SPECIFIC(apdu[len]) &&
|
||||
(!IS_CLOSING_TAG(apdu[len]))) {
|
||||
tag_len =
|
||||
(unsigned) 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;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#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_PROP_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, MAX_APDU); */
|
||||
*invoke_id = apdu[2]; /* invoke id - filled in by net layer */
|
||||
if (apdu[3] != SERVICE_CONFIRMED_READ_PROP_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_RPM_DATA rpmdata;
|
||||
|
||||
rpmdata.object_type = OBJECT_DEVICE;
|
||||
rpmdata.object_instance = 0;
|
||||
rpmdata.object_property = PROP_OBJECT_IDENTIFIER;
|
||||
rpmdata.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, &rpmdata);
|
||||
ct_test(pTest, test_len > 0);
|
||||
ct_test(pTest, rpmdata.object_type == OBJECT_DEVICE);
|
||||
ct_test(pTest, rpmdata.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, &rpmdata);
|
||||
ct_test(pTest, test_len > 0);
|
||||
ct_test(pTest, rpmdata.object_property == PROP_OBJECT_IDENTIFIER);
|
||||
ct_test(pTest, rpmdata.array_index == BACNET_ARRAY_ALL);
|
||||
len += test_len;
|
||||
test_len =
|
||||
rpm_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &rpmdata);
|
||||
ct_test(pTest, test_len > 0);
|
||||
ct_test(pTest, rpmdata.object_property == PROP_OBJECT_NAME);
|
||||
ct_test(pTest, rpmdata.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, &rpmdata);
|
||||
ct_test(pTest, test_len < 0);
|
||||
/* 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,
|
||||
&rpmdata);
|
||||
ct_test(pTest, test_len > 0);
|
||||
ct_test(pTest, rpmdata.object_type == OBJECT_ANALOG_INPUT);
|
||||
ct_test(pTest, rpmdata.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, &rpmdata);
|
||||
ct_test(pTest, test_len > 0);
|
||||
ct_test(pTest, rpmdata.object_property == PROP_OBJECT_IDENTIFIER);
|
||||
ct_test(pTest, rpmdata.array_index == BACNET_ARRAY_ALL);
|
||||
len += test_len;
|
||||
test_len =
|
||||
rpm_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &rpmdata);
|
||||
ct_test(pTest, test_len > 0);
|
||||
ct_test(pTest, rpmdata.object_property == PROP_ALL);
|
||||
ct_test(pTest, rpmdata.array_index == BACNET_ARRAY_ALL);
|
||||
len += test_len;
|
||||
test_len =
|
||||
rpm_decode_object_property(&service_request[len],
|
||||
service_request_len - len, &rpmdata);
|
||||
ct_test(pTest, test_len < 0);
|
||||
/* 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;
|
||||
uint32_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;
|
||||
BACNET_RPM_DATA rpmdata;
|
||||
|
||||
/* 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 */
|
||||
rpmdata.object_type = OBJECT_DEVICE;
|
||||
rpmdata.object_instance = 123;
|
||||
apdu_len += rpm_ack_encode_apdu_object_begin(&apdu[apdu_len], &rpmdata);
|
||||
/* 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 */
|
||||
rpmdata.object_type = OBJECT_ANALOG_INPUT;
|
||||
rpmdata.object_instance = 33;
|
||||
apdu_len += rpm_ack_encode_apdu_object_begin(&apdu[apdu_len], &rpmdata);
|
||||
/* 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 */
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
/*####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####*/
|
||||
|
||||
/** @file sbuf.c Static buffer library for deeply embedded system. */
|
||||
|
||||
/* 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, /* data block */
|
||||
unsigned size)
|
||||
{ /* actual size, in bytes, of the data block or array of data */
|
||||
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, /* data to place in buffer */
|
||||
unsigned data_size)
|
||||
{ /* how many bytes 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, /* data to place in buffer */
|
||||
unsigned data_size)
|
||||
{ /* how many bytes 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 to remove */
|
||||
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 */
|
||||
+380
@@ -0,0 +1,380 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2008 John Minack
|
||||
|
||||
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 "assert.h"
|
||||
#include "timestamp.h"
|
||||
|
||||
/** @file timestamp.c Encode/Decode BACnet Timestamps */
|
||||
|
||||
void bacapp_timestamp_sequence_set(
|
||||
BACNET_TIMESTAMP * dest,
|
||||
uint16_t sequenceNum)
|
||||
{
|
||||
if (dest) {
|
||||
dest->tag = TIME_STAMP_SEQUENCE;
|
||||
dest->value.sequenceNum = sequenceNum;
|
||||
}
|
||||
}
|
||||
|
||||
void bacapp_timestamp_time_set(
|
||||
BACNET_TIMESTAMP * dest,
|
||||
BACNET_TIME *btime)
|
||||
{
|
||||
if (dest && btime) {
|
||||
dest->tag = TIME_STAMP_TIME;
|
||||
datetime_copy_time(&dest->value.time, btime);
|
||||
}
|
||||
}
|
||||
|
||||
void bacapp_timestamp_datetime_set(
|
||||
BACNET_TIMESTAMP * dest,
|
||||
BACNET_DATE_TIME * bdateTime)
|
||||
{
|
||||
if (dest && bdateTime) {
|
||||
dest->tag = TIME_STAMP_DATETIME;
|
||||
datetime_copy(&dest->value.dateTime, bdateTime);
|
||||
}
|
||||
}
|
||||
|
||||
void bacapp_timestamp_copy(
|
||||
BACNET_TIMESTAMP * dest,
|
||||
BACNET_TIMESTAMP * src)
|
||||
{
|
||||
if (dest && src) {
|
||||
dest->tag = src->tag;
|
||||
switch (src->tag) {
|
||||
case TIME_STAMP_TIME:
|
||||
datetime_copy_time(&dest->value.time, &src->value.time);
|
||||
break;
|
||||
case TIME_STAMP_SEQUENCE:
|
||||
dest->value.sequenceNum = src->value.sequenceNum;
|
||||
break;
|
||||
case TIME_STAMP_DATETIME:
|
||||
datetime_copy(&dest->value.dateTime, &src->value.dateTime);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int bacapp_encode_timestamp(
|
||||
uint8_t * apdu,
|
||||
BACNET_TIMESTAMP * value)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
|
||||
if (value && apdu) {
|
||||
switch (value->tag) {
|
||||
case TIME_STAMP_TIME:
|
||||
len = encode_context_time(&apdu[0], 0, &value->value.time);
|
||||
break;
|
||||
|
||||
case TIME_STAMP_SEQUENCE:
|
||||
len =
|
||||
encode_context_unsigned(&apdu[0], 1,
|
||||
value->value.sequenceNum);
|
||||
break;
|
||||
|
||||
case TIME_STAMP_DATETIME:
|
||||
len =
|
||||
bacapp_encode_context_datetime(&apdu[0], 2,
|
||||
&value->value.dateTime);
|
||||
break;
|
||||
|
||||
default:
|
||||
len = 0;
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bacapp_encode_context_timestamp(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_TIMESTAMP * value)
|
||||
{
|
||||
int len = 0; /* length of each encoding */
|
||||
int apdu_len = 0;
|
||||
|
||||
if (value && apdu) {
|
||||
len = encode_opening_tag(&apdu[apdu_len], tag_number);
|
||||
apdu_len += len;
|
||||
len = bacapp_encode_timestamp(&apdu[apdu_len], value);
|
||||
apdu_len += len;
|
||||
len = encode_closing_tag(&apdu[apdu_len], tag_number);
|
||||
apdu_len += len;
|
||||
}
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int bacapp_decode_timestamp(
|
||||
uint8_t * apdu,
|
||||
BACNET_TIMESTAMP * value)
|
||||
{
|
||||
int len = 0;
|
||||
int section_len;
|
||||
uint32_t len_value_type;
|
||||
uint32_t sequenceNum;
|
||||
|
||||
if (apdu) {
|
||||
section_len =
|
||||
decode_tag_number_and_value(&apdu[len], &value->tag,
|
||||
&len_value_type);
|
||||
|
||||
if (-1 == section_len) {
|
||||
return -1;
|
||||
}
|
||||
switch (value->tag) {
|
||||
case TIME_STAMP_TIME:
|
||||
if ((section_len =
|
||||
decode_context_bacnet_time(&apdu[len], TIME_STAMP_TIME,
|
||||
&value->value.time)) == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
len += section_len;
|
||||
}
|
||||
break;
|
||||
|
||||
case TIME_STAMP_SEQUENCE:
|
||||
if ((section_len =
|
||||
decode_context_unsigned(&apdu[len],
|
||||
TIME_STAMP_SEQUENCE, &sequenceNum)) == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
if (sequenceNum <= 0xffff) {
|
||||
len += section_len;
|
||||
value->value.sequenceNum = (uint16_t) sequenceNum;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TIME_STAMP_DATETIME:
|
||||
if ((section_len =
|
||||
bacapp_decode_context_datetime(&apdu[len],
|
||||
TIME_STAMP_DATETIME,
|
||||
&value->value.dateTime)) == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
len += section_len;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int bacapp_decode_context_timestamp(
|
||||
uint8_t * apdu,
|
||||
uint8_t tag_number,
|
||||
BACNET_TIMESTAMP * value)
|
||||
{
|
||||
int len = 0;
|
||||
int section_len;
|
||||
|
||||
|
||||
if (decode_is_opening_tag_number(&apdu[len], tag_number)) {
|
||||
len++;
|
||||
section_len = bacapp_decode_timestamp(&apdu[len], value);
|
||||
if (section_len > 0) {
|
||||
len += section_len;
|
||||
if (decode_is_closing_tag_number(&apdu[len], tag_number)) {
|
||||
len++;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
|
||||
void testTimestampSequence(
|
||||
Test * pTest)
|
||||
{
|
||||
BACNET_TIMESTAMP testTimestampIn;
|
||||
BACNET_TIMESTAMP testTimestampOut;
|
||||
uint8_t buffer[MAX_APDU];
|
||||
int inLen;
|
||||
int outLen;
|
||||
|
||||
testTimestampIn.tag = TIME_STAMP_SEQUENCE;
|
||||
testTimestampIn.value.sequenceNum = 0x1234;
|
||||
|
||||
memset(&testTimestampOut, 0, sizeof(testTimestampOut));
|
||||
|
||||
|
||||
inLen = bacapp_encode_context_timestamp(buffer, 2, &testTimestampIn);
|
||||
outLen = bacapp_decode_context_timestamp(buffer, 2, &testTimestampOut);
|
||||
|
||||
ct_test(pTest, inLen == outLen);
|
||||
ct_test(pTest, testTimestampIn.tag == testTimestampOut.tag);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.sequenceNum ==
|
||||
testTimestampOut.value.sequenceNum);
|
||||
}
|
||||
|
||||
void testTimestampTime(
|
||||
Test * pTest)
|
||||
{
|
||||
BACNET_TIMESTAMP testTimestampIn;
|
||||
BACNET_TIMESTAMP testTimestampOut;
|
||||
uint8_t buffer[MAX_APDU];
|
||||
int inLen;
|
||||
int outLen;
|
||||
|
||||
testTimestampIn.tag = TIME_STAMP_TIME;
|
||||
testTimestampIn.value.time.hour = 1;
|
||||
testTimestampIn.value.time.min = 2;
|
||||
testTimestampIn.value.time.sec = 3;
|
||||
testTimestampIn.value.time.hundredths = 4;
|
||||
|
||||
memset(&testTimestampOut, 0, sizeof(testTimestampOut));
|
||||
|
||||
|
||||
inLen = bacapp_encode_context_timestamp(buffer, 2, &testTimestampIn);
|
||||
outLen = bacapp_decode_context_timestamp(buffer, 2, &testTimestampOut);
|
||||
|
||||
ct_test(pTest, inLen == outLen);
|
||||
ct_test(pTest, testTimestampIn.tag == testTimestampOut.tag);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.time.hour == testTimestampOut.value.time.hour);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.time.min == testTimestampOut.value.time.min);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.time.sec == testTimestampOut.value.time.sec);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.time.hundredths ==
|
||||
testTimestampOut.value.time.hundredths);
|
||||
}
|
||||
|
||||
void testTimestampTimeDate(
|
||||
Test * pTest)
|
||||
{
|
||||
BACNET_TIMESTAMP testTimestampIn;
|
||||
BACNET_TIMESTAMP testTimestampOut;
|
||||
uint8_t buffer[MAX_APDU];
|
||||
int inLen;
|
||||
int outLen;
|
||||
|
||||
testTimestampIn.tag = TIME_STAMP_DATETIME;
|
||||
testTimestampIn.value.dateTime.time.hour = 1;
|
||||
testTimestampIn.value.dateTime.time.min = 2;
|
||||
testTimestampIn.value.dateTime.time.sec = 3;
|
||||
testTimestampIn.value.dateTime.time.hundredths = 4;
|
||||
|
||||
testTimestampIn.value.dateTime.date.year = 1901;
|
||||
testTimestampIn.value.dateTime.date.month = 1;
|
||||
testTimestampIn.value.dateTime.date.wday = 2;
|
||||
testTimestampIn.value.dateTime.date.day = 3;
|
||||
|
||||
memset(&testTimestampOut, 0, sizeof(testTimestampOut));
|
||||
|
||||
inLen = bacapp_encode_context_timestamp(buffer, 2, &testTimestampIn);
|
||||
outLen = bacapp_decode_context_timestamp(buffer, 2, &testTimestampOut);
|
||||
|
||||
ct_test(pTest, inLen == outLen);
|
||||
ct_test(pTest, testTimestampIn.tag == testTimestampOut.tag);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.dateTime.time.hour ==
|
||||
testTimestampOut.value.dateTime.time.hour);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.dateTime.time.min ==
|
||||
testTimestampOut.value.dateTime.time.min);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.dateTime.time.sec ==
|
||||
testTimestampOut.value.dateTime.time.sec);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.dateTime.time.hundredths ==
|
||||
testTimestampOut.value.dateTime.time.hundredths);
|
||||
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.dateTime.date.year ==
|
||||
testTimestampOut.value.dateTime.date.year);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.dateTime.date.month ==
|
||||
testTimestampOut.value.dateTime.date.month);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.dateTime.date.wday ==
|
||||
testTimestampOut.value.dateTime.date.wday);
|
||||
ct_test(pTest,
|
||||
testTimestampIn.value.dateTime.date.day ==
|
||||
testTimestampOut.value.dateTime.date.day);
|
||||
|
||||
}
|
||||
|
||||
#ifdef TEST_TIME_STAMP
|
||||
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet Time Stamp", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testTimestampSequence);
|
||||
assert(rc);
|
||||
|
||||
rc = ct_addTestFunction(pTest, testTimestampTime);
|
||||
assert(rc);
|
||||
|
||||
rc = ct_addTestFunction(pTest, testTimestampTimeDate);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* TEST_TIME_STAMP */
|
||||
#endif /* TEST */
|
||||
+529
@@ -0,0 +1,529 @@
|
||||
/*####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"
|
||||
|
||||
/** @file timesync.c Encode/Decode TimeSync APDUs */
|
||||
#if BACNET_SVC_TS_A
|
||||
/* 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_application_date(&apdu[apdu_len], my_date);
|
||||
apdu_len += len;
|
||||
len = encode_application_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);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/** Handle a request to encode the list of timesync recipients.
|
||||
*
|
||||
* Invoked by a request to read the Device object's
|
||||
* PROP_TIME_SYNCHRONIZATION_RECIPIENTS.
|
||||
* Loops through the list of timesync recipients, and, for each one,
|
||||
* adds its data to the APDU.
|
||||
*
|
||||
* BACnetRecipient ::= CHOICE {
|
||||
* device [0] BACnetObjectIdentifier,
|
||||
* address [1] BACnetAddress
|
||||
* }
|
||||
*
|
||||
* BACnetAddress ::= SEQUENCE {
|
||||
* network-number Unsigned16, -- A value of 0 indicates the local network
|
||||
* mac-address OCTET STRING -- A string of length 0 indicates a broadcast
|
||||
* }
|
||||
*
|
||||
* @param apdu [out] Buffer in which the APDU contents are built.
|
||||
* @param max_apdu [in] Max length of the APDU buffer.
|
||||
* @param recipient [in] BACNET_RECIPIENT_LIST type linked list of recipients.
|
||||
*
|
||||
* @return How many bytes were encoded in the buffer, or
|
||||
* BACNET_STATUS_ABORT if the response would not fit within the buffer.
|
||||
*/
|
||||
int timesync_encode_timesync_recipients(
|
||||
uint8_t * apdu,
|
||||
unsigned max_apdu,
|
||||
BACNET_RECIPIENT_LIST * recipient)
|
||||
{
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
BACNET_OCTET_STRING octet_string;
|
||||
BACNET_RECIPIENT_LIST *pRecipient;
|
||||
|
||||
pRecipient = recipient;
|
||||
while (pRecipient != NULL) {
|
||||
if (pRecipient->tag == 0) {
|
||||
if (max_apdu >= (1 + 4)) {
|
||||
/* CHOICE - device [0] BACnetObjectIdentifier */
|
||||
len =
|
||||
encode_context_object_id(&apdu[apdu_len], 0,
|
||||
(int)pRecipient->type.device.type,
|
||||
pRecipient->type.device.instance);
|
||||
apdu_len += len;
|
||||
} else {
|
||||
return BACNET_STATUS_ABORT;
|
||||
}
|
||||
} else if (pRecipient->tag == 1) {
|
||||
if (pRecipient->type.address.net) {
|
||||
len = (int)(1 + 3 + 2 + pRecipient->type.address.len + 1);
|
||||
} else {
|
||||
len =
|
||||
(int)(1 + 3 + 2 + pRecipient->type.address.mac_len + 1);
|
||||
}
|
||||
if (max_apdu >= (unsigned)len) {
|
||||
/* CHOICE - address [1] BACnetAddress - opening */
|
||||
len = encode_opening_tag(&apdu[apdu_len], 1);
|
||||
apdu_len += len;
|
||||
/* network-number Unsigned16, */
|
||||
/* -- A value of 0 indicates the local network */
|
||||
len =
|
||||
encode_application_unsigned(&apdu[apdu_len],
|
||||
pRecipient->type.address.net);
|
||||
apdu_len += len;
|
||||
/* mac-address OCTET STRING */
|
||||
/* -- A string of length 0 indicates a broadcast */
|
||||
if (pRecipient->type.address.net == BACNET_BROADCAST_NETWORK) {
|
||||
octetstring_init(&octet_string, NULL, 0);
|
||||
} else if (pRecipient->type.address.net) {
|
||||
octetstring_init(&octet_string,
|
||||
&pRecipient->type.address.adr[0],
|
||||
pRecipient->type.address.len);
|
||||
} else {
|
||||
octetstring_init(&octet_string,
|
||||
&pRecipient->type.address.mac[0],
|
||||
pRecipient->type.address.mac_len);
|
||||
}
|
||||
len =
|
||||
encode_application_octet_string(&apdu[apdu_len],
|
||||
&octet_string);
|
||||
apdu_len += len;
|
||||
/* CHOICE - address [1] BACnetAddress - closing */
|
||||
len = encode_closing_tag(&apdu[apdu_len], 1);
|
||||
apdu_len += len;
|
||||
} else {
|
||||
/* not a valid tag - don't encode this one */
|
||||
}
|
||||
}
|
||||
pRecipient = pRecipient->next;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
/** Handle a request to decode a list of timesync recipients.
|
||||
*
|
||||
* Invoked by a request to write the Device object's
|
||||
* PROP_TIME_SYNCHRONIZATION_RECIPIENTS.
|
||||
* Loops through the list of timesync recipients, and, for each one,
|
||||
* adds its data from the APDU.
|
||||
*
|
||||
* BACnetRecipient ::= CHOICE {
|
||||
* device [0] BACnetObjectIdentifier,
|
||||
* address [1] BACnetAddress
|
||||
* }
|
||||
*
|
||||
* BACnetAddress ::= SEQUENCE {
|
||||
* network-number Unsigned16, -- A value of 0 indicates the local network
|
||||
* mac-address OCTET STRING -- A string of length 0 indicates a broadcast
|
||||
* }
|
||||
*
|
||||
* @param apdu [in] Buffer in which the APDU contents are read
|
||||
* @param max_apdu [in] length of the APDU buffer.
|
||||
* @param recipient [out] BACNET_RECIPIENT_LIST type linked list of recipients.
|
||||
*
|
||||
* @return How many bytes were decoded from the buffer, or
|
||||
* BACNET_STATUS_ABORT if there was a problem decoding the buffer
|
||||
*/
|
||||
int timesync_decode_timesync_recipients(
|
||||
uint8_t * apdu,
|
||||
unsigned max_apdu,
|
||||
BACNET_RECIPIENT_LIST * recipient)
|
||||
{
|
||||
int len = 0;
|
||||
int apdu_len = 0;
|
||||
int tag_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
uint32_t unsigned_value = 0;
|
||||
BACNET_OCTET_STRING octet_string;
|
||||
BACNET_RECIPIENT_LIST *pRecipient;
|
||||
|
||||
pRecipient = recipient;
|
||||
while (pRecipient != NULL) {
|
||||
/* device [0] BACnetObjectIdentifier */
|
||||
if (decode_is_context_tag(&apdu[apdu_len], 0)) {
|
||||
pRecipient->tag = 0;
|
||||
len =
|
||||
decode_context_object_id(&apdu[apdu_len], 0,
|
||||
&pRecipient->type.device.type,
|
||||
&pRecipient->type.device.instance);
|
||||
if (len < 0) {
|
||||
return BACNET_STATUS_ABORT;
|
||||
}
|
||||
apdu_len += len;
|
||||
} else if (decode_is_context_tag(&apdu[apdu_len], 1)) {
|
||||
apdu_len += 1;
|
||||
pRecipient->tag = 1;
|
||||
/* network-number Unsigned16 */
|
||||
tag_len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value_type);
|
||||
apdu_len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_UNSIGNED_INT) {
|
||||
return BACNET_STATUS_ABORT;
|
||||
}
|
||||
len =
|
||||
decode_unsigned(&apdu[apdu_len], len_value_type,
|
||||
&unsigned_value);
|
||||
pRecipient->type.address.net = unsigned_value;
|
||||
apdu_len += len;
|
||||
/* mac-address OCTET STRING */
|
||||
tag_len =
|
||||
decode_tag_number_and_value(&apdu[apdu_len], &tag_number,
|
||||
&len_value_type);
|
||||
apdu_len += tag_len;
|
||||
if (tag_number != BACNET_APPLICATION_TAG_OCTET_STRING) {
|
||||
return BACNET_STATUS_ABORT;
|
||||
}
|
||||
len = decode_octet_string(&apdu[0], len_value_type, &octet_string);
|
||||
apdu_len += len;
|
||||
if (octetstring_length(&octet_string) == 0) {
|
||||
/* -- A string of length 0 indicates a broadcast */
|
||||
} else if (pRecipient->type.address.net) {
|
||||
pRecipient->type.address.len =
|
||||
octetstring_copy_value(&pRecipient->type.address.adr[0],
|
||||
sizeof(pRecipient->type.address.adr), &octet_string);
|
||||
} else {
|
||||
pRecipient->type.address.mac_len =
|
||||
octetstring_copy_value(&pRecipient->type.address.mac[0],
|
||||
sizeof(pRecipient->type.address.mac), &octet_string);
|
||||
}
|
||||
} else {
|
||||
return BACNET_STATUS_ABORT;
|
||||
}
|
||||
pRecipient = pRecipient->next;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testTimeSyncRecipientData(
|
||||
Test * pTest,
|
||||
BACNET_RECIPIENT_LIST * recipient1,
|
||||
BACNET_RECIPIENT_LIST * recipient2)
|
||||
{
|
||||
unsigned i = 0;
|
||||
|
||||
if (recipient1 && recipient2) {
|
||||
ct_test(pTest, recipient1->tag == recipient2->tag);
|
||||
if (recipient1->tag == 0) {
|
||||
ct_test(pTest,
|
||||
recipient1->type.device.type == recipient2->type.device.type);
|
||||
ct_test(pTest,
|
||||
recipient1->type.device.instance ==
|
||||
recipient2->type.device.instance);
|
||||
} else if (recipient1->tag == 1) {
|
||||
ct_test(pTest,
|
||||
recipient1->type.address.net == recipient2->type.address.net);
|
||||
if (recipient1->type.address.net == BACNET_BROADCAST_NETWORK) {
|
||||
ct_test(pTest,
|
||||
recipient1->type.address.mac_len ==
|
||||
recipient2->type.address.mac_len);
|
||||
} else if (recipient1->type.address.net) {
|
||||
ct_test(pTest,
|
||||
recipient1->type.address.len ==
|
||||
recipient2->type.address.len);
|
||||
for (i = 0; i < recipient1->type.address.len; i++) {
|
||||
ct_test(pTest,
|
||||
recipient1->type.address.adr[i] ==
|
||||
recipient2->type.address.adr[i]);
|
||||
}
|
||||
} else {
|
||||
ct_test(pTest,
|
||||
recipient1->type.address.mac_len ==
|
||||
recipient2->type.address.mac_len);
|
||||
for (i = 0; i < recipient1->type.address.mac_len; i++) {
|
||||
ct_test(pTest,
|
||||
recipient1->type.address.mac[i] ==
|
||||
recipient2->type.address.mac[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ct_test(pTest, recipient1->tag <= 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void testTimeSyncRecipient(
|
||||
Test * pTest)
|
||||
{
|
||||
uint8_t apdu[480] = { 0 };
|
||||
int len = 0;
|
||||
BACNET_RECIPIENT_LIST recipient[4];
|
||||
BACNET_RECIPIENT_LIST test_recipient[4];
|
||||
|
||||
/* link the recipient list */
|
||||
recipient[0].next = &recipient[1];
|
||||
recipient[1].next = &recipient[2];
|
||||
recipient[2].next = &recipient[3];
|
||||
recipient[3].next = NULL;
|
||||
/* link the test recipient list */
|
||||
test_recipient[0].next = &test_recipient[1];
|
||||
test_recipient[1].next = &test_recipient[2];
|
||||
test_recipient[2].next = &test_recipient[3];
|
||||
test_recipient[3].next = NULL;
|
||||
/* load the test data - device */
|
||||
recipient[0].tag = 0;
|
||||
recipient[0].type.device.type = OBJECT_DEVICE;
|
||||
recipient[0].type.device.instance = 1234;
|
||||
/* load the test data - address */
|
||||
/* network = broadcast */
|
||||
recipient[1].tag = 1;
|
||||
recipient[1].type.address.net = BACNET_BROADCAST_NETWORK;
|
||||
recipient[2].type.address.mac_len = 0;
|
||||
/* network = non-zero */
|
||||
recipient[1].tag = 1;
|
||||
recipient[2].type.address.net = 4201;
|
||||
recipient[2].type.address.adr[0] = 127;
|
||||
recipient[2].type.address.len = 1;
|
||||
/* network = zero */
|
||||
recipient[2].type.address.net = 0;
|
||||
recipient[2].type.address.mac[0] = 10;
|
||||
recipient[2].type.address.mac[1] = 1;
|
||||
recipient[2].type.address.mac[2] = 0;
|
||||
recipient[2].type.address.mac[3] = 86;
|
||||
recipient[2].type.address.mac[4] = 0xBA;
|
||||
recipient[2].type.address.mac[5] = 0xC1;
|
||||
recipient[2].type.address.mac_len = 6;
|
||||
/* perform positive test */
|
||||
len =
|
||||
timesync_encode_timesync_recipients(&apdu[0], sizeof(apdu),
|
||||
&recipient[0]);
|
||||
ct_test(pTest, len != BACNET_STATUS_ABORT);
|
||||
ct_test(pTest, len > 0);
|
||||
len =
|
||||
timesync_decode_timesync_recipients(&apdu[0], sizeof(apdu),
|
||||
&test_recipient[0]);
|
||||
ct_test(pTest, len != BACNET_STATUS_ABORT);
|
||||
ct_test(pTest, len > 0);
|
||||
testTimeSyncRecipientData(pTest, &recipient[0], &test_recipient[0]);
|
||||
}
|
||||
|
||||
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, datetime_compare_time(my_time, &test_time) == 0);
|
||||
ct_test(pTest, datetime_compare_date(my_date, &test_date) == 0);
|
||||
|
||||
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, datetime_compare_time(my_time, &test_time) == 0);
|
||||
ct_test(pTest, datetime_compare_date(my_date, &test_date) == 0);
|
||||
}
|
||||
|
||||
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,410 @@
|
||||
/*####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 "datalink.h"
|
||||
#include "handlers.h"
|
||||
#include "address.h"
|
||||
#include "bacaddr.h"
|
||||
|
||||
/** @file tsm.c BACnet Transaction State Machine operations */
|
||||
|
||||
#if (MAX_TSM_TRANSACTIONS)
|
||||
/* 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];
|
||||
|
||||
/* invoke ID for incrementing between subsequent calls. */
|
||||
static uint8_t Current_Invoke_ID = 1;
|
||||
|
||||
static tsm_timeout_function Timeout_Function;
|
||||
|
||||
void tsm_set_timeout_handler(
|
||||
tsm_timeout_function pFunction)
|
||||
{
|
||||
Timeout_Function = pFunction;
|
||||
}
|
||||
|
||||
/* 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 = (uint8_t) 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 = (uint8_t) 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;
|
||||
}
|
||||
|
||||
/* sets the invokeID */
|
||||
|
||||
void tsm_invokeID_set(
|
||||
uint8_t invokeID)
|
||||
{
|
||||
if (invokeID == 0) {
|
||||
invokeID = 1;
|
||||
}
|
||||
Current_Invoke_ID = invokeID;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
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_Invoke_ID);
|
||||
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_Invoke_ID;
|
||||
TSM_List[index].state = TSM_STATE_IDLE;
|
||||
TSM_List[index].RequestTimer = apdu_timeout();
|
||||
/* update for the next call or check */
|
||||
Current_Invoke_ID++;
|
||||
/* skip zero - we treat that internally as invalid or no free */
|
||||
if (Current_Invoke_ID == 0) {
|
||||
Current_Invoke_ID = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* found! This invokeID is already used */
|
||||
/* try next one */
|
||||
Current_Invoke_ID++;
|
||||
/* skip zero - we treat that internally as invalid or no free */
|
||||
if (Current_Invoke_ID == 0) {
|
||||
Current_Invoke_ID = 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) {
|
||||
/* SendConfirmedUnsegmented */
|
||||
TSM_List[index].state = TSM_STATE_AWAIT_CONFIRMATION;
|
||||
TSM_List[index].RetryCount = 0;
|
||||
/* start the timer */
|
||||
TSM_List[index].RequestTimer = 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 = (uint16_t) 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 */
|
||||
|
||||
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;
|
||||
/* AWAIT_CONFIRMATION */
|
||||
if (TSM_List[i].RequestTimer == 0) {
|
||||
if (TSM_List[i].RetryCount < apdu_retries()) {
|
||||
TSM_List[i].RequestTimer = apdu_timeout();
|
||||
TSM_List[i].RetryCount++;
|
||||
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;
|
||||
if (TSM_List[i].InvokeID != 0) {
|
||||
if (Timeout_Function) {
|
||||
Timeout_Function(TSM_List[i].InvokeID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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 by the Transaction State Machine.
|
||||
* @param invokeID [in] The invokeID to be checked, normally of last message sent.
|
||||
* @return True if it is free (done with), False if still pending in the TSM.
|
||||
*/
|
||||
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 we failed get a confirmation for the message associated
|
||||
* with this invoke ID.
|
||||
* @param invokeID [in] The invokeID to be checked, normally of last message sent.
|
||||
* @return True if already failed, False if done or segmented or still waiting
|
||||
* for 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 */
|
||||
+221
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* Copyright (C) 2008 John Crispin <blogic@openwrt.org>
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <uci_config.h>
|
||||
#include <uci.h>
|
||||
#include "ucix.h"
|
||||
/*#include "log.h" */
|
||||
|
||||
static struct uci_ptr ptr;
|
||||
|
||||
static inline int ucix_get_ptr(
|
||||
struct uci_context *ctx,
|
||||
const char *p,
|
||||
const char *s,
|
||||
const char *o,
|
||||
const char *t)
|
||||
{
|
||||
memset(&ptr, 0, sizeof(ptr));
|
||||
ptr.package = p;
|
||||
ptr.section = s;
|
||||
ptr.option = o;
|
||||
ptr.value = t;
|
||||
return uci_lookup_ptr(ctx, &ptr, NULL, true);
|
||||
}
|
||||
|
||||
struct uci_context *ucix_init(
|
||||
const char *config_file)
|
||||
{
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
/* uci_add_history_path(ctx, "/var/state"); */
|
||||
uci_add_delta_path(ctx, "/var/state");
|
||||
if (uci_load(ctx, config_file, NULL) != UCI_OK) {
|
||||
fprintf(stderr, "%s/%s is missing or corrupt\n", ctx->savedir,
|
||||
config_file);
|
||||
return NULL;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
struct uci_context *ucix_init_path(
|
||||
const char *path,
|
||||
const char *config_file)
|
||||
{
|
||||
struct uci_context *ctx = uci_alloc_context();
|
||||
if (path)
|
||||
uci_set_confdir(ctx, path);
|
||||
if (uci_load(ctx, config_file, NULL) != UCI_OK) {
|
||||
fprintf(stderr, "%s/%s is missing or corrupt\n", ctx->savedir,
|
||||
config_file);
|
||||
return NULL;
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void ucix_cleanup(
|
||||
struct uci_context *ctx)
|
||||
{
|
||||
uci_free_context(ctx);
|
||||
}
|
||||
|
||||
void ucix_save(
|
||||
struct uci_context *ctx)
|
||||
{
|
||||
uci_set_savedir(ctx, "/tmp/.uci/");
|
||||
uci_save(ctx, NULL);
|
||||
}
|
||||
|
||||
void ucix_save_state(
|
||||
struct uci_context *ctx)
|
||||
{
|
||||
uci_set_savedir(ctx, "/var/state/");
|
||||
uci_save(ctx, NULL);
|
||||
}
|
||||
|
||||
const char *ucix_get_option(
|
||||
struct uci_context *ctx,
|
||||
const char *p,
|
||||
const char *s,
|
||||
const char *o)
|
||||
{
|
||||
struct uci_element *e = NULL;
|
||||
const char *value = NULL;
|
||||
if (ucix_get_ptr(ctx, p, s, o, NULL))
|
||||
return NULL;
|
||||
if (!(ptr.flags & UCI_LOOKUP_COMPLETE))
|
||||
return NULL;
|
||||
e = ptr.last;
|
||||
switch (e->type) {
|
||||
case UCI_TYPE_SECTION:
|
||||
value = uci_to_section(e)->type;
|
||||
break;
|
||||
case UCI_TYPE_OPTION:
|
||||
switch (ptr.o->type) {
|
||||
case UCI_TYPE_STRING:
|
||||
value = ptr.o->v.string;
|
||||
break;
|
||||
default:
|
||||
value = NULL;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
int ucix_get_option_int(
|
||||
struct uci_context *ctx,
|
||||
const char *p,
|
||||
const char *s,
|
||||
const char *o,
|
||||
int def)
|
||||
{
|
||||
const char *tmp = ucix_get_option(ctx, p, s, o);
|
||||
int ret = def;
|
||||
|
||||
if (tmp)
|
||||
ret = atoi(tmp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ucix_add_section(
|
||||
struct uci_context *ctx,
|
||||
const char *p,
|
||||
const char *s,
|
||||
const char *t)
|
||||
{
|
||||
if (ucix_get_ptr(ctx, p, s, NULL, t))
|
||||
return;
|
||||
uci_set(ctx, &ptr);
|
||||
}
|
||||
|
||||
void ucix_add_option(
|
||||
struct uci_context *ctx,
|
||||
const char *p,
|
||||
const char *s,
|
||||
const char *o,
|
||||
const char *t)
|
||||
{
|
||||
if (ucix_get_ptr(ctx, p, s, o, (t) ? (t) : ("")))
|
||||
return;
|
||||
uci_set(ctx, &ptr);
|
||||
}
|
||||
|
||||
void ucix_add_option_int(
|
||||
struct uci_context *ctx,
|
||||
const char *p,
|
||||
const char *s,
|
||||
const char *o,
|
||||
int t)
|
||||
{
|
||||
char tmp[64];
|
||||
snprintf(tmp, 64, "%d", t);
|
||||
ucix_add_option(ctx, p, s, o, tmp);
|
||||
}
|
||||
|
||||
void ucix_del(
|
||||
struct uci_context *ctx,
|
||||
const char *p,
|
||||
const char *s,
|
||||
const char *o)
|
||||
{
|
||||
if (!ucix_get_ptr(ctx, p, s, o, NULL))
|
||||
uci_delete(ctx, &ptr);
|
||||
}
|
||||
|
||||
void ucix_revert(
|
||||
struct uci_context *ctx,
|
||||
const char *p,
|
||||
const char *s,
|
||||
const char *o)
|
||||
{
|
||||
if (!ucix_get_ptr(ctx, p, s, o, NULL))
|
||||
uci_revert(ctx, &ptr);
|
||||
}
|
||||
|
||||
void ucix_for_each_section_type(
|
||||
struct uci_context *ctx,
|
||||
const char *p,
|
||||
const char *t,
|
||||
void (*cb) (const char *,
|
||||
void *),
|
||||
void *priv)
|
||||
{
|
||||
struct uci_element *e;
|
||||
if (ucix_get_ptr(ctx, p, NULL, NULL, NULL))
|
||||
return;
|
||||
uci_foreach_element(&ptr.p->sections, e)
|
||||
if (!strcmp(t, uci_to_section(e)->type))
|
||||
cb(e->name, priv);
|
||||
}
|
||||
|
||||
int ucix_commit(
|
||||
struct uci_context *ctx,
|
||||
const char *p)
|
||||
{
|
||||
if (ucix_get_ptr(ctx, p, NULL, NULL, NULL))
|
||||
return 1;
|
||||
return uci_commit(ctx, &ptr.p, false);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*####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"
|
||||
|
||||
/** @file version.c Sets the BACnet Version. */
|
||||
|
||||
char *BACnet_Version = BACNET_VERSION_TEXT;
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
/*####COPYRIGHTBEGIN####
|
||||
-------------------------------------------
|
||||
Copyright (C) 2015 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 <stdlib.h>
|
||||
#include "config.h"
|
||||
#include "bacdef.h"
|
||||
#include "keylist.h"
|
||||
/* me! */
|
||||
#include "vmac.h"
|
||||
|
||||
/** @file
|
||||
Handle VMAC address binding */
|
||||
|
||||
/* This module is used to handle the virtual MAC address binding that */
|
||||
/* occurs in BACnet for ZigBee or IPv6. */
|
||||
|
||||
/* Key List for storing the object data sorted by instance number */
|
||||
static OS_Keylist VMAC_List;
|
||||
|
||||
/**
|
||||
* Returns the number of VMAC in the list
|
||||
*/
|
||||
unsigned int VMAC_Count(void)
|
||||
{
|
||||
return (unsigned int)Keylist_Count(VMAC_List);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a VMAC to the list
|
||||
*
|
||||
* @param device_id - BACnet device object instance number
|
||||
* @param src - BACnet/IPv6 address
|
||||
*
|
||||
* @return true if the device ID and MAC are added
|
||||
*/
|
||||
bool VMAC_Add(uint32_t device_id, struct vmac_data *src)
|
||||
{
|
||||
bool status = false;
|
||||
struct vmac_data *pVMAC = NULL;
|
||||
int index = 0;
|
||||
size_t i = 0;
|
||||
|
||||
pVMAC = Keylist_Data(VMAC_List, device_id);
|
||||
if (!pVMAC) {
|
||||
pVMAC = calloc(1, sizeof(struct vmac_data));
|
||||
if (pVMAC) {
|
||||
/* copy the MAC into the data store */
|
||||
for (i = 0; i < sizeof(pVMAC->mac); i++) {
|
||||
if (i < src->mac_len) {
|
||||
pVMAC->mac[i] = src->mac[i];
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
pVMAC->mac_len = src->mac_len;
|
||||
index = Keylist_Data_Add(VMAC_List, device_id, pVMAC);
|
||||
if (index >= 0) {
|
||||
status = true;
|
||||
printf("VMAC %u added.\n", (unsigned int)device_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a VMAC in the list by seeking the Device ID, and deletes it.
|
||||
*
|
||||
* @param device_id - BACnet device object instance number
|
||||
*
|
||||
* @return pointer to the VMAC data from the list - be sure to free() it!
|
||||
*/
|
||||
bool VMAC_Delete(uint32_t device_id)
|
||||
{
|
||||
bool status = false;
|
||||
struct vmac_data *pVMAC;
|
||||
|
||||
pVMAC = Keylist_Data_Delete(VMAC_List, device_id);
|
||||
if (pVMAC) {
|
||||
free(pVMAC);
|
||||
status = true;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a VMAC in the list by seeking the Device ID.
|
||||
*
|
||||
* @param device_id - BACnet device object instance number
|
||||
*
|
||||
* @return pointer to the VMAC data from the list
|
||||
*/
|
||||
struct vmac_data *VMAC_Find_By_Key(uint32_t device_id)
|
||||
{
|
||||
return Keylist_Data(VMAC_List, device_id);
|
||||
}
|
||||
|
||||
/** Compare the VMAC address
|
||||
*
|
||||
* @param vmac1 - VMAC address that will be compared to vmac2
|
||||
* @param vmac2 - VMAC address that will be compared to vmac1
|
||||
*
|
||||
* @return true if the addresses are different
|
||||
*/
|
||||
bool VMAC_Different(
|
||||
struct vmac_data *vmac1,
|
||||
struct vmac_data *vmac2)
|
||||
{
|
||||
bool status = false;
|
||||
unsigned int i = 0;
|
||||
unsigned int mac_len = VMAC_MAC_MAX;
|
||||
|
||||
if (vmac1 && vmac2) {
|
||||
if (vmac1->mac_len != vmac2->mac_len) {
|
||||
status = true;
|
||||
} else {
|
||||
if (vmac1->mac_len < mac_len) {
|
||||
mac_len = (unsigned int)vmac1->mac_len;
|
||||
}
|
||||
for (i = 0; i < mac_len; i++) {
|
||||
if (vmac1->mac[i] != vmac2->mac[i]) {
|
||||
status = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/** Compare the VMAC address
|
||||
*
|
||||
* @param vmac1 - VMAC address that will be compared to vmac2
|
||||
* @param vmac2 - VMAC address that will be compared to vmac1
|
||||
*
|
||||
* @return true if the addresses are the same
|
||||
*/
|
||||
bool VMAC_Match(
|
||||
struct vmac_data *vmac1,
|
||||
struct vmac_data *vmac2)
|
||||
{
|
||||
bool status = false;
|
||||
unsigned int i = 0;
|
||||
unsigned int mac_len = VMAC_MAC_MAX;
|
||||
|
||||
if (vmac1 && vmac2 && vmac1->mac_len) {
|
||||
status = true;
|
||||
if (vmac1->mac_len != vmac2->mac_len) {
|
||||
status = false;
|
||||
} else {
|
||||
if (vmac1->mac_len < mac_len) {
|
||||
mac_len = (unsigned int)vmac1->mac_len;
|
||||
}
|
||||
for (i = 0; i < mac_len; i++) {
|
||||
if (vmac1->mac[i] != vmac2->mac[i]) {
|
||||
status = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a VMAC in the list by seeking a matching VMAC address
|
||||
*
|
||||
* @param vmac - VMAC address that will be sought
|
||||
* @param device_id - BACnet device object instance number
|
||||
*
|
||||
* @return true if the VMAC address was found
|
||||
*/
|
||||
bool VMAC_Find_By_Data(struct vmac_data *vmac, uint32_t *device_id)
|
||||
{
|
||||
bool status = false;
|
||||
struct vmac_data *list_vmac;
|
||||
int count = 0;
|
||||
int index = 0;
|
||||
|
||||
count = Keylist_Count(VMAC_List);
|
||||
while (count) {
|
||||
index = count - 1;
|
||||
list_vmac = Keylist_Data_Index(VMAC_List, index);
|
||||
if (list_vmac) {
|
||||
if (VMAC_Match(vmac, list_vmac)) {
|
||||
if (device_id) {
|
||||
*device_id = Keylist_Key(VMAC_List, index);
|
||||
}
|
||||
status = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
count--;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the memory used by the VMAC list data
|
||||
*/
|
||||
void VMAC_Cleanup(void)
|
||||
{
|
||||
struct vmac_data *pVMAC;
|
||||
|
||||
if (VMAC_List) {
|
||||
do {
|
||||
pVMAC = Keylist_Data_Pop(VMAC_List);
|
||||
if (pVMAC) {
|
||||
free(pVMAC);
|
||||
}
|
||||
} while (pVMAC);
|
||||
Keylist_Delete(VMAC_List);
|
||||
VMAC_List = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the VMAC list data
|
||||
*/
|
||||
void VMAC_Init(void)
|
||||
{
|
||||
VMAC_List = Keylist_Create();
|
||||
if (VMAC_List) {
|
||||
atexit(VMAC_Cleanup);
|
||||
printf("VMAC List initialized.\n");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TEST
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "ctest.h"
|
||||
|
||||
void testVMAC(
|
||||
Test * pTest)
|
||||
{
|
||||
uint32_t device_id = 123;
|
||||
uint32_t test_device_id = 0;
|
||||
struct vmac_data test_vmac_data;
|
||||
struct vmac_data *pVMAC;
|
||||
unsigned int i = 0;
|
||||
bool status = false;
|
||||
|
||||
VMAC_Init();
|
||||
for (i = 0; i < VMAC_MAC_MAX; i++) {
|
||||
test_vmac_data.mac[i] = 1 + i;
|
||||
}
|
||||
test_vmac_data.mac_len = VMAC_MAC_MAX;
|
||||
status = VMAC_Add(device_id, &test_vmac_data);
|
||||
ct_test(pTest, status);
|
||||
pVMAC = VMAC_Find_By_Key(0);
|
||||
ct_test(pTest, pVMAC == NULL);
|
||||
pVMAC = VMAC_Find_By_Key(device_id);
|
||||
ct_test(pTest, pVMAC);
|
||||
status = VMAC_Different(pVMAC, &test_vmac_data);
|
||||
ct_test(pTest, !status);
|
||||
status = VMAC_Match(pVMAC, &test_vmac_data);
|
||||
ct_test(pTest, status);
|
||||
status = VMAC_Find_By_Data(&test_vmac_data, &test_device_id);
|
||||
ct_test(pTest, status);
|
||||
ct_test(pTest, test_device_id == device_id);
|
||||
status = VMAC_Delete(device_id);
|
||||
ct_test(pTest, status);
|
||||
pVMAC = VMAC_Find_By_Key(device_id);
|
||||
ct_test(pTest, pVMAC == NULL);
|
||||
VMAC_Cleanup();
|
||||
}
|
||||
|
||||
#ifdef TEST_VMAC
|
||||
int main(
|
||||
void)
|
||||
{
|
||||
Test *pTest;
|
||||
bool rc;
|
||||
|
||||
pTest = ct_create("BACnet VMAC", NULL);
|
||||
/* individual tests */
|
||||
rc = ct_addTestFunction(pTest, testVMAC);
|
||||
assert(rc);
|
||||
|
||||
ct_setStream(pTest, stdout);
|
||||
ct_run(pTest);
|
||||
(void) ct_report(pTest);
|
||||
ct_destroy(pTest);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
+258
@@ -0,0 +1,258 @@
|
||||
/*####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"
|
||||
|
||||
/** @file whohas.c Encode/Decode Who-Has requests */
|
||||
|
||||
/* 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->is_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,
|
||||
(int) 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 */
|
||||
uint16_t 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->is_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->is_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.is_object_name == data->is_object_name);
|
||||
/* Object ID */
|
||||
if (data->is_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.is_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.is_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.is_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 */
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
/*####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 "whois.h"
|
||||
|
||||
/** @file whois.c Encode/Decode Who-Is requests */
|
||||
|
||||
/* 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)
|
||||
{
|
||||
unsigned 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 BACNET_STATUS_ERROR;
|
||||
}
|
||||
if (apdu_len > (unsigned)len) {
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
if (decoded_value <= BACNET_MAX_INSTANCE) {
|
||||
if (pLow_limit) {
|
||||
*pLow_limit = decoded_value;
|
||||
}
|
||||
}
|
||||
if (apdu_len > (unsigned)len) {
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number, &len_value);
|
||||
if (tag_number != 1) {
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
if (apdu_len > (unsigned)len) {
|
||||
len += decode_unsigned(&apdu[len], len_value, &decoded_value);
|
||||
if (decoded_value <= BACNET_MAX_INSTANCE) {
|
||||
if (pHigh_limit) {
|
||||
*pHigh_limit = decoded_value;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
} else {
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
} else {
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
} else {
|
||||
if (pLow_limit) {
|
||||
*pLow_limit = -1;
|
||||
}
|
||||
if (pHigh_limit) {
|
||||
*pHigh_limit = -1;
|
||||
}
|
||||
len = 0;
|
||||
}
|
||||
|
||||
return (int)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 BACNET_STATUS_ERROR;
|
||||
}
|
||||
/* optional limits - must be used as a pair */
|
||||
if (apdu_len >= 2) {
|
||||
/* optional checking - most likely was already done prior to this call */
|
||||
if (apdu[0] != PDU_TYPE_UNCONFIRMED_SERVICE_REQUEST) {
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
if (apdu[1] != SERVICE_UNCONFIRMED_WHO_IS) {
|
||||
return BACNET_STATUS_ERROR;
|
||||
}
|
||||
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 = 0;
|
||||
int32_t test_high_limit = 0;
|
||||
|
||||
/* normal who-is without limits */
|
||||
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 != BACNET_STATUS_ERROR);
|
||||
ct_test(pTest, test_low_limit == low_limit);
|
||||
ct_test(pTest, test_high_limit == high_limit);
|
||||
|
||||
/* normal who-is with limits - complete range */
|
||||
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 != BACNET_STATUS_ERROR);
|
||||
ct_test(pTest, test_low_limit == low_limit);
|
||||
ct_test(pTest, test_high_limit == high_limit);
|
||||
}
|
||||
}
|
||||
/* abnormal case:
|
||||
who-is with no limits, but with APDU containing 2 limits */
|
||||
low_limit = 0;
|
||||
high_limit = 0;
|
||||
len = whois_encode_apdu(&apdu[0], low_limit, high_limit);
|
||||
ct_test(pTest, len > 0);
|
||||
apdu_len = len;
|
||||
low_limit = -1;
|
||||
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 != BACNET_STATUS_ERROR);
|
||||
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,388 @@
|
||||
/*####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 "wp.h"
|
||||
|
||||
/** @file wp.c Encode/Decode BACnet Write Property APDUs */
|
||||
#if BACNET_SVC_WP_A
|
||||
/* encode service */
|
||||
int wp_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_WRITE_PROPERTY_DATA * wpdata)
|
||||
{
|
||||
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, MAX_APDU);
|
||||
apdu[2] = invoke_id;
|
||||
apdu[3] = SERVICE_CONFIRMED_WRITE_PROPERTY; /* service choice */
|
||||
apdu_len = 4;
|
||||
len =
|
||||
encode_context_object_id(&apdu[apdu_len], 0, wpdata->object_type,
|
||||
wpdata->object_instance);
|
||||
apdu_len += len;
|
||||
len =
|
||||
encode_context_enumerated(&apdu[apdu_len], 1,
|
||||
wpdata->object_property);
|
||||
apdu_len += len;
|
||||
/* optional array index; ALL is -1 which is assumed when missing */
|
||||
if (wpdata->array_index != BACNET_ARRAY_ALL) {
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 2,
|
||||
wpdata->array_index);
|
||||
apdu_len += len;
|
||||
}
|
||||
/* propertyValue */
|
||||
len = encode_opening_tag(&apdu[apdu_len], 3);
|
||||
apdu_len += len;
|
||||
for (len = 0; len < wpdata->application_data_len; len++) {
|
||||
apdu[apdu_len + len] = wpdata->application_data[len];
|
||||
}
|
||||
apdu_len += wpdata->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 (wpdata->priority != BACNET_NO_PRIORITY) {
|
||||
len =
|
||||
encode_context_unsigned(&apdu[apdu_len], 4, wpdata->priority);
|
||||
apdu_len += len;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 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 * wpdata)
|
||||
{
|
||||
int len = 0;
|
||||
int tag_len = 0;
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value_type = 0;
|
||||
uint16_t type = 0; /* for decoding */
|
||||
uint32_t property = 0; /* for decoding */
|
||||
uint32_t unsigned_value = 0;
|
||||
int i = 0; /* loop counter */
|
||||
|
||||
/* check for value pointers */
|
||||
if (apdu_len && wpdata) {
|
||||
/* Tag 0: Object ID */
|
||||
if (!decode_is_context_tag(&apdu[len++], 0))
|
||||
return -1;
|
||||
len += decode_object_id(&apdu[len], &type, &wpdata->object_instance);
|
||||
wpdata->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);
|
||||
wpdata->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);
|
||||
wpdata->array_index = unsigned_value;
|
||||
} else
|
||||
wpdata->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 */
|
||||
wpdata->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 < wpdata->application_data_len; i++) {
|
||||
wpdata->application_data[i] = apdu[len + i];
|
||||
}
|
||||
/* add on the data length */
|
||||
len += wpdata->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 */
|
||||
wpdata->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)) {
|
||||
wpdata->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 * wpdata)
|
||||
{
|
||||
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, MAX_APDU); */
|
||||
*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,
|
||||
wpdata);
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void testWritePropertyTag(
|
||||
Test * pTest,
|
||||
BACNET_APPLICATION_DATA_VALUE * value)
|
||||
{
|
||||
BACNET_WRITE_PROPERTY_DATA wpdata = { 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;
|
||||
|
||||
wpdata.application_data_len =
|
||||
bacapp_encode_application_data(&wpdata.application_data[0], value);
|
||||
len = wp_encode_apdu(&apdu[0], invoke_id, &wpdata);
|
||||
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 == wpdata.object_type);
|
||||
ct_test(pTest, test_data.object_instance == wpdata.object_instance);
|
||||
ct_test(pTest, test_data.object_property == wpdata.object_property);
|
||||
ct_test(pTest, test_data.array_index == wpdata.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
|
||||
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 */
|
||||
@@ -0,0 +1,357 @@
|
||||
/**************************************************************************
|
||||
*
|
||||
* Copyright (C) 2011 Krzysztof Malorny <malornykrzysztof@gmail.com>
|
||||
* Contributions by Nikola Jelic 2011 <nikola.jelic@euroicc.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
*********************************************************************/
|
||||
#include <stdint.h>
|
||||
#include "bacapp.h"
|
||||
#include "bacenum.h"
|
||||
#include "bacdcode.h"
|
||||
#include "bacdef.h"
|
||||
#include "wp.h"
|
||||
#include "wpm.h"
|
||||
#include "string.h"
|
||||
|
||||
/** @file wpm.c Encode/Decode BACnet Write Property Multiple APDUs */
|
||||
|
||||
/** Decoding for WritePropertyMultiple service, object ID.
|
||||
* @ingroup DSWPM
|
||||
* This handler will be invoked by write_property_multiple handler
|
||||
* if it has been enabled by a call to apdu_set_confirmed_handler().
|
||||
* This function decodes only the first tagged entity, which is
|
||||
* an object identifier. This function will return an error if:
|
||||
* - the tag is not the right value
|
||||
* - the number of bytes is not enough to decode for this entity
|
||||
* - the subsequent tag number is incorrect
|
||||
*
|
||||
* @param apdu [in] The contents of the APDU buffer.
|
||||
* @param apdu_len [in] The length of the APDU buffer.
|
||||
* @param data [out] The BACNET_WRITE_PROPERTY_DATA structure
|
||||
* which will contain the reponse values or error.
|
||||
*/
|
||||
int wpm_decode_object_id(
|
||||
uint8_t * apdu,
|
||||
uint16_t apdu_len,
|
||||
BACNET_WRITE_PROPERTY_DATA * wp_data)
|
||||
{
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
uint32_t object_instance = 0;
|
||||
uint16_t object_type = 0;
|
||||
uint16_t len = 0;
|
||||
|
||||
if (apdu && (apdu_len > 5) && wp_data) {
|
||||
/* Context tag 0 - Object ID */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number, &len_value);
|
||||
if ((tag_number == 0) && (apdu_len > len)) {
|
||||
apdu_len -= len;
|
||||
if (apdu_len >= 4) {
|
||||
len +=
|
||||
decode_object_id(&apdu[len], &object_type,
|
||||
&object_instance);
|
||||
wp_data->object_type = object_type;
|
||||
wp_data->object_instance = object_instance;
|
||||
apdu_len -= len;
|
||||
} else {
|
||||
wp_data->error_code =
|
||||
ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
} else {
|
||||
wp_data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
/* just test for the next tag - no need to decode it here */
|
||||
/* Context tag 1: sequence of BACnetPropertyValue */
|
||||
if (apdu_len && !decode_is_opening_tag_number(&apdu[len], 1)) {
|
||||
wp_data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
} else {
|
||||
wp_data->error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
|
||||
return (int) len;
|
||||
}
|
||||
|
||||
|
||||
int wpm_decode_object_property(
|
||||
uint8_t * apdu,
|
||||
uint16_t apdu_len,
|
||||
BACNET_WRITE_PROPERTY_DATA * wp_data)
|
||||
{
|
||||
uint8_t tag_number = 0;
|
||||
uint32_t len_value = 0;
|
||||
uint32_t ulVal = 0;
|
||||
int len = 0, i = 0;
|
||||
|
||||
|
||||
if ((apdu) && (apdu_len) && (wp_data)) {
|
||||
wp_data->array_index = BACNET_ARRAY_ALL;
|
||||
wp_data->priority = BACNET_MAX_PRIORITY;
|
||||
wp_data->application_data_len = 0;
|
||||
/* tag 0 - Property Identifier */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number, &len_value);
|
||||
if (tag_number == 0) {
|
||||
len += decode_enumerated(&apdu[len], len_value, &ulVal);
|
||||
wp_data->object_property = ulVal;
|
||||
} else {
|
||||
wp_data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
|
||||
/* tag 1 - Property Array Index - optional */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number, &len_value);
|
||||
if (tag_number == 1) {
|
||||
len += decode_unsigned(&apdu[len], len_value, &ulVal);
|
||||
wp_data->array_index = ulVal;
|
||||
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
}
|
||||
/* tag 2 - Property Value */
|
||||
if ((tag_number == 2) && (decode_is_opening_tag(&apdu[len - 1]))) {
|
||||
len--;
|
||||
wp_data->application_data_len =
|
||||
bacapp_data_len(&apdu[len], (unsigned) (apdu_len - len),
|
||||
wp_data->object_property);
|
||||
len++;
|
||||
|
||||
/* copy application data */
|
||||
for (i = 0; i < wp_data->application_data_len; i++) {
|
||||
wp_data->application_data[i] = apdu[len + i];
|
||||
}
|
||||
len += wp_data->application_data_len;
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number,
|
||||
&len_value);
|
||||
/* closing tag 2 */
|
||||
if ((tag_number != 2) && (decode_is_closing_tag(&apdu[len - 1]))) {
|
||||
wp_data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
} else {
|
||||
wp_data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
|
||||
/* tag 3 - Priority - optional */
|
||||
len +=
|
||||
decode_tag_number_and_value(&apdu[len], &tag_number, &len_value);
|
||||
if (tag_number == 3) {
|
||||
len += decode_unsigned(&apdu[len], len_value, &ulVal);
|
||||
wp_data->priority = ulVal;
|
||||
} else
|
||||
len--;
|
||||
} else {
|
||||
wp_data->error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER;
|
||||
return BACNET_STATUS_REJECT;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* encode functions */
|
||||
int wpm_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_WRITE_PROP_MULTIPLE; /* service choice */
|
||||
apdu_len = 4;
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
|
||||
}
|
||||
|
||||
int wpm_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 WriteAccessSpecification */
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 1);
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int wpm_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;
|
||||
}
|
||||
|
||||
int wpm_encode_apdu_object_property(
|
||||
uint8_t * apdu,
|
||||
BACNET_WRITE_PROPERTY_DATA * wpdata)
|
||||
{
|
||||
int apdu_len = 0; /* total length of the apdu, return value */
|
||||
int len = 0;
|
||||
|
||||
if (apdu) {
|
||||
apdu_len =
|
||||
encode_context_enumerated(&apdu[0], 0, wpdata->object_property);
|
||||
/* optional array index */
|
||||
if (wpdata->array_index != BACNET_ARRAY_ALL) {
|
||||
apdu_len +=
|
||||
encode_context_unsigned(&apdu[apdu_len], 1,
|
||||
wpdata->array_index);
|
||||
}
|
||||
apdu_len += encode_opening_tag(&apdu[apdu_len], 2);
|
||||
for (len = 0; len < wpdata->application_data_len; len++) {
|
||||
apdu[apdu_len] = wpdata->application_data[len];
|
||||
apdu_len++;
|
||||
}
|
||||
apdu_len += encode_closing_tag(&apdu[apdu_len], 2);
|
||||
if (wpdata->priority != BACNET_NO_PRIORITY) {
|
||||
apdu_len +=
|
||||
encode_context_unsigned(&apdu[apdu_len], 3, wpdata->priority);
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int wpm_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
size_t max_apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_WRITE_ACCESS_DATA * write_access_data)
|
||||
{
|
||||
int apdu_len = 0;
|
||||
int len = 0;
|
||||
BACNET_WRITE_ACCESS_DATA *wpm_object; /* current object */
|
||||
uint8_t apdu_temp[MAX_APDU]; /* temp for data before copy */
|
||||
BACNET_PROPERTY_VALUE *wpm_property; /* current property */
|
||||
BACNET_WRITE_PROPERTY_DATA wpdata; /* for compatibility with wpm_encode_apdu_object_property function */
|
||||
|
||||
if (apdu) {
|
||||
len = wpm_encode_apdu_init(&apdu[0], invoke_id);
|
||||
apdu_len += len;
|
||||
|
||||
wpm_object = write_access_data;
|
||||
|
||||
while (wpm_object) {
|
||||
|
||||
len =
|
||||
wpm_encode_apdu_object_begin(&apdu[apdu_len],
|
||||
wpm_object->object_type, wpm_object->object_instance);
|
||||
apdu_len += len;
|
||||
|
||||
wpm_property = wpm_object->listOfProperties;
|
||||
|
||||
while (wpm_property) {
|
||||
wpdata.object_property = wpm_property->propertyIdentifier;
|
||||
wpdata.array_index = wpm_property->propertyArrayIndex;
|
||||
wpdata.priority = wpm_property->priority;
|
||||
wpdata.application_data_len = bacapp_encode_data(&apdu_temp[0],
|
||||
&wpm_property->value);
|
||||
memcpy(&wpdata.application_data[0], &apdu_temp[0],
|
||||
(size_t)wpdata.application_data_len);
|
||||
len =
|
||||
wpm_encode_apdu_object_property(&apdu[apdu_len], &wpdata);
|
||||
apdu_len += len;
|
||||
|
||||
wpm_property = wpm_property->next;
|
||||
}
|
||||
|
||||
len = wpm_encode_apdu_object_end(&apdu[apdu_len]);
|
||||
apdu_len += len;
|
||||
|
||||
wpm_object = wpm_object->next;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu_len;
|
||||
}
|
||||
|
||||
int wpm_ack_encode_apdu_init(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (apdu) {
|
||||
apdu[len++] = PDU_TYPE_SIMPLE_ACK;
|
||||
apdu[len++] = invoke_id;
|
||||
apdu[len++] = SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
int wpm_error_ack_encode_apdu(
|
||||
uint8_t * apdu,
|
||||
uint8_t invoke_id,
|
||||
BACNET_WRITE_PROPERTY_DATA * wp_data)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (apdu) {
|
||||
apdu[len++] = PDU_TYPE_ERROR;
|
||||
apdu[len++] = invoke_id;
|
||||
apdu[len++] = SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE;
|
||||
|
||||
len += encode_opening_tag(&apdu[len], 0);
|
||||
len += encode_application_enumerated(&apdu[len], wp_data->error_class);
|
||||
len += encode_application_enumerated(&apdu[len], wp_data->error_code);
|
||||
len += encode_closing_tag(&apdu[len], 0);
|
||||
|
||||
len += encode_opening_tag(&apdu[len], 1);
|
||||
len +=
|
||||
encode_context_object_id(&apdu[len], 0, wp_data->object_type,
|
||||
wp_data->object_instance);
|
||||
len +=
|
||||
encode_context_enumerated(&apdu[len], 1, wp_data->object_property);
|
||||
|
||||
if (wp_data->array_index != BACNET_ARRAY_ALL)
|
||||
len +=
|
||||
encode_context_unsigned(&apdu[len], 2, wp_data->array_index);
|
||||
len += encode_closing_tag(&apdu[len], 1);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
Reference in New Issue
Block a user