wip
This commit is contained in:
		
							parent
							
								
									39922749ec
								
							
						
					
					
						commit
						a8dbc10b2b
					
				| @ -2,7 +2,7 @@ cmake_minimum_required (VERSION 3.15) | ||||
| project (simplesync) | ||||
| 
 | ||||
| set(CMAKE_CXX_FLAGS "-Wall -Wextra -pedantic") | ||||
| set(CMAKE_CXX_STANDARD 20) | ||||
| set(CMAKE_CXX_STANDARD 11) | ||||
| option(BUILD_TESTS "Build all tests." OFF) | ||||
| option(BUILD_PYLIB "Build python lib." OFF) | ||||
| 
 | ||||
| @ -52,7 +52,7 @@ install(EXPORT      simplesyncTargets | ||||
| 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  | ||||
| install(FILES       ${CMAKE_CURRENT_SOURCE_DIR}/include/simplesync.hpp  | ||||
|         DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/simplesync) | ||||
| export( EXPORT      simplesyncTargets | ||||
|         FILE        ${CMAKE_CURRENT_BINARY_DIR}/simplesync-targets.cmake) | ||||
|  | ||||
							
								
								
									
										23
									
								
								example/simplesynctui.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										23
									
								
								example/simplesynctui.py
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,23 @@ | ||||
| #!/usr/bin/python | ||||
| import serial | ||||
| import pysimplesync | ||||
| 
 | ||||
| try: | ||||
|     ser = serial.Serial("/dev/ttyACM0"); | ||||
| except: | ||||
|     ser = serial.Serial("/dev/ttyACM1"); | ||||
| 
 | ||||
| def write_to_ser(buf): | ||||
|     ser.write(bytes(buf)) | ||||
| sync = pysimplesync.SimpleSync(write_to_ser) | ||||
| 
 | ||||
| ser.reset_input_buffer() | ||||
| sync.request_all_interfaces() | ||||
| 
 | ||||
| while True: | ||||
|     sync.handle_stream(ser.read_until(b"\x00")) | ||||
|     print("\t".join([str(a) for a in list(sync)])) | ||||
|     sync["v2"] = -400 | ||||
|     sync["v1"] = 400 | ||||
|     # 11.62V := 604 | ||||
|     sync.update(); | ||||
| @ -1,23 +1,28 @@ | ||||
| #ifndef SIMPLESYNC_HPP | ||||
| #define SIMPLESYNC_HPP | ||||
| 
 | ||||
| #include <cstdint> | ||||
| #include <cstdlib> | ||||
| #include <cstring> | ||||
| #include <functional> | ||||
| #include <iostream> | ||||
| #include <vector> | ||||
| 
 | ||||
| #ifdef ARDUINO | ||||
| #include <Arduino.h> | ||||
| #ifdef __AVR__ | ||||
| #include "include/nonstd_functional.hpp" | ||||
| using nonstd::function; | ||||
| #else /* ndef __AVR__ */ | ||||
| using std::function; | ||||
| #define STDCLIB | ||||
| #endif | ||||
| #else /* ndef ARDUINO */ | ||||
| #define STDCLIB | ||||
| #endif | ||||
| #ifdef STDCLIB | ||||
| #include <cstdint> | ||||
| #include <cstdlib> | ||||
| #include <cstring> | ||||
| #include <functional> | ||||
| #include <iostream> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| using std::function; | ||||
| using std::string; | ||||
| #endif | ||||
| 
 | ||||
| namespace simplesync { | ||||
| @ -25,7 +30,7 @@ namespace simplesync { | ||||
| enum error_t { | ||||
|     BUFFER_OVERFLOW   = (-1), | ||||
|     INVALID_COMMAND   = (-2), | ||||
|     ID_INVALID      = (-3), | ||||
|     UNKNOWN_INTERFACE = (-3), | ||||
|     OUT_OF_MEMORY     = (-4), | ||||
|     CRC_MISSMATCH     = (-5), | ||||
| }; | ||||
| @ -71,7 +76,7 @@ inline int cobs_decode(uint8_t *buf_in, unsigned int &buf_in_size_used, uint8_t | ||||
|     } | ||||
|     buf_out_size_used = i_out; | ||||
|     buf_in_size_used  = i_in; | ||||
|     return 0; | ||||
|     return buf_out_size_used; | ||||
| } | ||||
| 
 | ||||
| inline unsigned int encode_zigzag(int input) { | ||||
| @ -80,33 +85,36 @@ inline unsigned int encode_zigzag(int input) { | ||||
| 
 | ||||
| inline unsigned int decode_zigzag(int input) { return (input >> 1) ^ (-(input & 1)); } | ||||
| 
 | ||||
| inline unsigned int encode_unsigned_varint(unsigned int input, uint8_t *out) { | ||||
| inline int encode_unsigned_varint(unsigned int input, uint8_t *out, unsigned int out_size) { | ||||
|     int offset = 0; | ||||
|     while (input > 0x7F) { | ||||
|         out[offset++] = input & 0x7F; | ||||
|         input >>= 7; | ||||
|         if (offset + 1 >= (int)out_size) return -1; | ||||
|     } | ||||
|     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 int encode_varint(int input, uint8_t *out, unsigned int out_size) { | ||||
|     return encode_unsigned_varint(encode_zigzag(input), out, out_size); | ||||
| } | ||||
| 
 | ||||
| inline unsigned int decode_unsigned_varint(uint8_t *input, unsigned int &out) { | ||||
| inline int decode_unsigned_varint(uint8_t *input, unsigned int input_size, unsigned int &out) { | ||||
|     int offset = 0; | ||||
|     out        = 0; | ||||
|     for (;; offset += 1) { | ||||
|         out |= ((unsigned int)(input[offset] & 0x7F)) << (7 * offset); | ||||
|         if (input[offset] & 0x80) break; | ||||
|         if (offset + 1 >= (int)input_size || offset + 1 >= (int)sizeof(unsigned int)) | ||||
|             return BUFFER_OVERFLOW; | ||||
|     } | ||||
|     return offset + 1; | ||||
| } | ||||
| 
 | ||||
| inline unsigned int decode_varint(uint8_t *input, int &out) { | ||||
| inline int decode_varint(uint8_t *input, unsigned int input_size, int &out) { | ||||
|     unsigned int out_uint; | ||||
|     int offset = decode_unsigned_varint(input, out_uint); | ||||
|     int offset = decode_unsigned_varint(input, input_size, out_uint); | ||||
|     out        = decode_zigzag(out_uint); | ||||
|     return offset; | ||||
| } | ||||
| @ -127,97 +135,228 @@ inline uint16_t calc_fletcher_checksum(uint8_t *buf, unsigned int buf_size) { | ||||
|     return (c1 << 8 | c0); | ||||
| } | ||||
| 
 | ||||
| template <typename time_t, typename timedelta_t, unsigned int BUF_SIZE = 256, | ||||
|           unsigned int str_len_max = 32> | ||||
| template <typename timepoint_t, typename timepointdelta_t, unsigned int BUF_SIZE = 256, | ||||
|           bool DYNAMIC_INTERFACES = false> | ||||
| class SimpleSync { | ||||
|   public: | ||||
|     static constexpr unsigned int STR_LEN_MAX = str_len_max; | ||||
|     class NumberInterface { | ||||
|     class Interface { | ||||
|       public: | ||||
|         time_t last_send; | ||||
|         char *key; | ||||
|         timedelta_t update_interval; | ||||
|         uint8_t type; | ||||
|         string key; | ||||
| 
 | ||||
|         std::function<int(uint8_t *buf, unsigned int buf_size)> pack; | ||||
|         std::function<int(uint8_t *buf, unsigned int buf_size)> unpack; | ||||
| 
 | ||||
|         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); | ||||
|         timepoint_t last_send; | ||||
| 
 | ||||
|         timepointdelta_t update_interval; | ||||
|         bool update_periodically; | ||||
| 
 | ||||
|         void *data; | ||||
|         bool data_owned; | ||||
| 
 | ||||
|         Interface(uint8_t type, string key, void *data, | ||||
|                   std::function<int(uint8_t *buf, unsigned int buf_size)> pack, | ||||
|                   std::function<int(uint8_t *buf, unsigned int buf_size)> unpack) | ||||
|             : type(type), key(key), pack(pack), unpack(unpack), update_periodically(false), | ||||
|               data(data), data_owned(false) {} | ||||
|         ~Interface() { | ||||
|             if (data != nullptr && data_owned) switch (type) { | ||||
|                 case 0x01: | ||||
|                     delete (int *)data; | ||||
|                     break; | ||||
|                 case 0x02: | ||||
|                     delete (string *)data; | ||||
|                     break; | ||||
|                 default: | ||||
|                     free(data); | ||||
|                     break; | ||||
|                 } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     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); | ||||
|     static Interface *NumberPtr(SimpleSync &sync, string key, int *value_ptr, bool ro = false) { | ||||
|         sync.interfaces.push_back(std::unique_ptr<Interface>(new Interface( | ||||
|             0x01, key, (void *)value_ptr, | ||||
|             [=](uint8_t *buf, unsigned int buf_size) -> int { /* pack */ | ||||
|                                                               if (buf_size < sizeof(int) * 8 / 7) | ||||
|                                                                   return BUFFER_OVERFLOW; | ||||
|                                                               return encode_varint(*value_ptr, buf, | ||||
|                                                                                    buf_size); | ||||
|             }, | ||||
|             [=](uint8_t *buf, unsigned int buf_size) -> int { /* unpack */ | ||||
|                                                               buf_size = 1 * buf_size; | ||||
|                                                               if (ro) { | ||||
|                                                                   int ro_value = 0; | ||||
|                                                                   int ret      = decode_varint( | ||||
|                                                                            buf, buf_size, ro_value); | ||||
|                                                                   // if (ro_value != *value) // TODO
 | ||||
|                                                                   // Error ro value
 | ||||
|                                                                   return ret; | ||||
|                                                               } | ||||
|                                                               return decode_varint(buf, buf_size, | ||||
|                                                                                    *value_ptr); | ||||
|             }))); | ||||
|         return sync.interfaces.back().get(); | ||||
|     } | ||||
|     }; | ||||
| 
 | ||||
|     std::vector<NumberInterface *> number_interfaces; | ||||
|     std::vector<StringInterface *> string_interfaces; | ||||
|     static Interface *NumberPtrPeriodic(SimpleSync &sync, string key, int *value_ptr, | ||||
|                                         timepointdelta_t update_interval, bool ro = false) { | ||||
|         auto i = NumberPtr(sync, key, value_ptr, ro); | ||||
|         if (i) { | ||||
|             i->update_interval     = update_interval; | ||||
|             i->update_periodically = true; | ||||
|         } | ||||
|         return i; | ||||
|     } | ||||
| 
 | ||||
|     void add_number(string key, int *value_ptr, timepointdelta_t update_interval) { | ||||
|         NumberPtrPeriodic(*this, key, value_ptr, update_interval); | ||||
|     } | ||||
|     void add_number_ro(string key, int *value_ptr, timepointdelta_t update_interval) { | ||||
|         NumberPtrPeriodic(*this, key, value_ptr, update_interval, true); | ||||
|     } | ||||
| 
 | ||||
|     static Interface *Number(SimpleSync &sync, string key) { | ||||
|         auto v_ptr   = new int; | ||||
|         Interface *i = NumberPtr(sync, key, v_ptr); | ||||
|         if (i) | ||||
|             i->data_owned = true; | ||||
|         else | ||||
|             delete v_ptr; | ||||
|         return i; | ||||
|     } | ||||
| 
 | ||||
|     static Interface *TextPtr(SimpleSync &sync, string key, string *value_ptr) { | ||||
|         sync.interfaces.push_back(std::unique_ptr<Interface>(new Interface( | ||||
|             0x02, key, (void *)value_ptr, | ||||
|             [=](uint8_t *buf, unsigned int buf_size) -> int { /* pack */ | ||||
|                                                               auto value_len = | ||||
|                                                                   1 + value_ptr->size(); | ||||
|                                                               if (value_len > buf_size) | ||||
|                                                                   return BUFFER_OVERFLOW; | ||||
|                                                               memcpy(buf, value_ptr->c_str(), | ||||
|                                                                      value_len - 1); | ||||
|                                                               buf[value_len - 1] = 0; | ||||
|                                                               return value_len; | ||||
|             }, | ||||
|             [=](uint8_t *buf, unsigned int buf_size) -> int { /* unpack */ | ||||
|                                                               auto strl = | ||||
|                                                                   strnlen((char *)buf, buf_size) + | ||||
|                                                                   1; | ||||
|                                                               if (strl > buf_size) | ||||
|                                                                   return BUFFER_OVERFLOW; | ||||
|                                                               value_ptr->assign((char *)buf); | ||||
|                                                               return strl; | ||||
|             }))); | ||||
|         return sync.interfaces.back().get(); | ||||
|     } | ||||
| 
 | ||||
|     static Interface *TextPtrPeriodic(SimpleSync &sync, string key, string *value_ptr, | ||||
|                                       timepointdelta_t update_interval) { | ||||
|         auto i = TextPtr(sync, key, value_ptr); | ||||
|         if (i) { | ||||
|             i->update_interval     = update_interval; | ||||
|             i->update_periodically = true; | ||||
|         } | ||||
|         return i; | ||||
|     } | ||||
| 
 | ||||
|     static Interface *Text(SimpleSync &sync, string key) { | ||||
|         auto v_ptr   = new string; | ||||
|         Interface *i = TextPtr(sync, key, v_ptr); | ||||
|         if (i) | ||||
|             i->data_owned = true; | ||||
|         else | ||||
|             delete v_ptr; | ||||
|         return i; | ||||
|     } | ||||
| 
 | ||||
|     // int parse_number(uint8_t *buf, unsigned int buf_size, int* value){
 | ||||
|     //     return decode_varint(&buf[parsed], *value);
 | ||||
|     // }
 | ||||
| 
 | ||||
|     std::vector<std::unique_ptr<Interface>> interfaces; | ||||
| 
 | ||||
|     function<int(uint8_t *, unsigned int)> write_pkg; | ||||
|     function<void(NumberInterface *&, char *, int)> number_update; | ||||
|     function<void(StringInterface *&, char *, char *)> string_update; | ||||
|     SimpleSync(function<int(uint8_t *, unsigned int)> write_pkg, | ||||
|                function<void(NumberInterface *&, char *, int)> number_update, | ||||
|                function<void(StringInterface *&, char *, char *)> string_update) | ||||
|         : write_pkg(write_pkg), number_update(number_update), string_update(string_update) {} | ||||
|     function<timepoint_t()> get_time; | ||||
|     SimpleSync(function<int(uint8_t *, unsigned int)> write_pkg, function<timepoint_t()> get_time) | ||||
|         : write_pkg(write_pkg), get_time(get_time) { | ||||
|         last_pkg_recived = get_time(); | ||||
|     } | ||||
| 
 | ||||
|     Interface *get_interface(const uint8_t type, const char *const key) const { | ||||
|         for (auto &i : interfaces) { | ||||
|             if (i->type == type && strcmp(i->key.c_str(), key) == 0) { | ||||
|                 return i.get(); | ||||
|             } | ||||
|         } | ||||
|         return nullptr; | ||||
|     } | ||||
|     Interface *get_or_create_interface(uint8_t type, const char *const key) { | ||||
|         Interface *i_exists = get_interface(type, key); | ||||
|         if (i_exists) { | ||||
|             return i_exists; | ||||
|         } | ||||
|         if (DYNAMIC_INTERFACES) { | ||||
|             switch (type) { | ||||
|             case 0x01: | ||||
|                 return Number(*this, key); | ||||
|             case 0x02: | ||||
|                 return Text(*this, key); | ||||
|             } | ||||
|         } | ||||
|         return nullptr; | ||||
|     } | ||||
| 
 | ||||
|     bool watchdog_enabled = false; | ||||
|     function<void()> watchdog_func; | ||||
|     timepointdelta_t watchdog_timeout; | ||||
|     timepoint_t last_pkg_recived; | ||||
|     void set_watchdog(const function<void()> &f, timepointdelta_t timeout) { | ||||
|         watchdog_enabled = bool(f); | ||||
|         if (!watchdog_enabled) return; | ||||
|         watchdog_timeout = timeout; | ||||
|         watchdog_func    = f; | ||||
|     } | ||||
| 
 | ||||
|     void check_watchdog(const timepoint_t &time) { | ||||
|         if (watchdog_enabled && time > last_pkg_recived + watchdog_timeout) watchdog_func(); | ||||
|     } | ||||
| 
 | ||||
|     int parse_pkg(uint8_t *buf, unsigned int buf_size) { | ||||
|         if (buf_size < 4) return CRC_MISSMATCH; | ||||
|         uint16_t crc = calc_fletcher_checksum(buf, buf_size - 2); | ||||
|         if (buf[--buf_size] != (crc & 0xFF) || (buf[--buf_size] != (crc >> 8))) | ||||
|             return CRC_MISSMATCH; | ||||
|         timepoint_t time = get_time(); | ||||
|         check_watchdog(time); | ||||
|         for (unsigned int parsed = 0; parsed < buf_size;) { | ||||
|             switch (buf[parsed]) { | ||||
|             uint8_t type = buf[parsed++]; | ||||
|             switch (type) { | ||||
|             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; | ||||
|                 } | ||||
|                 char *key   = (char *)&buf[parsed]; | ||||
|                 auto keylen = strnlen(key, buf_size - parsed) + 1; | ||||
|                 if (keylen + parsed >= buf_size) return BUFFER_OVERFLOW; | ||||
|                 parsed += keylen; | ||||
|                 if (keylen == 0) | ||||
|                     for (auto &ni : interfaces) ni->send_requested = true; | ||||
|             } break; | ||||
|             case 0x01: | ||||
|             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); | ||||
|                 char *key           = (char *)&buf[parsed]; | ||||
|                 unsigned int keylen = strnlen(key, buf_size - parsed) + 1; | ||||
|                 if (keylen + parsed >= buf_size) return BUFFER_OVERFLOW; | ||||
|                 parsed += keylen; | ||||
|                 Interface *ni = get_or_create_interface(type, key); | ||||
|                 if (ni == nullptr) return UNKNOWN_INTERFACE; | ||||
|                 ni->send_requested = false; | ||||
|                 int unpacked_bytes = ni->unpack(buf + parsed, buf_size - parsed); | ||||
|                 if (unpacked_bytes >= 0) { | ||||
|                     parsed += unpacked_bytes; | ||||
|                 } else { // ERROR -> return
 | ||||
|                     return unpacked_bytes; | ||||
|                 } | ||||
|             } break; | ||||
|             default: | ||||
| @ -225,80 +364,68 @@ class SimpleSync { | ||||
|                 return -1; | ||||
|             } | ||||
|         } | ||||
|         last_pkg_recived = time; | ||||
|         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); | ||||
|         uint8_t buf[] = {0x00, 0x00, 0xac, 0xcd, 0xef}; | ||||
|         encode_and_write_pkg(buf, 2); | ||||
|     } | ||||
| 
 | ||||
|     void update(time_t time) { | ||||
|   private: | ||||
|     void encode_and_write_pkg(uint8_t *buf, unsigned int buf_size) const { | ||||
|         if (buf_size <= 1) { | ||||
|             return; | ||||
|         } | ||||
|         uint8_t cobs_buf[BUF_SIZE]; | ||||
|         uint16_t crc        = calc_fletcher_checksum(buf, buf_size); | ||||
|         buf[buf_size++]     = crc >> 8; | ||||
|         buf[buf_size++]     = crc & 0xFF; | ||||
|         int cobs_buf_needed = cobs_encode(buf, buf_size, cobs_buf, BUF_SIZE); | ||||
|         if (cobs_buf_needed < 0) { | ||||
|             // TODO: error
 | ||||
|         } else { | ||||
|             write_pkg(cobs_buf, cobs_buf_needed); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|   public: | ||||
|     void update() { | ||||
|         timepoint_t time = get_time(); | ||||
|         check_watchdog(time); | ||||
|         uint8_t buf[BUF_SIZE]; | ||||
|         unsigned int buf_len_used = 0; | ||||
|         for (auto ni : number_interfaces) { | ||||
|         for (auto &ni : interfaces) { | ||||
|             if (ni->send_requested || | ||||
|                 (ni->last_send > time || ni->last_send <= time - ni->update_interval)) { | ||||
|                 unsigned int strl           = strlen(ni->key) + 1; | ||||
|                 unsigned int buf_len_needed = 1 + strl + 8 * sizeof(int) / 7; | ||||
|                 (ni->update_periodically && | ||||
|                  (ni->last_send > time || ni->last_send <= time - ni->update_interval))) { | ||||
|                 unsigned int strl = ni->key.size() + 1; | ||||
|                 unsigned int buf_len_needed = | ||||
|                     1 + strl + 32; // TODO get real pack size (not always 32)
 | ||||
|                 buf_len_needed += buf_len_needed / 0xFF + 3 + 2; // 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]; | ||||
|                     uint16_t crc        = calc_fletcher_checksum(buf, buf_len_used); | ||||
|                     buf[buf_len_used++] = crc >> 8; | ||||
|                     buf[buf_len_used++] = crc & 0xFF; | ||||
|                     unsigned int cobs_buf_needed = | ||||
|                         cobs_encode(buf, buf_len_used, cobs_buf, BUF_SIZE); | ||||
|                     write_pkg(cobs_buf, cobs_buf_needed); | ||||
|                     encode_and_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[buf_len_used++] = ni->type; | ||||
|                 memcpy((char *)&buf[buf_len_used], ni->key.c_str(), strl); | ||||
|                 buf_len_used += strl; | ||||
|                 buf_len_used += encode_varint(ni->value, &buf[buf_len_used]); | ||||
|                 int pack_len = ni->pack(&buf[buf_len_used], BUF_SIZE - buf_len_used); | ||||
|                 if (pack_len < 0) { | ||||
|                     // ERROR -> return
 | ||||
|                     buf_len_used = 0; | ||||
|                     return; | ||||
|                 } | ||||
|                 buf_len_used += pack_len; | ||||
|                 ni->last_send      = time; | ||||
|                 ni->send_requested = false; | ||||
|             } | ||||
|         } | ||||
|         for (auto ni : string_interfaces) { | ||||
|             if (ni->send_requested || | ||||
|                 (ni->last_send > time || ni->last_send <= time - ni->update_interval)) { | ||||
|                 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 + 2; // COBS overhead
 | ||||
|                 if (BUF_SIZE < buf_len_needed) continue; // Error: update to large, skip interface
 | ||||
|                 if (BUF_SIZE < buf_len_used + buf_len_needed + 2) { | ||||
|                     uint8_t cobs_buf[BUF_SIZE]; | ||||
|                     uint16_t crc        = calc_fletcher_checksum(buf, buf_len_used); | ||||
|                     buf[buf_len_used++] = crc >> 8; | ||||
|                     buf[buf_len_used++] = crc & 0xFF; | ||||
|                     unsigned int cobs_buf_needed = | ||||
|                         cobs_encode(buf, buf_len_used, cobs_buf, BUF_SIZE); | ||||
|                     write_pkg(cobs_buf, cobs_buf_needed); | ||||
|         encode_and_write_pkg(buf, buf_len_used); | ||||
|         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; | ||||
|             } | ||||
|         } | ||||
|         if (buf_len_used == 0) return; | ||||
|         uint8_t cobs_buf[BUF_SIZE]; | ||||
|         uint16_t crc                 = calc_fletcher_checksum(buf, buf_len_used); | ||||
|         buf[buf_len_used++]          = crc >> 8; | ||||
|         buf[buf_len_used++]          = crc & 0xFF; | ||||
|         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; | ||||
| @ -307,8 +434,10 @@ class SimpleSync { | ||||
|                 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); | ||||
|                 if (cobs_decode(buf + parsed, buf_used, packet_buf, packed_buf_used) >= 0) { | ||||
|                     if (parse_pkg(packet_buf, packed_buf_used) < 0) { | ||||
|                         // TODO error
 | ||||
|                     } | ||||
|                 } | ||||
|                 parsed = i + 1; | ||||
|             } | ||||
| @ -326,7 +455,7 @@ class SimpleSync { | ||||
|                 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) { | ||||
|                                 packed_buf_used) >= 0) { | ||||
|                     parse_pkg(packet_buf, packed_buf_used); | ||||
|                 } | ||||
|                 i += 1; | ||||
| @ -350,31 +479,29 @@ class SimpleSync { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     NumberInterface *get_number_if(const char *const key) const { | ||||
|         for (const auto ni : number_interfaces) { | ||||
|             if (strcmp(ni->key, key) == 0) return ni; | ||||
|         } | ||||
|     int *get_number(const char *const key) { | ||||
|         Interface *i = get_interface(0x01, key); | ||||
|         if (i) return (int *)i->data; | ||||
|         return nullptr; | ||||
|     } | ||||
| 
 | ||||
|     StringInterface *get_string_if(const char *const key) const { | ||||
|         for (const auto ni : string_interfaces) { | ||||
|             if (strcmp(ni->key, key) == 0) return ni; | ||||
|         } | ||||
|     string *get_string(const char *const key) { | ||||
|         Interface *i = get_interface(0x02, key); | ||||
|         if (i) return (string *)i->data; | ||||
|         return nullptr; | ||||
|     } | ||||
| 
 | ||||
| #ifdef ARDUINO | ||||
|     SimpleSync(Print *ostream, function<void(NumberInterface *&, char *, int)> number_update, | ||||
|                function<void(StringInterface *&, char *, char *)> string_update) | ||||
|         : write_pkg([ostream](uint8_t *buf, unsigned int buf_size) { | ||||
|     SimpleSync(Print *ostream, function<timepoint_t()> get_time) | ||||
|         : SimpleSync( | ||||
|               [ostream](uint8_t *buf, unsigned int buf_size) { | ||||
|                   if (ostream) { | ||||
|                       ostream->write(buf, buf_size); | ||||
|                       return 0; | ||||
|                   } | ||||
|                   return -1; | ||||
|           }), | ||||
|           number_update(number_update), string_update(string_update) {} | ||||
|               }, | ||||
|               get_time) {} | ||||
|     int handle_stream(Stream &in_stream) { | ||||
|         unsigned int buf_new_size = in_stream.available(); | ||||
|         if (buf_new_size + stream_buf_used > BUF_SIZE) { | ||||
| @ -390,81 +517,5 @@ class SimpleSync { | ||||
| #endif | ||||
| }; | ||||
| 
 | ||||
| template <typename time_t, typename timedelta_t, unsigned int buf_len = 256, | ||||
|           unsigned int str_len_max = 32> | ||||
| class SimpleSyncStatic : public SimpleSync<time_t, timedelta_t, buf_len, str_len_max> { | ||||
|   public: | ||||
|     SimpleSyncStatic(function<int(uint8_t *, unsigned int)> write_pkg) | ||||
|         : SimpleSync<time_t, timedelta_t, buf_len, str_len_max>(write_pkg, nullptr, nullptr){}; | ||||
| #ifdef ARDUINO | ||||
|     SimpleSyncStatic(Print *ostream) | ||||
|         : SimpleSync<time_t, timedelta_t, buf_len, str_len_max>(ostream, nullptr, nullptr) {} | ||||
| #endif | ||||
| }; | ||||
| template <typename time_t, typename timedelta_t, unsigned int buf_len = 256, | ||||
|           unsigned int str_len_max = 32> | ||||
| class SimpleSyncDynamic : public SimpleSync<time_t, timedelta_t, buf_len, str_len_max> { | ||||
|     std::vector<typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::NumberInterface *> | ||||
|         dyn_number_interfaces; | ||||
|     std::vector<typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::StringInterface *> | ||||
|         dyn_string_interfaces; | ||||
|     std::vector<char *> dyn_keys; | ||||
| 
 | ||||
|   public: | ||||
|     SimpleSyncDynamic( | ||||
|         function<int(uint8_t *, unsigned int)> write_pkg, | ||||
|         function< | ||||
|             void(typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::NumberInterface *&, | ||||
|                  char *, int)> | ||||
|             dyn_number_update, | ||||
|         function< | ||||
|             void(typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::StringInterface *&, | ||||
|                  char *, char *)> | ||||
|             dyn_string_update, | ||||
|         timedelta_t dyn_update_feq) | ||||
|         : SimpleSync<time_t, timedelta_t, buf_len, str_len_max>( | ||||
|               write_pkg, | ||||
|               [this, dyn_number_update, dyn_update_feq]( | ||||
|                   typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::NumberInterface * | ||||
|                       &ni, | ||||
|                   char *key, int value) { | ||||
|                   if (ni == nullptr) { | ||||
|                       auto allocated_key = (char *)malloc(strlen(key) + 1); | ||||
|                       strcpy(allocated_key, key); | ||||
|                       dyn_keys.push_back(allocated_key); | ||||
|                       ni                 = new SimpleSync<time_t, timedelta_t, buf_len, | ||||
|                                           str_len_max>::NumberInterface(*this, allocated_key, | ||||
|                                                                                         dyn_update_feq); | ||||
|                       ni->send_requested = false; | ||||
|                       dyn_number_interfaces.push_back(ni); | ||||
|                   } | ||||
|                   dyn_number_update(ni, key, value); | ||||
|               }, | ||||
|               [this, dyn_string_update, dyn_update_feq]( | ||||
|                   typename SimpleSync<time_t, timedelta_t, buf_len, str_len_max>::StringInterface * | ||||
|                       &ni, | ||||
|                   char *key, char *value) { | ||||
|                   if (ni == nullptr) { | ||||
|                       auto allocated_key = (char *)malloc(strlen(key) + 1); | ||||
|                       strcpy(allocated_key, key); | ||||
|                       dyn_keys.push_back(allocated_key); | ||||
|                       ni                 = new SimpleSync<time_t, timedelta_t, buf_len, | ||||
|                                           str_len_max>::StringInterface(*this, allocated_key, | ||||
|                                                                                         dyn_update_feq); | ||||
|                       ni->send_requested = false; | ||||
|                       dyn_string_interfaces.push_back(ni); | ||||
|                   } | ||||
|                   dyn_string_update(ni, key, value); | ||||
|               }){}; | ||||
|     ~SimpleSyncDynamic() { | ||||
|         for (auto i : dyn_number_interfaces) delete i; | ||||
|         for (auto i : dyn_string_interfaces) delete i; | ||||
|         for (auto i : dyn_keys) free(i); | ||||
|     }; | ||||
| #ifdef ARDUINO | ||||
|     SimpleSyncDynamic(Print *ostream) | ||||
|         : SimpleSync<time_t, timedelta_t, buf_len, str_len_max>(ostream, nullptr, nullptr) {} | ||||
| #endif | ||||
| }; | ||||
| } // namespace simplesync
 | ||||
| #endif // SIMPLESYNC_HPP
 | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| #include <chrono> | ||||
| #include <functional> | ||||
| #include <pybind11/cast.h> | ||||
| #include <pybind11/chrono.h> | ||||
| #include <pybind11/functional.h> | ||||
| @ -8,44 +9,48 @@ | ||||
| #include "simplesync.hpp" | ||||
| 
 | ||||
| namespace py = pybind11; | ||||
| typedef simplesync::SimpleSyncDynamic<std::chrono::time_point<std::chrono::steady_clock>, | ||||
|                                       std::chrono::nanoseconds, 1024> | ||||
| typedef simplesync::SimpleSync<std::chrono::time_point<std::chrono::steady_clock>, | ||||
|                                std::chrono::nanoseconds, 1024, true> | ||||
|     SimpleS; | ||||
| 
 | ||||
| PYBIND11_MODULE(pysimplesync, m) { | ||||
|     py::class_<SimpleS>(m, "SimpleSync") | ||||
|         .def(py::init([]() { | ||||
|         .def(py::init([](const std::function<void(py::bytes)> &write) { | ||||
|             return std::unique_ptr<SimpleS>(new SimpleS( | ||||
|                 [](uint8_t *buf, unsigned int buf_size) { | ||||
|                     py::print(py::bytes((char *)buf, buf_size)); | ||||
|                 [write](uint8_t *buf, unsigned int buf_size) { | ||||
|                     std::string sbuf; | ||||
|                     sbuf.assign((char *)buf, buf_size); | ||||
|                     write(py::bytes(sbuf)); | ||||
|                     // py::print(py::bytes((char *)buf, buf_size));
 | ||||
|                     return 0; | ||||
|                 }, | ||||
|                 [](SimpleS::NumberInterface *&, char *key, int value) { | ||||
|                     py::print(std::string(key), ": ", value); | ||||
|                 }, | ||||
|                 nullptr, std::chrono::nanoseconds::max())); | ||||
|                 []() { return (std::chrono::steady_clock::now()); })); | ||||
|         })) | ||||
|         .def("request_all_interfaces", &SimpleS::request_all_interfaces) | ||||
|         .def( | ||||
|             "__getitem__", | ||||
|             [](SimpleS &s, const std::string &key) -> py::object { | ||||
|                 auto ni = s.get_number_if(key.c_str()); | ||||
|                 if (ni) return py::int_(ni->value); | ||||
|                 auto si = s.get_string_if(key.c_str()); | ||||
|                 if (si) return py::str(si->value); | ||||
|                 auto ni = s.get_number(key.c_str()); | ||||
|                 if (ni) return py::int_(*ni); | ||||
|                 auto si = s.get_string(key.c_str()); | ||||
|                 if (si) return py::str(*si); | ||||
|                 throw pybind11::key_error(); | ||||
|             }, | ||||
|             py::is_operator()) | ||||
|         .def( | ||||
|             "__getitem__", | ||||
|             [](SimpleS &s, unsigned int i) -> py::tuple { | ||||
|                 if (i < s.number_interfaces.size()) { | ||||
|                     return py::make_tuple(py::str(s.number_interfaces[i]->key), | ||||
|                                           py::int_(s.number_interfaces[i]->value)); | ||||
|                 if (i < s.interfaces.size()) { | ||||
|                     auto &interface = s.interfaces[i]; | ||||
|                     if (!interface) throw pybind11::index_error(); | ||||
|                     switch (interface->type) { | ||||
|                     case 0x01: | ||||
|                         return py::make_tuple(py::str(interface->key), | ||||
|                                               py::int_(*s.get_number(interface->key.c_str()))); | ||||
|                     case 0x02: | ||||
|                         return py::make_tuple(py::str(interface->key), | ||||
|                                               py::str(*s.get_string(interface->key.c_str()))); | ||||
|                     } | ||||
|                 i -= s.number_interfaces.size(); | ||||
|                 if (i < s.string_interfaces.size()) { | ||||
|                     return py::make_tuple(py::str(s.string_interfaces[i]->key), | ||||
|                                           py::str(s.string_interfaces[i]->value)); | ||||
|                 } | ||||
|                 throw pybind11::index_error(); | ||||
|             }, | ||||
| @ -54,19 +59,16 @@ PYBIND11_MODULE(pysimplesync, m) { | ||||
|             "__setitem__", | ||||
|             [](SimpleS &s, const std::string &key, py::object value) { | ||||
|                 if (py::isinstance<py::int_>(value)) { | ||||
|                     auto ni = s.get_number_if(key.c_str()); | ||||
|                     auto ni = s.get_or_create_interface(0x01, key.c_str()); | ||||
|                     if (ni) { | ||||
|                         ni->value          = value.cast<int>(); | ||||
|                         *(int *)ni->data   = value.cast<int>(); | ||||
|                         ni->send_requested = true; | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|                 else if (py::isinstance<py::str>(value)) { | ||||
|                     auto ni = s.get_string_if(key.c_str()); | ||||
|                 } else if (py::isinstance<py::str>(value)) { | ||||
|                     auto ni = s.get_or_create_interface(0x02, key.c_str()); | ||||
|                     if (ni) { | ||||
|                         if(value.cast<std::string>().length() > SimpleS::STR_LEN_MAX) | ||||
|                           throw pybind11::buffer_error(); | ||||
|                         strcpy(ni->value, value.cast<std::string>().c_str()); | ||||
|                         *(std::string *)ni->data = value.cast<std::string>(); | ||||
|                         ni->send_requested       = true; | ||||
|                         return; | ||||
|                     } | ||||
| @ -75,7 +77,7 @@ PYBIND11_MODULE(pysimplesync, m) { | ||||
|                 throw pybind11::key_error(); | ||||
|             }, | ||||
|             py::is_operator()) | ||||
|         .def("update", [](SimpleS &s) { s.update(std::chrono::steady_clock::now()); }) | ||||
|         .def("update", &SimpleS::update) | ||||
|         .def("handle_stream", [](SimpleS &s, const std::string &buf_new) { | ||||
|             /* handle_stream(b'\x06\x01\x74\x65\x73\x74\x04\x6d\x21\x97\x00') */ | ||||
|             s.handle_stream((const uint8_t *)buf_new.data(), buf_new.length()); | ||||
|  | ||||
| @ -1,22 +1,22 @@ | ||||
| #include <cstdint> | ||||
| #include <cstring> | ||||
| #include <gtest/gtest.h> | ||||
| #include <iostream> | ||||
| 
 | ||||
| #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); | ||||
|         int offset = simplesync::encode_varint(i, buf, sizeof(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); | ||||
|         offset = simplesync::decode_varint(buf, sizeof(buf), out); | ||||
|         ASSERT_LE(1, offset); | ||||
|         ASSERT_EQ(out, i); | ||||
|     } | ||||
| @ -33,8 +33,8 @@ TEST(simplesync_test, cobs_encode) { | ||||
| 
 | ||||
|     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); | ||||
|     int decoded_bytes         = simplesync::cobs_decode(buf_coded, buf_in_used, buf, buf_out_used); | ||||
|     ASSERT_EQ(decoded_bytes, sizeof(buf_raw)); | ||||
|     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))); | ||||
| @ -46,33 +46,47 @@ unsigned int buf_used = 0; | ||||
| 
 | ||||
| TEST(simplesync_test, encode_decode) { | ||||
|     buf_used = 0; | ||||
|     typedef simplesync::SimpleSyncStatic<int, int, 999> S; | ||||
|     S s([](uint8_t *buf, unsigned int buf_size) { | ||||
|     int time = 0; | ||||
|     typedef simplesync::SimpleSync<int, int, 512, true> S; | ||||
|     S s_send( | ||||
|         [](uint8_t *buf, unsigned int buf_size) { | ||||
|             memcpy(stream_buf + buf_used, buf, buf_size); | ||||
|             buf_used += buf_size; | ||||
|             return 0; | ||||
|     }); | ||||
|         }, | ||||
|         [&]() { return time; }); | ||||
|     S s_recv([](uint8_t *, unsigned int) { return 0; }, [&]() { return time; }); | ||||
| 
 | ||||
|     S::NumberInterface n1(s, (char *)"n1", 0); | ||||
|     S::StringInterface s1(s, (char *)"s1", 0); | ||||
|     S::NumberInterface n2(s, (char *)"n2", 2); | ||||
|     int n1          = 4; | ||||
|     int n2          = 2; | ||||
|     std::string str = "hello"; | ||||
|     S::NumberPtrPeriodic(s_send, "n1", &n1, 0); | ||||
|     S::NumberPtrPeriodic(s_send, "n2", &n2, 2); | ||||
|     S::TextPtrPeriodic(s_send, "s1", &str, 0); | ||||
| 
 | ||||
|     n1.value = 4; | ||||
|     strcpy(s1.value, "hallo"); | ||||
|     n2.value = 2; | ||||
| 
 | ||||
|     for (int time = 0; time < 5; time += 1) { | ||||
|     for (time = 0; time < 5; time += 1) { | ||||
|         buf_used = 0; | ||||
|         s.update(time); | ||||
|         s.update(time); | ||||
|         s_send.update(); | ||||
|         s_send.update(); | ||||
|         for (unsigned int i = 0; i < buf_used; i += 1) | ||||
|             std::cout << std::hex << (int)stream_buf[i] << " "; | ||||
|         std::cout << std::dec << std::endl; | ||||
| 
 | ||||
|         unsigned int parsed = 0; | ||||
|         for (unsigned int i = 1; i <= buf_used; i += 1) | ||||
|             parsed += s.parse_stream_buf(stream_buf + parsed, i - parsed); | ||||
|             parsed += s_recv.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"); | ||||
|     str = "hollo2"; | ||||
|     ASSERT_NE(s_recv.get_number((char *)"n1"), nullptr); | ||||
|     ASSERT_EQ(*s_recv.get_number((char *)"n1"), n1); | ||||
|     ASSERT_NE(s_recv.get_number((char *)"n2"), nullptr); | ||||
|     ASSERT_EQ(*s_recv.get_number((char *)"n2"), n2); | ||||
|     ASSERT_STREQ(s_send.get_string((char *)"s1")->c_str(), "hollo2"); | ||||
|     ASSERT_STREQ(str.c_str(), "hollo2"); | ||||
|     ASSERT_STREQ(s_recv.get_string((char *)"s1")->c_str(), "hello"); | ||||
|     ASSERT_EQ(4, n1); | ||||
|     ASSERT_EQ(2, n2); | ||||
| } | ||||
| 
 | ||||
| int main(int argc, char **argv) { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user