This commit is contained in:
Nils Schulte 2023-12-11 21:19:02 +01:00
commit 7fd631541b
6 changed files with 490 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build

77
CMakeLists.txt Executable file
View File

@ -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
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
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()

4
README.md Normal file
View File

@ -0,0 +1,4 @@
## Build
```
mkdir -p build && (cd build && cmake -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug .. && cmake --build . ) && build/simplesync_test
```

View File

@ -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)

326
src/simplesync.hpp Normal file
View File

@ -0,0 +1,326 @@
#ifndef SIMPLESYNC_HPP
#define SIMPLESYNC_HPP
#include <cstdint>
#include <cstdlib>
#include <cstring>
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

74
test/simplesync_test.cpp Normal file
View File

@ -0,0 +1,74 @@
#include <cstdint>
#include <gtest/gtest.h>
#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 <BSONPP.h>
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();
}