diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 91e05d2..162df5a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -33,7 +33,7 @@ jobs: - uses: actions/upload-artifact@v2 with: name: test-server-${{ matrix.target }} - path: test/build/test_server.elf + path: test/build/test_node.elf test-pyfibre: needs: [compile] @@ -70,12 +70,12 @@ jobs: python3 --version - chmod +x ./artifacts/test-server-$ARCH/test_server.elf - ls ./artifacts/test-server-$ARCH/test_server.elf + chmod +x ./artifacts/test-server-$ARCH/test_node.elf + ls ./artifacts/test-server-$ARCH/test_node.elf cp ./artifacts/libfibre-$ARCH/* ./python/fibre/ # Launch test server in background - FIBRE_LOG=5 ./artifacts/test-server-$ARCH/test_server.elf >test_server.log 2>&1 & + FIBRE_LOG=5 ./artifacts/test-server-$ARCH/test_node.elf --server --domain tcp-server:address=localhost,port=14220 >test_server.log 2>&1 & # TODO: try launching client/server in reverse order sleep 1 @@ -91,7 +91,7 @@ jobs: CLIENT_STATUS="fail" fi - # Tell test_server.elf politely to finish (if it's still running) + # Tell test_node.elf politely to finish (if it's still running) echo "terminiating test server" @@ -132,7 +132,9 @@ jobs: - name: Check C++ Formatting run: | clang-format --version - # TODO: we run this only on one file for now until we have it properly configured - clang-format -style=file --Werror --dry-run test/test_server.cpp + # TODO: we run this only on a few selected files for now until we have it properly configured + clang-format -style=file --Werror --dry-run test/test_node.cpp + clang-format -style=file --Werror --dry-run test/test_node.hpp + clang-format -style=file --Werror --dry-run sim/sim_main.cpp # TODO: check if interface_generator outputs the same thing with Python 3.5 and Python 3.8 diff --git a/.gitignore b/.gitignore index 85ec9ec..48e843b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ __pycache__ # Test build files /test/autogen /test/build -/test/test_server.elf # Tup database /.tup diff --git a/.vscode/launch.json b/.vscode/launch.json index 7859a7f..3dabcf8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -54,12 +54,35 @@ "name": "C++ Test Server", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/test/build/test_server.elf", + "program": "${workspaceFolder}/test/build/test_node.elf", "stopAtEntry": false, "cwd": "${workspaceFolder}/test", "environment": [{"name": "FIBRE_LOG", "value": "5"}], "externalConsole": false, "MIMode": "gdb", + "args": ["--server", "--domain", "tcp-server:address=localhost,port=14220"], + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "symbolLoadInfo": { + "loadAll": true, + "exceptionList": "" + } + }, + { + "name": "Simulation", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/sim/build/fibre_sim", + "stopAtEntry": false, + "cwd": "${workspaceFolder}/sim", + "environment": [{"name": "FIBRE_LOG", "value": "5"}], + "externalConsole": false, + "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", diff --git a/Tuprules.lua b/Tuprules.lua new file mode 100644 index 0000000..a817e53 --- /dev/null +++ b/Tuprules.lua @@ -0,0 +1,10 @@ + +function compile(src_file, extra_inputs) + obj_file = 'build/'..tup.file(src_file)..'.o' + tup.frule{ + inputs={src_file, extra_inputs=extra_inputs}, + command='^co^ '..CXX..' -c %f '..tostring(CFLAGS)..' -o %o', + outputs={obj_file} + } + return obj_file +end diff --git a/cpp/README.md b/cpp/README.md index 8b5b352..1a03ad1 100644 --- a/cpp/README.md +++ b/cpp/README.md @@ -34,7 +34,7 @@ If your application doesn't use tup, you have to manually check which code files Currently there's no nice walkthrough for this but here are two applications that you can use as an example: - The [ODrive Firmware](https://github.com/madcowswe/ODrive/tree/devel/Firmware) - - The [test server](https://github.com/samuelsadok/fibre/blob/devel/test/test_server.cpp) + - The [test node](https://github.com/samuelsadok/fibre/blob/devel/test/test_node.cpp) ## Configuring `libfibre` diff --git a/cpp/endpoints_template.j2 b/cpp/endpoints_template.j2 deleted file mode 100644 index edae7e3..0000000 --- a/cpp/endpoints_template.j2 +++ /dev/null @@ -1,93 +0,0 @@ -/*[# This is the original template, thus the warning below does not apply to this file #] - * ============================ WARNING ============================ - * ==== This is an autogenerated file. ==== - * ==== Any changes to this file will be lost when recompiling. ==== - * ================================================================= - * - * This file contains the toplevel handler for Fibre v0.1 endpoint operations. - * - * This endpoint-oriented approach will be deprecated in Fibre v0.2 in favor of - * a function-oriented approach and a more powerful object model. - * - */ -#ifndef __FIBRE_ENDPOINTS_HPP -#define __FIBRE_ENDPOINTS_HPP - -//#include -#include -#include -#include - -// Note: with -Og the functions with large switch statements reserves a huge amount -// of stack space because they reserves separate space for the stack frame of each -// of the inlined functions. -// The minimum known set of flags to prevent this is `-O1 -fipa-sra`. -// `-O2`, `-O3` and `-Os` are supersets of this. - -#pragma GCC push_options -#pragma GCC optimize ("s") - -namespace fibre { - -const unsigned char embedded_json[] = [[embedded_endpoint_definitions | to_c_string]]; -const size_t embedded_json_length = sizeof(embedded_json) - 1; -const uint16_t json_crc_ = calc_crc16(PROTOCOL_VERSION, embedded_json, embedded_json_length); -const uint32_t json_version_id_ = (json_crc_ << 16) | calc_crc16(json_crc_, embedded_json, embedded_json_length); -/* -static void get_property(Introspectable& result, size_t idx) { - switch (idx) { -[%- for endpoint in endpoints %] -[%- if endpoint.function.name == 'exchange' and endpoint.in_bindings | list == ['obj'] %] - case [[endpoint.id]]: { [[(endpoint.in_bindings['obj'] + '$') | replace(')$', ', &result.storage_)')]]; result.type_info_ = &FibrePropertyTypeInfo<[[endpoint.function.in['obj'].type.c_name]]>::singleton; } break; -[%- endif %] -[%- endfor %] - default: break; - } -} - - -bool endpoint_handler(int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer) { - //Introspectable property = get_property(idx); - //if property.is_valid() - - switch (idx) { -[%- for endpoint in endpoints %] -[%- if (endpoint.function.name == 'exchange' or endpoint.function.name == 'read') and endpoint.in_bindings | list == ['obj'] %] - case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_name]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_name]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; -[%- else %] - case [[endpoint.id]]: { return [[endpoint.function.fullname | to_snake_case]]([% for k, arg in endpoint.function.in.items() %][% if k in endpoint.in_bindings %]static_cast<[[arg.type.c_name]]>([[endpoint.in_bindings[k]]])[% else %]std::nullopt[% endif %], [% endfor %][% for k, arg in endpoint.function.out.items() %][% if k in endpoint.out_bindings %]static_cast<[[arg.type.c_name]]*>([[endpoint.out_bindings[k]]])[% else %]nullptr[% endif %], [% endfor %]input_buffer, output_buffer); } break; -[%- endif %] -[%- endfor %] - default: return false; - } -} - -bool is_endpoint_ref_valid(endpoint_ref_t endpoint_ref) { - if (endpoint_ref.json_crc != json_crc_) { - return false; - } - - switch (endpoint_ref.endpoint_id) { -[%- for endpoint in endpoints %] - case [[endpoint.id]]: return true; -[%- endfor %] - default: return false; - } -} - -bool set_endpoint_from_float(endpoint_ref_t endpoint_ref, float value) { - if (endpoint_ref.json_crc != json_crc_) { - return false; - } - - Introspectable property{}; - get_property(property, endpoint_ref.endpoint_id); - const FloatSettableTypeInfo* type_info = dynamic_cast(property.get_type_info()); - return type_info && type_info->set_float(property, value); -} -*/ -} - -#pragma GCC pop_options - -#endif // __FIBRE_ENDPOINTS_HPP \ No newline at end of file diff --git a/cpp/fibre.cpp b/cpp/fibre.cpp index a309847..e82733a 100644 --- a/cpp/fibre.cpp +++ b/cpp/fibre.cpp @@ -13,6 +13,10 @@ #include #endif +#if FIBRE_ENABLE_TEXT_LOGGING +#include +#endif + #if FIBRE_ENABLE_EVENT_LOOP # ifdef __linux__ # include "platform_support/epoll_event_loop.hpp" @@ -167,7 +171,7 @@ static std::string get_local_time() { std::to_string((now.time_since_epoch() - seconds_since_epoch).count()); } -void fibre::log_to_stderr(const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, const char* text) { +void fibre::log_to_stderr(void* ctx, const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, const char* text) { switch ((LogLevel)level) { case LogLevel::kDebug: //std::cerr << "\x1b[93;1m"; // yellow @@ -181,7 +185,7 @@ void fibre::log_to_stderr(const char* file, unsigned line, int level, uintptr_t std::cerr << get_local_time() << " [" << file << ":" << line << "] " << text << "\x1b[0m" << std::endl; } #else -void fibre::log_to_stderr(const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, const char* text) { +void fibre::log_to_stderr(void* ctx, const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, const char* text) { // ignore } #endif @@ -282,13 +286,23 @@ void Domain::add_channels(ChannelDiscoveryResult result) { } #if FIBRE_ENABLE_CLIENT || FIBRE_ENABLE_SERVER - // Deleted during on_stopped() - auto protocol = new fibre::LegacyProtocolPacketBased(this, result.rx_channel, result.tx_channel, result.mtu); + if (result.packetized) { + // Deleted during on_stopped_p() + auto protocol = new fibre::LegacyProtocolPacketBased(this, result.rx_channel, result.tx_channel, result.mtu); #if FIBRE_ENABLE_CLIENT - protocol->start(MEMBER_CB(this, on_found_root_object), MEMBER_CB(this, on_lost_root_object), MEMBER_CB(this, on_stopped)); + protocol->start(MEMBER_CB(this, on_found_root_object), MEMBER_CB(this, on_lost_root_object), MEMBER_CB(this, on_stopped_p)); #else - protocol->start(MEMBER_CB(this, on_stopped)); + protocol->start(MEMBER_CB(this, on_stopped_p)); #endif + } else { + // Deleted during on_stopped_s() + auto protocol = new fibre::LegacyProtocolStreamBased(this, result.rx_channel, result.tx_channel); +#if FIBRE_ENABLE_CLIENT + protocol->start(MEMBER_CB(this, on_found_root_object), MEMBER_CB(this, on_lost_root_object), MEMBER_CB(this, on_stopped_s)); +#else + protocol->start(MEMBER_CB(this, on_stopped_s)); +#endif + } #endif } @@ -308,10 +322,15 @@ void Domain::on_lost_root_object(LegacyObjectClient* obj_client, std::shared_ptr } #endif -void Domain::on_stopped(LegacyProtocolPacketBased* protocol, StreamStatus status) { +void Domain::on_stopped_p(LegacyProtocolPacketBased* protocol, StreamStatus status) { delete protocol; } +void Domain::on_stopped_s(LegacyProtocolPacketBased* protocol, StreamStatus status) { + size_t offset = (size_t)&((LegacyProtocolStreamBased*)nullptr)->inner_protocol_; + delete (LegacyProtocolStreamBased*)((uintptr_t)protocol - offset); +} + #if FIBRE_ENABLE_SERVER ServerFunctionDefinition* Domain::get_server_function(ServerFunctionId id) { if (id < n_static_server_functions) { diff --git a/cpp/function_stubs_template.j2 b/cpp/function_stubs_template.j2 deleted file mode 100644 index ac25923..0000000 --- a/cpp/function_stubs_template.j2 +++ /dev/null @@ -1,40 +0,0 @@ -/*[# This is the original template, thus the warning below does not apply to this file #] - * ============================ WARNING ============================ - * ==== This is an autogenerated file. ==== - * ==== Any changes to this file will be lost when recompiling. ==== - * ================================================================= - * - * This file contains serializing/deserializing stubs for the functions defined - * in your interface file. - * - */ - -#include - -[% for intf in interfaces.values() %] -[% for func in intf.functions.values() %] -static inline bool [[func.fullname | to_snake_case]]([% for arg in func.in.values() %]std::optional<[[arg.type.c_name]]> in_[[arg.name]], [% endfor %][% for arg in func.out.values() %][[arg.type.c_name]]* out_[[arg.name]], [% endfor %]fibre::cbufptr_t* input_buffer, fibre::bufptr_t* output_buffer) { -[%- if func.in %] - bool success = [% for arg in func.in.values() %](in_[[arg.name]].has_value() || (in_[[arg.name]] = fibre::Codec<[[arg.type.c_name]]>::decode(input_buffer)).has_value()[% if arg.optional %] || true[% endif %])[% if not loop.last %] - && [% endif %][% endfor %]; -[%- else %] - bool success = true; -[%- endif %] - if (!success) { - return false; - } -[%- if func.implementation %] - [% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_name]][[', ' if not loop.last]][% endfor %]> ret = [% endif %][[func.implementation]]([% for arg in func.in.values() %](*in_[[arg.name]][% if not arg.optional %])[% endif %][[', ' if not loop.last]][% endfor %]); -[%- else %] - [% if func.out %]std::tuple<[% for arg in func.out.values() %][[arg.type.c_name]][[', ' if not loop.last]][% endfor %]> ret = [% endif %](*in_[[(func.in.values() | first).name]])->[[func.name]]([% for arg in func.in.values() | skip_first %][% if not arg.optional %]*[% endif %]in_[[arg.name]][[', ' if not loop.last]][% endfor %]); -[%- endif %] -[%- if func.out %] - return [% for arg in func.out.values() %]((out_[[arg.name]] && ((*out_[[arg.name]] = std::get<[[loop.index0]]>(ret)), true)) || fibre::Codec<[[arg.type.c_name]]>::encode(std::get<[[loop.index0]]>(ret), output_buffer))[% if not loop.last %] - && [% endif %][% endfor %]; -[%- else %] - return true; -[%- endif %] -} -[% endfor %] -[% endfor %] - diff --git a/cpp/include/fibre/channel_discoverer.hpp b/cpp/include/fibre/channel_discoverer.hpp index cadedee..757a263 100644 --- a/cpp/include/fibre/channel_discoverer.hpp +++ b/cpp/include/fibre/channel_discoverer.hpp @@ -13,6 +13,7 @@ struct ChannelDiscoveryResult { AsyncStreamSource* rx_channel; AsyncStreamSink* tx_channel; size_t mtu; + bool packetized; }; struct ChannelDiscoveryContext {}; diff --git a/cpp/include/fibre/fibre.hpp b/cpp/include/fibre/fibre.hpp index 0dfb13a..c001ee0 100644 --- a/cpp/include/fibre/fibre.hpp +++ b/cpp/include/fibre/fibre.hpp @@ -171,7 +171,8 @@ class Domain { void on_found_root_object(LegacyObjectClient* obj_client, std::shared_ptr obj); void on_lost_root_object(LegacyObjectClient* obj_client, std::shared_ptr obj); #endif - void on_stopped(LegacyProtocolPacketBased* protocol, StreamStatus status); + void on_stopped_p(LegacyProtocolPacketBased* protocol, StreamStatus status); + void on_stopped_s(LegacyProtocolPacketBased* protocol, StreamStatus status); #if FIBRE_ALLOW_HEAP std::unordered_map channel_discovery_handles; @@ -205,7 +206,7 @@ void close(Context*); * If Fibre is compiled with FIBRE_ENABLE_TEXT_LOGGING=1 this function logs the * event to stderr. Otherwise it does nothing. */ -void log_to_stderr(const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, const char* text); +void log_to_stderr(void* ctx, const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, const char* text); LogLevel get_log_verbosity(); diff --git a/cpp/include/fibre/libfibre.h b/cpp/include/fibre/libfibre.h index 59ccd7a..7050b1c 100644 --- a/cpp/include/fibre/libfibre.h +++ b/cpp/include/fibre/libfibre.h @@ -176,7 +176,8 @@ struct LibFibreLogger { int verbosity; // TODO: document (see fibre.hpp for now) - void(*log)(const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, const char* text); + void(*log)(void* ctx, const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, const char* text); + void* ctx; }; /** @@ -361,7 +362,7 @@ FIBRE_PUBLIC void libfibre_close_domain(LibFibreDomain* domain); * * The channels can be closed with libfibre_close_tx() and libfibre_close_rx(). */ -FIBRE_PUBLIC void libfibre_add_channels(LibFibreDomain* domain, LibFibreRxStream** tx_channel, LibFibreTxStream** rx_channel, size_t mtu); +FIBRE_PUBLIC void libfibre_add_channels(LibFibreDomain* domain, LibFibreRxStream** tx_channel, LibFibreTxStream** rx_channel, size_t mtu, bool packetized); /** * @brief Starts looking for Fibre objects that match the specifications. diff --git a/cpp/include/fibre/logging.hpp b/cpp/include/fibre/logging.hpp index d975300..81de3e5 100644 --- a/cpp/include/fibre/logging.hpp +++ b/cpp/include/fibre/logging.hpp @@ -24,6 +24,7 @@ enum class LogLevel : int { /** * @brief Log function callback type * + * @param ctx: An opaque user defined context pointer. * @param file: The file name of the call site. Valid until the program terminates. * @param line: The line number of the call site. * @param info0: A general purpose information parameter. The meaning of this depends on the call site. @@ -31,7 +32,7 @@ enum class LogLevel : int { * @param text: Text to log. Valid only for the duration of the log call. Always * Null if Fibre is compiled with FIBRE_ENABLE_TEXT_LOGGING=0. */ -typedef void(*log_fn_t)(const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, const char* text); +typedef Callback log_fn_t; class Logger { public: @@ -39,7 +40,7 @@ class Logger { : impl_{impl}, verbosity_{verbosity} {} template - void log(const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, TFunc text_gen) { + void log(const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, TFunc text_gen) const { if (level <= (int)verbosity_) { const char* c_str = nullptr; #if FIBRE_ENABLE_TEXT_LOGGING @@ -48,13 +49,13 @@ class Logger { std::string str = stream.str(); c_str = str.c_str(); #endif - impl_(file, line, level, info0, info1, c_str); + impl_.invoke(file, line, level, info0, info1, c_str); } } static Logger none() { return { - [](const char*, unsigned, int, uintptr_t, uintptr_t, const char*){}, + {[](void*, const char*, unsigned, int, uintptr_t, uintptr_t, const char*){}, nullptr}, (LogLevel)-1 }; } diff --git a/cpp/include/fibre/rich_status.hpp b/cpp/include/fibre/rich_status.hpp index e3f9451..be2f34b 100644 --- a/cpp/include/fibre/rich_status.hpp +++ b/cpp/include/fibre/rich_status.hpp @@ -108,8 +108,8 @@ class RichStatusOr { #else -#define F_MAKE_ERR(msg) RichStatus{{}, __FILE__, __LINE__, RichStatus::success()} -#define F_AMEND_ERR(inner, msg) RichStatus{{}, __FILE__, __LINE__, (inner)} +#define F_MAKE_ERR(msg) RichStatus{0, __FILE__, __LINE__, RichStatus::success()} +#define F_AMEND_ERR(inner, msg) RichStatus{0, __FILE__, __LINE__, (inner)} #endif diff --git a/cpp/legacy_endpoints_template.j2 b/cpp/legacy_endpoints_template.j2 index aa8678a..c88a456 100644 --- a/cpp/legacy_endpoints_template.j2 +++ b/cpp/legacy_endpoints_template.j2 @@ -20,11 +20,14 @@ namespace fibre { const unsigned char embedded_json[] = [[endpoint_descr | to_c_string]]; const size_t embedded_json_length = sizeof(embedded_json) - 1; -static EndpointDefinition endpoint_table[] = { +EndpointDefinition endpoint_table[] = { [%- for ep in endpoint_table %] [[ep]], [%- endfor %] }; +const uint16_t json_crc_ = calc_crc16(PROTOCOL_VERSION, embedded_json, embedded_json_length); +const uint32_t json_version_id_ = (json_crc_ << 16) | calc_crc16(json_crc_, embedded_json, embedded_json_length); +const size_t n_endpoints = sizeof(endpoint_table) / sizeof(endpoint_table[0]); } diff --git a/cpp/legacy_object_server.cpp b/cpp/legacy_object_server.cpp index 9a7f140..8900391 100644 --- a/cpp/legacy_object_server.cpp +++ b/cpp/legacy_object_server.cpp @@ -6,50 +6,6 @@ namespace fibre { -enum class EndpointType { - kFunctionTrigger, - kFunctionInput, - kFunctionOutput, - kRoProperty, - kRwProperty, -}; - -struct EndpointDefinition { - EndpointType type; - union { - struct { - ServerFunctionId function_id; - ServerObjectId object_id; - } function_trigger; - struct { - unsigned size; - } function_input; - struct { - unsigned size; - } function_output; - struct { - ServerFunctionId read_function_id; - ServerObjectId object_id; - } ro_property; - struct { - ServerFunctionId read_function_id; - ServerFunctionId exchange_function_id; - ServerObjectId object_id; - } rw_property; - }; -}; - -} - -// TODO: don't hardcode path -#include "../test/autogen/endpoints.hpp" - -namespace fibre { - -const uint16_t json_crc_ = calc_crc16(PROTOCOL_VERSION, embedded_json, embedded_json_length); -const uint32_t json_version_id_ = (json_crc_ << 16) | calc_crc16(json_crc_, embedded_json, embedded_json_length); -static constexpr size_t n_endpoints = sizeof(endpoint_table) / sizeof(endpoint_table[0]); - // Returns part of the JSON interface definition. diff --git a/cpp/legacy_object_server.hpp b/cpp/legacy_object_server.hpp index eb87927..3a08911 100644 --- a/cpp/legacy_object_server.hpp +++ b/cpp/legacy_object_server.hpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace fibre { @@ -36,8 +37,46 @@ struct LegacyObjectServer { RichStatus endpoint_handler(Domain* domain, int idx, cbufptr_t* input_buffer, bufptr_t* output_buffer); }; +enum class EndpointType { + kFunctionTrigger, + kFunctionInput, + kFunctionOutput, + kRoProperty, + kRwProperty, +}; + +struct EndpointDefinition { + EndpointType type; + union { + struct { + ServerFunctionId function_id; + ServerObjectId object_id; + } function_trigger; + struct { + unsigned size; + } function_input; + struct { + unsigned size; + } function_output; + struct { + ServerFunctionId read_function_id; + ServerObjectId object_id; + } ro_property; + struct { + ServerFunctionId read_function_id; + ServerFunctionId exchange_function_id; + ServerObjectId object_id; + } rw_property; + }; +}; + +// Defined in autogenerated endpoints.cpp +extern const unsigned char embedded_json[]; +extern const size_t embedded_json_length; +extern EndpointDefinition endpoint_table[]; extern const uint16_t json_crc_; extern const uint32_t json_version_id_; +extern const size_t n_endpoints; } diff --git a/cpp/legacy_protocol.cpp b/cpp/legacy_protocol.cpp index f99864f..57c0ca4 100644 --- a/cpp/legacy_protocol.cpp +++ b/cpp/legacy_protocol.cpp @@ -458,7 +458,10 @@ void LegacyProtocolPacketBased::on_read_finished(ReadResult result) { cbufptr_t input_buffer{rx_buf.begin(), rx_buf.end() - 2}; bufptr_t output_buffer{tx_buf_ + 2, expected_response_length}; - server_.endpoint_handler(domain_, endpoint_id, &input_buffer, &output_buffer); + F_LOG_IF_ERR(domain_->ctx->logger, + server_.endpoint_handler(domain_, endpoint_id, &input_buffer, &output_buffer), + "endpoint handler failed"); + // Send response if (expect_response) { diff --git a/cpp/legacy_protocol.hpp b/cpp/legacy_protocol.hpp index 68c153b..1bb1060 100644 --- a/cpp/legacy_protocol.hpp +++ b/cpp/legacy_protocol.hpp @@ -170,7 +170,6 @@ struct LegacyProtocolStreamBased { void start(Callback on_stopped) { inner_protocol_.start(on_stopped); } #endif -private: PacketUnwrapper unwrapper_; PacketWrapper wrapper_; LegacyProtocolPacketBased inner_protocol_; diff --git a/cpp/libfibre.cpp b/cpp/libfibre.cpp index 8d5b9aa..4fdb3d4 100644 --- a/cpp/libfibre.cpp +++ b/cpp/libfibre.cpp @@ -283,7 +283,7 @@ LibFibreCtx* libfibre_open(LibFibreEventLoop event_loop, LibFibreLogger logger) LibFibreCtx* ctx = new LibFibreCtx(); ctx->event_loop = new ExternalEventLoop(event_loop); - Logger fibre_logger = logger.log ? Logger{logger.log, (LogLevel)logger.verbosity} : Logger::none(); + Logger fibre_logger = logger.log ? Logger{{logger.log, logger.ctx}, (LogLevel)logger.verbosity} : Logger::none(); if (F_LOG_IF_ERR(fibre_logger, fibre::open(ctx->event_loop, fibre_logger, &ctx->fibre_ctx), "failed to open fibre")) { delete ctx->event_loop; @@ -338,7 +338,7 @@ void libfibre_close_domain(LibFibreDomain* domain) { from_c(domain)->ctx->close_domain(from_c(domain)); } -void libfibre_add_channels(LibFibreDomain* domain, LibFibreRxStream** tx_channel, LibFibreTxStream** rx_channel, size_t mtu) { +void libfibre_add_channels(LibFibreDomain* domain, LibFibreRxStream** tx_channel, LibFibreTxStream** rx_channel, size_t mtu, bool packetized) { fibre::AsyncStreamLink* tx_link = new fibre::AsyncStreamLink(); // libfibre => backend fibre::AsyncStreamLink* rx_link = new fibre::AsyncStreamLink(); // backend => libfibre LibFibreRxStream* tx = new LibFibreRxStream(); // libfibre => backend @@ -369,7 +369,7 @@ void libfibre_add_channels(LibFibreDomain* domain, LibFibreRxStream** tx_channel *rx_channel = rx; } - fibre::ChannelDiscoveryResult result = {fibre::kFibreOk, rx_link, tx_link, mtu}; + fibre::ChannelDiscoveryResult result = {fibre::kFibreOk, rx_link, tx_link, mtu, packetized}; from_c(domain)->add_channels(result); } diff --git a/cpp/package.lua b/cpp/package.lua index d159943..95355e9 100644 --- a/cpp/package.lua +++ b/cpp/package.lua @@ -108,6 +108,19 @@ function get_fibre_package(args) return pkg end +function fibre_autogen(yaml_file) + python_command = 'python3 -B' + interface_generator = '../tools/interface_generator.py' + tup.frule{inputs={'../cpp/static_exports_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..yaml_file..' --template %f --output %o', outputs='autogen/static_exports.cpp'} + tup.frule{inputs={'../cpp/interfaces_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..yaml_file..' --template %f --output %o', outputs='autogen/interfaces.hpp'} + tup.frule{inputs={'../cpp/legacy_endpoints_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..yaml_file..' --template %f --output %o', outputs='autogen/endpoints.cpp'} + + return { + code_files={'autogen/static_exports.cpp', 'autogen/endpoints.cpp'}, + autogen_headers={'autogen/interfaces.hpp'} + } +end + -- Runs the specified shell command immediately (not as part of the dependency -- graph). -- Returns the values (return_code, stdout) where stdout has the trailing new diff --git a/cpp/platform_support/libusb_transport.cpp b/cpp/platform_support/libusb_transport.cpp index 588aa27..6a8b434 100644 --- a/cpp/platform_support/libusb_transport.cpp +++ b/cpp/platform_support/libusb_transport.cpp @@ -513,7 +513,7 @@ void LibusbDiscoverer::consider_device(struct libusb_device *device, MyChannelDi ep_out = nullptr; } - subscription->domain->add_channels({kFibreOk, ep_in, ep_out, mtu}); + subscription->domain->add_channels({kFibreOk, ep_in, ep_out, mtu, true}); } } diff --git a/cpp/platform_support/posix_tcp_backend.cpp b/cpp/platform_support/posix_tcp_backend.cpp index f80a2c1..d233206 100644 --- a/cpp/platform_support/posix_tcp_backend.cpp +++ b/cpp/platform_support/posix_tcp_backend.cpp @@ -121,7 +121,7 @@ void PosixTcpBackend::TcpChannelDiscoveryContext::on_connected(RichStatus status auto socket = new PosixSocket{}; // TODO: free status = socket->init(parent->event_loop_, parent->logger_, socket_id); if (!status.is_error()) { - domain->add_channels({kFibreOk, socket, socket, SIZE_MAX}); + domain->add_channels({kFibreOk, socket, socket, SIZE_MAX, false}); return; } delete socket; diff --git a/js/fibre.js b/js/fibre.js index 901bf5c..e3a14b8 100644 --- a/js/fibre.js +++ b/js/fibre.js @@ -381,7 +381,7 @@ class LibFibre { // Function signature codes are documented here: // https://github.com/aheejin/emscripten/blob/master/site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst#calling-javascript-functions-as-function-pointers-from-c - this._log = wasm.addFunction((file, line, level, info0, info1, text) => { + this._log = wasm.addFunction((ctx, file, line, level, info0, info1, text) => { file = this.wasm.UTF8ArrayToString(this.wasm.Module.HEAPU8, file); text = this.wasm.UTF8ArrayToString(this.wasm.Module.HEAPU8, text); if (level <= 2) { @@ -489,6 +489,7 @@ class LibFibre { const logger_array = (new Uint32Array(wasm.Module.HEAPU8.buffer, logger, 2)); logger_array[0] = log_verbosity; logger_array[1] = this._log; + logger_array[2] = 0; this._handle = this.wasm.Module._libfibre_open(event_loop, logger); @@ -516,7 +517,7 @@ class LibFibre { addChannels(domainHandle, mtu) { console.log("add channel with mtu " + mtu); const [txChannelId, rxChannelId] = this._withOutputArg((txChannelIdPtr, rxChannelIdPtr) => - this.wasm.Module._libfibre_add_channels(domainHandle, txChannelIdPtr, rxChannelIdPtr, mtu) + this.wasm.Module._libfibre_add_channels(domainHandle, txChannelIdPtr, rxChannelIdPtr, mtu, true) ); return [ new RxStream(this, txChannelId), // libfibre => backend diff --git a/python/fibre/libfibre.py b/python/fibre/libfibre.py index 55eacfe..a94c5a2 100644 --- a/python/fibre/libfibre.py +++ b/python/fibre/libfibre.py @@ -80,7 +80,7 @@ def get_first(lst, predicate, default): CallLaterSignature = CFUNCTYPE(c_void_p, c_float, CFUNCTYPE(None, c_void_p), POINTER(c_int)) CancelTimerSignature = CFUNCTYPE(c_int, c_void_p) -LogSignature = CFUNCTYPE(None, c_char_p, c_uint, c_int, c_size_t, c_size_t, c_char_p) +LogSignature = CFUNCTYPE(None, c_void_p, c_char_p, c_uint, c_int, c_size_t, c_size_t, c_char_p) OnFoundObjectSignature = CFUNCTYPE(None, c_void_p, c_void_p, c_void_p) OnLostObjectSignature = CFUNCTYPE(None, c_void_p, c_void_p) @@ -122,6 +122,7 @@ class LibFibreLogger(Structure): _fields_ = [ ("verbosity", c_int), ("log", LogSignature), + ("ctx", c_void_p), ] class LibFibreFunctionInfo(Structure): @@ -809,11 +810,12 @@ def __init__(self): logger = LibFibreLogger() logger.verbosity = int(os.environ.get('FIBRE_LOG', '2')) logger.log = self.c_log + logger.ctx = None self.ctx = c_void_p(libfibre_open(event_loop, logger)) assert(self.ctx) - def _log(self, file, line, level, info0, info1, text): + def _log(self, ctx, file, line, level, info0, info1, text): file = string_at(file).decode('utf-8') text = string_at(text).decode('utf-8') color = "\x1b[91;1m" if level <= 2 else "" diff --git a/test/Tupfile.lua b/test/Tupfile.lua index b6bf288..6512a6c 100644 --- a/test/Tupfile.lua +++ b/test/Tupfile.lua @@ -2,35 +2,16 @@ fibre_cpp_dir = '../cpp' tup.include(fibre_cpp_dir..'/package.lua') -python_command = 'python3 -B' - -interface_generator = '../tools/interface_generator.py' -interface_yaml = 'test-interface.yaml' -root_interface = 'TestIntf1' - -tup.frule{inputs={'../cpp/static_exports_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..interface_yaml..' --template %f --output %o', outputs='autogen/static_exports.cpp'} -tup.frule{inputs={'../cpp/interfaces_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..interface_yaml..' --template %f --output %o', outputs='autogen/interfaces.hpp'} -tup.frule{inputs={'../cpp/legacy_endpoints_template.j2'}, command=python_command..' '..interface_generator..' --definitions '..interface_yaml..' --template %f --output %o', outputs='autogen/endpoints.hpp'} - - CXX='clang++' LINKER='clang++' -CFLAGS={'-g'} +CFLAGS={'-g', '-I.', '-DSTANDALONE_NODE'} LDFLAGS={} +object_files = {} -function compile(src_file) - obj_file = 'build/'..tup.file(src_file)..'.o' - tup.frule{ - inputs={src_file, extra_inputs={'autogen/interfaces.hpp', 'autogen/endpoints.hpp'}}, - command='^co^ '..CXX..' -c %f '..tostring(CFLAGS)..' -o %o', - outputs={obj_file} - } - return obj_file -end fibre_pkg = get_fibre_package({ enable_server=true, - enable_client=false, + enable_client=true, enable_event_loop=true, allow_heap=true, enable_libusb_backend=false, @@ -45,19 +26,22 @@ for _, inc in pairs(fibre_pkg.include_dirs) do CFLAGS += '-I'..fibre_cpp_dir..'/'..inc end -object_files = {} -object_files += compile('test_server.cpp') -object_files += compile('autogen/static_exports.cpp') ---object_files += compile('new_autogen/endpoints_new.cpp') - for _, src in pairs(fibre_pkg.code_files) do - object_files += compile(fibre_cpp_dir..'/'..src, fibre_cpp_dir..'/'..src..'.o') + object_files += compile(fibre_cpp_dir..'/'..src) +end + + +autogen_pkg = fibre_autogen('test-interface.yaml') + +object_files += compile('test_node.cpp', autogen_pkg.autogen_headers) + +-- TODO: move up +for _, src in pairs(autogen_pkg.code_files) do + object_files += compile(src, autogen_pkg.autogen_headers) end ---compile('autogen/fibre_exports.cpp', 'build/autogen_fibre_exports.cpp.o') ---object_files += compile('autogen/fibre_exports.cpp') -compile_outname='build/test_server.elf' +compile_outname='build/test_node.elf' tup.frule{ inputs=object_files, diff --git a/test/test-interface.yaml b/test/test-interface.yaml index b9cc529..a83e5bc 100644 --- a/test/test-interface.yaml +++ b/test/test-interface.yaml @@ -33,5 +33,6 @@ interfaces: func21: {in: {in1: uint32, in2: uint32}, out: {out1: uint32}} func22: {in: {in1: uint32, in2: uint32}, out: {out1: uint32, out2: uint32}} +# TODO: these should be given as command line option to the interface generator exports: test_object: TestIntf1 diff --git a/test/test_node.cpp b/test/test_node.cpp new file mode 100644 index 0000000..feb53e6 --- /dev/null +++ b/test/test_node.cpp @@ -0,0 +1,220 @@ + +#include "test_node.hpp" +#include +#include +#include +#include +#include +#include + +class Subclass { +public: + uint32_t subfunc() { + std::cout << "subfunc called" << std::endl; + return 321; + } +}; + +class TestClass { +public: + Subclass subobj_; + + uint32_t prop_uint32_ = 135; + uint32_t prop_uint32_rw_ = 246; + + void func00() { + std::cout << "func00 called" << std::endl; + } + + uint32_t func01() { + std::cout << "func01 called" << std::endl; + return 123; + } + + std::tuple func02() { + std::cout << "func02 called" << std::endl; + return {456, 789}; + } + + void func10(uint32_t) { + std::cout << "func10 called" << std::endl; + } + + uint32_t func11(uint32_t) { + std::cout << "func11 called" << std::endl; + return 123; + } + + std::tuple func12(uint32_t) { + std::cout << "func12 called" << std::endl; + return {456, 789}; + } + + void func20(uint32_t, uint32_t) { + std::cout << "func20 called" << std::endl; + } + + uint32_t func21(uint32_t, uint32_t) { + std::cout << "func21 called" << std::endl; + return 123; + } + + std::tuple func22(uint32_t, uint32_t) { + std::cout << "func22 called" << std::endl; + return {456, 789}; + } +}; + +TestClass test_object; + +TestIntf1Wrapper test_object_wrapper{test_object}; +TestIntf1Intf* test_object_ptr = &test_object_wrapper; + +using namespace fibre; + +RichStatus TestNode::start(fibre::EventLoop* event_loop, + std::string domain_path, bool enable_server, + bool enable_client, fibre::Logger logger) { + logger_ = logger; + fibre::Context* fibre_ctx; + F_RET_IF_ERR(fibre::open(event_loop, logger, &fibre_ctx), + "failed to open fibre"); + + domain_ = fibre_ctx->create_domain(domain_path); + + if (enable_server) { + // TODO: implement dynamic publishing of objects. Currently + // objects can only be published statically. + // domain_->publish_object(test_object); + } + + if (enable_client) { +#if FIBRE_ENABLE_CLIENT + domain_->start_discovery(MEMBER_CB(this, on_found_object), + MEMBER_CB(this, on_lost_object)); +#else + return F_MAKE_ERR("client support not compiled in"); +#endif + } + + return RichStatus::success(); +} + +void SyncUnwrapper::call( + bufptr_t inputs, size_t output_size, + Callback on_call_finished) { + if (inputs.size() > sizeof(tx_buf)) { + on_call_finished.invoke(this, kFibreOutOfMemory, {}); + } else if (output_size > sizeof(rx_buf)) { + on_call_finished.invoke(this, kFibreOutOfMemory, {}); + } + std::copy(inputs.begin(), inputs.end(), tx_buf); + + on_call_finished_ = on_call_finished; + tx_bufptr = {tx_buf, inputs.size()}; + rx_bufptr = {rx_buf, output_size}; + + func->call(&ctx, {kFibreClosed, tx_bufptr, rx_bufptr}, + MEMBER_CB(this, on_continue)); +} + +std::optional SyncUnwrapper::on_continue( + CallBufferRelease call_buffers) { + tx_bufptr.begin() = call_buffers.tx_end; + rx_bufptr.begin() = call_buffers.rx_end; + + if (call_buffers.status != kFibreOk) { + on_call_finished_.invoke(this, call_buffers.status, + {rx_buf, rx_bufptr.begin()}); + return std::nullopt; + } + + return CallBuffers{kFibreClosed, tx_bufptr, rx_bufptr}; +} + +void TestNode::on_found_object(fibre::Object* obj, fibre::Interface* intf) { + F_LOG_D(logger_, "discovered Object!"); + fibre::InterfaceInfo* info = intf->get_info(); + + auto it = std::find_if(info->functions.begin(), info->functions.end(), + [](fibre::Function* func) { + auto info = func->get_info(); + bool match = info->name == "func00"; + func->free_info(info); + return match; + }); + + if (it == info->functions.end()) { + F_LOG_E(logger_, "function not found"); + } else { + F_LOG_D(logger_, "calling func00..."); + + SyncUnwrapper* call = new SyncUnwrapper{*it}; + uint8_t tx_buf[sizeof(Object*)]; + *(Object**)tx_buf = obj; + call->call(tx_buf, 0, MEMBER_CB(this, on_finished_call)); + } + + intf->free_info(info); +} + +void TestNode::on_lost_object(fibre::Object* obj) {} + +void TestNode::on_finished_call(SyncUnwrapper* call, Status status, + cbufptr_t out) { + F_LOG_D(logger_, "call finished"); + delete call; +} + +#if STANDALONE_NODE + +int main(int argc, const char** argv) { + bool enable_server = false; + bool enable_client = false; + std::optional domain_path; + + while (argv++, --argc) { + if (std::string{*argv} == "--server") { + enable_server = true; + } else if (std::string{*argv} == "--client") { + enable_client = true; + } else if (std::string{*argv} == "--domain") { + if (!(argv++, --argc)) { + printf("expected domain string after --domain\n"); + return -1; + } + domain_path = std::string{*argv}; + } else { + printf("invalid argument: %s\n", *argv); + return -1; + } + } + + if (!domain_path.has_value()) { + printf("domain string must be provided with --domain\n"); + return -1; + } + + printf("Starting Fibre node...\n"); + + TestNode node; + fibre::Logger logger = fibre::Logger{{fibre::log_to_stderr, nullptr}, + fibre::get_log_verbosity()}; + + fibre::RichStatus result = + fibre::launch_event_loop(logger, [&](fibre::EventLoop* event_loop) { + printf("Hello from event loop...\n"); + auto res2 = node.start(event_loop, *domain_path, enable_server, + enable_client, logger); + F_LOG_IF_ERR(logger, res2, "failed to start node"); + }); + + bool failed = F_LOG_IF_ERR(logger, result, "event loop failed"); + + printf("test server terminated %s\n", + failed ? "with an error" : "nominally"); + + return failed ? 1 : 0; +} + +#endif diff --git a/test/test_node.hpp b/test/test_node.hpp new file mode 100644 index 0000000..9e65001 --- /dev/null +++ b/test/test_node.hpp @@ -0,0 +1,36 @@ +#ifndef __TEST_NODE_HPP +#define __TEST_NODE_HPP + +#include + +namespace fibre { + +struct SyncUnwrapper { + fibre::Function* func; + void* ctx = nullptr; + cbufptr_t tx_bufptr; + bufptr_t rx_bufptr; + uint8_t tx_buf[128]; + uint8_t rx_buf[128]; + Callback on_call_finished_; + void call( + bufptr_t inputs, size_t output_size, + Callback on_call_finished); + std::optional on_continue(CallBufferRelease call_buffers); +}; + +} // namespace fibre + +struct TestNode { + fibre::RichStatus start(fibre::EventLoop* event_loop, + std::string domain_path, bool enable_server, + bool enable_client, fibre::Logger logger); + void on_found_object(fibre::Object* obj, fibre::Interface* intf); + void on_lost_object(fibre::Object* obj); + void on_finished_call(fibre::SyncUnwrapper* call, fibre::Status success, + fibre::cbufptr_t out); + fibre::Logger logger_ = fibre::Logger::none(); + fibre::Domain* domain_ = nullptr; +}; + +#endif // __TEST_NODE_HPP diff --git a/test/test_server.cpp b/test/test_server.cpp deleted file mode 100644 index fa796ab..0000000 --- a/test/test_server.cpp +++ /dev/null @@ -1,102 +0,0 @@ - -#include "autogen/interfaces.hpp" -#include -#include -#include -#include -#include - -class Subclass { -public: - uint32_t subfunc() { - std::cout << "subfunc called" << std::endl; - return 321; - } -}; - -class TestClass { -public: - Subclass subobj_; - - uint32_t prop_uint32_ = 135; - uint32_t prop_uint32_rw_ = 246; - - void func00() { - std::cout << "func00 called" << std::endl; - } - - uint32_t func01() { - std::cout << "func01 called" << std::endl; - return 123; - } - - std::tuple func02() { - std::cout << "func02 called" << std::endl; - return {456, 789}; - } - - void func10(uint32_t) { - std::cout << "func10 called" << std::endl; - } - - uint32_t func11(uint32_t) { - std::cout << "func11 called" << std::endl; - return 123; - } - - std::tuple func12(uint32_t) { - std::cout << "func12 called" << std::endl; - return {456, 789}; - } - - void func20(uint32_t, uint32_t) { - std::cout << "func20 called" << std::endl; - } - - uint32_t func21(uint32_t, uint32_t) { - std::cout << "func21 called" << std::endl; - return 123; - } - - std::tuple func22(uint32_t, uint32_t) { - std::cout << "func22 called" << std::endl; - return {456, 789}; - } -}; - -TestClass test_object; - -TestIntf1Wrapper test_object_wrapper{test_object}; -TestIntf1Intf* test_object_ptr = &test_object_wrapper; - -int main() { - printf("Starting Fibre server...\n"); - - fibre::Logger logger = - fibre::Logger{fibre::log_to_stderr, fibre::get_log_verbosity()}; - - fibre::RichStatus result = - fibre::launch_event_loop(logger, [&](fibre::EventLoop* event_loop) { - printf("Hello from event loop...\n"); - - fibre::Context* fibre_ctx; - if (F_LOG_IF_ERR(logger, - fibre::open(event_loop, logger, &fibre_ctx), - "failed to open fibre")) { - return; - } - - fibre_ctx->create_domain("tcp-server:address=localhost,port=14220"); - - // TODO: implement dynamic publishing of objects. Currently - // object can only be published statically. - // domain->publish_object(test_object); - }); - - bool failed = F_LOG_IF_ERR(logger, result, "event loop failed"); - - printf("test server terminated %s\n", - failed ? "with an error" : "nominally"); - - return failed ? 1 : 0; -}