diff --git a/CMakeLists.txt b/CMakeLists.txt index e37d57be..0001d589 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -551,6 +551,10 @@ add_library(${PROJECT_NAME} src/bacnet/basic/services.h src/bacnet/basic/sys/bigend.c 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.h src/bacnet/basic/sys/days.c diff --git a/ports/stm32f4xx/CMakeLists.txt b/ports/stm32f4xx/CMakeLists.txt index 0b76804a..1ba66bf3 100644 --- a/ports/stm32f4xx/CMakeLists.txt +++ b/ports/stm32f4xx/CMakeLists.txt @@ -166,6 +166,7 @@ set(BACNET_PROJECT_SOURCE ${LIBRARY_BACNET_BASIC}/object/ms-input.c ${LIBRARY_BACNET_BASIC}/object/mso.c ${LIBRARY_BACNET_BASIC}/object/msv.c + ${LIBRARY_BACNET_BASIC}/object/bacfile.c ${LIBRARY_BACNET_BASIC}/object/program.c ${LIBRARY_BACNET_BASIC}/program/ubasic/ubasic.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/s_iam.c ${LIBRARY_BACNET_BASIC}/service/s_ihave.c + ${LIBRARY_BACNET_BASIC}/service/h_arf.c ${LIBRARY_BACNET_BASIC}/tsm/tsm.c + ${LIBRARY_BACNET_BASIC}/sys/bsramfs.c ${LIBRARY_BACNET_BASIC}/sys/debug.c ${LIBRARY_BACNET_BASIC}/sys/datetime_mstimer.c ${LIBRARY_BACNET_BASIC}/sys/days.c @@ -194,6 +197,7 @@ set(BACNET_PROJECT_SOURCE ${LIBRARY_BACNET_BASIC}/sys/mstimer.c ${LIBRARY_BACNET_CORE}/abort.c + ${LIBRARY_BACNET_CORE}/arf.c ${LIBRARY_BACNET_CORE}/bacaction.c ${LIBRARY_BACNET_CORE}/bacaddr.c ${LIBRARY_BACNET_CORE}/bacapp.c @@ -252,6 +256,7 @@ target_compile_definitions(${EXECUTABLE} PRIVATE -DMAX_APDU=480 -DBIG_ENDIAN=0 -DMAX_TSM_TRANSACTIONS=1 + -DBACFILE -DBACAPP_MINIMAL ) diff --git a/ports/stm32f4xx/Makefile b/ports/stm32f4xx/Makefile index a1df0099..d860f4fb 100644 --- a/ports/stm32f4xx/Makefile +++ b/ports/stm32f4xx/Makefile @@ -45,10 +45,12 @@ BASIC_SRC = \ $(BACNET_BASIC)/object/ms-input.c \ $(BACNET_BASIC)/object/mso.c \ $(BACNET_BASIC)/object/msv.c \ + $(BACNET_BASIC)/object/bacfile.c \ $(BACNET_BASIC)/object/program.c \ $(BACNET_BASIC)/program/ubasic/ubasic.c \ $(BACNET_BASIC)/program/ubasic/tokenizer.c \ $(BACNET_BASIC)/service/h_apdu.c \ + $(BACNET_BASIC)/service/h_arf.c \ $(BACNET_BASIC)/service/h_dcc.c \ $(BACNET_BASIC)/service/h_rd.c \ $(BACNET_BASIC)/service/h_rp.c \ @@ -60,6 +62,7 @@ BASIC_SRC = \ $(BACNET_BASIC)/service/h_noserv.c \ $(BACNET_BASIC)/service/s_iam.c \ $(BACNET_BASIC)/service/s_ihave.c \ + $(BACNET_BASIC)/sys/bsramfs.c \ $(BACNET_BASIC)/sys/debug.c \ $(BACNET_BASIC)/sys/datetime_mstimer.c \ $(BACNET_BASIC)/sys/days.c \ @@ -72,6 +75,7 @@ BASIC_SRC = \ BACNET_SRC = \ $(BACNET_CORE)/abort.c \ + $(BACNET_CORE)/arf.c \ $(BACNET_CORE)/bacaction.c \ $(BACNET_CORE)/bacaddr.c \ $(BACNET_CORE)/bacapp.c \ @@ -177,6 +181,7 @@ BACNET_FLAGS += -DBIG_ENDIAN=0 BACNET_FLAGS += -DMAX_TSM_TRANSACTIONS=0 BACNET_FLAGS += -DMAX_CHARACTER_STRING_BYTES=64 BACNET_FLAGS += -DMAX_OCTET_STRING_BYTES=64 +BACNET_FLAGS += -DBACFILE BACNET_FLAGS += -DBACAPP_MINIMAL BACNET_FLAGS += -DBACNET_STACK_DEPRECATED_DISABLE # if called from root Makefile, PRINT was already defined diff --git a/ports/stm32f4xx/bacnet.c b/ports/stm32f4xx/bacnet.c index 7ab8529e..97063c75 100644 --- a/ports/stm32f4xx/bacnet.c +++ b/ports/stm32f4xx/bacnet.c @@ -61,6 +61,8 @@ void bacnet_init(void) apdu_set_confirmed_handler( SERVICE_CONFIRMED_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 */ mstimer_set(&DCC_Timer, DCC_CYCLE_SECONDS * 1000); } diff --git a/ports/stm32f4xx/device.c b/ports/stm32f4xx/device.c index 71490c8f..2d8de4bb 100644 --- a/ports/stm32f4xx/device.c +++ b/ports/stm32f4xx/device.c @@ -31,6 +31,9 @@ #include "bacnet/basic/object/mso.h" #include "bacnet/basic/object/msv.h" #include "bacnet/basic/object/program.h" +#if defined(BACFILE) +#include "bacnet/basic/object/bacfile.h" +#endif #if (BACNET_PROTOCOL_REVISION >= 17) #include "bacnet/basic/object/netport.h" #endif @@ -94,6 +97,11 @@ static struct my_object_functions { Program_Index_To_Instance, Program_Valid_Instance, Program_Object_Name, Program_Read_Property, 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) { OBJECT_NETWORK_PORT, Network_Port_Init, Network_Port_Count, Network_Port_Index_To_Instance, Network_Port_Valid_Instance, diff --git a/ports/stm32f4xx/main.c b/ports/stm32f4xx/main.c index f0bd661e..de57bc5e 100644 --- a/ports/stm32f4xx/main.c +++ b/ports/stm32f4xx/main.c @@ -15,7 +15,10 @@ #include "stm32f4xx_rcc.h" #include "stm32f4xx_rng.h" #include "system_stm32f4xx.h" +#include "bacnet/basic/object/bacfile.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/ringbuf.h" #include "bacnet/datalink/datalink.h" @@ -88,6 +91,7 @@ static const char *UBASIC_Program_3 = "end;"; /* uBASIC data tree for each program running */ 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 @@ -105,6 +109,7 @@ int __io_putchar(int ch) */ int main(void) { + size_t i; /*At this stage the microcontroller clock setting is already configured, this is done through SystemInit() function which is called from startup file (startup_stm32f4xx.s) before to branch to application main. @@ -165,11 +170,28 @@ int main(void) } /* initialize application layer*/ bacnet_init(); - /* configure a program */ + bacfile_sramfs_init(); + /* configure the program object and loop time */ Program_UBASIC_Init(10); - Program_UBASIC_Create(1, &UBASIC_Data[0], UBASIC_Program_1); - Program_UBASIC_Create(2, &UBASIC_Data[1], UBASIC_Program_2); - Program_UBASIC_Create(3, &UBASIC_Data[2], UBASIC_Program_3); + /* create the uBASIC programs and link to file objects */ + Static_Files[0].data = (char *)UBASIC_Program_1; + 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 (;;) { led_task(); bacnet_task(); diff --git a/src/bacnet/basic/object/bacfile.c b/src/bacnet/basic/object/bacfile.c index 0a171040..7beff35d 100644 --- a/src/bacnet/basic/object/bacfile.c +++ b/src/bacnet/basic/object/bacfile.c @@ -194,7 +194,9 @@ bool bacfile_object_name( status = characterstring_init_ansi(object_name, pObject->Object_Name); } else { - snprintf(name_text, sizeof(name_text), "FILE %u", object_instance); + snprintf( + name_text, sizeof(name_text), "FILE %lu", + (unsigned long)object_instance); status = characterstring_init_ansi(object_name, name_text); } } @@ -265,7 +267,7 @@ bool bacfile_valid_instance(uint32_t object_instance) * @brief Determines the number of objects * @return Number of objects */ -uint32_t bacfile_count(void) +unsigned bacfile_count(void) { return Keylist_Count(Object_List); } @@ -1022,12 +1024,22 @@ bool bacfile_read_record_data(BACNET_ATOMIC_READ_FILE_DATA *data) return found; } +/** + * @brief Write the data received to the file specified + * @param data - pointer to the data to write + * @return true - if successful + * @return false - if failed or access denied + */ bool bacfile_write_stream_data(BACNET_ATOMIC_WRITE_FILE_DATA *data) { const char *pathname = NULL; bool status = false; size_t bytes_written = 0; + if (bacfile_read_only(data->object_instance)) { + /* if the file is read-only, then we cannot write to it */ + return false; + } pathname = bacfile_pathname(data->object_instance); if (pathname) { status = true; @@ -1052,7 +1064,7 @@ bool bacfile_write_stream_data(BACNET_ATOMIC_WRITE_FILE_DATA *data) * @brief Write the data received to the file specified * @param data - pointer to the data to write * @return true - if successful - * @return false - if failed + * @return false - if failed or access denied */ bool bacfile_write_record_data(const BACNET_ATOMIC_WRITE_FILE_DATA *data) { @@ -1060,6 +1072,10 @@ bool bacfile_write_record_data(const BACNET_ATOMIC_WRITE_FILE_DATA *data) bool found = false; size_t i = 0; + if (bacfile_read_only(data->object_instance)) { + /* if the file is read-only, then we cannot write to it */ + return false; + } pathname = bacfile_pathname(data->object_instance); if (pathname) { found = true; diff --git a/src/bacnet/basic/object/bacfile.h b/src/bacnet/basic/object/bacfile.h index d0e3b90a..bb71dff4 100644 --- a/src/bacnet/basic/object/bacfile.h +++ b/src/bacnet/basic/object/bacfile.h @@ -39,7 +39,7 @@ const char *bacfile_name_ansi(uint32_t object_instance); BACNET_STACK_EXPORT bool bacfile_valid_instance(uint32_t object_instance); BACNET_STACK_EXPORT -uint32_t bacfile_count(void); +unsigned bacfile_count(void); BACNET_STACK_EXPORT uint32_t bacfile_index_to_instance(unsigned find_index); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/sys/bramfs.c b/src/bacnet/basic/sys/bramfs.c new file mode 100644 index 00000000..538bc75c --- /dev/null +++ b/src/bacnet/basic/sys/bramfs.c @@ -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 +#include +#include +#include +#include +#include +/* 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(); +} diff --git a/src/bacnet/basic/sys/bramfs.h b/src/bacnet/basic/sys/bramfs.h new file mode 100644 index 00000000..fa5aafdf --- /dev/null +++ b/src/bacnet/basic/sys/bramfs.h @@ -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 diff --git a/src/bacnet/basic/sys/bsramfs.c b/src/bacnet/basic/sys/bsramfs.c new file mode 100644 index 00000000..4ca18865 --- /dev/null +++ b/src/bacnet/basic/sys/bsramfs.c @@ -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 +#include +#include +#include +/* 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 +} diff --git a/src/bacnet/basic/sys/bsramfs.h b/src/bacnet/basic/sys/bsramfs.h new file mode 100644 index 00000000..95c7ed5f --- /dev/null +++ b/src/bacnet/basic/sys/bsramfs.h @@ -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 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5e0bd444..6053a59a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -181,6 +181,8 @@ list(APPEND testdirs # basic/program bacnet/basic/program/ubasic # basic/sys + bacnet/basic/sys/bramfs + bacnet/basic/sys/bsramfs bacnet/basic/sys/color_rgb bacnet/basic/sys/days bacnet/basic/sys/dst diff --git a/test/bacnet/basic/sys/bramfs/CMakeLists.txt b/test/bacnet/basic/sys/bramfs/CMakeLists.txt new file mode 100644 index 00000000..1d5fce5e --- /dev/null +++ b/test/bacnet/basic/sys/bramfs/CMakeLists.txt @@ -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 + ) diff --git a/test/bacnet/basic/sys/bramfs/src/main.c b/test/bacnet/basic/sys/bramfs/src/main.c new file mode 100644 index 00000000..204ce0e6 --- /dev/null +++ b/test/bacnet/basic/sys/bramfs/src/main.c @@ -0,0 +1,225 @@ +/* @file + * @brief tests the BACnet RAM File System (BRAMFS) + * @date August 2025 + * @author Steve Karg + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include + +/** + * @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 diff --git a/test/bacnet/basic/sys/bsramfs/CMakeLists.txt b/test/bacnet/basic/sys/bsramfs/CMakeLists.txt new file mode 100644 index 00000000..f108e327 --- /dev/null +++ b/test/bacnet/basic/sys/bsramfs/CMakeLists.txt @@ -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 + ) diff --git a/test/bacnet/basic/sys/bsramfs/src/main.c b/test/bacnet/basic/sys/bsramfs/src/main.c new file mode 100644 index 00000000..556ffeb4 --- /dev/null +++ b/test/bacnet/basic/sys/bsramfs/src/main.c @@ -0,0 +1,143 @@ +/* @file + * @brief tests the BACnet RAM File System (BSRAMFS) + * @date August 2025 + * @author Steve Karg + * @copyright SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include + +/** + * @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