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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user