1072 lines
32 KiB
C
1072 lines
32 KiB
C
/**
|
|
* @file
|
|
* @author Steve Karg
|
|
* @date 2005
|
|
* @brief A basic BACnet File Object implementation.
|
|
* @copyright SPDX-License-Identifier: MIT
|
|
*/
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
/* BACnet Stack defines - first */
|
|
#include "bacnet/bacdef.h"
|
|
/* BACnet Stack API */
|
|
#include "bacnet/bacapp.h"
|
|
#include "bacnet/bacdcode.h"
|
|
#include "bacnet/datetime.h"
|
|
#include "bacnet/npdu.h"
|
|
#include "bacnet/apdu.h"
|
|
#include "bacnet/arf.h"
|
|
#include "bacnet/awf.h"
|
|
#include "bacnet/rp.h"
|
|
#include "bacnet/wp.h"
|
|
#include "bacnet/datalink/datalink.h"
|
|
#include "bacnet/basic/binding/address.h"
|
|
#include "bacnet/basic/object/bacfile.h"
|
|
#include "bacnet/basic/services.h"
|
|
#include "bacnet/basic/sys/keylist.h"
|
|
#include "bacnet/basic/tsm/tsm.h"
|
|
|
|
#ifndef FILE_RECORD_SIZE
|
|
#define FILE_RECORD_SIZE MAX_OCTET_STRING_BYTES
|
|
#endif
|
|
struct object_data {
|
|
char *Object_Name;
|
|
char *Pathname;
|
|
BACNET_DATE_TIME Modification_Date;
|
|
char *File_Type;
|
|
bool File_Access_Stream:1;
|
|
bool Read_Only : 1;
|
|
bool Archive : 1;
|
|
};
|
|
/* Key List for storing the object data sorted by instance number */
|
|
static OS_Keylist Object_List;
|
|
/* common object type */
|
|
static const BACNET_OBJECT_TYPE Object_Type = OBJECT_FILE;
|
|
/* These three arrays are used by the ReadPropertyMultiple handler */
|
|
static const int bacfile_Properties_Required[] = { PROP_OBJECT_IDENTIFIER,
|
|
PROP_OBJECT_NAME, PROP_OBJECT_TYPE, PROP_FILE_TYPE, PROP_FILE_SIZE,
|
|
PROP_MODIFICATION_DATE, PROP_ARCHIVE, PROP_READ_ONLY,
|
|
PROP_FILE_ACCESS_METHOD, -1 };
|
|
|
|
static const int bacfile_Properties_Optional[] = { PROP_DESCRIPTION, -1 };
|
|
|
|
static const int bacfile_Properties_Proprietary[] = { -1 };
|
|
|
|
/**
|
|
* @brief 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 optional properties for this object.
|
|
* @param pProprietary - pointer to list of int terminated by -1, of
|
|
* BACnet proprietary properties for this object.
|
|
*/
|
|
void BACfile_Property_Lists(
|
|
const int **pRequired, const int **pOptional, const int **pProprietary)
|
|
{
|
|
if (pRequired) {
|
|
*pRequired = bacfile_Properties_Required;
|
|
}
|
|
if (pOptional) {
|
|
*pOptional = bacfile_Properties_Optional;
|
|
}
|
|
if (pProprietary) {
|
|
*pProprietary = bacfile_Properties_Proprietary;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* @brief duplicate a string (replacement for POSIX strdup)
|
|
* @param s - string to duplicate
|
|
* @return a pointer to a new string on success, or a null pointer
|
|
*/
|
|
static char *bacfile_strdup(const char *s) {
|
|
size_t size = strlen(s) + 1;
|
|
char *p = malloc(size);
|
|
if (p != NULL) {
|
|
memcpy(p, s, size);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, returns the pathname
|
|
* @param object_instance - object-instance number of the object
|
|
* @return internal file system path and name, or NULL if not set
|
|
*/
|
|
const char *bacfile_pathname(uint32_t object_instance)
|
|
{
|
|
struct object_data *pObject;
|
|
const char *pathname = NULL;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
pathname = pObject->Pathname;
|
|
}
|
|
|
|
return pathname;
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, sets the pathname
|
|
* @param object_instance - object-instance number of the object
|
|
* @param pathname - internal file system path and name
|
|
*/
|
|
void bacfile_pathname_set(uint32_t object_instance, const char *pathname)
|
|
{
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
if (pObject->Pathname) {
|
|
free(pObject->Pathname);
|
|
}
|
|
pObject->Pathname = bacfile_strdup(pathname);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief For a given pathname, gets the object instance-number
|
|
* @param pathname - internal file system path and name
|
|
* @return object-instance number of the object,
|
|
* or #BACNET_MAX_INSTANCE if not found
|
|
*/
|
|
uint32_t bacfile_pathname_instance(
|
|
const char *pathname)
|
|
{
|
|
struct object_data *pObject;
|
|
int count = 0;
|
|
int index = 0;
|
|
KEY key = BACNET_MAX_INSTANCE;
|
|
|
|
count = Keylist_Count(Object_List);
|
|
while (count) {
|
|
pObject = Keylist_Data_Index(Object_List, index);
|
|
if (strcmp(pathname, pObject->Pathname) == 0) {
|
|
Keylist_Index_Key(Object_List, index, &key);
|
|
break;
|
|
}
|
|
count--;
|
|
index++;
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
/**
|
|
* 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 bacfile_object_name(
|
|
uint32_t object_instance, BACNET_CHARACTER_STRING *object_name)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject;
|
|
char name_text[32];
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
if (pObject->Object_Name) {
|
|
status = characterstring_init_ansi(object_name,
|
|
pObject->Object_Name);
|
|
} else {
|
|
snprintf(name_text, sizeof(name_text), "FILE %u",
|
|
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 bacfile_object_name_set(uint32_t object_instance, char *new_name)
|
|
{
|
|
bool status = false; /* return value */
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject && new_name) {
|
|
status = true;
|
|
pObject->Object_Name = new_name;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @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 bacfile_valid_instance(uint32_t object_instance)
|
|
{
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Determines the number of objects
|
|
* @return Number of objects
|
|
*/
|
|
uint32_t bacfile_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 object count
|
|
* @param find_index - 0..N value
|
|
* @return object instance-number for the given index
|
|
*/
|
|
uint32_t bacfile_index_to_instance(unsigned find_index)
|
|
{
|
|
KEY key = UINT32_MAX;
|
|
|
|
Keylist_Index_Key(Object_List, find_index, &key);
|
|
|
|
return key;
|
|
}
|
|
|
|
/**
|
|
* @brief Determines the file size for a given file
|
|
* @param pFile - file handle
|
|
* @return file size in bytes, or 0 if not found
|
|
*/
|
|
static long fsize(FILE *pFile)
|
|
{
|
|
long size = 0;
|
|
long origin = 0;
|
|
|
|
if (pFile) {
|
|
origin = ftell(pFile);
|
|
fseek(pFile, 0L, SEEK_END);
|
|
size = ftell(pFile);
|
|
fseek(pFile, origin, SEEK_SET);
|
|
}
|
|
return (size);
|
|
}
|
|
|
|
/**
|
|
* @brief Read the entire file into a buffer
|
|
* @param object_instance - object-instance number of the object
|
|
* @param buffer - data store from the file
|
|
* @param buffer_size - in bytes
|
|
* @return file size in bytes
|
|
*/
|
|
uint32_t bacfile_read(uint32_t object_instance, uint8_t *buffer,
|
|
uint32_t buffer_size)
|
|
{
|
|
const char *pFilename = NULL;
|
|
FILE *pFile = NULL;
|
|
long file_size = 0;
|
|
|
|
pFilename = bacfile_pathname(object_instance);
|
|
if (pFilename) {
|
|
pFile = fopen(pFilename, "rb");
|
|
if (pFile) {
|
|
file_size = fsize(pFile);
|
|
if (buffer && (buffer_size >= file_size)) {
|
|
if (fread(buffer, file_size, 1, pFile) == 0) {
|
|
file_size = 0;
|
|
}
|
|
}
|
|
fclose(pFile);
|
|
}
|
|
}
|
|
|
|
return (uint32_t)file_size;
|
|
}
|
|
|
|
/**
|
|
* @brief Write the entire file from a buffer
|
|
* @param object_instance - object-instance number of the object
|
|
* @param buffer - data store for the file
|
|
* @param buffer_size - in bytes
|
|
* @return file size in bytes
|
|
*/
|
|
uint32_t bacfile_write(uint32_t object_instance, uint8_t *buffer,
|
|
uint32_t buffer_size)
|
|
{
|
|
const char *pFilename = NULL;
|
|
FILE *pFile = NULL;
|
|
long file_size = 0;
|
|
|
|
pFilename = bacfile_pathname(object_instance);
|
|
if (pFilename) {
|
|
/* open the file as a clean slate when starting at 0 */
|
|
pFile = fopen(pFilename, "wb");
|
|
if (pFile) {
|
|
if (fwrite(buffer, buffer_size, 1, pFile) == 1) {
|
|
file_size = buffer_size;
|
|
}
|
|
fclose(pFile);
|
|
}
|
|
}
|
|
|
|
return (uint32_t)file_size;
|
|
}
|
|
|
|
/**
|
|
* @brief Determines the file size for a given file
|
|
* @param pFile - file handle
|
|
* @return file size in bytes, or 0 if not found
|
|
*/
|
|
BACNET_UNSIGNED_INTEGER bacfile_file_size(uint32_t object_instance)
|
|
{
|
|
const char *pFilename = NULL;
|
|
FILE *pFile = NULL;
|
|
long file_position = 0;
|
|
BACNET_UNSIGNED_INTEGER file_size = 0;
|
|
|
|
pFilename = bacfile_pathname(object_instance);
|
|
if (pFilename) {
|
|
pFile = fopen(pFilename, "rb");
|
|
if (pFile) {
|
|
file_position = fsize(pFile);
|
|
if (file_position >= 0) {
|
|
file_size = (BACNET_UNSIGNED_INTEGER)file_position;
|
|
}
|
|
fclose(pFile);
|
|
}
|
|
}
|
|
|
|
return file_size;
|
|
}
|
|
|
|
/**
|
|
* @brief Sets the file size property value
|
|
* @param object_instance - object-instance number of the object
|
|
* @param file_size - value of the file size property
|
|
* @return true if file size is writable
|
|
*/
|
|
bool bacfile_file_size_set(
|
|
uint32_t object_instance,
|
|
BACNET_UNSIGNED_INTEGER file_size)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
if (pObject->File_Access_Stream) {
|
|
(void)file_size;
|
|
/* FIXME: add clever POSIX file stuff here */
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Determines the file size property value
|
|
* @param object_instance - object-instance number of the object
|
|
* @return value of the file size property
|
|
*/
|
|
const char * bacfile_file_type(
|
|
uint32_t object_instance)
|
|
{
|
|
struct object_data *pObject;
|
|
const char * mime_type = "application/octet-stream";
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
if (pObject->File_Type) {
|
|
mime_type = pObject->File_Type;
|
|
}
|
|
}
|
|
|
|
return mime_type;
|
|
}
|
|
|
|
/**
|
|
* @brief Sets the file type (MIME) property value
|
|
* @param object_instance - object-instance number of the object
|
|
* @param mime_type - value of the file type property
|
|
*/
|
|
void bacfile_file_type_set(
|
|
uint32_t object_instance,
|
|
const char *mime_type)
|
|
{
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
if (pObject->File_Type) {
|
|
if (strcmp(pObject->File_Type, mime_type) != 0) {
|
|
free(pObject->File_Type);
|
|
pObject->File_Type = bacfile_strdup(mime_type);
|
|
}
|
|
} else {
|
|
pObject->File_Type = bacfile_strdup(mime_type);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, return the flag
|
|
* @note 12.13.8 Archive
|
|
* This property, of type BOOLEAN, indicates whether the File
|
|
* object has been saved for historical or backup purposes. This
|
|
* property shall be logical TRUE only if no changes have been
|
|
* made to the file data by internal processes or through File
|
|
* Access Services since the last time the object was archived.
|
|
* @param object_instance - object-instance number of the object
|
|
* @return true if the property is true
|
|
*/
|
|
bool bacfile_archive(
|
|
uint32_t object_instance)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
status = pObject->Archive;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, return the flag
|
|
* @note 12.13.8 Archive
|
|
* This property, of type BOOLEAN, indicates whether the File
|
|
* object has been saved for historical or backup purposes. This
|
|
* property shall be logical TRUE only if no changes have been
|
|
* made to the file data by internal processes or through File
|
|
* Access Services since the last time the object was archived.
|
|
* @param object_instance - object-instance number of the object
|
|
* @return true if the property is true
|
|
*/
|
|
bool bacfile_archive_set(
|
|
uint32_t object_instance, bool archive)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
pObject->Archive = archive;
|
|
status = true;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, return the flag
|
|
* @param object_instance - object-instance number of the object
|
|
* @return true if the property is true
|
|
*/
|
|
bool bacfile_read_only(
|
|
uint32_t object_instance)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
status = pObject->Read_Only;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Sets the file archive property value
|
|
* @param object_instance - object-instance number of the object
|
|
* @param archive - value of the file archive property
|
|
* @return true if the file exists and read-only is writeable
|
|
*/
|
|
bool bacfile_read_only_set(
|
|
uint32_t object_instance,
|
|
bool read_only)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
pObject->Read_Only = read_only;
|
|
status = true;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, return the flag
|
|
* @param object_instance - object-instance number of the object
|
|
* @return true if the property is true
|
|
*/
|
|
void bacfile_modification_date(
|
|
uint32_t object_instance, BACNET_DATE_TIME *bdatetime)
|
|
{
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
datetime_copy(bdatetime, &pObject->Modification_Date);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief For a given object instance-number, return the flag
|
|
* @param object_instance - object-instance number of the object
|
|
* @return true if the property is true
|
|
*/
|
|
bool bacfile_file_access_stream(
|
|
uint32_t object_instance)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
status = pObject->File_Access_Stream;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Sets the file access property value
|
|
* @param object_instance - object-instance number of the object
|
|
* @param access - value of the property
|
|
* @return true if the file exists and property is writeable
|
|
*/
|
|
bool bacfile_file_access_stream_set(
|
|
uint32_t object_instance,
|
|
bool access)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject;
|
|
|
|
pObject = Keylist_Data(Object_List, object_instance);
|
|
if (pObject) {
|
|
pObject->File_Access_Stream = access;
|
|
status = true;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief 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 bacfile_read_property(BACNET_READ_PROPERTY_DATA *rpdata)
|
|
{
|
|
int apdu_len = 0; /* return value */
|
|
BACNET_CHARACTER_STRING char_string;
|
|
BACNET_DATE_TIME bdatetime;
|
|
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], OBJECT_FILE, rpdata->object_instance);
|
|
break;
|
|
case PROP_OBJECT_NAME:
|
|
bacfile_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], Object_Type);
|
|
break;
|
|
case PROP_DESCRIPTION:
|
|
characterstring_init_ansi(
|
|
&char_string, bacfile_pathname(rpdata->object_instance));
|
|
apdu_len =
|
|
encode_application_character_string(&apdu[0], &char_string);
|
|
break;
|
|
case PROP_FILE_TYPE:
|
|
characterstring_init_ansi(&char_string,
|
|
bacfile_file_type(rpdata->object_instance));
|
|
apdu_len =
|
|
encode_application_character_string(&apdu[0], &char_string);
|
|
break;
|
|
case PROP_FILE_SIZE:
|
|
apdu_len = encode_application_unsigned(
|
|
&apdu[0], bacfile_file_size(rpdata->object_instance));
|
|
break;
|
|
case PROP_MODIFICATION_DATE:
|
|
bacfile_modification_date(rpdata->object_instance, &bdatetime);
|
|
apdu_len = bacapp_encode_datetime(apdu, &bdatetime);
|
|
break;
|
|
case PROP_ARCHIVE:
|
|
apdu_len = encode_application_boolean(&apdu[0],
|
|
bacfile_archive(rpdata->object_instance));
|
|
break;
|
|
case PROP_READ_ONLY:
|
|
apdu_len = encode_application_boolean(&apdu[0],
|
|
bacfile_read_only(rpdata->object_instance));
|
|
break;
|
|
case PROP_FILE_ACCESS_METHOD:
|
|
if (bacfile_file_access_stream(rpdata->object_instance)) {
|
|
apdu_len = encode_application_enumerated(
|
|
&apdu[0], FILE_STREAM_ACCESS);
|
|
} else {
|
|
apdu_len = encode_application_enumerated(
|
|
&apdu[0], FILE_RECORD_ACCESS);
|
|
}
|
|
break;
|
|
default:
|
|
rpdata->error_class = ERROR_CLASS_PROPERTY;
|
|
rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
|
apdu_len = BACNET_STATUS_ERROR;
|
|
break;
|
|
}
|
|
|
|
return apdu_len;
|
|
}
|
|
|
|
/**
|
|
* @brief 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 bacfile_write_property(BACNET_WRITE_PROPERTY_DATA *wp_data)
|
|
{
|
|
bool status = false; /* return value */
|
|
int len = 0;
|
|
BACNET_APPLICATION_DATA_VALUE value;
|
|
|
|
if (!bacfile_valid_instance(wp_data->object_instance)) {
|
|
wp_data->error_class = ERROR_CLASS_OBJECT;
|
|
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
|
|
return false;
|
|
}
|
|
/* only array properties can have array options */
|
|
if (wp_data->array_index != BACNET_ARRAY_ALL) {
|
|
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
|
wp_data->error_code = ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY;
|
|
return false;
|
|
}
|
|
/* decode the some of the request */
|
|
len = bacapp_decode_application_data(
|
|
wp_data->application_data, wp_data->application_data_len, &value);
|
|
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;
|
|
}
|
|
/* FIXME: len < application_data_len: more data? */
|
|
switch (wp_data->object_property) {
|
|
case PROP_ARCHIVE:
|
|
status = write_property_type_valid(
|
|
wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN);
|
|
if (status) {
|
|
status = bacfile_archive_set(
|
|
wp_data->object_instance, value.type.Boolean);
|
|
}
|
|
break;
|
|
case PROP_FILE_SIZE:
|
|
/* If the file size can be changed by writing to the file,
|
|
and File_Access_Method is STREAM_ACCESS, then this property
|
|
shall be writable. */
|
|
status = write_property_type_valid(
|
|
wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT);
|
|
if (status) {
|
|
status =
|
|
bacfile_file_size_set(wp_data->object_instance,
|
|
value.type.Unsigned_Int);
|
|
if (!status) {
|
|
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
|
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
|
}
|
|
}
|
|
break;
|
|
case PROP_OBJECT_IDENTIFIER:
|
|
case PROP_OBJECT_NAME:
|
|
case PROP_OBJECT_TYPE:
|
|
case PROP_DESCRIPTION:
|
|
case PROP_FILE_TYPE:
|
|
case PROP_MODIFICATION_DATE:
|
|
case PROP_READ_ONLY:
|
|
case PROP_FILE_ACCESS_METHOD:
|
|
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
|
wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
|
|
break;
|
|
default:
|
|
wp_data->error_class = ERROR_CLASS_PROPERTY;
|
|
wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
|
|
break;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
#if MAX_TSM_TRANSACTIONS
|
|
/* this is one way to match up the invoke ID with */
|
|
/* the file ID from the AtomicReadFile request. */
|
|
/* Another way would be to store the */
|
|
/* invokeID and file instance in a list or table */
|
|
/* when the request was sent */
|
|
uint32_t bacfile_instance_from_tsm(uint8_t invokeID)
|
|
{
|
|
BACNET_NPDU_DATA npdu_data = { 0 }; /* dummy for getting npdu length */
|
|
BACNET_CONFIRMED_SERVICE_DATA service_data = { 0 };
|
|
uint8_t service_choice = 0;
|
|
uint8_t *service_request = NULL;
|
|
uint16_t service_request_len = 0;
|
|
BACNET_ADDRESS dest; /* where the original packet was destined */
|
|
uint8_t apdu[MAX_PDU] = { 0 }; /* original APDU packet */
|
|
uint16_t apdu_len = 0; /* original APDU packet length */
|
|
int len = 0; /* apdu header length */
|
|
BACNET_ATOMIC_READ_FILE_DATA data = { 0 };
|
|
uint32_t object_instance = BACNET_MAX_INSTANCE + 1; /* return value */
|
|
bool found = false;
|
|
|
|
found = tsm_get_transaction_pdu(
|
|
invokeID, &dest, &npdu_data, &apdu[0], &apdu_len);
|
|
if (found) {
|
|
if (!npdu_data.network_layer_message &&
|
|
npdu_data.data_expecting_reply &&
|
|
((apdu[0] & 0xF0) == PDU_TYPE_CONFIRMED_SERVICE_REQUEST)) {
|
|
len = apdu_decode_confirmed_service_request(&apdu[0], apdu_len,
|
|
&service_data, &service_choice, &service_request,
|
|
&service_request_len);
|
|
if ((len > 0) &&
|
|
(service_choice == SERVICE_CONFIRMED_ATOMIC_READ_FILE)) {
|
|
len = arf_decode_service_request(
|
|
service_request, service_request_len, &data);
|
|
if (len > 0) {
|
|
if (data.object_type == OBJECT_FILE) {
|
|
object_instance = data.object_instance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return object_instance;
|
|
}
|
|
#endif
|
|
|
|
bool bacfile_read_stream_data(BACNET_ATOMIC_READ_FILE_DATA *data)
|
|
{
|
|
const char *pFilename = NULL;
|
|
bool found = false;
|
|
FILE *pFile = NULL;
|
|
size_t len = 0;
|
|
|
|
pFilename = bacfile_pathname(data->object_instance);
|
|
if (pFilename) {
|
|
found = true;
|
|
pFile = fopen(pFilename, "rb");
|
|
if (pFile) {
|
|
(void)fseek(pFile, data->type.stream.fileStartPosition, SEEK_SET);
|
|
len = fread(octetstring_value(&data->fileData[0]), 1,
|
|
data->type.stream.requestedOctetCount, pFile);
|
|
if (len < data->type.stream.requestedOctetCount) {
|
|
data->endOfFile = true;
|
|
} else {
|
|
data->endOfFile = false;
|
|
}
|
|
octetstring_truncate(&data->fileData[0], len);
|
|
fclose(pFile);
|
|
} else {
|
|
octetstring_truncate(&data->fileData[0], 0);
|
|
data->endOfFile = true;
|
|
}
|
|
} else {
|
|
octetstring_truncate(&data->fileData[0], 0);
|
|
data->endOfFile = true;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
bool bacfile_write_stream_data(BACNET_ATOMIC_WRITE_FILE_DATA *data)
|
|
{
|
|
const char *pFilename = NULL;
|
|
bool found = false;
|
|
FILE *pFile = NULL;
|
|
|
|
pFilename = bacfile_pathname(data->object_instance);
|
|
if (pFilename) {
|
|
found = true;
|
|
if (data->type.stream.fileStartPosition == 0) {
|
|
/* open the file as a clean slate when starting at 0 */
|
|
pFile = fopen(pFilename, "wb");
|
|
} else if (data->type.stream.fileStartPosition == -1) {
|
|
/* If 'File Start Position' parameter has the special
|
|
value -1, then the write operation shall be treated
|
|
as an append to the current end of file. */
|
|
pFile = fopen(pFilename, "ab+");
|
|
} else {
|
|
/* open for update */
|
|
pFile = fopen(pFilename, "rb+");
|
|
}
|
|
if (pFile) {
|
|
if (data->type.stream.fileStartPosition != -1) {
|
|
(void)fseek(
|
|
pFile, data->type.stream.fileStartPosition, SEEK_SET);
|
|
}
|
|
if (fwrite(octetstring_value(&data->fileData[0]),
|
|
octetstring_length(&data->fileData[0]), 1, pFile) != 1) {
|
|
/* do something if it fails? */
|
|
}
|
|
fclose(pFile);
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
bool bacfile_write_record_data(BACNET_ATOMIC_WRITE_FILE_DATA *data)
|
|
{
|
|
const char *pFilename = NULL;
|
|
bool found = false;
|
|
FILE *pFile = NULL;
|
|
uint32_t i = 0;
|
|
char dummy_data[FILE_RECORD_SIZE];
|
|
char *pData = NULL;
|
|
|
|
pFilename = bacfile_pathname(data->object_instance);
|
|
if (pFilename) {
|
|
found = true;
|
|
if (data->type.record.fileStartRecord == 0) {
|
|
/* open the file as a clean slate when starting at 0 */
|
|
pFile = fopen(pFilename, "wb");
|
|
} else if (data->type.record.fileStartRecord == -1) {
|
|
/* If 'File Start Record' parameter has the special
|
|
value -1, then the write operation shall be treated
|
|
as an append to the current end of file. */
|
|
pFile = fopen(pFilename, "ab+");
|
|
} else {
|
|
/* open for update */
|
|
pFile = fopen(pFilename, "rb+");
|
|
}
|
|
if (pFile) {
|
|
if ((data->type.record.fileStartRecord != -1) &&
|
|
(data->type.record.fileStartRecord > 0)) {
|
|
for (i = 0; i < (uint32_t)data->type.record.fileStartRecord;
|
|
i++) {
|
|
pData = fgets(&dummy_data[0], sizeof(dummy_data), pFile);
|
|
if ((pData == NULL) || feof(pFile)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (i = 0; i < data->type.record.returnedRecordCount; i++) {
|
|
if (fwrite(octetstring_value(&data->fileData[i]),
|
|
octetstring_length(&data->fileData[i]), 1,
|
|
pFile) != 1) {
|
|
/* do something if it fails? */
|
|
}
|
|
}
|
|
fclose(pFile);
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
bool bacfile_read_ack_stream_data(
|
|
uint32_t instance, BACNET_ATOMIC_READ_FILE_DATA *data)
|
|
{
|
|
bool found = false;
|
|
FILE *pFile = NULL;
|
|
const char *pFilename = NULL;
|
|
|
|
pFilename = bacfile_pathname(instance);
|
|
if (pFilename) {
|
|
found = true;
|
|
pFile = fopen(pFilename, "rb+");
|
|
if (pFile) {
|
|
(void)fseek(pFile, data->type.stream.fileStartPosition, SEEK_SET);
|
|
if (fwrite(octetstring_value(&data->fileData[0]),
|
|
octetstring_length(&data->fileData[0]), 1, pFile) != 1) {
|
|
#if PRINT_ENABLED
|
|
fprintf(stderr, "Failed to write to %s (%lu)!\n", pFilename,
|
|
(unsigned long)instance);
|
|
#endif
|
|
}
|
|
fclose(pFile);
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
bool bacfile_read_ack_record_data(
|
|
uint32_t instance, BACNET_ATOMIC_READ_FILE_DATA *data)
|
|
{
|
|
bool found = false;
|
|
FILE *pFile = NULL;
|
|
const char *pFilename = NULL;
|
|
uint32_t i = 0;
|
|
char dummy_data[MAX_OCTET_STRING_BYTES] = { 0 };
|
|
char *pData = NULL;
|
|
|
|
pFilename = bacfile_pathname(instance);
|
|
if (pFilename) {
|
|
found = true;
|
|
pFile = fopen(pFilename, "rb+");
|
|
if (pFile) {
|
|
if (data->type.record.fileStartRecord > 0) {
|
|
for (i = 0; i < (uint32_t)data->type.record.fileStartRecord;
|
|
i++) {
|
|
pData = fgets(&dummy_data[0], sizeof(dummy_data), pFile);
|
|
if ((pData == NULL) || feof(pFile)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (i = 0; i < data->type.record.RecordCount; i++) {
|
|
if (fwrite(octetstring_value(&data->fileData[i]),
|
|
octetstring_length(&data->fileData[i]), 1,
|
|
pFile) != 1) {
|
|
#if PRINT_ENABLED
|
|
fprintf(stderr, "Failed to write to %s (%lu)!\n", pFilename,
|
|
(unsigned long)instance);
|
|
#endif
|
|
}
|
|
}
|
|
fclose(pFile);
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Creates a File object
|
|
* @param object_instance - object-instance number of the object
|
|
* @return the object-instance that was created, or BACNET_MAX_INSTANCE
|
|
*/
|
|
uint32_t bacfile_create(uint32_t object_instance)
|
|
{
|
|
struct object_data *pObject = NULL;
|
|
int index = 0;
|
|
|
|
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) {
|
|
pObject->Object_Name = NULL;
|
|
pObject->Pathname = NULL;
|
|
/* April Fool's Day */
|
|
datetime_set_values(&pObject->Modification_Date,
|
|
2006, 4, 1, 7, 0, 3, 1);
|
|
pObject->Read_Only = false;
|
|
pObject->Archive = false;
|
|
pObject->File_Access_Stream = true;
|
|
/* add to list */
|
|
index = Keylist_Data_Add(Object_List, object_instance, pObject);
|
|
if (index < 0) {
|
|
free(pObject);
|
|
return BACNET_MAX_INSTANCE;
|
|
}
|
|
} else {
|
|
return BACNET_MAX_INSTANCE;
|
|
}
|
|
}
|
|
|
|
return object_instance;
|
|
}
|
|
|
|
/**
|
|
* @brief Deletes an object
|
|
* @param object_instance - object-instance number of the object
|
|
* @return true if the object-instance was deleted
|
|
*/
|
|
bool bacfile_delete(uint32_t object_instance)
|
|
{
|
|
bool status = false;
|
|
struct object_data *pObject = NULL;
|
|
|
|
pObject = Keylist_Data_Delete(Object_List, object_instance);
|
|
if (pObject) {
|
|
free(pObject);
|
|
status = true;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* @brief Deletes all the objects and their data
|
|
*/
|
|
void bacfile_cleanup(void)
|
|
{
|
|
struct object_data *pObject;
|
|
|
|
if (Object_List) {
|
|
do {
|
|
pObject = Keylist_Data_Pop(Object_List);
|
|
if (pObject) {
|
|
free(pObject);
|
|
}
|
|
} while (pObject);
|
|
Keylist_Delete(Object_List);
|
|
Object_List = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Initializes the object data
|
|
*/
|
|
void bacfile_init(void)
|
|
{
|
|
if (!Object_List) {
|
|
Object_List = Keylist_Create();
|
|
}
|
|
}
|