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
+4
View File
@@ -551,6 +551,10 @@ add_library(${PROJECT_NAME}
src/bacnet/basic/services.h src/bacnet/basic/services.h
src/bacnet/basic/sys/bigend.c src/bacnet/basic/sys/bigend.c
src/bacnet/basic/sys/bigend.h src/bacnet/basic/sys/bigend.h
src/bacnet/basic/sys/bramfs.c
src/bacnet/basic/sys/bramfs.h
src/bacnet/basic/sys/bsramfs.c
src/bacnet/basic/sys/bsramfs.h
src/bacnet/basic/sys/color_rgb.c src/bacnet/basic/sys/color_rgb.c
src/bacnet/basic/sys/color_rgb.h src/bacnet/basic/sys/color_rgb.h
src/bacnet/basic/sys/days.c src/bacnet/basic/sys/days.c
+5
View File
@@ -166,6 +166,7 @@ set(BACNET_PROJECT_SOURCE
${LIBRARY_BACNET_BASIC}/object/ms-input.c ${LIBRARY_BACNET_BASIC}/object/ms-input.c
${LIBRARY_BACNET_BASIC}/object/mso.c ${LIBRARY_BACNET_BASIC}/object/mso.c
${LIBRARY_BACNET_BASIC}/object/msv.c ${LIBRARY_BACNET_BASIC}/object/msv.c
${LIBRARY_BACNET_BASIC}/object/bacfile.c
${LIBRARY_BACNET_BASIC}/object/program.c ${LIBRARY_BACNET_BASIC}/object/program.c
${LIBRARY_BACNET_BASIC}/program/ubasic/ubasic.c ${LIBRARY_BACNET_BASIC}/program/ubasic/ubasic.c
${LIBRARY_BACNET_BASIC}/program/ubasic/tokenizer.c ${LIBRARY_BACNET_BASIC}/program/ubasic/tokenizer.c
@@ -183,7 +184,9 @@ set(BACNET_PROJECT_SOURCE
${LIBRARY_BACNET_BASIC}/service/h_noserv.c ${LIBRARY_BACNET_BASIC}/service/h_noserv.c
${LIBRARY_BACNET_BASIC}/service/s_iam.c ${LIBRARY_BACNET_BASIC}/service/s_iam.c
${LIBRARY_BACNET_BASIC}/service/s_ihave.c ${LIBRARY_BACNET_BASIC}/service/s_ihave.c
${LIBRARY_BACNET_BASIC}/service/h_arf.c
${LIBRARY_BACNET_BASIC}/tsm/tsm.c ${LIBRARY_BACNET_BASIC}/tsm/tsm.c
${LIBRARY_BACNET_BASIC}/sys/bsramfs.c
${LIBRARY_BACNET_BASIC}/sys/debug.c ${LIBRARY_BACNET_BASIC}/sys/debug.c
${LIBRARY_BACNET_BASIC}/sys/datetime_mstimer.c ${LIBRARY_BACNET_BASIC}/sys/datetime_mstimer.c
${LIBRARY_BACNET_BASIC}/sys/days.c ${LIBRARY_BACNET_BASIC}/sys/days.c
@@ -194,6 +197,7 @@ set(BACNET_PROJECT_SOURCE
${LIBRARY_BACNET_BASIC}/sys/mstimer.c ${LIBRARY_BACNET_BASIC}/sys/mstimer.c
${LIBRARY_BACNET_CORE}/abort.c ${LIBRARY_BACNET_CORE}/abort.c
${LIBRARY_BACNET_CORE}/arf.c
${LIBRARY_BACNET_CORE}/bacaction.c ${LIBRARY_BACNET_CORE}/bacaction.c
${LIBRARY_BACNET_CORE}/bacaddr.c ${LIBRARY_BACNET_CORE}/bacaddr.c
${LIBRARY_BACNET_CORE}/bacapp.c ${LIBRARY_BACNET_CORE}/bacapp.c
@@ -252,6 +256,7 @@ target_compile_definitions(${EXECUTABLE} PRIVATE
-DMAX_APDU=480 -DMAX_APDU=480
-DBIG_ENDIAN=0 -DBIG_ENDIAN=0
-DMAX_TSM_TRANSACTIONS=1 -DMAX_TSM_TRANSACTIONS=1
-DBACFILE
-DBACAPP_MINIMAL -DBACAPP_MINIMAL
) )
+5
View File
@@ -45,10 +45,12 @@ BASIC_SRC = \
$(BACNET_BASIC)/object/ms-input.c \ $(BACNET_BASIC)/object/ms-input.c \
$(BACNET_BASIC)/object/mso.c \ $(BACNET_BASIC)/object/mso.c \
$(BACNET_BASIC)/object/msv.c \ $(BACNET_BASIC)/object/msv.c \
$(BACNET_BASIC)/object/bacfile.c \
$(BACNET_BASIC)/object/program.c \ $(BACNET_BASIC)/object/program.c \
$(BACNET_BASIC)/program/ubasic/ubasic.c \ $(BACNET_BASIC)/program/ubasic/ubasic.c \
$(BACNET_BASIC)/program/ubasic/tokenizer.c \ $(BACNET_BASIC)/program/ubasic/tokenizer.c \
$(BACNET_BASIC)/service/h_apdu.c \ $(BACNET_BASIC)/service/h_apdu.c \
$(BACNET_BASIC)/service/h_arf.c \
$(BACNET_BASIC)/service/h_dcc.c \ $(BACNET_BASIC)/service/h_dcc.c \
$(BACNET_BASIC)/service/h_rd.c \ $(BACNET_BASIC)/service/h_rd.c \
$(BACNET_BASIC)/service/h_rp.c \ $(BACNET_BASIC)/service/h_rp.c \
@@ -60,6 +62,7 @@ BASIC_SRC = \
$(BACNET_BASIC)/service/h_noserv.c \ $(BACNET_BASIC)/service/h_noserv.c \
$(BACNET_BASIC)/service/s_iam.c \ $(BACNET_BASIC)/service/s_iam.c \
$(BACNET_BASIC)/service/s_ihave.c \ $(BACNET_BASIC)/service/s_ihave.c \
$(BACNET_BASIC)/sys/bsramfs.c \
$(BACNET_BASIC)/sys/debug.c \ $(BACNET_BASIC)/sys/debug.c \
$(BACNET_BASIC)/sys/datetime_mstimer.c \ $(BACNET_BASIC)/sys/datetime_mstimer.c \
$(BACNET_BASIC)/sys/days.c \ $(BACNET_BASIC)/sys/days.c \
@@ -72,6 +75,7 @@ BASIC_SRC = \
BACNET_SRC = \ BACNET_SRC = \
$(BACNET_CORE)/abort.c \ $(BACNET_CORE)/abort.c \
$(BACNET_CORE)/arf.c \
$(BACNET_CORE)/bacaction.c \ $(BACNET_CORE)/bacaction.c \
$(BACNET_CORE)/bacaddr.c \ $(BACNET_CORE)/bacaddr.c \
$(BACNET_CORE)/bacapp.c \ $(BACNET_CORE)/bacapp.c \
@@ -177,6 +181,7 @@ BACNET_FLAGS += -DBIG_ENDIAN=0
BACNET_FLAGS += -DMAX_TSM_TRANSACTIONS=0 BACNET_FLAGS += -DMAX_TSM_TRANSACTIONS=0
BACNET_FLAGS += -DMAX_CHARACTER_STRING_BYTES=64 BACNET_FLAGS += -DMAX_CHARACTER_STRING_BYTES=64
BACNET_FLAGS += -DMAX_OCTET_STRING_BYTES=64 BACNET_FLAGS += -DMAX_OCTET_STRING_BYTES=64
BACNET_FLAGS += -DBACFILE
BACNET_FLAGS += -DBACAPP_MINIMAL BACNET_FLAGS += -DBACAPP_MINIMAL
BACNET_FLAGS += -DBACNET_STACK_DEPRECATED_DISABLE BACNET_FLAGS += -DBACNET_STACK_DEPRECATED_DISABLE
# if called from root Makefile, PRINT was already defined # if called from root Makefile, PRINT was already defined
+2
View File
@@ -61,6 +61,8 @@ void bacnet_init(void)
apdu_set_confirmed_handler( apdu_set_confirmed_handler(
SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL, SERVICE_CONFIRMED_DEVICE_COMMUNICATION_CONTROL,
handler_device_communication_control); handler_device_communication_control);
apdu_set_confirmed_handler(
SERVICE_CONFIRMED_ATOMIC_READ_FILE, handler_atomic_read_file);
/* start the cyclic 1 second timer for DCC */ /* start the cyclic 1 second timer for DCC */
mstimer_set(&DCC_Timer, DCC_CYCLE_SECONDS * 1000); mstimer_set(&DCC_Timer, DCC_CYCLE_SECONDS * 1000);
} }
+8
View File
@@ -31,6 +31,9 @@
#include "bacnet/basic/object/mso.h" #include "bacnet/basic/object/mso.h"
#include "bacnet/basic/object/msv.h" #include "bacnet/basic/object/msv.h"
#include "bacnet/basic/object/program.h" #include "bacnet/basic/object/program.h"
#if defined(BACFILE)
#include "bacnet/basic/object/bacfile.h"
#endif
#if (BACNET_PROTOCOL_REVISION >= 17) #if (BACNET_PROTOCOL_REVISION >= 17)
#include "bacnet/basic/object/netport.h" #include "bacnet/basic/object/netport.h"
#endif #endif
@@ -94,6 +97,11 @@ static struct my_object_functions {
Program_Index_To_Instance, Program_Valid_Instance, Program_Index_To_Instance, Program_Valid_Instance,
Program_Object_Name, Program_Read_Property, Program_Object_Name, Program_Read_Property,
Program_Write_Property, Program_Property_Lists}, Program_Write_Property, Program_Property_Lists},
#if defined(BACFILE)
{ OBJECT_FILE, bacfile_init, bacfile_count, bacfile_index_to_instance,
bacfile_valid_instance, bacfile_object_name, bacfile_read_property,
bacfile_write_property, BACfile_Property_Lists },
#endif
#if (BACNET_PROTOCOL_REVISION >= 17) #if (BACNET_PROTOCOL_REVISION >= 17)
{ OBJECT_NETWORK_PORT, Network_Port_Init, Network_Port_Count, { OBJECT_NETWORK_PORT, Network_Port_Init, Network_Port_Count,
Network_Port_Index_To_Instance, Network_Port_Valid_Instance, Network_Port_Index_To_Instance, Network_Port_Valid_Instance,
+26 -4
View File
@@ -15,7 +15,10 @@
#include "stm32f4xx_rcc.h" #include "stm32f4xx_rcc.h"
#include "stm32f4xx_rng.h" #include "stm32f4xx_rng.h"
#include "system_stm32f4xx.h" #include "system_stm32f4xx.h"
#include "bacnet/basic/object/bacfile.h"
#include "bacnet/basic/object/device.h" #include "bacnet/basic/object/device.h"
#include "bacnet/basic/object/program.h"
#include "bacnet/basic/sys/bsramfs.h"
#include "bacnet/basic/sys/mstimer.h" #include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/sys/ringbuf.h" #include "bacnet/basic/sys/ringbuf.h"
#include "bacnet/datalink/datalink.h" #include "bacnet/datalink/datalink.h"
@@ -88,6 +91,7 @@ static const char *UBASIC_Program_3 =
"end;"; "end;";
/* uBASIC data tree for each program running */ /* uBASIC data tree for each program running */
static struct ubasic_data UBASIC_Data[3]; static struct ubasic_data UBASIC_Data[3];
static struct bacnet_file_sramfs_data Static_Files[3];
/** /**
* @brief Called from _write() function from printf and friends * @brief Called from _write() function from printf and friends
@@ -105,6 +109,7 @@ int __io_putchar(int ch)
*/ */
int main(void) int main(void)
{ {
size_t i;
/*At this stage the microcontroller clock setting is already configured, /*At this stage the microcontroller clock setting is already configured,
this is done through SystemInit() function which is called from startup this is done through SystemInit() function which is called from startup
file (startup_stm32f4xx.s) before to branch to application main. file (startup_stm32f4xx.s) before to branch to application main.
@@ -165,11 +170,28 @@ int main(void)
} }
/* initialize application layer*/ /* initialize application layer*/
bacnet_init(); bacnet_init();
/* configure a program */ bacfile_sramfs_init();
/* configure the program object and loop time */
Program_UBASIC_Init(10); Program_UBASIC_Init(10);
Program_UBASIC_Create(1, &UBASIC_Data[0], UBASIC_Program_1); /* create the uBASIC programs and link to file objects */
Program_UBASIC_Create(2, &UBASIC_Data[1], UBASIC_Program_2); Static_Files[0].data = (char *)UBASIC_Program_1;
Program_UBASIC_Create(3, &UBASIC_Data[2], UBASIC_Program_3); Static_Files[0].size = strlen(UBASIC_Program_1);
Static_Files[0].pathname = "/program1.bas";
Static_Files[1].data = (char *)UBASIC_Program_2;
Static_Files[1].size = strlen(UBASIC_Program_2);
Static_Files[1].pathname = "/program2.bas";
Static_Files[2].data = (char *)UBASIC_Program_3;
Static_Files[2].size = strlen(UBASIC_Program_3);
Static_Files[2].pathname = "/program3.bas";
for (i = 0; i < ARRAY_SIZE(Static_Files); i++) {
bacfile_create(1 + i);
bacfile_pathname_set(1 + i, Static_Files[i].pathname);
bacfile_read_only_set(1 + i, true);
bacfile_sramfs_add(&Static_Files[i]);
Program_UBASIC_Create(1 + i, &UBASIC_Data[i], Static_Files[i].data);
Program_Instance_Of_Set(1 + i, Static_Files[i].pathname);
}
/* loop forever */
for (;;) { for (;;) {
led_task(); led_task();
bacnet_task(); bacnet_task();
+19 -3
View File
@@ -194,7 +194,9 @@ bool bacfile_object_name(
status = status =
characterstring_init_ansi(object_name, pObject->Object_Name); characterstring_init_ansi(object_name, pObject->Object_Name);
} else { } 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); 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 * @brief Determines the number of objects
* @return Number of objects * @return Number of objects
*/ */
uint32_t bacfile_count(void) unsigned bacfile_count(void)
{ {
return Keylist_Count(Object_List); return Keylist_Count(Object_List);
} }
@@ -1022,12 +1024,22 @@ bool bacfile_read_record_data(BACNET_ATOMIC_READ_FILE_DATA *data)
return found; 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) bool bacfile_write_stream_data(BACNET_ATOMIC_WRITE_FILE_DATA *data)
{ {
const char *pathname = NULL; const char *pathname = NULL;
bool status = false; bool status = false;
size_t bytes_written = 0; 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); pathname = bacfile_pathname(data->object_instance);
if (pathname) { if (pathname) {
status = true; 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 * @brief Write the data received to the file specified
* @param data - pointer to the data to write * @param data - pointer to the data to write
* @return true - if successful * @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) 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; bool found = false;
size_t i = 0; 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); pathname = bacfile_pathname(data->object_instance);
if (pathname) { if (pathname) {
found = true; found = true;
+1 -1
View File
@@ -39,7 +39,7 @@ const char *bacfile_name_ansi(uint32_t object_instance);
BACNET_STACK_EXPORT BACNET_STACK_EXPORT
bool bacfile_valid_instance(uint32_t object_instance); bool bacfile_valid_instance(uint32_t object_instance);
BACNET_STACK_EXPORT BACNET_STACK_EXPORT
uint32_t bacfile_count(void); unsigned bacfile_count(void);
BACNET_STACK_EXPORT BACNET_STACK_EXPORT
uint32_t bacfile_index_to_instance(unsigned find_index); uint32_t bacfile_index_to_instance(unsigned find_index);
BACNET_STACK_EXPORT 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
+2
View File
@@ -181,6 +181,8 @@ list(APPEND testdirs
# basic/program # basic/program
bacnet/basic/program/ubasic bacnet/basic/program/ubasic
# basic/sys # basic/sys
bacnet/basic/sys/bramfs
bacnet/basic/sys/bsramfs
bacnet/basic/sys/color_rgb bacnet/basic/sys/color_rgb
bacnet/basic/sys/days bacnet/basic/sys/days
bacnet/basic/sys/dst bacnet/basic/sys/dst
@@ -0,0 +1,45 @@
# SPDX-License-Identifier: MIT
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME)
project(test_${basename}
VERSION 1.0.0
LANGUAGES C)
string(REGEX REPLACE
"/test/bacnet/[a-zA-Z_/-]*$"
"/src"
SRC_DIR
${CMAKE_CURRENT_SOURCE_DIR})
string(REGEX REPLACE
"/test/bacnet/[a-zA-Z_/-]*$"
"/test"
TST_DIR
${CMAKE_CURRENT_SOURCE_DIR})
set(ZTST_DIR "${TST_DIR}/ztest/src")
add_compile_definitions(
BIG_ENDIAN=0
CONFIG_ZTEST=1
)
include_directories(
${SRC_DIR}
${TST_DIR}/ztest/include
)
add_executable(${PROJECT_NAME}
# File(s) under test
${SRC_DIR}/bacnet/basic/sys/bramfs.c
# BACnet library files
${SRC_DIR}/bacnet/bacstr.c
${SRC_DIR}/bacnet/basic/sys/keylist.c
${SRC_DIR}/bacnet/datalink/cobs.c
# Support files and stubs (pathname alphabetical)
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
${ZTST_DIR}/ztest.c
)
+225
View File
@@ -0,0 +1,225 @@
/* @file
* @brief tests the BACnet RAM File System (BRAMFS)
* @date August 2025
* @author Steve Karg <Steve Karg <skarg@users.sourceforge.net>
* @copyright SPDX-License-Identifier: MIT
*/
#include <limits.h>
#include <zephyr/ztest.h>
#include <bacnet/bacstr.h>
#include <bacnet/basic/sys/bramfs.h>
/**
* @addtogroup bacnet_tests
* @{
*/
/**
* @brief Unit Test for the BACnet RAM File System (BRAMFS)
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(bramfs_tests, test_BRAMFS_stream)
#else
static void test_BRAMFS_stream(void)
#endif
{
const char *pathname = "testfile.txt";
size_t file_size = 0;
uint8_t null_file_data[256] = { 0 };
uint8_t test_file_data[256] = { 0 };
/* data less than 256 bytes */
uint8_t file_data[] = {
"This is a test file for the BACnet RAM File System (BRAMFS). "
"It contains some sample data to be read and written."
};
uint8_t file_data_2[] = {
"This is a second test file for the BACnet RAM File System (BRAMFS). "
"It contains some additional sample data to be read and written."
};
uint8_t file_data_small[] = { "Small file data" };
int32_t fileStartPosition = 0;
/* Initialize the BRAMFS */
bacfile_ramfs_init();
file_size = bacfile_ramfs_file_size(pathname);
zassert_equal(file_size, 0, "File size should be 0 after initialization");
zassert_true(
bacfile_ramfs_file_size_set(pathname, sizeof(null_file_data)),
"Failed to set file size");
file_size = bacfile_ramfs_file_size(pathname);
zassert_equal(
file_size, sizeof(null_file_data),
"File size should be 256 after setting");
file_size = bacfile_ramfs_read_stream_data(
pathname, 0, test_file_data, sizeof(test_file_data));
zassert_equal(
file_size, sizeof(test_file_data), "file_size=%zu", file_size);
zassert_true(
memcmp(test_file_data, null_file_data, sizeof(test_file_data)) == 0,
"File data should be zeroed out initially");
file_size = bacfile_ramfs_write_stream_data(
pathname, 0, file_data, sizeof(file_data));
zassert_equal(file_size, sizeof(file_data), "file_size=%zu", file_size);
file_size = bacfile_ramfs_read_stream_data(
pathname, 0, test_file_data, sizeof(test_file_data));
zassert_equal(file_size, sizeof(file_data), "file_size=%zu", file_size);
zassert_true(
memcmp(test_file_data, file_data, sizeof(file_data)) == 0,
"File data should match written data");
/* append data to the end of the file */
fileStartPosition = -1;
file_size = bacfile_ramfs_write_stream_data(
pathname, fileStartPosition, file_data_2, sizeof(file_data_2));
zassert_equal(file_size, sizeof(file_data_2), "file_size=%zu", file_size);
file_size = bacfile_ramfs_read_stream_data(
pathname, sizeof(file_data), test_file_data, sizeof(test_file_data));
zassert_equal(file_size, sizeof(file_data_2), "file_size=%zu", file_size);
zassert_true(
memcmp(test_file_data, file_data_2, sizeof(file_data_2)) == 0,
"File data should match appended data");
/* write a smaller file */
fileStartPosition = 0;
file_size = bacfile_ramfs_write_stream_data(
pathname, fileStartPosition, file_data_small, sizeof(file_data_small));
zassert_equal(
file_size, sizeof(file_data_small), "file_size=%zu", file_size);
file_size = bacfile_ramfs_read_stream_data(
pathname, 0, test_file_data, sizeof(test_file_data));
zassert_equal(
file_size, sizeof(file_data_small), "file_size=%zu", file_size);
zassert_true(
memcmp(test_file_data, file_data_small, sizeof(file_data_small)) == 0,
"File data should match smaller written data");
file_size = bacfile_ramfs_file_size(pathname);
zassert_equal(
file_size, sizeof(file_data_small),
"File size should be %u after shrinking", sizeof(file_data_small));
/* shrink the file by writing zero at zero */
zassert_true(
bacfile_ramfs_file_size_set(pathname, 0),
"Failed to set file size to 0");
file_size = bacfile_ramfs_file_size(pathname);
zassert_equal(file_size, 0, "File size should be 0 after shrinking");
/* check a NULL pathname */
file_size = bacfile_ramfs_file_size(NULL);
zassert_equal(file_size, 0, "File size should be 0 on a null pathname");
/* append data to the end of the file */
fileStartPosition = 5;
file_size = bacfile_ramfs_write_stream_data(
pathname, fileStartPosition, file_data, sizeof(file_data));
zassert_equal(file_size, sizeof(file_data), "file_size=%zu", file_size);
file_size = bacfile_ramfs_read_stream_data(
pathname, fileStartPosition, test_file_data, sizeof(test_file_data));
zassert_equal(file_size, sizeof(file_data), "file_size=%zu", file_size);
zassert_true(
memcmp(test_file_data, file_data, sizeof(file_data)) == 0,
"File data should match written data at position %d",
fileStartPosition);
file_size = bacfile_ramfs_file_size(pathname);
zassert_equal(
file_size, sizeof(file_data) + fileStartPosition,
"File size should be %u after appending",
sizeof(file_data) + fileStartPosition);
bacfile_ramfs_deinit();
}
/**
* @brief Unit Test for the BACnet RAM File System (BRAMFS)
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(bramfs_tests, test_BRAMFS_records)
#else
static void test_BRAMFS_records(void)
#endif
{
const char *pathname = "testfile.txt";
bool status = false;
char record_1[] = { "This is the first record in the file." };
char record_2[] = { "This is the second record in the file." };
char record_3[] = { "This is the third record in the file." };
size_t record_len = 0;
/* Initialize the BRAMFS */
bacfile_ramfs_init();
/* no data in the file - expect failure */
record_len = bacnet_strnlen(record_1, sizeof(record_1));
status = bacfile_ramfs_read_record_data(
pathname, 0, 0, (uint8_t *)record_1, record_len);
zassert_false(status, "Read record 1 should fail on empty file");
record_len = bacnet_strnlen(record_1, sizeof(record_1));
status = bacfile_ramfs_read_record_data(
pathname, 0, 1, (uint8_t *)record_2, record_len);
zassert_false(status, "Read record 2 should fail on empty file");
record_len = bacnet_strnlen(record_3, sizeof(record_3));
status = bacfile_ramfs_read_record_data(
pathname, 0, 2, (uint8_t *)record_3, record_len);
zassert_false(status, "Read record 3 should fail on empty file");
/* write the first record */
record_len = bacnet_strnlen(record_1, sizeof(record_1));
status = bacfile_ramfs_write_record_data(
pathname, 0, 0, (const uint8_t *)record_1, record_len);
zassert_true(status, "Write record 1 should succeed");
/* read the first record */
status = bacfile_ramfs_read_record_data(
pathname, 0, 0, (uint8_t *)record_1, record_len);
zassert_true(status, "Read record 1 should succeed");
zassert_true(
memcmp(record_1, record_1, record_len) == 0,
"Record 1 data should match written data");
/* write the second record as an append */
record_len = bacnet_strnlen(record_2, sizeof(record_2));
status = bacfile_ramfs_write_record_data(
pathname, -1, 1, (const uint8_t *)record_2, record_len);
zassert_true(status, "Write record 2 should succeed");
/* read the second record */
status = bacfile_ramfs_read_record_data(
pathname, 0, 1, (uint8_t *)record_2, record_len);
zassert_true(status, "Read record 2 should succeed");
zassert_true(
memcmp(record_2, record_2, record_len) == 0,
"Record 2 data should match written data");
/* overwrite the third record at index 1 */
record_len = bacnet_strnlen(record_3, sizeof(record_3));
status = bacfile_ramfs_write_record_data(
pathname, 0, 1, (const uint8_t *)record_3, record_len);
zassert_true(status, "Write record 3 should succeed");
/* read the third record */
status = bacfile_ramfs_read_record_data(
pathname, 0, 1, (uint8_t *)record_2, record_len);
zassert_true(status, "Read record 2 should succeed");
zassert_true(
memcmp(record_2, record_3, record_len) == 0,
"Record 2 data should match written record 3 data");
/* read the first record again */
record_len = bacnet_strnlen(record_1, sizeof(record_1));
status = bacfile_ramfs_read_record_data(
pathname, 0, 0, (uint8_t *)record_1, record_len);
zassert_true(status, "Read record 1 should succeed");
zassert_true(
memcmp(record_1, record_1, record_len) == 0,
"Record 1 data should match written data");
bacfile_ramfs_deinit();
}
/**
* @}
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST_SUITE(bramfs_tests, NULL, NULL, NULL, NULL, NULL);
#else
void test_main(void)
{
ztest_test_suite(
bramfs_tests, ztest_unit_test(test_BRAMFS_stream),
ztest_unit_test(test_BRAMFS_records));
ztest_run_test_suite(bramfs_tests);
}
#endif
@@ -0,0 +1,45 @@
# SPDX-License-Identifier: MIT
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME)
project(test_${basename}
VERSION 1.0.0
LANGUAGES C)
string(REGEX REPLACE
"/test/bacnet/[a-zA-Z_/-]*$"
"/src"
SRC_DIR
${CMAKE_CURRENT_SOURCE_DIR})
string(REGEX REPLACE
"/test/bacnet/[a-zA-Z_/-]*$"
"/test"
TST_DIR
${CMAKE_CURRENT_SOURCE_DIR})
set(ZTST_DIR "${TST_DIR}/ztest/src")
add_compile_definitions(
BIG_ENDIAN=0
CONFIG_ZTEST=1
)
include_directories(
${SRC_DIR}
${TST_DIR}/ztest/include
)
add_executable(${PROJECT_NAME}
# File(s) under test
${SRC_DIR}/bacnet/basic/sys/bsramfs.c
# BACnet library files
${SRC_DIR}/bacnet/bacstr.c
${SRC_DIR}/bacnet/basic/sys/keylist.c
${SRC_DIR}/bacnet/datalink/cobs.c
# Support files and stubs (pathname alphabetical)
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
${ZTST_DIR}/ztest.c
)
+143
View File
@@ -0,0 +1,143 @@
/* @file
* @brief tests the BACnet RAM File System (BSRAMFS)
* @date August 2025
* @author Steve Karg <Steve Karg <skarg@users.sourceforge.net>
* @copyright SPDX-License-Identifier: MIT
*/
#include <limits.h>
#include <zephyr/ztest.h>
#include <bacnet/bacstr.h>
#include <bacnet/basic/sys/bsramfs.h>
/**
* @addtogroup bacnet_tests
* @{
*/
/**
* @brief Unit Test for the BACnet static RAM File System (BSRAMFS)
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(bramfs_tests, test_BSRAMFS_stream)
#else
static void test_BSRAMFS_stream(void)
#endif
{
struct bacnet_file_sramfs_data file_data[3] = {
{ 0, NULL, "testfile1.txt", NULL },
{ 0, NULL, "testfile2.txt", NULL },
{ 0, NULL, "testfile3.txt", NULL }
};
size_t file_size = 0, i = 0;
uint8_t test_file_data[256] = { 0 };
/* data less than 256 bytes */
uint8_t file_data_1[] = {
"This is a first test file for the BACnet RAM File System (BSRAMFS). "
"It contains some sample data to be read and written."
};
uint8_t file_data_2[] = {
"This is a second test file for the BACnet RAM File System (BSRAMFS). "
"It contains some additional sample data to be read and written."
};
uint8_t file_data_3[] = { "Small file data" };
/* Initialize the BSRAMFS */
bacfile_sramfs_init();
for (i = 0; i < ARRAY_SIZE(file_data); i++) {
file_size = bacfile_sramfs_file_size(file_data[i].pathname);
zassert_equal(
file_size, 0, "File size should be 0 after initialization");
}
/* add static files to the file system */
file_data[0].data = (char *)file_data_1;
file_data[0].size = sizeof(file_data_1);
zassert_true(
bacfile_sramfs_add(&file_data[0]), "Failed to add file_data[0]");
file_data[1].data = (char *)file_data_2;
file_data[1].size = sizeof(file_data_2);
zassert_true(
bacfile_sramfs_add(&file_data[1]), "Failed to add file_data[1]");
file_data[2].data = (char *)file_data_3;
file_data[2].size = sizeof(file_data_3);
zassert_true(
bacfile_sramfs_add(&file_data[2]), "Failed to add file_data[2]");
/* read back the files and check the data */
file_size = bacfile_sramfs_read_stream_data(
file_data[0].pathname, 0, test_file_data, sizeof(test_file_data));
zassert_equal(file_size, sizeof(file_data_1), "file_size=%zu", file_size);
zassert_true(
memcmp(test_file_data, file_data_1, sizeof(file_data_1)) == 0,
"File data should match!");
}
/**
* @brief Unit Test for the BACnet RAM File System (BSRAMFS)
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(bramfs_tests, test_BSRAMFS_records)
#else
static void test_BSRAMFS_records(void)
#endif
{
struct bacnet_file_sramfs_data file_data[1] = {
{ 0,
"This is the first record in the file.\0"
"This is the second record in the file.\0"
"This is the third record in the file.",
"testfile.txt", NULL }
};
bool status = false;
const char *pathname = file_data[0].pathname;
char record_1[MAX_OCTET_STRING_BYTES] = { 0 };
char record_2[MAX_OCTET_STRING_BYTES] = { 0 };
char record_3[MAX_OCTET_STRING_BYTES] = { 0 };
/* Initialize the BSRAMFS */
bacfile_sramfs_init();
/* no data in the file - expect failure */
status = bacfile_sramfs_read_record_data(
pathname, 0, 0, (uint8_t *)record_1, sizeof(record_1));
zassert_false(status, "Read record 1 should fail on empty file");
status = bacfile_sramfs_read_record_data(
pathname, 0, 1, (uint8_t *)record_2, sizeof(record_2));
zassert_false(status, "Read record 2 should fail on empty file");
status = bacfile_sramfs_read_record_data(
pathname, 0, 2, (uint8_t *)record_3, sizeof(record_3));
zassert_false(status, "Read record 3 should fail on empty file");
/* add the static file with records */
bacfile_sramfs_add(&file_data[0]);
/* read the first record */
status = bacfile_sramfs_read_record_data(
pathname, 0, 0, (uint8_t *)record_1, sizeof(record_1));
zassert_true(status, "Read record 1 should succeed");
/* read the second record */
status = bacfile_sramfs_read_record_data(
pathname, 0, 1, (uint8_t *)record_2, sizeof(record_2));
zassert_true(status, "Read record 2 should succeed");
/* read the third record */
status = bacfile_sramfs_read_record_data(
pathname, 0, 2, (uint8_t *)record_3, sizeof(record_3));
zassert_true(status, "Read record 3 should succeed");
}
/**
* @}
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST_SUITE(bramfs_tests, NULL, NULL, NULL, NULL, NULL);
#else
void test_main(void)
{
ztest_test_suite(
bramfs_tests, ztest_unit_test(test_BSRAMFS_stream),
ztest_unit_test(test_BSRAMFS_records));
ztest_run_test_suite(bramfs_tests);
}
#endif