commit 7fd631541b577f1b90f4eaacf51590826a25e738 Author: Nils Schulte Date: Mon Dec 11 21:19:02 2023 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100755 index 0000000..ad2bce8 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,77 @@ +cmake_minimum_required (VERSION 3.15) +project (simplesync) + +set(CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic") +set(CMAKE_CXX_STANDARD 11) +option(BUILD_TESTS "Build all tests." OFF) + +add_definitions(-D__LINUX_BUILD) + +include_directories(src) + +set(SRCS src/simplesync.hpp) + +# Build the shared library + +# Build the static library +add_library(simplesync INTERFACE ${SRCS}) +set_target_properties(simplesync PROPERTIES OUTPUT_NAME "simplesync") +target_include_directories(simplesync + INTERFACE + "$" + "$") + +install(TARGETS simplesync + EXPORT simplesyncTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} +) + +add_library(simplesync::simplesync ALIAS simplesync) + +include(GNUInstallDirs) +set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}/simplesync + CACHE PATH "Location of header files" ) +set(SYSCONFIG_INSTALL_DIR ${CMAKE_INSTALL_SYSCONFDIR}/simplesync + CACHE PATH "Location of configuration files" ) +#... +include(CMakePackageConfigHelpers) +configure_package_config_file(cmake/simplesync-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/simplesync-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/simplesync + PATH_VARS INCLUDE_INSTALL_DIR SYSCONFIG_INSTALL_DIR) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/simplesync-config-version.cmake + VERSION 1.0.1 + COMPATIBILITY SameMajorVersion ) +install(EXPORT simplesyncTargets + FILE simplesync-targets.cmake + NAMESPACE simplesync:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/simplesync ) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/simplesync-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/simplesync-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/simplesync ) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/simplesync.hpp + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/simplesync) +export( EXPORT simplesyncTargets + FILE ${CMAKE_CURRENT_BINARY_DIR}/simplesync-targets.cmake) + +if (BUILD_TESTS) + include(FetchContent) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG f8d7d77 + ) + + FetchContent_MakeAvailable(googletest) + + enable_testing() + + include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR} .) + + add_executable(${PROJECT_NAME}_test test/simplesync_test.cpp) + target_link_libraries(${PROJECT_NAME}_test gtest gtest_main simplesync) +endif() diff --git a/README.md b/README.md new file mode 100644 index 0000000..9536f22 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +## Build +``` +mkdir -p build && (cd build && cmake -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug .. && cmake --build . ) && build/simplesync_test +``` diff --git a/cmake/simplesync-config.cmake.in b/cmake/simplesync-config.cmake.in new file mode 100644 index 0000000..dfcfa16 --- /dev/null +++ b/cmake/simplesync-config.cmake.in @@ -0,0 +1,8 @@ +@PACKAGE_INIT@ +include("${CMAKE_CURRENT_LIST_DIR}/simplesync-targets.cmake") +set_and_check(simplesync_INCLUDE_DIR "@PACKAGE_INCLUDE_INSTALL_DIR@") +#set_and_check(simplesync_SYSCONFIG_DIR "@PACKAGE_SYSCONFIG_INSTALL_DIR@") +set_and_check(simplesync_LIBRARY_DIRS "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_LIBDIR@") +set(simplesync_LIBRARIES "@PROJECT_NAME@") + +check_required_components(simplesync) diff --git a/src/simplesync.hpp b/src/simplesync.hpp new file mode 100644 index 0000000..98add4c --- /dev/null +++ b/src/simplesync.hpp @@ -0,0 +1,326 @@ +#ifndef SIMPLESYNC_HPP +#define SIMPLESYNC_HPP + +#include +#include +#include + +namespace simplesync { +typedef uint32_t id_t; +enum error_t { + BUFFER_OVERFLOW = (-1), + INVALID_COMMAND = (-2), + ID_INVALID = (-3), + OUT_OF_MEMORY = (-4), +}; +enum command_t { + DESCRIBE_IF = 0, + UPDATE_DESCTIPTION = 1, + REQUEST_DATA = 2, + UPDATE_DATA = 3, +}; +enum data_type_t { + BOOL = 0, + STATE = 1, + NUMBER = 2, + UNIT = 3, + STRING = 4, +}; + +inline unsigned int encode_zigzag(int input) { + return (input << 1) ^ (input >> (sizeof(int) * 8 - 1)); +} + +inline unsigned int decode_zigzag(int input) { + return (input >> 1) ^ (-(input & 1)); +} + +inline unsigned int encode_unsigned_varint(unsigned int input, uint8_t *out) { + int offset = 0; + while (input > 0x7F) { + out[offset++] = input & 0x7F; + input >>= 7; + } + out[offset++] = input | 0x80; + return offset; +} + +inline unsigned int encode_varint(int input, uint8_t *out) { + return encode_unsigned_varint(encode_zigzag(input), out); +} + +inline unsigned int decode_unsigned_varint(uint8_t *input, unsigned int &out) { + int offset = 0; + out = 0; + for (;; offset += 1) { + out |= ((unsigned int)(input[offset] & 0x7F)) << (7 * offset); + if (input[offset] & 0x80) + break; + } + return offset + 1; +} + +inline unsigned int decode_varint(uint8_t *input, int &out) { + unsigned int out_uint; + int offset = decode_unsigned_varint(input, out_uint); + out = decode_zigzag(out_uint); + return offset; +} + +class SimpleSync; +class Interface { +public: + bool sync_requested = false; + bool description_requested = false; + +protected: + friend class SimpleSync; + Interface *next = nullptr; + char *name; + bool allocated_name = false; + id_t idx; + data_type_t data_type; + Interface(const char *name, data_type_t data_type, SimpleSync &sync); + int describe(uint8_t *buf, unsigned int buf_size) { + int offset = 0; + offset += encode_unsigned_varint(idx, buf + offset); + offset += encode_unsigned_varint(data_type, buf + offset); + unsigned int name_len = strlen(name); + offset += encode_unsigned_varint(name_len, buf + offset); + if (offset + name_len >= buf_size) + return BUFFER_OVERFLOW; + memcpy(buf + offset, name, name_len); + offset += name_len; + return offset; + }; + + int parse_description(uint8_t *buf) { + int offset = 0; + offset += decode_unsigned_varint(buf + offset, idx); + + unsigned int data_type_int; + offset += decode_unsigned_varint(buf + offset, data_type_int); + data_type = data_type_t(data_type_int); + + unsigned int name_len; + offset += decode_unsigned_varint(buf + offset, name_len); + name = (char *)realloc((void *)name, name_len); + if (name == nullptr) + return OUT_OF_MEMORY; + allocated_name = true; + memcpy(name, buf + offset, name_len); + offset += name_len; + return offset; + }; + + virtual ~Interface() { free(name); }; + +public: + // static int parse_command_type(uint8_t* buf, command_t& command, unsigned + // int& data_length) { + // command = command_t(buf[0]); + // int command_size = 1; + // switch (command_t(buf[0])) { + // case (STATE): + // case (STRING): + // case (INTERFACE): + // command_size += sizeof(uint32_t); + // memcpy(&data_length, &buf[1], sizeof(uint32_t)); // FIXME(): + // handle endianess break; + // case (REQUEST_IF): + // data_length = 0; + // break; + // case (BOOLEAN): + // data_length = 1; + // break; + // case (NUMBER32): + // data_length = 4; + // break; + // case (NUMBER64): + // data_length = 8; + // break; + // } + // return command_size; + // switch (command) { + // case(DESCRIBE_IF): + // break; + // case(UPDATE_DATA): + // break; + // case(REQUEST_DATA): + // break; + // } + // } + virtual int update(uint8_t *buf) = 0; + virtual int write_update(uint8_t *buf, unsigned int buf_size) = 0; +}; + +class NumberPointerInterface : public Interface { + int *pointer = nullptr; + +public: + NumberPointerInterface(const char *name, int *pointer, SimpleSync &sync) + : Interface(name, data_type_t::NUMBER, sync), pointer(pointer){}; + virtual int update(uint8_t *buf) { return decode_varint(buf, *pointer); }; + virtual int write_update(uint8_t *buf, unsigned int buf_size) { + if (buf_size < sizeof(int) * 8 / 7) + return BUFFER_OVERFLOW; + return encode_varint(*pointer, buf); + }; +}; + +inline int parse_command(uint8_t *buf, command_t &command) { + command = command_t(buf[0]); + return 1; +} + +inline int write_command(uint8_t *buf, command_t command) { + buf[0] = (uint8_t)command; + return 1; +} + +class SimpleSync { + friend class Interface; + Interface *first = nullptr; + +public: + unsigned int num_interfaces() { + unsigned int ret = 0; + for (Interface *c = first; c != nullptr; c = c->next) + ret += 1; + return ret; + } + int request_interface_descriptions(uint8_t *buf) { + buf[0] = command_t::DESCRIBE_IF; + buf[1] = 0; + return 2; + } + + int update(uint8_t *buf_in, unsigned int buf_size) { + int offset = 0; + + command_t command; + offset += parse_command(&buf_in[offset], command); + id_t if_id; + offset += decode_unsigned_varint(&buf_in[offset], if_id); + if ((unsigned int)offset >= buf_size) + return BUFFER_OVERFLOW; + if (if_id == 0) { + if (command == command_t::DESCRIBE_IF) { + for (Interface *current = first; current->next != nullptr; + current = current->next) { + current->description_requested = true; + } + } else if (command == command_t::REQUEST_DATA) { + for (Interface *current = first; current->next != nullptr; + current = current->next) { + current->sync_requested = true; + } + } else if (command == command_t::UPDATE_DATA) { + return ID_INVALID; + } else { + return INVALID_COMMAND; + } + } + + if (command == command_t::DESCRIBE_IF) { + for (Interface *current = first; current->next != nullptr; + current = current->next) { + if (if_id == current->idx) { + current->description_requested = true; + break; + } + } + } else if (command == command_t::REQUEST_DATA) { + for (Interface *current = first; current->next != nullptr; + current = current->next) { + if (if_id == current->idx) { + current->sync_requested = true; + break; + } + } + } else if (command == command_t::UPDATE_DATA) { + for (Interface *current = first;; current = current->next) { + if (current == nullptr) + return ID_INVALID; + + if (if_id == current->idx) { + int data_offset = current->update(&buf_in[offset]); + if (data_offset < 0) { + return BUFFER_OVERFLOW; + } + offset += data_offset; + return offset; + } + } + } else if (command == command_t::UPDATE_DESCTIPTION) { + for (Interface *current = first;; current = current->next) { + if (current == nullptr) { + // FIXME new description + return ID_INVALID; + } + + if (if_id == current->idx) { + int data_offset = current->parse_description(&buf_in[offset]); + if (data_offset < 0) { + return BUFFER_OVERFLOW; + } + offset += data_offset; + return offset; + } + } + } + return offset; + } + + int generate_message(uint8_t *buf, unsigned int buf_size) { + if (first == nullptr) + return 0; + int offset = 0; + int new_offset = 0; + for (Interface *current = first; current != nullptr; + current = current->next) { + id_t if_id = current->idx; + + if (current->description_requested) { + offset += write_command(buf + offset, UPDATE_DESCTIPTION); + offset += encode_unsigned_varint(if_id, buf + offset); + new_offset = current->describe(buf + offset, buf_size - offset); + } else if (current->sync_requested) { + offset += write_command(buf + offset, UPDATE_DATA); + offset += encode_unsigned_varint(if_id, buf + offset); + new_offset = current->write_update(buf + offset, buf_size - offset); + } + if (new_offset > 0) { + offset += new_offset; + if (current->description_requested) { + current->description_requested = false; + } else if (current->sync_requested) { + current->sync_requested = false; + } + return offset; + } else { + offset = 0; + } + } + return 0; + } +}; + +inline Interface::Interface(const char *name, const data_type_t data_type, + SimpleSync &sync) + : name(nullptr), data_type(data_type) { + unsigned int name_len = strlen(name); + this->name = (char *)realloc((void *)this->name, name_len); + memcpy(this->name, name, name_len); + idx = sync.num_interfaces() + 16; + if (sync.first == nullptr) { + sync.first = this; + return; + } + Interface *last = sync.first; + for (; last->next != nullptr; last = last->next) + ; + last->next = this; +} +} // namespace simplesync +#endif // SIMPLESYNC_HPP diff --git a/test/simplesync_test.cpp b/test/simplesync_test.cpp new file mode 100644 index 0000000..b83aeae --- /dev/null +++ b/test/simplesync_test.cpp @@ -0,0 +1,74 @@ +#include +#include + +#include "simplesync.hpp" + +TEST(simplesync_test, varint) { + + uint8_t buf[1024]; + for (int i = -999999; i < 999999; i += 29) { + int offset = simplesync::encode_varint(i, buf); + ASSERT_LE(1, offset); + for (int o = 0; o < offset - 1; o += 1) + EXPECT_FALSE(buf[o] & 0x80) + << "offset:" << offset << " " << (uint32_t)(buf[o] & 0x7F); + EXPECT_TRUE(buf[offset - 1] & 0x80) + << "offset:" << offset << " " << (uint32_t)(buf[offset - 1] & 0x7F); + int out; + offset = simplesync::decode_varint(buf, out); + ASSERT_LE(1, offset); + ASSERT_EQ(out, i); + } +} + +TEST(simplesync_test, encode_decode) { + + uint8_t buf[1024]; + unsigned int buf_size = sizeof(buf); + + simplesync::SimpleSync s; + + int number = 42; + simplesync::NumberPointerInterface number_if("n1", &number, s); + ASSERT_EQ(s.num_interfaces(), 1); + + number_if.sync_requested = true; + int buf_used = s.generate_message(buf, buf_size); + ASSERT_LE(1, buf_used); + + number = 0; + + int buf_parsed = s.update(buf, buf_used); + ASSERT_LE(0, buf_parsed); + ASSERT_EQ(buf_parsed, buf_used); + ASSERT_EQ(number, 42); +} + +/*#include + +TEST(simplesync_test, encode_decode) { + + const char* test_str_key = "stringKey"; + const char* test_str_value = "Just a medium length string"; + // This is the buffer to be used by the library. + uint8_t buffer[256]; + BSONPP doc(buffer, sizeof(buffer)); + doc.append(test_str_key, "Just a medium length string"); + doc.append("num", 10234234); + + // To read BSON from a buffer pass in the buffer, length and false. By default + // when passed a buffer it clears the buffer and initialises a new BSON + // object. Set false to avoid that. + BSONPP parsed(buffer, sizeof(buffer), false); + + char *val; + ASSERT_EQ(BSONPP_SUCCESS, parsed.get(test_str_key, &val)); + EXPECT_STREQ(val, test_str_value); + + // ASSERT_TRUE(strcmp(decoded.str, "Hello") == 0); +}*/ + +int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}