Added dynamic and static RAM file systems to use with file objects. (#1058)

* Added dynamic RAM file system to use with basic bacnet file object.

* Added static RAM file system to use with basic bacnet file object.

* Added check for read-only during AtomicWriteFile service API for BACnet File object.

* Change stm32f4xx example to use static RAM file system.

* Fixed bacfile_count() function return type
This commit is contained in:
Steve Karg
2025-08-08 15:35:13 -05:00
committed by GitHub
parent e67777e345
commit 3f8b8b5619
17 changed files with 1324 additions and 8 deletions
+19 -3
View File
@@ -194,7 +194,9 @@ bool bacfile_object_name(
status =
characterstring_init_ansi(object_name, pObject->Object_Name);
} else {
snprintf(name_text, sizeof(name_text), "FILE %u", object_instance);
snprintf(
name_text, sizeof(name_text), "FILE %lu",
(unsigned long)object_instance);
status = characterstring_init_ansi(object_name, name_text);
}
}
@@ -265,7 +267,7 @@ bool bacfile_valid_instance(uint32_t object_instance)
* @brief Determines the number of objects
* @return Number of objects
*/
uint32_t bacfile_count(void)
unsigned bacfile_count(void)
{
return Keylist_Count(Object_List);
}
@@ -1022,12 +1024,22 @@ bool bacfile_read_record_data(BACNET_ATOMIC_READ_FILE_DATA *data)
return found;
}
/**
* @brief Write the data received to the file specified
* @param data - pointer to the data to write
* @return true - if successful
* @return false - if failed or access denied
*/
bool bacfile_write_stream_data(BACNET_ATOMIC_WRITE_FILE_DATA *data)
{
const char *pathname = NULL;
bool status = false;
size_t bytes_written = 0;
if (bacfile_read_only(data->object_instance)) {
/* if the file is read-only, then we cannot write to it */
return false;
}
pathname = bacfile_pathname(data->object_instance);
if (pathname) {
status = true;
@@ -1052,7 +1064,7 @@ bool bacfile_write_stream_data(BACNET_ATOMIC_WRITE_FILE_DATA *data)
* @brief Write the data received to the file specified
* @param data - pointer to the data to write
* @return true - if successful
* @return false - if failed
* @return false - if failed or access denied
*/
bool bacfile_write_record_data(const BACNET_ATOMIC_WRITE_FILE_DATA *data)
{
@@ -1060,6 +1072,10 @@ bool bacfile_write_record_data(const BACNET_ATOMIC_WRITE_FILE_DATA *data)
bool found = false;
size_t i = 0;
if (bacfile_read_only(data->object_instance)) {
/* if the file is read-only, then we cannot write to it */
return false;
}
pathname = bacfile_pathname(data->object_instance);
if (pathname) {
found = true;
+1 -1
View File
@@ -39,7 +39,7 @@ const char *bacfile_name_ansi(uint32_t object_instance);
BACNET_STACK_EXPORT
bool bacfile_valid_instance(uint32_t object_instance);
BACNET_STACK_EXPORT
uint32_t bacfile_count(void);
unsigned bacfile_count(void);
BACNET_STACK_EXPORT
uint32_t bacfile_index_to_instance(unsigned find_index);
BACNET_STACK_EXPORT
+458
View File
@@ -0,0 +1,458 @@
/**
* @file
* @brief A dynamic RAM file system BACnet File Object implementation.
* @author Steve Karg
* @date 2025
* @copyright SPDX-License-Identifier: MIT
*/
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
#include "bacnet/basic/sys/keylist.h"
#include "bacnet/basic/object/bacfile.h"
#include "bacnet/datalink/cobs.h"
/* Key List for storing the object data sorted by instance number */
static OS_Keylist File_List;
struct file_data {
size_t size; /* size of the file in bytes */
char *data; /* data buffer */
};
#define CRC32K_INITIAL_VALUE (0xFFFFFFFF)
/**
* @brief Get a CRC32K checksum for a pathname to use as a hash key
* @param pathname - the pathname to calculate the CRC32K for
* @return CRC32K value
*/
static uint32_t pathname_crc32k(const char *pathname)
{
uint32_t crc32K = CRC32K_INITIAL_VALUE;
size_t len, i;
len = strlen(pathname);
for (i = 0; i < len; i++) {
crc32K = cobs_crc32k(pathname[i], crc32K);
}
return crc32K;
}
/**
* @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
*/
static struct file_data *bacfile_ramfs_open(const char *pathname)
{
struct file_data *pFile = NULL;
uint32_t crc32K;
int index;
if (!pathname || pathname[0] == 0) {
return NULL; /* invalid pathname */
}
crc32K = pathname_crc32k(pathname);
pFile = Keylist_Data(File_List, crc32K);
if (!pFile) {
pFile = calloc(1, sizeof(struct file_data));
if (pFile) {
pFile->size = 0;
pFile->data = NULL;
index = Keylist_Data_Add(File_List, crc32K, pFile);
if (index < 0) {
free(pFile);
pFile = NULL;
}
}
}
return pFile;
}
/**
* @brief Determines the file size for a given file
* @param pathname - name of the file to get the size for
* @return file size in bytes, or 0 if not found
*/
const char *bacfile_ramfs_file_data(const char *pathname)
{
struct file_data *pFile;
const char *file_data = NULL;
pFile = bacfile_ramfs_open(pathname);
if (pFile) {
file_data = pFile->data;
}
return file_data;
}
/**
* @brief Determines the file size for a given file
* @param pathname - name of the file to get the size for
* @return file size in bytes, or 0 if not found
*/
size_t bacfile_ramfs_file_size(const char *pathname)
{
struct file_data *pFile;
size_t file_size = 0;
pFile = bacfile_ramfs_open(pathname);
if (pFile) {
file_size = pFile->size;
}
return file_size;
}
/**
* @brief Sets the file size property value
* @param pathname - name of the file to set the size for
* @param file_size - value of the file size property
* @return true if file size is writable
*/
bool bacfile_ramfs_file_size_set(const char *pathname, size_t new_size)
{
struct file_data *pFile;
char *new_data;
bool status = false;
pFile = bacfile_ramfs_open(pathname);
if (pFile) {
if (new_size > 0) {
new_data = realloc(pFile->data, new_size);
if (new_data) {
pFile->data = new_data;
pFile->size = new_size;
status = true;
}
} else {
/* free the data */
free(pFile->data);
pFile->data = NULL;
pFile->size = 0;
status = true;
}
}
return status;
}
/**
* @brief Reads stream data from a file
* @param pathname - name of the file to read from
* @param fileStartPosition - starting position in the file
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
* @return number of bytes read, or 0 if not successful
*/
size_t bacfile_ramfs_read_stream_data(
const char *pathname,
int32_t fileStartPosition,
uint8_t *fileData,
size_t fileDataLen)
{
struct file_data *pFile;
size_t len = 0;
pFile = bacfile_ramfs_open(pathname);
if (pFile) {
if (fileStartPosition + fileDataLen > pFile->size) {
/* read only up to the end of the file */
len = pFile->size - fileStartPosition;
} else {
len = fileDataLen;
}
memcpy(fileData, pFile->data + fileStartPosition, len);
}
return len;
}
/**
* @brief Writes stream data to a file
* @param pathname - name of the file to write to
* @param fileStartPosition - starting position in the file
* @param fileData - data buffer to write from
* @param fileDataLen - size of the data buffer
* @return number of bytes written, or 0 if not successful
*/
size_t bacfile_ramfs_write_stream_data(
const char *pathname,
int32_t fileStartPosition,
const uint8_t *fileData,
size_t fileDataLen)
{
size_t bytes_written = 0;
struct file_data *pFile;
size_t old_size;
char *new_data = NULL;
pFile = bacfile_ramfs_open(pathname);
if (pFile) {
if (fileStartPosition == 0) {
/* open the file as a clean slate when starting at 0 */
new_data = realloc(pFile->data, fileDataLen);
if (new_data) {
pFile->data = new_data;
memcpy(pFile->data, fileData, fileDataLen);
pFile->size = fileDataLen;
bytes_written = fileDataLen;
}
} else if (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. */
old_size = pFile->size;
pFile->size += fileDataLen;
new_data = realloc(pFile->data, pFile->size);
if (new_data) {
pFile->data = new_data;
memcpy(pFile->data + old_size, fileData, fileDataLen);
bytes_written = fileDataLen;
}
} else {
/* open for update */
if (fileStartPosition + fileDataLen > pFile->size) {
/* extend the file size */
new_data =
realloc(pFile->data, fileStartPosition + fileDataLen);
if (new_data) {
pFile->data = new_data;
pFile->size = fileStartPosition + fileDataLen;
memcpy(
pFile->data + fileStartPosition, fileData, fileDataLen);
bytes_written = fileDataLen;
}
} else {
memcpy(pFile->data + fileStartPosition, fileData, fileDataLen);
bytes_written = fileDataLen;
}
}
}
return bytes_written;
}
/**
* @brief Count the number of records in a file
* @param records - string of null-terminated records
* @return number of records
*/
static size_t record_count(const char *records)
{
size_t count = 0;
int len = 0;
if (records) {
do {
len = bacnet_strnlen(records, MAX_OCTET_STRING_BYTES);
if (len > 0) {
count++;
records = records + len + 1;
}
} while (len > 0);
}
return count;
}
/**
* @brief Get the specific record at index 0..N
* @param records - string of null-terminated records
* @param record_index - record index number 0..N of the records
* @return record, or NULL
*/
static char *record_by_index(char *records, size_t index)
{
size_t count = 0;
int len = 0;
if (records) {
do {
len = bacnet_strnlen(records, MAX_OCTET_STRING_BYTES);
if (len > 0) {
if (index == count) {
return records;
}
count++;
records = records + len + 1;
}
} while (len > 0);
}
return NULL;
}
/**
* @brief Writes record data to a file
* @param pathname - name of the file to write to
* @param fileStartRecord - starting record in the file
* @param fileIndexRecord - index of the record to read
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
* @return true if successful, false otherwise
*/
bool bacfile_ramfs_write_record_data(
const char *pathname,
int32_t fileStartRecord,
size_t fileIndexRecord,
const uint8_t *fileData,
size_t fileDataLen)
{
bool status = false;
size_t fileSeekRecord;
size_t fileRecordCount;
struct file_data *pFile;
char *record;
size_t record_len;
size_t tail_record_len;
char fileDataStr[MAX_OCTET_STRING_BYTES + 1] = {
0
}; /* +1 for null terminator */
size_t fileDataStrLen = 0;
pFile = bacfile_ramfs_open(pathname);
if (pFile) {
fileRecordCount = record_count(pFile->data);
if (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,
and fileIndexRecord can be ignored. */
fileSeekRecord = fileRecordCount;
} else {
fileSeekRecord = fileStartRecord + fileIndexRecord;
if (fileSeekRecord > fileRecordCount) {
/* cannot write more than 1 record beyond the end of the file */
return false;
}
}
/* sanitize the incoming record; assume from an octetstring */
fileDataStrLen = min(fileDataLen, MAX_OCTET_STRING_BYTES);
memcpy(fileDataStr, fileData, fileDataStrLen);
fileDataStr[fileDataStrLen] = 0; /* null-terminate */
if (fileDataStrLen == 0) {
return false; /* nothing to write */
}
if (fileSeekRecord < fileRecordCount) {
/* find the old record length */
record = record_by_index(pFile->data, fileSeekRecord);
record_len = bacnet_strnlen(record, MAX_OCTET_STRING_BYTES);
tail_record_len = pFile->size - (record - pFile->data) - record_len;
/* reallocate file to make room for new record */
record = realloc(
pFile->data, pFile->size - record_len + fileDataStrLen + 1);
if (!record) {
return false; /* out of memory */
}
pFile->data = record;
/* find the old record position after a realloc */
record = record_by_index(pFile->data, fileSeekRecord);
/* move all existing records after the inserted record */
if (tail_record_len > 0) {
memmove(
record + fileDataStrLen, record + record_len,
tail_record_len);
}
} else {
/* extend the file by this one record */
record = realloc(pFile->data, pFile->size + fileDataStrLen + 1);
if (!record) {
return false; /* out of memory */
}
pFile->data = record;
record = pFile->data + pFile->size;
pFile->size += fileDataStrLen + 1; /* +1 for the null terminator */
}
/* copy new record data into file */
memmove(record, fileDataStr, fileDataStrLen);
record[fileDataStrLen] = 0; /* null-terminate */
status = true;
}
return status;
}
/**
* @brief Reads record data from a file
* @param pathname - name of the file to read from
* @param fileStartRecord - starting record in the file
* @param fileIndexRecord - index of the record to read
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
* @return true if successful, false otherwise
*/
bool bacfile_ramfs_read_record_data(
const char *pathname,
int32_t fileStartRecord,
size_t fileIndexRecord,
uint8_t *fileData,
size_t fileDataLen)
{
bool status = false;
size_t fileSeekRecord;
struct file_data *pFile;
char *record;
size_t record_len;
pFile = bacfile_ramfs_open(pathname);
if (pFile) {
fileSeekRecord = fileStartRecord + fileIndexRecord;
/* seek to the start record */
record = record_by_index(pFile->data, fileSeekRecord);
if (record) {
record_len = bacnet_strnlen(record, MAX_OCTET_STRING_BYTES);
if ((record_len > 0) && (record_len <= fileDataLen)) {
/* copy the record data */
memcpy(fileData, record, record_len);
status = true;
}
}
}
return status;
}
/**
* @brief Deletes the files and their data
*/
void bacfile_ramfs_deinit(void)
{
struct file_data *pFile = NULL;
if (File_List) {
do {
pFile = Keylist_Data_Pop(File_List);
if (pFile) {
free(pFile);
}
} while (pFile);
Keylist_Delete(File_List);
File_List = NULL;
}
}
/**
* @brief Initializes the object data
*/
void bacfile_ramfs_init(void)
{
#if defined(BACFILE)
bacfile_write_stream_data_callback_set(bacfile_ramfs_write_stream_data);
bacfile_read_stream_data_callback_set(bacfile_ramfs_read_stream_data);
bacfile_write_record_data_callback_set(bacfile_ramfs_write_record_data);
bacfile_read_record_data_callback_set(bacfile_ramfs_read_record_data);
bacfile_file_size_callback_set(bacfile_ramfs_file_size);
bacfile_file_size_set_callback_set(bacfile_ramfs_file_size_set);
#endif
File_List = Keylist_Create();
}
+58
View File
@@ -0,0 +1,58 @@
/**
* @file
* @brief A dynamic RAM based BACnet File Object implementation.
* @author Steve Karg
* @date July 2025
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_FILE_RAMFS_H
#define BACNET_FILE_RAMFS_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
BACNET_STACK_EXPORT
const char *bacfile_ramfs_file_data(const char *pathname);
BACNET_STACK_EXPORT
size_t bacfile_ramfs_file_size(const char *pathname);
BACNET_STACK_EXPORT
bool bacfile_ramfs_file_size_set(const char *pathname, size_t file_size);
BACNET_STACK_EXPORT
size_t bacfile_ramfs_read_stream_data(
const char *pathname,
int32_t fileStartPosition,
uint8_t *fileData,
size_t fileDataLen);
BACNET_STACK_EXPORT
size_t bacfile_ramfs_write_stream_data(
const char *pathname,
int32_t fileStartPosition,
const uint8_t *fileData,
size_t fileDataLen);
BACNET_STACK_EXPORT
bool bacfile_ramfs_write_record_data(
const char *pathname,
int32_t fileStartRecord,
size_t fileIndexRecord,
const uint8_t *fileData,
size_t fileDataLen);
BACNET_STACK_EXPORT
bool bacfile_ramfs_read_record_data(
const char *pathname,
int32_t fileStartRecord,
size_t fileIndexRecord,
uint8_t *fileData,
size_t fileDataLen);
BACNET_STACK_EXPORT
void bacfile_ramfs_deinit(void);
BACNET_STACK_EXPORT
void bacfile_ramfs_init(void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+211
View File
@@ -0,0 +1,211 @@
/**
* @file
* @brief A static RAM file system BACnet File Object implementation.
* @author Steve Karg
* @date 2025
* @copyright SPDX-License-Identifier: MIT
*/
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
#include "bacnet/basic/object/bacfile.h"
#include "bacnet/basic/sys/bsramfs.h"
static struct bacnet_file_sramfs_data *File_List;
bool bacfile_sramfs_add(struct bacnet_file_sramfs_data *file_data)
{
struct bacnet_file_sramfs_data *head = NULL;
if (!file_data) {
return false; /* invalid data */
}
head = File_List;
if (!head) {
/* first file data */
File_List = file_data;
} else {
/* find the end of the list */
while (head->next) {
head = head->next;
}
/* add to the end of the list */
head->next = file_data;
}
return true;
}
/**
* @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
*/
static struct bacnet_file_sramfs_data *bacfile_sramfs_open(const char *pathname)
{
struct bacnet_file_sramfs_data *head;
if (!pathname || pathname[0] == 0) {
return NULL; /* invalid pathname */
}
head = File_List;
while (head) {
if (head->pathname) {
if (strcmp(head->pathname, pathname) == 0) {
return head; /* found the file */
}
}
head = head->next;
}
return NULL;
}
/**
* @brief Determines the file size for a given file
* @param pathname - name of the file to get the size for
* @return file size in bytes, or 0 if not found
*/
const char *bacfile_sramfs_file_data(const char *pathname)
{
struct bacnet_file_sramfs_data *pFile;
const char *file_data = NULL;
pFile = bacfile_sramfs_open(pathname);
if (pFile) {
file_data = pFile->data;
}
return file_data;
}
/**
* @brief Determines the file size for a given file
* @param pathname - name of the file to get the size for
* @return file size in bytes, or 0 if not found
*/
size_t bacfile_sramfs_file_size(const char *pathname)
{
struct bacnet_file_sramfs_data *pFile;
size_t file_size = 0;
pFile = bacfile_sramfs_open(pathname);
if (pFile) {
file_size = pFile->size;
}
return file_size;
}
/**
* @brief Reads stream data from a file
* @param pathname - name of the file to read from
* @param fileStartPosition - starting position in the file
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
* @return number of bytes read, or 0 if not successful
*/
size_t bacfile_sramfs_read_stream_data(
const char *pathname,
int32_t fileStartPosition,
uint8_t *fileData,
size_t fileDataLen)
{
struct bacnet_file_sramfs_data *pFile;
size_t len = 0;
pFile = bacfile_sramfs_open(pathname);
if (pFile) {
if (fileStartPosition + fileDataLen > pFile->size) {
/* read only up to the end of the file */
len = pFile->size - fileStartPosition;
} else {
len = fileDataLen;
}
memcpy(fileData, pFile->data + fileStartPosition, len);
}
return len;
}
/**
* @brief Get the specific record at index 0..N
* @param records - string of null-terminated records
* @param record_index - record index number 0..N of the records
* @return record, or NULL
*/
static char *record_by_index(char *records, size_t index)
{
size_t count = 0;
int len = 0;
if (records) {
do {
len = bacnet_strnlen(records, MAX_OCTET_STRING_BYTES);
if (len > 0) {
if (index == count) {
return records;
}
count++;
records = records + len + 1;
}
} while (len > 0);
}
return NULL;
}
/**
* @brief Reads record data from a file
* @param pathname - name of the file to read from
* @param fileStartRecord - starting record in the file
* @param fileIndexRecord - index of the record to read
* @param fileData - data buffer to read into
* @param fileDataLen - size of the data buffer
* @return true if successful, false otherwise
*/
bool bacfile_sramfs_read_record_data(
const char *pathname,
int32_t fileStartRecord,
size_t fileIndexRecord,
uint8_t *fileData,
size_t fileDataLen)
{
bool status = false;
size_t fileSeekRecord;
struct bacnet_file_sramfs_data *pFile;
char *record;
size_t record_len;
pFile = bacfile_sramfs_open(pathname);
if (pFile) {
fileSeekRecord = fileStartRecord + fileIndexRecord;
/* seek to the start record */
record = record_by_index(pFile->data, fileSeekRecord);
if (record) {
record_len = bacnet_strnlen(record, MAX_OCTET_STRING_BYTES);
if ((record_len > 0) && (record_len <= fileDataLen)) {
/* copy the record data */
memcpy(fileData, record, record_len);
status = true;
}
}
}
return status;
}
/**
* @brief Initializes the object data
*/
void bacfile_sramfs_init(void)
{
#if defined(BACFILE)
bacfile_read_stream_data_callback_set(bacfile_sramfs_read_stream_data);
bacfile_read_record_data_callback_set(bacfile_sramfs_read_record_data);
bacfile_file_size_callback_set(bacfile_sramfs_file_size);
#endif
}
+67
View File
@@ -0,0 +1,67 @@
/**
* @file
* @brief A static RAM based BACnet File Object implementation.
* @author Steve Karg
* @date July 2025
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_FILE_SRAMFS_H
#define BACNET_FILE_SRAMFS_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
struct bacnet_file_sramfs_data {
size_t size; /* size of the file in bytes */
char *data; /* data buffer */
char *pathname; /* pathname of the file */
struct bacnet_file_sramfs_data *next; /* pointer to the next file data */
};
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
BACNET_STACK_EXPORT
const char *bacfile_sramfs_file_data(const char *pathname);
BACNET_STACK_EXPORT
bool bacfile_sramfs_file_data_set(const char *pathname, const char *file_data);
BACNET_STACK_EXPORT
size_t bacfile_sramfs_file_size(const char *pathname);
BACNET_STACK_EXPORT
bool bacfile_sramfs_file_size_set(const char *pathname, size_t file_size);
BACNET_STACK_EXPORT
size_t bacfile_sramfs_read_stream_data(
const char *pathname,
int32_t fileStartPosition,
uint8_t *fileData,
size_t fileDataLen);
BACNET_STACK_EXPORT
size_t bacfile_sramfs_write_stream_data(
const char *pathname,
int32_t fileStartPosition,
const uint8_t *fileData,
size_t fileDataLen);
BACNET_STACK_EXPORT
bool bacfile_sramfs_write_record_data(
const char *pathname,
int32_t fileStartRecord,
size_t fileIndexRecord,
const uint8_t *fileData,
size_t fileDataLen);
BACNET_STACK_EXPORT
bool bacfile_sramfs_read_record_data(
const char *pathname,
int32_t fileStartRecord,
size_t fileIndexRecord,
uint8_t *fileData,
size_t fileDataLen);
BACNET_STACK_EXPORT
bool bacfile_sramfs_add(struct bacnet_file_sramfs_data *file_data);
BACNET_STACK_EXPORT
void bacfile_sramfs_init(void);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif