f408b6eb7f
* Expanded Object_List to Object_List[MAX_NUM_DEVICES] array to support per-device objects for virtual remote devices * Added multi-device iteration for COV handler, device timer, and intrinsic reporting * Added apps/gateway2 demo application * When MAX_NUM_DEVICES == 1, behavior is identical to the original implementation * Conditional compilation with macros ensures no impact on non-gateway applications --------- Signed-off-by: Hyeongjun Kim <hjun1.kim@samsung.com> Co-authored-by: haemeok-kang <haemeok.kang@samsung.com>
1579 lines
51 KiB
C
1579 lines
51 KiB
C
/**
|
|
* @file
|
|
* @brief Audit Log object, customize for your use
|
|
* @details An Audit Log object combines audit notifications
|
|
* from operation sources and operation targets and stores the
|
|
* combined record in an internal buffer for subsequent retrieval.
|
|
* Each timestamped buffer entry is called an audit log "record."
|
|
*
|
|
* Each Audit Log object maintains an internal, persistent, optionally
|
|
* fixed-size log buffer. This log buffer fills or grows as audit
|
|
* log records are added. If the log buffer becomes full, the least recent
|
|
* log records are overwritten when new log records are added.
|
|
* Log buffers are transferred as a list of BACnetAuditLogRecord values
|
|
* using the ReadRange and AuditLogQuery services. Each log record in the
|
|
* log buffer has an implied sequence number that is equal to the value
|
|
* of the Total_Record_Count property immediately after the record is added.
|
|
* See Clause 19.6 for a full description of how audit notifications are
|
|
* added to audit logs.
|
|
*
|
|
* As records are added into the log, the Audit Log object will scan
|
|
* existing entries for a matching record. A record is a match if:
|
|
* (a) the record contains the timestamp for the opposite actor (the
|
|
* record contains the operation source timestamp when merging
|
|
* in an operation target notification and vice-versa);
|
|
* (b) the operation-source, operation, invoke-id, target-device,
|
|
* target-property, are all equal;
|
|
* (c) if the user-id, user-role, target-value fields are provided
|
|
* in both notifications then they are equal; and
|
|
* (d) if the source-timestamp and target-timestamp values are
|
|
* approximately equal (+/- APDU_Timeout * 2).
|
|
*
|
|
* If a match is found, the existing log record is updated.
|
|
* Otherwise, a new record is created. If a match is found,
|
|
* and it already contains both an operation source and an
|
|
* operation target portion, then the notification is dropped.
|
|
* When creating a new record, those fields which are not supplied
|
|
* in the notification (such as the 'Source Timestamp' when a
|
|
* server notification is received) shall be absent from the record.
|
|
* When updating an existing record, those fields not supplied in the
|
|
* original notification are updated from the new notification, if present.
|
|
* For the 'Current Value' field, a value provided by the operation target
|
|
* device shall always take precedence over a value provided by an operation
|
|
* source device. As such, if the values provided in the peer notifications
|
|
* differ, the operation target value shall be the one used in the record.
|
|
*
|
|
* Logging may be enabled and disabled through the Enable property.
|
|
* Audit Log enabling and disabling is recorded in the audit log buffer.
|
|
*
|
|
* Unlike other log objects, Audit Log objects do not use the BUFFER_READY
|
|
* event algorithm.
|
|
*
|
|
* The acquisition of log records by remote devices has
|
|
* no effect upon the state of the Audit Log object itself. This allows
|
|
* completely independent, but properly sequential, access to its log records
|
|
* by all remote devices. Any remote device can independently update its
|
|
* log records at any time.
|
|
*
|
|
* Audit Log objects may optionally support forwarding of audit notifications
|
|
* to “parent” audit logs. This functionality improves the reliability of the
|
|
* audit system by allowing intermediaries to buffer audit notifications
|
|
* in the case where the ultimate audit logger is offline for a short period
|
|
* of time. It is expected that intermediaries be capable of storing a larger
|
|
* number of records than devices which report auditable actions. It is also
|
|
* useful for buffering of audit notifications so they can be sent in bulk to
|
|
* the parent audit log. When operating in this mode, with the
|
|
* Delete_On_Forward property set to TRUE, the object is not required
|
|
* to perform audit notification matching and combining.
|
|
*
|
|
* @author Mikhail Antropov <michail.antropov@dsr-corporation.com>
|
|
* @author Steve Karg <skarg@users.sourceforge.net>
|
|
* @date July 2023
|
|
* @copyright SPDX-License-Identifier: MIT
|
|
*/
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
/* BACnet Stack defines - first */
|
|
#include "bacnet/bacdef.h"
|
|
/* BACnet Stack API */
|
|
#include "bacnet/bacdcode.h"
|
|
#include "bacnet/bacapp.h"
|
|
#include "bacnet/bacaudit.h"
|
|
#include "bacnet/apdu.h"
|
|
#include "bacnet/bacdevobjpropref.h"
|
|
#include "bacnet/datetime.h"
|
|
#include "bacnet/rp.h"
|
|
#include "bacnet/wp.h"
|
|
#include "bacnet/basic/services.h"
|
|
#include "bacnet/basic/sys/keylist.h"
|
|
/* BACnet Stack Objects */
|
|
#include "bacnet/basic/object/device.h"
|
|
/* me! */
|
|
#include "auditlog.h"
|
|
|
|
#ifndef BACNET_AUDIT_LOG_RECORDS_MAX
|
|
#define BACNET_AUDIT_LOG_RECORDS_MAX 128
|
|
#endif
|
|
|
|
struct object_data {
|
|
bool Enable;
|
|
bool Out_Of_Service;
|
|
int Buffer_Size;
|
|
OS_Keylist Records;
|
|
int Record_Count_Total;
|
|
const char *Object_Name;
|
|
const char *Description;
|
|
void *Context;
|
|
};
|
|
/* Key List for storing the object data sorted by instance number */
|
|
static OS_Keylist Object_Lists[MAX_NUM_DEVICES];
|
|
#ifdef BAC_ROUTING
|
|
#define Object_List (Object_Lists[Routed_Device_Object_Index()])
|
|
#else
|
|
#define Object_List (Object_Lists[0])
|
|
#endif
|
|
|
|
static const int32_t Properties_Required[] = {
|
|
/* required properties that are supported for this object */
|
|
PROP_OBJECT_IDENTIFIER,
|
|
PROP_OBJECT_NAME,
|
|
PROP_OBJECT_TYPE,
|
|
PROP_STATUS_FLAGS,
|
|
PROP_EVENT_STATE,
|
|
PROP_ENABLE,
|
|
PROP_BUFFER_SIZE,
|
|
PROP_LOG_BUFFER,
|
|
PROP_RECORD_COUNT,
|
|
PROP_TOTAL_RECORD_COUNT,
|
|
-1
|
|
};
|
|
|
|
static const int32_t Properties_Optional[] = { PROP_DESCRIPTION, -1 };
|
|
|
|
static const int32_t Properties_Proprietary[] = { -1 };
|
|
|
|
static const int32_t BACnetARRAY_Properties[] = {
|
|
/* standard properties that are arrays for this object */
|
|
PROP_LOG_BUFFER,
|
|
PROP_EVENT_TIME_STAMPS,
|
|
PROP_EVENT_MESSAGE_TEXTS,
|
|
PROP_EVENT_MESSAGE_TEXTS_CONFIG,
|
|
PROP_TAGS,
|
|
-1
|
|
};
|
|
|
|
static const int32_t Writable_Properties[] = {
|
|
/* Every object shall have a Writable Property_List property
|
|
which is a BACnetARRAY of property identifiers,
|
|
one property identifier for each property within this object
|
|
that is always writable. */
|
|
PROP_ENABLE, PROP_BUFFER_SIZE, -1
|
|
};
|
|
|
|
/**
|
|
* @brief Determine if the object property is a BACnetARRAY property
|
|
* @param object_property - object-property to be checked
|
|
* @return true if the property is a BACnetARRAY property
|
|
*/
|
|
static bool BACnetARRAY_Property(int object_property)
|
|
{
|
|
return property_list_member(BACnetARRAY_Properties, object_property);
|
|
}
|
|
|
|
/**
|
|
* Returns the list of required, optional, and proprietary properties.
|
|
* Used by ReadPropertyMultiple service.
|
|
*
|
|
* @param pRequired - pointer to list of int terminated by -1, of
|
|
* BACnet required properties for this object.
|
|
* @param pOptional - pointer to list of int terminated by -1, of
|
|
* BACnet optkional properties for this object.
|
|
* @param pProprietary - pointer to list of int terminated by -1, of
|
|
* BACnet proprietary properties for this object.
|
|
*/
|
|
void Audit_Log_Property_Lists(
|
|
const int32_t **pRequired,
|
|
const int32_t **pOptional,
|
|
const int32_t **pProprietary)
|
|
{
|
|
if (pRequired) {
|
|
*pRequired = Properties_Required;
|
|
}
|
|
if (pOptional) {
|
|
*pOptional = Properties_Optional;
|
|
}
|
|
if (pProprietary) {
|
|
*pProprietary = Properties_Proprietary;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the list of writable properties for an Audit Log object
|
|
* @param object_instance - object-instance number of the object
|
|
* @param properties - Pointer to the pointer of writable properties.
|
|
*/
|
|
void Audit_Log_Writable_Property_List(
|
|
uint32_t object_instance, const int32_t **properties)
|
|
{
|
|
(void)object_instance;
|
|
if (properties) {
|
|
*properties = Writable_Properties;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Gets an object from the list using an instance number as the key
|
|
* @param object_instance - object-instance number of the object
|
|
* @return object found in the list, or NULL if not found
|
|
*/
|
|
static struct object_data *Object_Data(uint32_t object_instance)
|
|
{
|
|
return Keylist_Data(Object_List, object_instance);
|
|
}
|
|
|
|
/**
|
|
* @brief Determines if a given object instance is valid
|
|
* @param object_instance - object-instance number of the object
|
|
* @return true if the instance is valid, and false if not
|
|
*/
|
|
bool Audit_Log_Valid_Instance(uint32_t object_instance)
|
|
{
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Determines the number of objects
|
|
* @return Number of objects
|
|
*/
|
|
unsigned Audit_Log_Count(void)
|
|
{
|
|
return Keylist_Count(Object_List);
|
|
}
|
|
|
|
/**
|
|
* @brief Determines the object instance-number for a given 0..N index
|
|
* of objects where N is the count.
|
|
* @param index - 0..N value
|
|
* @return object instance-number for a valid given index, or UINT32_MAX
|
|
*/
|
|
uint32_t Audit_Log_Index_To_Instance(unsigned index)
|
|
{
|
|
uint32_t instance = UINT32_MAX;
|
|
|
|
(void)Keylist_Index_Key(Object_List, index, &instance);
|
|
|
|
return instance;
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, determines a 0..N index
|
|
* of objects where N is the count.
|
|
* @param object_instance - object-instance number of the object
|
|
* @return index for the given instance-number, or count if not valid.
|
|
*/
|
|
unsigned Audit_Log_Instance_To_Index(uint32_t object_instance)
|
|
{
|
|
return Keylist_Index(Object_List, object_instance);
|
|
}
|
|
|
|
/**
|
|
* For a given object instance-number, returns the Audit Log entity by index.
|
|
*
|
|
* @param object_instance - object-instance number of the object
|
|
* @param index - index of entity
|
|
*
|
|
* @return Audit Log entity, or NULL if not found.
|
|
*/
|
|
BACNET_AUDIT_LOG_RECORD *
|
|
Audit_Log_Record_Entry(uint32_t object_instance, uint32_t index)
|
|
{
|
|
BACNET_AUDIT_LOG_RECORD *entry = NULL;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
entry = Keylist_Data_Index(pObject->Records, index);
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
/**
|
|
* @brief Delete a record entry from the log buffer
|
|
* @param object_instance - object-instance number of the object
|
|
* @param index - record index of entity
|
|
*/
|
|
void Audit_Log_Record_Entry_Delete(uint32_t object_instance, uint32_t index)
|
|
{
|
|
BACNET_AUDIT_LOG_RECORD *entry = NULL;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
entry = Keylist_Data_Delete(pObject->Records, index);
|
|
free(entry);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* For a given object instance-number, adds a Audit Log entity to entities list.
|
|
*
|
|
* @note If the log buffer becomes full, the least recent log records are
|
|
* overwritten when new log records are added.
|
|
*
|
|
* @param object_instance - object-instance number of the object
|
|
* @param entity - Audit Log entity
|
|
*
|
|
* @return true if the entity is add successfully.
|
|
*/
|
|
bool Audit_Log_Record_Entry_Add(
|
|
uint32_t object_instance, const BACNET_AUDIT_LOG_RECORD *value)
|
|
{
|
|
BACNET_AUDIT_LOG_RECORD *entry = NULL;
|
|
struct object_data *pObject;
|
|
int index;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (!pObject) {
|
|
return false;
|
|
}
|
|
if (Keylist_Count(pObject->Records) >= pObject->Buffer_Size) {
|
|
/* log is full, so delete oldest record before adding a new record */
|
|
entry = Keylist_Data_Delete_By_Index(pObject->Records, 0);
|
|
}
|
|
if (!entry) {
|
|
entry = calloc(1, sizeof(BACNET_AUDIT_LOG_RECORD));
|
|
if (!entry) {
|
|
return false;
|
|
}
|
|
}
|
|
memcpy(entry, value, sizeof(BACNET_AUDIT_LOG_RECORD));
|
|
index =
|
|
Keylist_Data_Add(pObject->Records, pObject->Record_Count_Total, entry);
|
|
if (index < 0) {
|
|
free(entry);
|
|
return false;
|
|
}
|
|
pObject->Record_Count_Total++;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the log record maximum length for this object instance
|
|
* @param object_instance - object-instance number of the object
|
|
* @return maximum number of log records
|
|
* @note For products that support very large log objects,
|
|
* the value 2^32-1 is reserved to indicate that the buffer size is
|
|
* unknown and is constrained solely by currently available resources.
|
|
*/
|
|
uint32_t Audit_Log_Buffer_Size(uint32_t object_instance)
|
|
{
|
|
struct object_data *pObject;
|
|
pObject = Object_Data(object_instance);
|
|
if (!pObject) {
|
|
return 0;
|
|
}
|
|
|
|
return pObject->Buffer_Size;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the log record maximum length for this object instance
|
|
* @param object_instance - object-instance number of the object
|
|
* @param buffer_size - maximum number of log records
|
|
* @return true if the maximum number of log records is set
|
|
*/
|
|
bool Audit_Log_Buffer_Size_Set(uint32_t object_instance, uint32_t buffer_size)
|
|
{
|
|
struct object_data *pObject;
|
|
BACNET_AUDIT_LOG_RECORD *entry = NULL;
|
|
int i;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (!pObject) {
|
|
return false;
|
|
}
|
|
if (buffer_size > INT_MAX) {
|
|
return false;
|
|
}
|
|
if (buffer_size < pObject->Buffer_Size) {
|
|
/* The disposition of existing log records when Buffer_Size is written
|
|
is a local matter. We can shrink the log buffer. */
|
|
for (i = buffer_size; i < pObject->Buffer_Size; i++) {
|
|
entry = Keylist_Data_Delete_By_Index(pObject->Records, i);
|
|
free(entry);
|
|
}
|
|
}
|
|
pObject->Buffer_Size = buffer_size;
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* For a given object instance-number, loads the object-name into
|
|
* a characterstring. Note that the object name must be unique
|
|
* within this device.
|
|
*
|
|
* @param object_instance - object-instance number of the object
|
|
* @param object_name - holds the object-name retrieved
|
|
*
|
|
* @return true if object-name was retrieved
|
|
*/
|
|
bool Audit_Log_Object_Name(
|
|
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject;
|
|
char name_text[32] = "AUDIT-LOG-4194303";
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
if (pObject->Object_Name) {
|
|
status =
|
|
characterstring_init_ansi(object_name, pObject->Object_Name);
|
|
} else {
|
|
snprintf(
|
|
name_text, sizeof(name_text), "AUDIT-LOG-%lu",
|
|
(unsigned long)object_instance);
|
|
status = characterstring_init_ansi(object_name, name_text);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* For a given object instance-number, sets the object-name
|
|
* Note that the object name must be unique within this device.
|
|
*
|
|
* @param object_instance - object-instance number of the object
|
|
* @param new_name - holds the object-name to be set
|
|
*
|
|
* @return true if object-name was set
|
|
*/
|
|
bool Audit_Log_Name_Set(uint32_t object_instance, const char *new_name)
|
|
{
|
|
bool status = false; /* return value */
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
status = true;
|
|
pObject->Object_Name = new_name;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Return the object name C string
|
|
* @param object_instance [in] BACnet object instance number
|
|
* @return object name or NULL if not found
|
|
*/
|
|
const char *Audit_Log_Name_ASCII(uint32_t object_instance)
|
|
{
|
|
const char *name = NULL;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
name = pObject->Object_Name;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* For a given object instance-number, returns the description
|
|
*
|
|
* @param object_instance - object-instance number of the object
|
|
*
|
|
* @return description text or NULL if not found
|
|
*/
|
|
const char *Audit_Log_Description(uint32_t object_instance)
|
|
{
|
|
const char *name = NULL;
|
|
const struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
if (pObject->Description) {
|
|
name = pObject->Description;
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* For a given object instance-number, sets the description
|
|
*
|
|
* @param object_instance - object-instance number of the object
|
|
* @param new_name - holds the description to be set
|
|
*
|
|
* @return true if object-name was set
|
|
*/
|
|
bool Audit_Log_Description_Set(uint32_t object_instance, const char *new_name)
|
|
{
|
|
bool status = false; /* return value */
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
status = true;
|
|
pObject->Description = new_name;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Determines a object enabled flag state
|
|
*
|
|
* @note Logging occurs if and only if Enable is TRUE.
|
|
* Log_Buffer records of type log-status are recorded
|
|
* without regard to the value of the Enable property.
|
|
*
|
|
* @param object_instance - object-instance number of the object
|
|
* @return enabled status flag
|
|
*/
|
|
bool Audit_Log_Enable(uint32_t object_instance)
|
|
{
|
|
bool value = false;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
value = pObject->Enable;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* @brief Apply the log enabled algormithm
|
|
* @param pObject - object data
|
|
* @param enable log enable flag
|
|
* @return true if the log enable flag is applied
|
|
*/
|
|
bool Audit_Log_Enable_Set(uint32_t object_instance, bool enable)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
if (pObject->Enable != enable) {
|
|
/* Only trigger this validation on a potential change of state */
|
|
pObject->Enable = enable;
|
|
if (enable == false) {
|
|
Audit_Log_Record_Status_Insert(
|
|
object_instance, LOG_STATUS_LOG_DISABLED, true);
|
|
} else {
|
|
Audit_Log_Record_Status_Insert(
|
|
object_instance, LOG_STATUS_LOG_DISABLED, false);
|
|
}
|
|
}
|
|
status = true;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, sets the object enabled flag
|
|
* @param object_instance - object-instance number of the object
|
|
* @param enable - holds the value to be set
|
|
* @param error_class - BACnet error class
|
|
* @param error_code - BACnet error code
|
|
* @return true if set
|
|
*/
|
|
static bool Audit_Log_Enable_Write(
|
|
uint32_t object_instance,
|
|
bool enable,
|
|
BACNET_ERROR_CLASS *error_class,
|
|
BACNET_ERROR_CODE *error_code)
|
|
{
|
|
bool status = false;
|
|
|
|
status = Audit_Log_Enable_Set(object_instance, enable);
|
|
if (!status) {
|
|
*error_class = ERROR_CLASS_OBJECT;
|
|
*error_code = ERROR_CODE_LOG_BUFFER_FULL;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, determines the property value
|
|
* @param object_instance - object-instance number of the object
|
|
* @return the property value
|
|
*/
|
|
uint32_t Audit_Log_Record_Count(uint32_t object_instance)
|
|
{
|
|
uint32_t record_count = 0;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
record_count = Keylist_Count(pObject->Records);
|
|
}
|
|
|
|
return record_count;
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, determines the property value
|
|
* @param object_instance - object-instance number of the object
|
|
* @return the property value
|
|
*/
|
|
uint32_t Audit_Log_Total_Record_Count(uint32_t object_instance)
|
|
{
|
|
uint32_t total_count = 0;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
total_count = pObject->Record_Count_Total;
|
|
}
|
|
|
|
return total_count;
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, writes the property value
|
|
* @details If writable, it may not be written when Enable is TRUE.
|
|
* @param object_instance - object-instance number of the object
|
|
* @param buffer_size - holds the value to be set
|
|
* @param error_class - BACnet error class
|
|
* @param error_code - BACnet error code
|
|
* @return true if set
|
|
*/
|
|
bool Audit_Log_Buffer_Size_Write(
|
|
uint32_t object_instance,
|
|
uint32_t buffer_size,
|
|
BACNET_ERROR_CLASS *error_class,
|
|
BACNET_ERROR_CODE *error_code)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (pObject) {
|
|
if (pObject->Enable) {
|
|
*error_class = ERROR_CLASS_PROPERTY;
|
|
*error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
|
} else if (buffer_size > INT_MAX) {
|
|
/* keylist library uses 'int' so that is our limit */
|
|
*error_class = ERROR_CLASS_PROPERTY;
|
|
*error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
|
} else {
|
|
status = Audit_Log_Buffer_Size_Set(object_instance, buffer_size);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* ReadProperty handler for this object. For the given ReadProperty
|
|
* data, the application_data is loaded or the error flags are set.
|
|
*
|
|
* @param rpdata - BACNET_READ_PROPERTY_DATA data, including
|
|
* requested data and space for the reply, or error response.
|
|
*
|
|
* @return number of APDU bytes in the response, or
|
|
* BACNET_STATUS_ERROR on error.
|
|
*/
|
|
int Audit_Log_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
|
|
{
|
|
int apdu_len = 0; /* return value */
|
|
BACNET_CHARACTER_STRING char_string = { 0 };
|
|
BACNET_BIT_STRING bit_string = { 0 };
|
|
uint8_t *apdu = NULL;
|
|
|
|
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
|
|
(rpdata->application_data_len == 0)) {
|
|
return 0;
|
|
}
|
|
apdu = rpdata->application_data;
|
|
switch (rpdata->object_property) {
|
|
case PROP_OBJECT_IDENTIFIER:
|
|
apdu_len = encode_application_object_id(
|
|
&apdu[0], rpdata->object_type, rpdata->object_instance);
|
|
break;
|
|
case PROP_OBJECT_NAME:
|
|
Audit_Log_Object_Name(rpdata->object_instance, &char_string);
|
|
apdu_len =
|
|
encode_application_character_string(&apdu[0], &char_string);
|
|
break;
|
|
case PROP_OBJECT_TYPE:
|
|
apdu_len =
|
|
encode_application_enumerated(&apdu[0], rpdata->object_type);
|
|
break;
|
|
case PROP_ENABLE:
|
|
apdu_len = encode_application_boolean(
|
|
&apdu[0], Audit_Log_Enable(rpdata->object_instance));
|
|
break;
|
|
case PROP_BUFFER_SIZE:
|
|
apdu_len = encode_application_unsigned(
|
|
&apdu[0], Audit_Log_Buffer_Size(rpdata->object_instance));
|
|
break;
|
|
case PROP_LOG_BUFFER:
|
|
/* You can only read the buffer via the ReadRange service */
|
|
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
|
rpdata->error_code = ERROR_CODE_READ_ACCESS_DENIED;
|
|
apdu_len = BACNET_STATUS_ERROR;
|
|
break;
|
|
case PROP_RECORD_COUNT:
|
|
apdu_len = encode_application_unsigned(
|
|
&apdu[0], Audit_Log_Record_Count(rpdata->object_instance));
|
|
break;
|
|
case PROP_TOTAL_RECORD_COUNT:
|
|
apdu_len = encode_application_unsigned(
|
|
&apdu[0],
|
|
Audit_Log_Total_Record_Count(rpdata->object_instance));
|
|
break;
|
|
case PROP_EVENT_STATE:
|
|
/* note: see the details in the standard on how to use this */
|
|
apdu_len =
|
|
encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL);
|
|
break;
|
|
case PROP_STATUS_FLAGS:
|
|
/* note: see the details in the standard on how to use these */
|
|
bitstring_init(&bit_string);
|
|
bitstring_set_bit(&bit_string, STATUS_FLAG_IN_ALARM, false);
|
|
bitstring_set_bit(&bit_string, STATUS_FLAG_FAULT, false);
|
|
/* OVERRIDDEN The value of this flag shall be Logical FALSE */
|
|
bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
|
|
/* OUT_OF_SERVICE The value of this flag shall be Logical FALSE */
|
|
bitstring_set_bit(&bit_string, STATUS_FLAG_OUT_OF_SERVICE, false);
|
|
apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
|
|
break;
|
|
case PROP_DESCRIPTION:
|
|
characterstring_init_ansi(
|
|
&char_string, Audit_Log_Description(rpdata->object_instance));
|
|
apdu_len =
|
|
encode_application_character_string(&apdu[0], &char_string);
|
|
break;
|
|
default:
|
|
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
|
rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
|
apdu_len = BACNET_STATUS_ERROR;
|
|
break;
|
|
}
|
|
/* only array properties can have array options */
|
|
if ((apdu_len >= 0) && (!BACnetARRAY_Property(rpdata->object_property)) &&
|
|
(rpdata->array_index != BACNET_ARRAY_ALL)) {
|
|
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
|
rpdata->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
|
apdu_len = BACNET_STATUS_ERROR;
|
|
}
|
|
|
|
return apdu_len;
|
|
}
|
|
|
|
/**
|
|
* WriteProperty handler for this object. For the given WriteProperty
|
|
* data, the application_data is loaded or the error flags are set.
|
|
*
|
|
* @param wp_data - BACNET_WRITE_PROPERTY_DATA data, including
|
|
* requested data and space for the reply, or error response.
|
|
*
|
|
* @return false if an error is loaded, true if no errors
|
|
*/
|
|
bool Audit_Log_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
|
{
|
|
bool status = false; /* return value */
|
|
int len = 0;
|
|
BACNET_APPLICATION_DATA_VALUE value;
|
|
|
|
/* decode the some of the request */
|
|
len = bacapp_decode_application_data(
|
|
wp_data->application_data, wp_data->application_data_len, &value);
|
|
/* FIXME: len < application_data_len: more data? */
|
|
if (len < 0) {
|
|
/* error while decoding - a value larger than we can handle */
|
|
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
|
wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE;
|
|
return false;
|
|
}
|
|
if ((!BACnetARRAY_Property(wp_data->object_property)) &&
|
|
(wp_data->array_index != BACNET_ARRAY_ALL)) {
|
|
/* only array properties can have array options */
|
|
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
|
wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
|
return false;
|
|
}
|
|
switch (wp_data->object_property) {
|
|
case PROP_ENABLE:
|
|
status = write_property_type_valid(
|
|
wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN);
|
|
if (status) {
|
|
status = Audit_Log_Enable_Write(
|
|
wp_data->object_instance, value.type.Boolean,
|
|
&wp_data->error_class, &wp_data->error_code);
|
|
}
|
|
break;
|
|
case PROP_BUFFER_SIZE:
|
|
status = write_property_type_valid(
|
|
wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
|
|
if (status) {
|
|
status = Audit_Log_Buffer_Size_Write(
|
|
wp_data->object_instance, value.type.Unsigned_Int,
|
|
&wp_data->error_class, &wp_data->error_code);
|
|
}
|
|
break;
|
|
default:
|
|
if (property_lists_member(
|
|
Properties_Required, Properties_Optional,
|
|
Properties_Proprietary, wp_data->object_property)) {
|
|
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
|
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
|
} else {
|
|
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
|
wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Inserts a status notification into a audit log
|
|
*
|
|
* @param instance - object-instance number of the object
|
|
* @param log_status - log status
|
|
* @param state - notificate state
|
|
*/
|
|
void Audit_Log_Record_Status_Insert(
|
|
uint32_t object_instance, BACNET_LOG_STATUS log_status, bool state)
|
|
{
|
|
BACNET_AUDIT_LOG_RECORD record;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (!pObject) {
|
|
return;
|
|
}
|
|
datetime_local(&record.timestamp.date, &record.timestamp.time, NULL, NULL);
|
|
record.tag = AUDIT_LOG_DATUM_TAG_STATUS;
|
|
record.log_datum.log_status = 0;
|
|
/* Note we set the bits in correct order so that we can place them directly
|
|
* into the bitstring structure later on when we have to encode them */
|
|
switch (log_status) {
|
|
case LOG_STATUS_LOG_DISABLED:
|
|
if (state) {
|
|
record.log_datum.log_status = 1 << LOG_STATUS_LOG_DISABLED;
|
|
}
|
|
break;
|
|
case LOG_STATUS_BUFFER_PURGED:
|
|
if (state) {
|
|
record.log_datum.log_status = 1 << LOG_STATUS_BUFFER_PURGED;
|
|
}
|
|
break;
|
|
case LOG_STATUS_LOG_INTERRUPTED:
|
|
if (state) {
|
|
record.log_datum.log_status = 1 << LOG_STATUS_LOG_INTERRUPTED;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
Audit_Log_Record_Entry_Add(object_instance, &record);
|
|
}
|
|
|
|
/**
|
|
* @brief Determines if a given log notification is the same as another
|
|
* @param object_instance - object-instance number of the object
|
|
* @param record - log record
|
|
* @return the index of the found log record, or -1 if not found
|
|
*/
|
|
static int Audit_Log_Record_Search(
|
|
uint32_t object_instance, BACNET_AUDIT_LOG_RECORD *record)
|
|
{
|
|
int i;
|
|
BACNET_AUDIT_LOG_RECORD *entry;
|
|
struct object_data *pObject;
|
|
|
|
if (!record) {
|
|
return -1;
|
|
}
|
|
pObject = Object_Data(object_instance);
|
|
if (!pObject) {
|
|
return -1;
|
|
}
|
|
for (i = 0; i < pObject->Buffer_Size; i++) {
|
|
entry = Audit_Log_Record_Entry(object_instance, i);
|
|
if (!entry) {
|
|
break;
|
|
}
|
|
if (entry->tag == record->tag) {
|
|
if (entry->tag == AUDIT_LOG_DATUM_TAG_STATUS) {
|
|
if (entry->log_datum.log_status ==
|
|
record->log_datum.log_status) {
|
|
return i;
|
|
}
|
|
} else if (entry->tag == AUDIT_LOG_DATUM_TAG_NOTIFICATION) {
|
|
if (bacnet_audit_log_notification_same(
|
|
&entry->log_datum.notification,
|
|
&record->log_datum.notification)) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Insert a notification record into a audit log.
|
|
*
|
|
* @param object_instance - object-instance number of the object
|
|
* @param notif - notificatione
|
|
*/
|
|
void Audit_Log_Record_Notification_Insert(
|
|
uint32_t object_instance, BACNET_AUDIT_NOTIFICATION *notification)
|
|
{
|
|
BACNET_AUDIT_LOG_RECORD seek_entry = { 0 };
|
|
struct object_data *pObject;
|
|
int index;
|
|
|
|
pObject = Object_Data(object_instance);
|
|
if (!pObject) {
|
|
return;
|
|
}
|
|
if (!Audit_Log_Enable(object_instance)) {
|
|
return;
|
|
}
|
|
/* As records are added into the log, the Audit Log object will scan
|
|
existing entries for a matching record. */
|
|
datetime_local(
|
|
&seek_entry.timestamp.date, &seek_entry.timestamp.time, NULL, NULL);
|
|
seek_entry.tag = AUDIT_LOG_DATUM_TAG_NOTIFICATION;
|
|
memcpy(
|
|
&seek_entry.log_datum.notification, notification,
|
|
sizeof(BACNET_AUDIT_NOTIFICATION));
|
|
index = Audit_Log_Record_Search(object_instance, &seek_entry);
|
|
if (index >= 0) {
|
|
/* If a match is found, the existing record is updated with the new
|
|
time stamp and the record is moved to the end of the list.
|
|
i.e. delete the old entry and add the new entry */
|
|
Audit_Log_Record_Entry_Delete(object_instance, index);
|
|
}
|
|
Audit_Log_Record_Entry_Add(object_instance, &seek_entry);
|
|
}
|
|
|
|
/**
|
|
* For a given read range request, encodes log records
|
|
*
|
|
* @param apdu - buffer to hold the bytes
|
|
* @param pRequest - the read range request
|
|
*
|
|
* @return number of bytes encoded, or 0 if unable to encode.
|
|
*/
|
|
int Audit_Log_Read_Range(uint8_t *apdu, BACNET_READ_RANGE_DATA *pRequest)
|
|
{
|
|
int apdu_len = 0;
|
|
|
|
/* buffer and buffer size is passed in BACNET_READ_RANGE_DATA */
|
|
(void)apdu;
|
|
/* Initialise result flags to all false */
|
|
bitstring_init(&pRequest->ResultFlags);
|
|
bitstring_set_bit(&pRequest->ResultFlags, RESULT_FLAG_FIRST_ITEM, false);
|
|
bitstring_set_bit(&pRequest->ResultFlags, RESULT_FLAG_LAST_ITEM, false);
|
|
bitstring_set_bit(&pRequest->ResultFlags, RESULT_FLAG_MORE_ITEMS, false);
|
|
pRequest->ItemCount = 0; /* Start out with nothing */
|
|
if ((pRequest->RequestType == RR_BY_POSITION) ||
|
|
(pRequest->RequestType == RR_READ_ALL)) {
|
|
apdu_len = Audit_Log_Read_Range_By_Position(pRequest);
|
|
} else if (pRequest->RequestType == RR_BY_SEQUENCE) {
|
|
apdu_len = Audit_Log_Read_Range_By_Sequence(pRequest);
|
|
} else {
|
|
apdu_len = Audit_Log_Read_Range_By_Time(pRequest);
|
|
}
|
|
|
|
return apdu_len;
|
|
}
|
|
|
|
/**
|
|
* @brief Handle encoding for the By Position and All options.
|
|
* Does All option by converting to a By Position request starting at index
|
|
* 1 and of maximum log size length.
|
|
* @param pRequest - the read range request
|
|
* @return number of bytes encoded, or 0 if unable to encode.
|
|
*/
|
|
int Audit_Log_Read_Range_By_Position(BACNET_READ_RANGE_DATA *pRequest)
|
|
{
|
|
int log_index = 0;
|
|
int apdu_len = 0;
|
|
size_t apdu_size;
|
|
int len;
|
|
uint8_t *apdu;
|
|
uint32_t record_count = 0;
|
|
BACNET_AUDIT_LOG_RECORD *entry = NULL;
|
|
int32_t iTemp = 0;
|
|
uint32_t uiIndex = 0; /* Current entry number */
|
|
uint32_t uiFirst = 0; /* Entry number we started encoding from */
|
|
uint32_t uiLast = 0; /* Entry number we finished encoding on */
|
|
uint32_t uiTarget = 0; /* Last entry we are required to encode */
|
|
|
|
record_count = Audit_Log_Record_Count(pRequest->object_instance);
|
|
/* See how much space we have */
|
|
apdu_size = pRequest->application_data_len - pRequest->Overhead;
|
|
if (pRequest->RequestType == RR_READ_ALL) {
|
|
/*
|
|
* Read all the list or as much as will fit in the buffer by selecting
|
|
* a range that covers the whole list and falling through to the next
|
|
* section of code
|
|
*/
|
|
pRequest->Count = record_count;
|
|
/* Starting at the beginning */
|
|
pRequest->Range.RefIndex = 1;
|
|
}
|
|
if (pRequest->Count < 0) {
|
|
/*
|
|
* negative count means work from index backwards
|
|
*
|
|
* Convert from end index/negative count to
|
|
* start index/positive count and then process as
|
|
* normal. This assumes that the order to return items
|
|
* is always first to last, if this is not true we will
|
|
* have to handle this differently.
|
|
*
|
|
* Note: We need to be careful about how we convert these
|
|
* values due to the mix of signed and unsigned types - don't
|
|
* try to optimise the code unless you understand all the
|
|
* implications of the data type conversions!
|
|
*/
|
|
/* pull out and convert to signed */
|
|
iTemp = pRequest->Range.RefIndex;
|
|
/* Adjust backwards, remember count is -ve */
|
|
iTemp += pRequest->Count + 1;
|
|
if (iTemp < 1) {
|
|
/* if count is too much, return from 1 to start index */
|
|
pRequest->Count = pRequest->Range.RefIndex;
|
|
pRequest->Range.RefIndex = 1;
|
|
} else {
|
|
/* Otherwise adjust the start index and make count +ve */
|
|
pRequest->Range.RefIndex = iTemp;
|
|
pRequest->Count = -pRequest->Count;
|
|
}
|
|
}
|
|
/* From here on in we only have a starting point and a positive count */
|
|
if (pRequest->Range.RefIndex > record_count) {
|
|
/* Nothing to return as we are past the end of the list */
|
|
return 0;
|
|
}
|
|
/* Index of last required entry */
|
|
uiTarget = pRequest->Range.RefIndex + pRequest->Count - 1;
|
|
if (uiTarget > record_count) {
|
|
/* Capped at end of list if necessary */
|
|
uiTarget = record_count;
|
|
}
|
|
uiIndex = pRequest->Range.RefIndex;
|
|
/* Record where we started from */
|
|
uiFirst = uiIndex;
|
|
apdu = pRequest->application_data;
|
|
while (uiIndex <= uiTarget) {
|
|
log_index = uiIndex - 1;
|
|
entry = Audit_Log_Record_Entry(pRequest->object_instance, log_index);
|
|
len = bacnet_audit_log_record_encode(NULL, entry);
|
|
if (len > (apdu_size - apdu_len)) {
|
|
/*
|
|
* Can't fit any more in! We just set the result flag to say there
|
|
* was more and drop out of the loop early
|
|
*/
|
|
bitstring_set_bit(
|
|
&pRequest->ResultFlags, RESULT_FLAG_MORE_ITEMS, true);
|
|
break;
|
|
}
|
|
len = bacnet_audit_log_record_encode(apdu, entry);
|
|
apdu += len;
|
|
apdu_len += len;
|
|
/* Record the last entry encoded */
|
|
uiLast = uiIndex;
|
|
/* and get ready for next one */
|
|
uiIndex++;
|
|
/* Chalk up another one for the response count */
|
|
pRequest->ItemCount++;
|
|
}
|
|
/* Set remaining result flags if necessary */
|
|
if (uiFirst == 1) {
|
|
bitstring_set_bit(&pRequest->ResultFlags, RESULT_FLAG_FIRST_ITEM, true);
|
|
}
|
|
if (uiLast == record_count) {
|
|
bitstring_set_bit(&pRequest->ResultFlags, RESULT_FLAG_LAST_ITEM, true);
|
|
}
|
|
|
|
return apdu_len;
|
|
}
|
|
|
|
/**
|
|
* Handle encoding for the By Sequence option.
|
|
* The fact that the buffer always has at least a single entry is used
|
|
* implicetly in the following as we don't have to handle the case of an
|
|
* empty buffer.
|
|
*
|
|
* @param apdu - buffer to hold the bytes
|
|
* @param pRequest - the read range request
|
|
*
|
|
* @return number of bytes encoded, or 0 if unable to encode.
|
|
*/
|
|
int Audit_Log_Read_Range_By_Sequence(BACNET_READ_RANGE_DATA *pRequest)
|
|
{
|
|
int apdu_len = 0;
|
|
size_t apdu_size;
|
|
int len;
|
|
uint8_t *apdu;
|
|
int record_index = 0;
|
|
uint32_t record_count;
|
|
uint32_t total_record_count;
|
|
BACNET_AUDIT_LOG_RECORD *entry = NULL;
|
|
uint32_t uiIndex = 0; /* Current entry number */
|
|
uint32_t uiFirst = 0; /* Entry number we started encoding from */
|
|
uint32_t uiLast = 0; /* Entry number we finished encoding on */
|
|
uint32_t uiSequence = 0; /* Tracking sequence number when encoding */
|
|
uint32_t uiFirstSeq = 0; /* Sequence number for 1st record in log */
|
|
uint32_t uiBegin = 0; /* Starting Sequence number for request */
|
|
uint32_t uiEnd = 0; /* Ending Sequence number for request */
|
|
bool bWrapReq =
|
|
false; /* Has request sequence range spanned the max for uint32_t? */
|
|
bool bWrapLog =
|
|
false; /* Has log sequence range spanned the max for uint32_t? */
|
|
|
|
/* See how much space we have */
|
|
apdu = pRequest->application_data;
|
|
apdu_size = pRequest->application_data_len - pRequest->Overhead;
|
|
record_count = Audit_Log_Record_Count(pRequest->object_instance);
|
|
total_record_count =
|
|
Audit_Log_Total_Record_Count(pRequest->object_instance);
|
|
/* Figure out the sequence number for the first record, last is
|
|
* ulTotalRecordCount */
|
|
uiFirstSeq = total_record_count - record_count - 1;
|
|
/* Calculate start and end sequence numbers from request */
|
|
if (pRequest->Count < 0) {
|
|
uiBegin = pRequest->Range.RefSeqNum + pRequest->Count + 1;
|
|
uiEnd = pRequest->Range.RefSeqNum;
|
|
} else {
|
|
uiBegin = pRequest->Range.RefSeqNum;
|
|
uiEnd = pRequest->Range.RefSeqNum + pRequest->Count - 1;
|
|
}
|
|
/* See if we have any wrap around situations */
|
|
if (uiBegin > uiEnd) {
|
|
bWrapReq = true;
|
|
}
|
|
if (uiFirstSeq > total_record_count) {
|
|
bWrapLog = true;
|
|
}
|
|
|
|
if ((bWrapReq == false) && (bWrapLog == false)) {
|
|
/* Simple case no wraps */
|
|
/* If no overlap between request range and buffer contents bail out */
|
|
if ((uiEnd < uiFirstSeq) || (uiBegin > total_record_count)) {
|
|
return (0);
|
|
}
|
|
/* Truncate range if necessary so it is guaranteed to lie
|
|
* between the first and last sequence numbers in the buffer
|
|
* inclusive.
|
|
*/
|
|
if (uiBegin < uiFirstSeq) {
|
|
uiBegin = uiFirstSeq;
|
|
}
|
|
|
|
if (uiEnd > total_record_count) {
|
|
uiEnd = total_record_count;
|
|
}
|
|
} else {
|
|
/* There are wrap arounds to contend with */
|
|
/* First check for non overlap condition as it is common to all */
|
|
if ((uiBegin > total_record_count) && (uiEnd < uiFirstSeq)) {
|
|
return (0);
|
|
}
|
|
|
|
if (bWrapLog == false) {
|
|
/* Only request range wraps */
|
|
if (uiEnd < uiFirstSeq) {
|
|
uiEnd = total_record_count;
|
|
if (uiBegin < uiFirstSeq) {
|
|
uiBegin = uiFirstSeq;
|
|
}
|
|
} else {
|
|
uiBegin = uiFirstSeq;
|
|
if (uiEnd > total_record_count) {
|
|
uiEnd = total_record_count;
|
|
}
|
|
}
|
|
} else if (bWrapReq == false) {
|
|
/* Only log wraps */
|
|
if (uiBegin > total_record_count) {
|
|
if (uiBegin > uiFirstSeq) {
|
|
uiBegin = uiFirstSeq;
|
|
}
|
|
} else {
|
|
if (uiEnd > total_record_count) {
|
|
uiEnd = total_record_count;
|
|
}
|
|
}
|
|
} else { /* Both wrap */
|
|
if (uiBegin < uiFirstSeq) {
|
|
uiBegin = uiFirstSeq;
|
|
}
|
|
|
|
if (uiEnd > total_record_count) {
|
|
uiEnd = total_record_count;
|
|
}
|
|
}
|
|
}
|
|
/* We now have a range that lies completely within the log buffer
|
|
* and we need to figure out where that starts in the buffer.
|
|
*/
|
|
uiIndex = uiBegin - uiFirstSeq + 1;
|
|
uiSequence = uiBegin;
|
|
uiFirst = uiIndex; /* Record where we started from */
|
|
while (uiSequence != uiEnd + 1) {
|
|
record_index = uiIndex - 1;
|
|
entry = Audit_Log_Record_Entry(pRequest->object_instance, record_index);
|
|
len = bacnet_audit_log_record_encode(NULL, entry);
|
|
if (len > (apdu_size - apdu_len)) {
|
|
/*
|
|
* Can't fit any more in! We just set the result flag to say there
|
|
* was more and drop out of the loop early
|
|
*/
|
|
bitstring_set_bit(
|
|
&pRequest->ResultFlags, RESULT_FLAG_MORE_ITEMS, true);
|
|
break;
|
|
}
|
|
len = bacnet_audit_log_record_encode(apdu, entry);
|
|
apdu += len;
|
|
apdu_len += len;
|
|
uiLast = uiIndex; /* Record the last entry encoded */
|
|
uiIndex++; /* and get ready for next one */
|
|
uiSequence++;
|
|
pRequest->ItemCount++; /* Chalk up another one for the response count */
|
|
}
|
|
|
|
/* Set remaining result flags if necessary */
|
|
if (uiFirst == 1) {
|
|
bitstring_set_bit(&pRequest->ResultFlags, RESULT_FLAG_FIRST_ITEM, true);
|
|
}
|
|
|
|
if (uiLast == record_count) {
|
|
bitstring_set_bit(&pRequest->ResultFlags, RESULT_FLAG_LAST_ITEM, true);
|
|
}
|
|
pRequest->FirstSequence = uiBegin;
|
|
|
|
return apdu_len;
|
|
}
|
|
|
|
/**
|
|
* Handle encoding for the By Time option.
|
|
* The fact that the buffer always has at least a single entry is used
|
|
* implicetly in the following as we don't have to handle the case of an
|
|
* empty buffer.
|
|
*
|
|
* @param apdu - buffer to hold the bytes
|
|
* @param pRequest - the read range request
|
|
*
|
|
* @return number of bytes encoded, or 0 if unable to encode.
|
|
*/
|
|
int Audit_Log_Read_Range_By_Time(BACNET_READ_RANGE_DATA *pRequest)
|
|
{
|
|
int apdu_len = 0;
|
|
size_t apdu_size;
|
|
int len;
|
|
uint8_t *apdu;
|
|
int record_index = 0;
|
|
uint32_t record_count;
|
|
uint32_t total_record_count;
|
|
BACNET_AUDIT_LOG_RECORD *entry = NULL;
|
|
int diff;
|
|
|
|
int32_t iTemp = 0;
|
|
int iCount = 0;
|
|
uint32_t uiIndex = 0; /* Current entry number */
|
|
uint32_t uiFirst = 0; /* Entry number we started encoding from */
|
|
uint32_t uiLast = 0; /* Entry number we finished encoding on */
|
|
uint32_t uiFirstSeq = 0; /* Sequence number for 1st record in log */
|
|
|
|
/* See how much space we have */
|
|
apdu = pRequest->application_data;
|
|
apdu_size = pRequest->application_data_len - pRequest->Overhead;
|
|
record_count = Audit_Log_Record_Count(pRequest->object_instance);
|
|
total_record_count =
|
|
Audit_Log_Total_Record_Count(pRequest->object_instance);
|
|
if (pRequest->Count < 0) {
|
|
/* Start at end of log and look for record which has
|
|
* timestamp greater than or equal to the reference.
|
|
*/
|
|
iCount = record_count - 1;
|
|
/* Start out with the sequence number for the last record */
|
|
uiFirstSeq = total_record_count;
|
|
for (;;) {
|
|
record_index = iCount;
|
|
entry =
|
|
Audit_Log_Record_Entry(pRequest->object_instance, record_index);
|
|
diff =
|
|
datetime_compare(&entry->timestamp, &pRequest->Range.RefTime);
|
|
if (diff < 0) {
|
|
/* If datetime1 is before datetime2, returns negative.*/
|
|
break;
|
|
}
|
|
uiFirstSeq--;
|
|
if (iCount) {
|
|
iCount--;
|
|
} else {
|
|
/* end of records, not found */
|
|
return 0;
|
|
}
|
|
}
|
|
/* We have an and point for our request,
|
|
* now work backwards to find where we should start from
|
|
*/
|
|
pRequest->Count = -pRequest->Count; /* Convert to +ve count */
|
|
/* If count would bring us back beyond the limits
|
|
* Of the buffer then pin it to the start of the buffer
|
|
* otherwise adjust starting point and sequence number
|
|
* appropriately.
|
|
*/
|
|
iTemp = pRequest->Count - 1;
|
|
if (iTemp > iCount) {
|
|
uiFirstSeq -= iCount;
|
|
pRequest->Count = iCount + 1;
|
|
iCount = 0;
|
|
} else {
|
|
uiFirstSeq -= iTemp;
|
|
iCount -= iTemp;
|
|
}
|
|
} else {
|
|
/* Start at beginning of log and look for 1st record which has
|
|
* timestamp greater than the reference time.
|
|
*/
|
|
iCount = 0;
|
|
/* Figure out the sequence number for the first record, last is
|
|
* ulTotalRecordCount */
|
|
uiFirstSeq = total_record_count - record_count - 1;
|
|
for (;;) {
|
|
record_index = iCount;
|
|
entry =
|
|
Audit_Log_Record_Entry(pRequest->object_instance, record_index);
|
|
diff =
|
|
datetime_compare(&entry->timestamp, &pRequest->Range.RefTime);
|
|
if (diff > 0) {
|
|
/* If datetime1 is after datetime2, returns positive.*/
|
|
break;
|
|
}
|
|
uiFirstSeq++;
|
|
iCount++;
|
|
if ((uint32_t)iCount == record_count) {
|
|
return (0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We now have a starting point for the operation and a +ve count */
|
|
uiIndex = iCount + 1; /* Convert to BACnet 1 based reference */
|
|
uiFirst = uiIndex; /* Record where we started from */
|
|
iCount = pRequest->Count;
|
|
while (iCount != 0) {
|
|
record_index = uiIndex - 1;
|
|
entry = Audit_Log_Record_Entry(pRequest->object_instance, record_index);
|
|
len = bacnet_audit_log_record_encode(NULL, entry);
|
|
if (len > (apdu_size - apdu_len)) {
|
|
/*
|
|
* Can't fit any more in! We just set the result flag to say there
|
|
* was more and drop out of the loop early
|
|
*/
|
|
bitstring_set_bit(
|
|
&pRequest->ResultFlags, RESULT_FLAG_MORE_ITEMS, true);
|
|
break;
|
|
}
|
|
len = bacnet_audit_log_record_encode(apdu, entry);
|
|
apdu += len;
|
|
apdu_len += len;
|
|
uiLast = uiIndex; /* Record the last entry encoded */
|
|
uiIndex++; /* and get ready for next one */
|
|
pRequest->ItemCount++; /* Chalk up another one for the response count */
|
|
iCount--; /* And finally cross another one off the requested count */
|
|
|
|
if (uiIndex > record_count) {
|
|
/* Finish up if we hit the end of the log */
|
|
break;
|
|
}
|
|
}
|
|
/* Set remaining result flags if necessary */
|
|
if (uiFirst == 1) {
|
|
bitstring_set_bit(&pRequest->ResultFlags, RESULT_FLAG_FIRST_ITEM, true);
|
|
}
|
|
if (uiLast == record_count) {
|
|
bitstring_set_bit(&pRequest->ResultFlags, RESULT_FLAG_LAST_ITEM, true);
|
|
}
|
|
pRequest->FirstSequence = uiFirstSeq;
|
|
|
|
return apdu_len;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the context used with a specific object instance
|
|
* @param object_instance [in] BACnet object instance number
|
|
* @param context [in] pointer to the context
|
|
*/
|
|
void *Audit_Log_Context_Get(uint32_t object_instance)
|
|
{
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
return pObject->Context;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Set the context used with a specific object instance
|
|
* @param object_instance [in] BACnet object instance number
|
|
* @param context [in] pointer to the context
|
|
*/
|
|
void Audit_Log_Context_Set(uint32_t object_instance, void *context)
|
|
{
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
pObject->Context = context;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Creates a Audit Log object
|
|
* @param object_instance - object-instance number of the object
|
|
* @return object_instance if the object is created, else BACNET_MAX_INSTANCE
|
|
*/
|
|
uint32_t Audit_Log_Create(uint32_t object_instance)
|
|
{
|
|
struct object_data *pObject = NULL;
|
|
int index = 0;
|
|
|
|
if (!Object_List) {
|
|
Object_List = Keylist_Create();
|
|
}
|
|
if (object_instance > BACNET_MAX_INSTANCE) {
|
|
return BACNET_MAX_INSTANCE;
|
|
} else if (object_instance == BACNET_MAX_INSTANCE) {
|
|
/* wildcard instance */
|
|
/* the Object_Identifier property of the newly created object
|
|
shall be initialized to a value that is unique within the
|
|
responding BACnet-user device. The method used to generate
|
|
the object identifier is a local matter.*/
|
|
object_instance = Keylist_Next_Empty_Key(Object_List, 1);
|
|
}
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (!pObject) {
|
|
pObject = calloc(1, sizeof(struct object_data));
|
|
if (!pObject) {
|
|
return BACNET_MAX_INSTANCE;
|
|
}
|
|
pObject->Object_Name = NULL;
|
|
pObject->Description = NULL;
|
|
pObject->Records = Keylist_Create();
|
|
pObject->Buffer_Size = BACNET_AUDIT_LOG_RECORDS_MAX;
|
|
pObject->Enable = false;
|
|
pObject->Out_Of_Service = false;
|
|
/* add to list */
|
|
index = Keylist_Data_Add(Object_List, object_instance, pObject);
|
|
if (index < 0) {
|
|
free(pObject);
|
|
return BACNET_MAX_INSTANCE;
|
|
}
|
|
}
|
|
|
|
return object_instance;
|
|
}
|
|
|
|
/**
|
|
* @brief Deletes all the Audit Logs and their data
|
|
*/
|
|
static void Audit_Log_Records_Cleanup(OS_Keylist list)
|
|
{
|
|
BACNET_AUDIT_LOG_RECORD *entry;
|
|
|
|
while (Keylist_Count(list) > 0) {
|
|
entry = Keylist_Data_Pop(list);
|
|
free(entry);
|
|
}
|
|
Keylist_Delete(list);
|
|
}
|
|
|
|
/**
|
|
* @brief Deletes an Audit Log object
|
|
* @param object_instance - object-instance number of the object
|
|
* @return true if the object is deleted
|
|
*/
|
|
bool Audit_Log_Delete(uint32_t object_instance)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject = NULL;
|
|
|
|
pObject = Keylist_Data_Delete(Object_List, object_instance);
|
|
if (pObject) {
|
|
Audit_Log_Records_Cleanup(pObject->Records);
|
|
free(pObject);
|
|
status = true;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Deletes all the Audit Logs and their data
|
|
*/
|
|
void Audit_Log_Cleanup(void)
|
|
{
|
|
struct object_data *pObject;
|
|
uint16_t dev_id;
|
|
#ifdef BAC_ROUTING
|
|
uint16_t current_dev_id = Routed_Device_Object_Index();
|
|
#endif
|
|
|
|
for (dev_id = 0; dev_id < MAX_NUM_DEVICES; dev_id++) {
|
|
#ifdef BAC_ROUTING
|
|
Set_Routed_Device_Object_Index(dev_id);
|
|
#endif
|
|
if (Object_List) {
|
|
do {
|
|
pObject = Keylist_Data_Pop(Object_List);
|
|
if (pObject) {
|
|
Audit_Log_Records_Cleanup(pObject->Records);
|
|
free(pObject);
|
|
}
|
|
} while (pObject);
|
|
Keylist_Delete(Object_List);
|
|
Object_List = NULL;
|
|
}
|
|
}
|
|
|
|
#ifdef BAC_ROUTING
|
|
Set_Routed_Device_Object_Index(current_dev_id);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @brief Initializes the Audit Log object data
|
|
*/
|
|
void Audit_Log_Init(void)
|
|
{
|
|
uint16_t dev_id;
|
|
#ifdef BAC_ROUTING
|
|
uint16_t current_dev_id = Routed_Device_Object_Index();
|
|
#endif
|
|
|
|
for (dev_id = 0; dev_id < MAX_NUM_DEVICES; dev_id++) {
|
|
#ifdef BAC_ROUTING
|
|
Set_Routed_Device_Object_Index(dev_id);
|
|
#endif
|
|
if (!Object_List) {
|
|
Object_List = Keylist_Create();
|
|
}
|
|
}
|
|
|
|
#ifdef BAC_ROUTING
|
|
Set_Routed_Device_Object_Index(current_dev_id);
|
|
#endif
|
|
}
|