From 39b6807510755e7b2d4cdd0ac564bfad0c4ed21b Mon Sep 17 00:00:00 2001 From: Nils Schulte Date: Tue, 9 Jan 2024 17:01:14 +0100 Subject: [PATCH] wip --- .clang-format | 9 ++ .gitignore | 1 + CMakeLists.txt | 23 ++- LICENSE.txt | 287 +++++++++++++++++++++++++++++++++ README.md | 2 +- include/simplesync.hpp | 340 +++++++++++++++++++++++++++++++++++++++ library.json | 32 ++++ src/python_module.cpp | 18 +++ src/simplesync.hpp | 326 ------------------------------------- src/utils.h | 72 +++++++++ test/simplesync_test.cpp | 113 +++++++------ 11 files changed, 838 insertions(+), 385 deletions(-) create mode 100644 .clang-format create mode 100644 LICENSE.txt create mode 100644 include/simplesync.hpp create mode 100644 library.json create mode 100644 src/python_module.cpp delete mode 100644 src/simplesync.hpp create mode 100644 src/utils.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..aafcd26 --- /dev/null +++ b/.clang-format @@ -0,0 +1,9 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +ColumnLimit: 100 +IndentWidth: 4 +AlignConsecutiveAssignments: true +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +... diff --git a/.gitignore b/.gitignore index 378eac2..9785597 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ build +.cache diff --git a/CMakeLists.txt b/CMakeLists.txt index ad2bce8..1bf8039 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,16 +2,15 @@ cmake_minimum_required (VERSION 3.15) project (simplesync) set(CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic") -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 20) option(BUILD_TESTS "Build all tests." OFF) +option(BUILD_PYLIB "Build python lib." OFF) add_definitions(-D__LINUX_BUILD) -include_directories(src) +include_directories(include) -set(SRCS src/simplesync.hpp) - -# Build the shared library +set(SRCS )#src/simplesync.hpp) # Build the static library add_library(simplesync INTERFACE ${SRCS}) @@ -58,6 +57,20 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/simplesync.hpp export( EXPORT simplesyncTargets FILE ${CMAKE_CURRENT_BINARY_DIR}/simplesync-targets.cmake) +if (BUILD_PYLIB) + include(FetchContent) + FetchContent_Declare( + pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11 + GIT_TAG v2.11.1 + ) + FetchContent_MakeAvailable(pybind11) + find_package(pybind11 CONFIG) + + pybind11_add_module(pysimplesync src/python_module.cpp) + +endif() + if (BUILD_TESTS) include(FetchContent) FetchContent_Declare( diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..4153cd3 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,287 @@ + EUROPEAN UNION PUBLIC LICENCE v. 1.2 + EUPL © the European Union 2007, 2016 + +This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined +below) which is provided under the terms of this Licence. Any use of the Work, +other than as authorised under this Licence is prohibited (to the extent such +use is covered by a right of the copyright holder of the Work). + +The Work is provided under the terms of this Licence when the Licensor (as +defined below) has placed the following notice immediately following the +copyright notice for the Work: + + Licensed under the EUPL + +or has expressed by any other means his willingness to license under the EUPL. + +1. Definitions + +In this Licence, the following terms have the following meaning: + +- ‘The Licence’: this Licence. + +- ‘The Original Work’: the work or software distributed or communicated by the + Licensor under this Licence, available as Source Code and also as Executable + Code as the case may be. + +- ‘Derivative Works’: the works or software that could be created by the + Licensee, based upon the Original Work or modifications thereof. This Licence + does not define the extent of modification or dependence on the Original Work + required in order to classify a work as a Derivative Work; this extent is + determined by copyright law applicable in the country mentioned in Article 15. + +- ‘The Work’: the Original Work or its Derivative Works. + +- ‘The Source Code’: the human-readable form of the Work which is the most + convenient for people to study and modify. + +- ‘The Executable Code’: any code which has generally been compiled and which is + meant to be interpreted by a computer as a program. + +- ‘The Licensor’: the natural or legal person that distributes or communicates + the Work under the Licence. + +- ‘Contributor(s)’: any natural or legal person who modifies the Work under the + Licence, or otherwise contributes to the creation of a Derivative Work. + +- ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of + the Work under the terms of the Licence. + +- ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, + renting, distributing, communicating, transmitting, or otherwise making + available, online or offline, copies of the Work or providing access to its + essential functionalities at the disposal of any other natural or legal + person. + +2. Scope of the rights granted by the Licence + +The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, +sublicensable licence to do the following, for the duration of copyright vested +in the Original Work: + +- use the Work in any circumstance and for all usage, +- reproduce the Work, +- modify the Work, and make Derivative Works based upon the Work, +- communicate to the public, including the right to make available or display + the Work or copies thereof to the public and perform publicly, as the case may + be, the Work, +- distribute the Work or copies thereof, +- lend and rent the Work or copies thereof, +- sublicense rights in the Work or copies thereof. + +Those rights can be exercised on any media, supports and formats, whether now +known or later invented, as far as the applicable law permits so. + +In the countries where moral rights apply, the Licensor waives his right to +exercise his moral right to the extent allowed by law in order to make effective +the licence of the economic rights here above listed. + +The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to +any patents held by the Licensor, to the extent necessary to make use of the +rights granted on the Work under this Licence. + +3. Communication of the Source Code + +The Licensor may provide the Work either in its Source Code form, or as +Executable Code. If the Work is provided as Executable Code, the Licensor +provides in addition a machine-readable copy of the Source Code of the Work +along with each copy of the Work that the Licensor distributes or indicates, in +a notice following the copyright notice attached to the Work, a repository where +the Source Code is easily and freely accessible for as long as the Licensor +continues to distribute or communicate the Work. + +4. Limitations on copyright + +Nothing in this Licence is intended to deprive the Licensee of the benefits from +any exception or limitation to the exclusive rights of the rights owners in the +Work, of the exhaustion of those rights or of other applicable limitations +thereto. + +5. Obligations of the Licensee + +The grant of the rights mentioned above is subject to some restrictions and +obligations imposed on the Licensee. Those obligations are the following: + +Attribution right: The Licensee shall keep intact all copyright, patent or +trademarks notices and all notices that refer to the Licence and to the +disclaimer of warranties. The Licensee must include a copy of such notices and a +copy of the Licence with every copy of the Work he/she distributes or +communicates. The Licensee must cause any Derivative Work to carry prominent +notices stating that the Work has been modified and the date of modification. + +Copyleft clause: If the Licensee distributes or communicates copies of the +Original Works or Derivative Works, this Distribution or Communication will be +done under the terms of this Licence or of a later version of this Licence +unless the Original Work is expressly distributed only under this version of the +Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee +(becoming Licensor) cannot offer or impose any additional terms or conditions on +the Work or Derivative Work that alter or restrict the terms of the Licence. + +Compatibility clause: If the Licensee Distributes or Communicates Derivative +Works or copies thereof based upon both the Work and another work licensed under +a Compatible Licence, this Distribution or Communication can be done under the +terms of this Compatible Licence. For the sake of this clause, ‘Compatible +Licence’ refers to the licences listed in the appendix attached to this Licence. +Should the Licensee's obligations under the Compatible Licence conflict with +his/her obligations under this Licence, the obligations of the Compatible +Licence shall prevail. + +Provision of Source Code: When distributing or communicating copies of the Work, +the Licensee will provide a machine-readable copy of the Source Code or indicate +a repository where this Source will be easily and freely available for as long +as the Licensee continues to distribute or communicate the Work. + +Legal Protection: This Licence does not grant permission to use the trade names, +trademarks, service marks, or names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the copyright notice. + +6. Chain of Authorship + +The original Licensor warrants that the copyright in the Original Work granted +hereunder is owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each Contributor warrants that the copyright in the modifications he/she brings +to the Work are owned by him/her or licensed to him/her and that he/she has the +power and authority to grant the Licence. + +Each time You accept the Licence, the original Licensor and subsequent +Contributors grant You a licence to their contributions to the Work, under the +terms of this Licence. + +7. Disclaimer of Warranty + +The Work is a work in progress, which is continuously improved by numerous +Contributors. It is not a finished work and may therefore contain defects or +‘bugs’ inherent to this type of development. + +For the above reason, the Work is provided under the Licence on an ‘as is’ basis +and without warranties of any kind concerning the Work, including without +limitation merchantability, fitness for a particular purpose, absence of defects +or errors, accuracy, non-infringement of intellectual property rights other than +copyright as stated in Article 6 of this Licence. + +This disclaimer of warranty is an essential part of the Licence and a condition +for the grant of any rights to the Work. + +8. Disclaimer of Liability + +Except in the cases of wilful misconduct or damages directly caused to natural +persons, the Licensor will in no event be liable for any direct or indirect, +material or moral, damages of any kind, arising out of the Licence or of the use +of the Work, including without limitation, damages for loss of goodwill, work +stoppage, computer failure or malfunction, loss of data or any commercial +damage, even if the Licensor has been advised of the possibility of such damage. +However, the Licensor will be liable under statutory product liability laws as +far such laws apply to the Work. + +9. Additional agreements + +While distributing the Work, You may choose to conclude an additional agreement, +defining obligations or services consistent with this Licence. However, if +accepting obligations, You may act only on your own behalf and on your sole +responsibility, not on behalf of the original Licensor or any other Contributor, +and only if You agree to indemnify, defend, and hold each Contributor harmless +for any liability incurred by, or claims asserted against such Contributor by +the fact You have accepted any warranty or additional liability. + +10. Acceptance of the Licence + +The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ +placed under the bottom of a window displaying the text of this Licence or by +affirming consent in any other similar way, in accordance with the rules of +applicable law. Clicking on that icon indicates your clear and irrevocable +acceptance of this Licence and all of its terms and conditions. + +Similarly, you irrevocably accept this Licence and all of its terms and +conditions by exercising any rights granted to You by Article 2 of this Licence, +such as the use of the Work, the creation by You of a Derivative Work or the +Distribution or Communication by You of the Work or copies thereof. + +11. Information to the public + +In case of any Distribution or Communication of the Work by means of electronic +communication by You (for example, by offering to download the Work from a +remote location) the distribution channel or media (for example, a website) must +at least provide to the public the information requested by the applicable law +regarding the Licensor, the Licence and the way it may be accessible, concluded, +stored and reproduced by the Licensee. + +12. Termination of the Licence + +The Licence and the rights granted hereunder will terminate automatically upon +any breach by the Licensee of the terms of the Licence. + +Such a termination will not terminate the licences of any person who has +received the Work from the Licensee under the Licence, provided such persons +remain in full compliance with the Licence. + +13. Miscellaneous + +Without prejudice of Article 9 above, the Licence represents the complete +agreement between the Parties as to the Work. + +If any provision of the Licence is invalid or unenforceable under applicable +law, this will not affect the validity or enforceability of the Licence as a +whole. Such provision will be construed or reformed so as necessary to make it +valid and enforceable. + +The European Commission may publish other linguistic versions or new versions of +this Licence or updated versions of the Appendix, so far this is required and +reasonable, without reducing the scope of the rights granted by the Licence. New +versions of the Licence will be published with a unique version number. + +All linguistic versions of this Licence, approved by the European Commission, +have identical value. Parties can take advantage of the linguistic version of +their choice. + +14. Jurisdiction + +Without prejudice to specific agreement between parties, + +- any litigation resulting from the interpretation of this License, arising + between the European Union institutions, bodies, offices or agencies, as a + Licensor, and any Licensee, will be subject to the jurisdiction of the Court + of Justice of the European Union, as laid down in article 272 of the Treaty on + the Functioning of the European Union, + +- any litigation arising between other parties and resulting from the + interpretation of this License, will be subject to the exclusive jurisdiction + of the competent court where the Licensor resides or conducts its primary + business. + +15. Applicable Law + +Without prejudice to specific agreement between parties, + +- this Licence shall be governed by the law of the European Union Member State + where the Licensor has his seat, resides or has his registered office, + +- this licence shall be governed by Belgian law if the Licensor has no seat, + residence or registered office inside a European Union Member State. + +Appendix + +‘Compatible Licences’ according to Article 5 EUPL are: + +- GNU General Public License (GPL) v. 2, v. 3 +- GNU Affero General Public License (AGPL) v. 3 +- Open Software License (OSL) v. 2.1, v. 3.0 +- Eclipse Public License (EPL) v. 1.0 +- CeCILL v. 2.0, v. 2.1 +- Mozilla Public Licence (MPL) v. 2 +- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 +- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for + works other than software +- European Union Public Licence (EUPL) v. 1.1, v. 1.2 +- Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong + Reciprocity (LiLiQ-R+). + +The European Commission may update this Appendix to later versions of the above +licences without producing a new version of the EUPL, as long as they provide +the rights granted in Article 2 of this Licence and protect the covered Source +Code from exclusive appropriation. + +All other changes or additions to this Appendix require the production of a new +EUPL version. diff --git a/README.md b/README.md index 9536f22..b4cc22e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ ## Build ``` -mkdir -p build && (cd build && cmake -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug .. && cmake --build . ) && build/simplesync_test +mkdir -p build && (cd build && cmake -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .. && cmake --build . ) && build/simplesync_test ``` diff --git a/include/simplesync.hpp b/include/simplesync.hpp new file mode 100644 index 0000000..6714c86 --- /dev/null +++ b/include/simplesync.hpp @@ -0,0 +1,340 @@ +#ifndef SIMPLESYNC_HPP +#define SIMPLESYNC_HPP + +#include +#include +#include +#include +#include + +#ifdef ARDUINO +#include +#endif + +namespace simplesync { + +enum error_t { + BUFFER_OVERFLOW = (-1), + INVALID_COMMAND = (-2), + ID_INVALID = (-3), + OUT_OF_MEMORY = (-4), +}; + +inline int cobs_encode(uint8_t *buf_in, unsigned int buf_in_size, uint8_t *buf_out, + unsigned int buf_out_size) { + unsigned int i_out = 1; + unsigned int last0 = 0; + for (unsigned int i_in = 0; i_in < buf_in_size; i_in += 1, i_out += 1) { + if (i_out >= buf_out_size) return OUT_OF_MEMORY; + if ((buf_in[i_in] != 0x00) && ((i_out - last0) < 0xFF)) { + buf_out[i_out] = buf_in[i_in]; + } else { + buf_out[last0] = i_out - last0; + last0 = i_out; + } + } + buf_out[last0] = i_out - last0; + buf_out[i_out] = 0x00; + return i_out + 1; +} + +inline int cobs_decode(uint8_t *buf_in, unsigned int &buf_in_size_used, uint8_t *buf_out, + unsigned int &buf_out_size_used) { + unsigned int i_out = 0; + unsigned int next0 = 0; + bool skip_next0 = true; + unsigned int i_in; + for (i_in = 0; i_in < buf_in_size_used || buf_in[i_in] == 0x00; i_in += 1) { + if (i_out >= buf_out_size_used) { + buf_out_size_used = i_out; + buf_in_size_used = i_in; + return OUT_OF_MEMORY; + } + if (next0 == i_in) { + if (buf_in[i_in] == 0x00) break; + if (!skip_next0) buf_out[i_out++] = 0x00; + next0 = i_in + buf_in[i_in]; + skip_next0 = (next0 - i_in) == 0xFF; + } else { + buf_out[i_out++] = buf_in[i_in]; + } + } + buf_out_size_used = i_out; + buf_in_size_used = i_in; + return 0; +} + +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; +} + +template +class SimpleSync { + public: + class NumberInterface { + public: + time_t last_send; + char *key; + timedelta_t update_interval; + bool send_requested = true; + int value; + NumberInterface(SimpleSync &s, char *key, timedelta_t update_interval) + : key(key), update_interval(update_interval) { + s.number_interfaces.push_back(this); + } + }; + + class StringInterface { + public: + time_t last_send; + char *key; + timedelta_t update_interval; + bool send_requested = true; + char value[str_len_max + 1]; + StringInterface(SimpleSync &s, char *key, timedelta_t update_interval) + : key(key), update_interval(update_interval) { + value[str_len_max] = 0x00; + s.string_interfaces.push_back(this); + } + }; + + std::vector number_interfaces; + std::vector string_interfaces; + + int (*write_pkg)(uint8_t *, unsigned int); + void (*number_update)(NumberInterface *&, char *, int); + void (*string_update)(StringInterface *&, char *, char *); + SimpleSync(int (*write_stream)(uint8_t *, unsigned int), + void (*number_update)(NumberInterface *&, char *, int), + void (*string_update)(StringInterface *&, char *, char *)) + : write_pkg(write_stream), number_update(number_update), string_update(string_update) {} + + int parse_pkg(uint8_t *buf, unsigned int buf_size) { + for (unsigned int parsed = 0; parsed < buf_size;) { + switch (buf[parsed]) { + case 0x00: { + for (auto ni : number_interfaces) ni->send_requested = true; + for (auto ni : string_interfaces) ni->send_requested = true; + } break; + case 0x01: { + char *key = (char *)&buf[parsed + 1]; + parsed += 1 + strlen(key) + 1; + int value; + parsed += decode_varint(&buf[parsed], value); + NumberInterface *ni_ptr = nullptr; + for (auto ni : number_interfaces) { + if (strcmp(ni->key, key) == 0) { + ni_ptr = ni; + break; + } + } + if (number_update) { + number_update(ni_ptr, key, value); + } + if (ni_ptr) { + ni_ptr->value = value; + } + } break; + case 0x02: { + char *key = (char *)&buf[parsed + 1]; + parsed += 1 + strlen(key) + 1; + char *value = (char *)&buf[parsed]; + unsigned int strl = strlen(value) + 1; + parsed += strl; + StringInterface *stri_ptr = nullptr; + for (StringInterface *si : string_interfaces) { + if (strcmp(si->key, key) == 0) { + stri_ptr = si; + break; + } + } + if (string_update) { + string_update(stri_ptr, key, value); + } + if (stri_ptr) { + memcpy(stri_ptr->value, value, str_len_max > strl ? strl : str_len_max); + } + } break; + default: + // Error: Unknown datatype, ignore rest of the packet + return -1; + } + } + return 0; + } + + void request_all_interfaces() const { + uint8_t buf[] = {0x00, 0x00}; + unsigned int buf_len_used = sizeof(buf); + uint8_t cobs_buf[10]; + unsigned int cobs_buf_needed = cobs_encode(buf, buf_len_used, cobs_buf, BUF_SIZE); + write_pkg(cobs_buf, cobs_buf_needed); + } + + void update(time_t time) { + uint8_t buf[BUF_SIZE]; + unsigned int buf_len_used = 0; + for (auto ni : number_interfaces) { + if (ni->send_requested || ni->last_send > time || + ni->last_send + ni->update_interval <= time) { + unsigned int strl = strlen(ni->key) + 1; + unsigned int buf_len_needed = 1 + strl + 8 * sizeof(int) / 7; + if (BUF_SIZE < buf_len_needed) continue; // Error: update to large, skip interface + if (BUF_SIZE < buf_len_used + buf_len_needed) { + write_pkg(buf, buf_len_used); + buf_len_used = 0; + } + buf[buf_len_used++] = 0x01; + memcpy((char *)&buf[buf_len_used], ni->key, strl); + buf_len_used += strl; + buf_len_used += encode_varint(ni->value, &buf[buf_len_used]); + ni->last_send = time; + ni->send_requested = false; + } + } + for (auto ni : string_interfaces) { + if (ni->send_requested || ni->last_send > time || + ni->last_send + ni->update_interval <= time) { + unsigned int strl = strlen(ni->key) + 1; + unsigned int strl_value = strlen(ni->value) + 1; + unsigned int buf_len_needed = 1 + strl + strl_value; + buf_len_needed = buf_len_needed / 0xFF + 3; // COBS overhead + if (BUF_SIZE < buf_len_needed) continue; // Error: update to large, skip interface + if (BUF_SIZE < buf_len_used + buf_len_needed) { + uint8_t cobs_buf[BUF_SIZE]; + unsigned int cobs_buf_needed = + cobs_encode(buf, buf_len_used, cobs_buf, BUF_SIZE); + write_pkg(cobs_buf, cobs_buf_needed); + buf_len_used = 0; + } + buf[buf_len_used++] = 0x02; + memcpy((char *)&buf[buf_len_used], ni->key, strl); + buf_len_used += strl; + memcpy((char *)&buf[buf_len_used], ni->value, strl_value); + buf_len_used += strl_value; + ni->last_send = time; + ni->send_requested = false; + } + } + uint8_t cobs_buf[BUF_SIZE]; + unsigned int cobs_buf_needed = cobs_encode(buf, buf_len_used, cobs_buf, BUF_SIZE); + write_pkg(cobs_buf, cobs_buf_needed); + } + + int parse_stream_buf(uint8_t *buf, unsigned int buf_size) { + int parsed = 0; + for (unsigned int i = 0; i < buf_size; i += 1) { + if (buf[i] == 0x00) { + unsigned int buf_used = buf_size - parsed; + uint8_t packet_buf[BUF_SIZE]; + unsigned int packed_buf_used = sizeof(packet_buf); + if (cobs_decode(buf + parsed, buf_used, packet_buf, packed_buf_used) == 0) { + parse_pkg(packet_buf, packed_buf_used); + } + parsed = i + 1; + } + } + return parsed; + } + + protected: + uint8_t stream_buf[BUF_SIZE]; + unsigned int stream_buf_used = 0; + void handle_stream_buf(unsigned int buf_new_size) { + for (unsigned int i = stream_buf_used - buf_new_size; i < stream_buf_used; i += 1) { + if (stream_buf[i] == 0x00) { + unsigned int stream_buf_pkg_delimiter = i; + uint8_t packet_buf[BUF_SIZE]; + unsigned int packed_buf_used = sizeof(packet_buf); + if (cobs_decode(stream_buf, stream_buf_pkg_delimiter, packet_buf, + packed_buf_used) == 0) { + parse_pkg(packet_buf, packed_buf_used); + } + i += 1; + memmove(stream_buf, stream_buf + i, i); + stream_buf_used -= i; + i = 0; + } + } + } + + public: + int handle_stream(uint8_t *buf_new, unsigned int buf_new_size) { + if (buf_new_size + stream_buf_used >= BUF_SIZE) { + /* ERROR: buffer overflow -> discared data */ + stream_buf_used = 0; + return BUFFER_OVERFLOW; + } + memcpy(stream_buf + stream_buf_used, buf_new, buf_new_size); + stream_buf_used += buf_new_size; + handle_stream_buf(buf_new_size); + return 0; + } + +#ifdef ARDUINO + Print *ostream = nullptr; + + int write_arduino_stream(uint8_t *buf, unsigned int buf_size) { + if (ostream) { + ostream->write(buf, buf_size); + return 0; + } + return -1; + } + + SimpleSync(Print *ostream, void (*number_update)(NumberInterface *&, char *, int), + void (*string_update)(StringInterface *&, char *, char *)) + : write_pkg(&write_arduino_stream), number_update(number_update), + string_update(string_update), ostream(ostream) {} + + int handle_stream(Stream &s) { + unsigned int buf_new_size = s.available(); + s.readBytes(stream_buf, buf_new_size); + handle_stream(buf_new_size); + } +#endif +}; + +template +class SimpleSyncStatic : public SimpleSync { + public: + SimpleSyncStatic(int (*write_stream)(uint8_t *, unsigned int)) + : SimpleSync(write_stream, nullptr, nullptr){}; +}; +} // namespace simplesync +#endif // SIMPLESYNC_HPP diff --git a/library.json b/library.json new file mode 100644 index 0000000..580960c --- /dev/null +++ b/library.json @@ -0,0 +1,32 @@ +{ + "name": "SimpleSync", + "version": "0.0.1", + "description": "simple messaging protocol", + "repository": + { + "type": "git", + "url": "https://git.nilsschulte.de/nils/simplesync.git" + }, + "authors": + [ + { + "name": "Nils Schulte", + "email": "git@nilsschulte.de", + "url": "https://nilsschulte.de/", + "maintainer": "true" + } + ], + "license": "EUPL-1.2", + "homepage": "https://git.nilsschulte.de/nils/simplesync/", + // "dependencies": { + // "ownername/print": "~1.3.0" + // }, + "build": { + "srcFilter": [ + "-", + "-" + ] + }, + "frameworks": "*", + "platforms": "*" +} diff --git a/src/python_module.cpp b/src/python_module.cpp new file mode 100644 index 0000000..3a7a71a --- /dev/null +++ b/src/python_module.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +#include "simplesync.hpp" + +namespace py = pybind11; +typedef simplesync::SimpleSync, + std::chrono::nanoseconds> + SimpleS; + + +PYBIND11_MODULE(simplesync, m) { + py::class_(m, "SimpleSync") + .def(py::init([]() { return std::unique_ptr(new SimpleS(nullptr,nullptr,nullptr)); })) + .def(update); + +} diff --git a/src/simplesync.hpp b/src/simplesync.hpp deleted file mode 100644 index 98add4c..0000000 --- a/src/simplesync.hpp +++ /dev/null @@ -1,326 +0,0 @@ -#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/src/utils.h b/src/utils.h new file mode 100644 index 0000000..0a1ba1c --- /dev/null +++ b/src/utils.h @@ -0,0 +1,72 @@ + +inline int cobs_encode(uint8_t *buf_in, unsigned int buf_in_size, uint8_t *buf_out, + unsigned int buf_out_size) { + unsigned int i_out = 1; + unsigned int last0 = 0; + for (unsigned int i_in = 0; i_in < buf_in_size; i_in += 1, i_out += 1) { + if (i_out >= buf_out_size) return OUT_OF_MEMORY; + if ((buf_in[i_in] != 0x00) && ((i_out - last0) < 0xFF)) { + buf_out[i_out] = buf_in[i_in]; + } else { + buf_out[last0] = i_out - last0; + last0 = i_out; + } + } + buf_out[last0] = i_out - last0; + buf_out[i_out] = 0x00; + return i_out + 1; +} + +inline int cobs_decode(uint8_t *buf_in, unsigned int buf_in_size, uint8_t *buf_out, + unsigned int buf_out_size) { + unsigned int i_out = 0; + unsigned int next0 = 0; + bool skip_next0 = true; + for (unsigned int i_in = 0; i_in < buf_in_size || buf_in[i_in] == 0x00; i_in += 1) { + if (i_out >= buf_out_size) return OUT_OF_MEMORY; + if (next0 == i_in) { + if (!skip_next0) buf_out[i_out++] = 0x00; + next0 = buf_in[i_in]; + skip_next0 = (next0 - i_in) == 0xFF; + } else + buf_out[i_out++] = buf_in[i_in]; + } + return i_out-1; +} + +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; +} diff --git a/test/simplesync_test.cpp b/test/simplesync_test.cpp index b83aeae..5d00915 100644 --- a/test/simplesync_test.cpp +++ b/test/simplesync_test.cpp @@ -1,74 +1,81 @@ #include +#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); - } + 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) { +TEST(simplesync_test, cobs_encode) { + uint8_t buf_raw[] = {0x11, 0x22, 0x00, 0x33}; + uint8_t buf_coded[] = {0x03, 0x11, 0x22, 0x02, 0x33, 0x00}; - uint8_t buf[1024]; - unsigned int buf_size = sizeof(buf); + uint8_t buf[sizeof(buf_coded) + 1]; + unsigned int buf_used = simplesync::cobs_encode(buf_raw, sizeof(buf_raw), buf, sizeof(buf)); + ASSERT_EQ(buf_used, sizeof(buf_coded)); + ASSERT_EQ(0, memcmp(buf_coded, buf, sizeof(buf_coded))); - 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); + unsigned int buf_out_used = sizeof(buf); + unsigned int buf_in_used = sizeof(buf_coded); + int error = simplesync::cobs_decode(buf_coded, buf_in_used, buf, buf_out_used); + ASSERT_EQ(error, 0); + ASSERT_EQ(buf_used, sizeof(buf_coded)); + ASSERT_EQ(buf_out_used, sizeof(buf_raw)); + ASSERT_EQ(0, memcmp(buf_raw, buf, sizeof(buf_raw))); } -/*#include +uint8_t stream_buf[1024]; +unsigned int buf_size = sizeof(stream_buf); +unsigned int buf_used = 0; TEST(simplesync_test, encode_decode) { + buf_used = 0; + typedef simplesync::SimpleSyncStatic S; + S s([](uint8_t *buf, unsigned int buf_size) { + memcpy(stream_buf + buf_used, buf, buf_size); + buf_used += buf_size; + return 0; + }); - 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); + S::NumberInterface n1(s, (char *)"n1", 0); + S::StringInterface s1(s, (char *)"s1", 0); + S::NumberInterface n2(s, (char *)"n2", 2); - // 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); + n1.value = 4; + strcpy(s1.value, "hallo"); + n2.value = 2; - 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); -}*/ + for (int time = 0; time < 5; time += 1) { + buf_used = 0; + s.update(time); + s.update(time); + unsigned int parsed = 0; + for (unsigned int i = 1; i <= buf_used; i += 1) + parsed += s.parse_stream_buf(stream_buf + parsed, i - parsed); + ASSERT_EQ(parsed, buf_used); + } + ASSERT_EQ(n1.value, 4); + ASSERT_EQ(n2.value, 2); + ASSERT_STREQ(s1.value,"hallo"); +} int main(int argc, char **argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); }