diff --git a/Firmware/fibre-cpp/Dockerfile b/Firmware/fibre-cpp/Dockerfile new file mode 100644 index 00000000..212b779c --- /dev/null +++ b/Firmware/fibre-cpp/Dockerfile @@ -0,0 +1,62 @@ +FROM archlinux:base-devel + +# Set up package manager +RUN echo "[custom]" >> /etc/pacman.conf && \ + echo "SigLevel = Required TrustedOnly" >> /etc/pacman.conf && \ + echo "Server = https://innovation-labs.appinstall.ch/archlinux/\$repo/os/\$arch" >> /etc/pacman.conf && \ + pacman-key --init && \ + pacman-key --recv-keys 0CB4116A1A3A789937D6DEFB506F27823D2B7B33 && \ + pacman-key --lsign-key 0CB4116A1A3A789937D6DEFB506F27823D2B7B33 && \ + pacman -Syu --noconfirm + +# Install prerequisites for the following targets: +# - Linux (AMD64) +# - Linux (ARM) +# - Windows (AMD64) +# - macOS (x86_32/AMD64) +# - WebAssembly +RUN pacman -S --noconfirm tup clang gcc binutils wget && \ + pacman -S --noconfirm arm-linux-gnueabihf-gcc arm-linux-gnueabihf-binutils && \ + pacman -S --noconfirm mingw-w64-gcc mingw-w64-binutils p7zip && \ + pacman -S --noconfirm apple-darwin-osxcross && \ + pacman -S --noconfirm emscripten + +ENV PATH=${PATH}:/opt/osxcross/bin +ENV PATH=${PATH}:/usr/lib/emscripten + +COPY get_dependencies.sh /get_dependencies.sh + +# Download and compile dependencies +RUN /get_dependencies.sh download_deb_pkg libusb-dev-amd64 "http://mirrors.kernel.org/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.23-2build1_amd64.deb" && \ + /get_dependencies.sh download_deb_pkg libusb-amd64 "http://mirrors.kernel.org/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.23-2build1_amd64.deb" && \ + /get_dependencies.sh download_deb_pkg libusb-i386 "http://mirrors.kernel.org/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.23-2build1_i386.deb" && \ + /get_dependencies.sh download_deb_pkg libusb-dev-i386 "http://mirrors.kernel.org/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.23-2build1_i386.deb" && \ + /get_dependencies.sh download_deb_pkg libusb-armhf "http://mirrordirector.raspbian.org/raspbian/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.24-2_armhf.deb" && \ + /get_dependencies.sh download_deb_pkg libusb-dev-armhf "http://mirrordirector.raspbian.org/raspbian/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.24-2_armhf.deb" && \ + /get_dependencies.sh download_deb_pkg libstdc++-linux-armhf "http://mirrors.kernel.org/ubuntu/pool/universe/g/gcc-10-cross/libstdc++-10-dev-armhf-cross_10-20200411-0ubuntu1cross1_all.deb" + + +RUN /get_dependencies.sh patch_macos_sdk && \ + CC='/opt/osxcross/bin/o64-clang' LD_LIBRARY_PATH="/opt/osxcross/lib" CFLAGS='-I/opt/osxcross/SDK/MacOSX10.13.sdk/usr/include -arch i386 -arch x86_64' MACOSX_DEPLOYMENT_TARGET='10.9' /get_dependencies.sh compile_libusb 'macos-amd64' 'x86_64-apple-darwin17' + +RUN mkdir -p "third_party/libusb-windows" && \ + pushd "third_party/libusb-windows" > /dev/null && \ + wget "https://github.com/libusb/libusb/releases/download/v1.0.23/libusb-1.0.23.7z" && \ + 7z x -o"libusb-1.0.23" "libusb-1.0.23.7z" + +# Make Emscripten build its standard libraries for the WebAssembly target +RUN echo "void test() {}" | em++ -x c - -o /tmp/a.out + +ENV THIRD_PARTY=/ + +# Set up entrypoint +RUN echo "#!/bin/bash" > /entrypoint.sh && \ + echo "set -euo pipefail" >> /entrypoint.sh && \ + echo "echo building \$@" >> /entrypoint.sh && \ + echo "tup generate --config \$@ /tmp/build.sh" >> /entrypoint.sh && \ + echo "exec /usr/bin/bash -x -e /tmp/build.sh" >> /entrypoint.sh && \ + chmod +x /entrypoint.sh && \ + mkdir /build + +WORKDIR /build +ENTRYPOINT ["/entrypoint.sh"] diff --git a/Firmware/fibre-cpp/README.md b/Firmware/fibre-cpp/README.md index 370bf48a..85493c94 100644 --- a/Firmware/fibre-cpp/README.md +++ b/Firmware/fibre-cpp/README.md @@ -1,22 +1,66 @@ # fibre-cpp -This directory provides the C++ reference implementation of [Fibre](https://github.com/samuelsadok/fibre). Its home is located [here](https://github.com/samuelsadok/fibre/tree/master/cpp). There's also a standalone repository for this directory [here](https://github.com/samuelsadok/fibre-cpp). +This directory provides the C/C++ reference implementation of [Fibre](https://github.com/samuelsadok/fibre). Its home is located [here](https://github.com/samuelsadok/fibre/tree/master/cpp). There's also a standalone repository for this directory [here](https://github.com/samuelsadok/fibre-cpp). -## How to use +## Overview -The code files in this directory can be embedded directly into user applications by including the C and C++ files in the user application's compile and link process. This is the recommended approach when including Fibre in embedded systems. Currently there's no nice walkthrough for this but you can refer to the [ODrive Firmware](https://github.com/madcowswe/ODrive/tree/devel/Firmware) as an example. +There are two approaches to include Fibre in your project: -If your application runs on a major Operating System it's recommended that you link to **libfibre** instead. This is essentially fibre-cpp packaged as a nice shared library (aka DLL) accompanied by a header file. We provide precompiled libfibre binaries for most platforms on the main project's [release page](https://github.com/samuelsadok/fibre/releases). The API is documented in [libfibre.h](include/fibre/libfibre.h). + 1. **Embedding fibre-cpp:** Your application's build process includes the source code files of fibre-cpp. Your application uses Fibre's C++ API to interact with Fibre. This is the recommended approach for embedded systems. + 2. **Linking to libfibre:** Your application links to a separately compiled library `libfibre` and uses Fibre's C API to interact with this library. You can obtain precompiled binaries on the main project's [release page](https://github.com/samuelsadok/fibre/releases). This is the recommended approach for desktop systems, where you want all backends enabled, because you can avoid the burden of collecting build dependencies. _Note:_ currently only the client role is supported with this approach. That means you can use it to discover and access remote objects but you cannot use it to expose local objects yet. -_Note:_ Libfibre's API currently only exposes the client role, not the server role. That means you can use it to discover and access remote objects but you cannot use it to expose local objects yet. For this you have to embed the code files directly. +## Configuring fibre-cpp -_Note:_ fibre-cpp makes use of dynamic memory if and only if the client role is used (this limitation might be lifted at some point). +Various preprocessor defines can be used to customize Fibre: -## How to embed + - `FIBRE_ENABLE_SERVER={0|1}` (_default 0_): Enable support for exposing objects to remote peers. + - `FIBRE_ENABLE_CLIENT={0|1}` (_default 0_): Enable support for discovering and using objects exposed by remote peers. + - `FIBRE_ENABLE_EVENT_LOOP={0|1}` (_default 0_): Enable the builtin event loop implementation. Not supported on all platforms. + - `FIBRE_ALLOW_HEAP={0|1}` (_default 0_): Allow Fibre to allocate memory on the heap using `malloc` and `free`. If this option is disabled only one Fibre instance can be opened. Currently `FIBRE_ENABLE_CLIENT` (and several other options) cannot be used together with this option. + - `FIBRE_MAX_LOG_VERBOSITY={0...5}` (_default 5_): The maximum log verbosity that will be compiled into the binary. In embedded systems it's recommended to set this to 0 to reduce binary size. On platforms that support environment variables the actual run time log verbosity can be changed by setting the environment variable `FIBRE_LOG={0...5}`. + - `FIBRE_DEFAULT_LOG_VERBOSITY={0...5}` (_default 5_): The default log verbosity that will be used unless overridden by other means (for instance through the environment variables). + - `FIBRE_ENABLE_LIBUSB_BACKEND={0|1}` (_default 0_): Enable libusb backend for host side USB support. This requires `FIBRE_ALLOC_HEAP=1`. + - `FIBRE_ENABLE_TCP_CLIENT_BACKEND={0|1}` (_default 0_): Enable TCP client backend. This requires `FIBRE_ALLOC_HEAP=1`. + - `FIBRE_ENABLE_TCP_SERVER_BACKEND={0|1}` (_default 0_): Enable TCP server backend. This requires `FIBRE_ALLOC_HEAP=1`. -Refer to +## Adding fibre-cpp to your application's build process -## `libfibre` Build Instructions +If your application uses [tup](http://gittup.org/tup/) as build system you can directly call the function `get_fibre_package()` in [package.lua](package.lua) as part of your build process. This function spits out a list of code files and compiler flags needed to compile fibre-cpp for a given configuration. Refer to [package.lua](package.lua) for more details. + +If your application doesn't use tup, you have to manually check which code files you need. + +## Using fibre-cpp + +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) + +## Configuring `libfibre` + +A file called tup.config can be placed in this directory to customize the build. See [configs](configs/) for examples. + +## Compiling `libfibre` + +Before you compile libfibre yourself consider if the [official releases](https://github.com/samuelsadok/fibre/releases) may be suitable for you instead. + +The recommended way for compiling libfibre is using Docker. You can use the same docker container to cross-compile for all supported targets. + +However if you're actively developing fibre you may want to compile natively for faster compile times. + +### Docker + +The following example compiles libfibre for the `linux-amd64` target. Refer to the "configs/" folder for a list of supported targets. You can also add new targets there but you may need to modify the Dockerfile to include tooling for your new target. + +``` +docker build -t fibre-compiler . +docker run -it -v /tmp/build:/build/cpp/build --entrypoint bash fibre-compiler -c "rm -rd /build/cpp/build/*" +docker run -it -v "$(pwd)":/build -v /tmp/build:/build/build -w /build fibre-compiler configs/linux-amd64.config +``` + +The output file is now located under `/tmp/build/libfibre-linux-amd64.so` on your host system. + +If something fails you can enter the container interactively with `docker run -it -v "$(pwd)":/build -v /tmp/build:/build/build -w /build --entrypoint bash fibre-compiler`. ### Windows 1. Download MinGW from [here](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/installer/mingw-w64-install.exe/download) and install it. @@ -25,15 +69,18 @@ Refer to 4. Navigate to this directory and run `make` ### Ubuntu - 1. `sudo apt-get libusb-1.0-dev` + 1. `sudo apt-get install libusb-1.0-0-dev` 2. Navigate to this directory and run `make` ### macOS 1. `brew install libusb` 2. Navigate to this directory and run `make` -### Cross-compile `libfibre` on Linux for all other platforms -The file `./compile_for_all_platforms.sh` cross-compiles libfibre for all supported platforms. This is mainly intended for CI to generate releases. It written to run on Arch Linux only. Check the script to see which packages need to be installed first. +## Using `libfibre` + +The API is documented in [libfibre.h](include/fibre/libfibre.h). + +To compile your application you need to link against the libfibre binary (`-L/path/to/libfibre.so`) and add "libfibre.h" to your include path under a folder named "fibre", e.g. `-I/path/to/fibre-cpp/include`. ## Notes for Contributors diff --git a/Firmware/fibre-cpp/Tupfile.lua b/Firmware/fibre-cpp/Tupfile.lua index 96fe3059..592ff462 100644 --- a/Firmware/fibre-cpp/Tupfile.lua +++ b/Firmware/fibre-cpp/Tupfile.lua @@ -1,36 +1,15 @@ --- Projects that include libfibre and also use tup can place a Tuprules.lua file --- into their root directory with the line `no_libfibre = true` to prevent --- libfibre from building. -if no_libfibre == true then - return -end - tup.include('package.lua') -CFLAGS = {'-I./include -fPIC -std=c++11 -DFIBRE_COMPILE -DFIBRE_ENABLE_CLIENT'} +CFLAGS = {'-fPIC -std=c++11 -DFIBRE_COMPILE'} LDFLAGS = {'-static-libstdc++'} --- 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 --- line removed. -function run_now(command) - local handle - handle = io.popen(command) - local output = handle:read("*a") - local rc = {handle:close()} - if not rc[1] then - error("failed to invoke "..command) - end - return string.sub(output, 0, -2) -end if tup.getconfig("CC") == "" then - CC = 'clang++' + CXX = 'clang++' LINKER = 'clang++' else - CC = tup.getconfig("CC") + CXX = tup.getconfig("CC") LINKER = tup.getconfig("CC") end @@ -42,22 +21,15 @@ function get_bool_config(name, default) elseif tup.getconfig(name) == "false" then return false else - error(name.." ("..tup.getconfig(name).." must be 'true' or 'false'.") + error(name.." ("..tup.getconfig(name)..") must be 'true' or 'false'.") end end CFLAGS += tup.getconfig("CFLAGS") LDFLAGS += tup.getconfig("LDFLAGS") DEBUG = get_bool_config("DEBUG", true) -USE_PKGCONF = get_bool_config("USE_PKGCONF", true) -ENABLE_LIBUSB = get_bool_config("ENABLE_LIBUSB", true) -if USE_PKGCONF and ENABLE_LIBUSB then - CFLAGS += run_now("pkgconf libusb-1.0 --cflags") - LDFLAGS += run_now("pkgconf libusb-1.0 --libs") -end - -machine = run_now(CC..' -dumpmachine') -- works with both clang and GCC +machine = run_now(CXX..' -dumpmachine') -- works with both clang and GCC BUILD_TYPE='-shared' @@ -92,29 +64,39 @@ else CFLAGS += '-O3' -- TODO: add back -lfto end -function compile(src_file, obj_file) +function compile(src_file) + obj_file = 'build/'..tup.file(src_file)..'.o' tup.frule{ inputs={src_file}, - command='^co^ '..CC..' -c %f '..tostring(CFLAGS)..' -fdebug-prefix-map=/Data/Projects/fibre/cpp/build-local=/Data/Projects/fibre/cpp -o %o', + command='^co^ '..CXX..' -c %f '..tostring(CFLAGS)..' -o %o', outputs={obj_file} } + return obj_file end -code_files = fibre_package.core_files +pkg = get_fibre_package({ + enable_server=false, + enable_client=true, + enable_tcp_server_backend=get_bool_config("ENABLE_TCP_SERVER_BACKEND", true), + enable_tcp_client_backend=get_bool_config("ENABLE_TCP_CLIENT_BACKEND", true), + enable_libusb_backend=get_bool_config("ENABLE_LIBUSB_BACKEND", true), + allow_heap=true, + pkgconf=tup.getconfig("USE_PKGCONF") or nil +}) -if ENABLE_LIBUSB then - tup.append_table(code_files, fibre_package.features["LIBUSB"]) - CFLAGS += '-DFIBRE_ENABLE_LIBUSB=1' -end -if get_bool_config("ENABLE_LOGGING", true) then - tup.append_table(code_files, fibre_package.features["LOGGING"]) +CFLAGS += pkg.cflags +LDFLAGS += pkg.ldflags + +for _, inc in pairs(pkg.include_dirs) do + CFLAGS += '-I./'..inc end -for _, src_file in pairs(code_files) do - obj_file = "build/"..src_file:gsub("/","_")..".o" - object_files += obj_file - compile(src_file, obj_file) +for _, src_file in pairs(pkg.code_files) do + object_files += compile(src_file) end +object_files += compile('libfibre.cpp') + +outname = 'build/'..outname if not STRIP then compile_outname=outname diff --git a/Firmware/fibre-cpp/channel_discoverer.cpp b/Firmware/fibre-cpp/channel_discoverer.cpp new file mode 100644 index 00000000..9556e4d4 --- /dev/null +++ b/Firmware/fibre-cpp/channel_discoverer.cpp @@ -0,0 +1,47 @@ + +#include +#include +#include +#include + +using namespace fibre; + +bool ChannelDiscoverer::try_parse_key(const char* begin, const char* end, const char* key, const char** val_begin, const char** val_end) { + size_t keylen = strlen(key); + + while (begin != end) { + const char* next_delim = std::find(begin, end, ','); + + if ((next_delim - begin >= keylen) && (memcmp(begin, key, keylen) == 0)) { + if (next_delim - begin == keylen) { + // The key exists but has no value + *val_begin = *val_end = next_delim; + return true; + } else if (begin[keylen] == '=') { + *val_begin = begin + keylen + 1; + *val_end = next_delim; + return true; + } + } + + begin = std::min(next_delim + 1, end); + } + + return false; // key not found +} + +bool ChannelDiscoverer::try_parse_key(const char* begin, const char* end, const char* key, int* val) { + const char* val_begin; + const char* val_end; + if (!try_parse_key(begin, end, key, &val_begin, &val_end)) { + return false; + } + + // Copy value to a null-terminated buffer + char buf[val_end - val_begin + 1]; + memcpy(buf, val_begin, val_end - val_begin); + buf[val_end - val_begin] = 0; + + return sscanf(buf, "0x%x", val) == 1 + || sscanf(buf, "%d", val) == 1; +} diff --git a/Firmware/fibre-cpp/compile_for_all_platforms.sh b/Firmware/fibre-cpp/compile_for_all_platforms.sh deleted file mode 100755 index 8d7627df..00000000 --- a/Firmware/fibre-cpp/compile_for_all_platforms.sh +++ /dev/null @@ -1,184 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# Prerequisites: -# Arch Linux: -# gcc binutils -# arm-linux-gnueabihf-gcc arm-linux-gnueabihf-binutils -# mingw-w64-gcc mingw-w64-binutils -# p7zip -# apple-darwin-osxcross - -mkdir -p third_party - -# Usage: download_deb_pkg destination-dir url -function download_deb_pkg() { - dir="$1" - url="$2" - file="$(sed 's|^.*/\([^/]*\)$|\1|' <<< "$url")" - - pushd third_party > /dev/null - if ! [ -f "${file}" ]; then - wget "${url}" - fi - if ! [ -d "${dir}/usr" ]; then - ar x "${file}" "data.tar.xz" - mkdir -p "${dir}" - tar -xvf "data.tar.xz" -C "${dir}" - fi - popd > /dev/null -} - -# Usage: compile_libusb arch-name arch -function compile_libusb() { - arch_name="$1" - arch="$2" - libusb_version=1.0.23 - - pushd third_party > /dev/null - if ! [ -f "libusb-${libusb_version}.tar.bz2" ]; then - wget "https://github.com/libusb/libusb/releases/download/v${libusb_version}/libusb-${libusb_version}.tar.bz2" - fi - if ! [ -d "libusb-${libusb_version}" ]; then - tar -xvf "libusb-${libusb_version}.tar.bz2" - fi - - mkdir -p "libusb-${libusb_version}/build-${arch_name}" - pushd "libusb-${libusb_version}/build-${arch_name}" > /dev/null - unset LDFLAGS - if ! [ -f "libusb/.libs/libusb-1.0.a" ]; then - ../configure --host="$arch" \ - --enable-static \ - --prefix=/opt/osxcross/ \ - --disable-dependency-tracking - # They broke parallel building in libusb 1.20 - make - fi - popd > /dev/null - popd > /dev/null -} - - -### Download/compile prerequisites - -download_deb_pkg libusb-dev-amd64 "http://mirrors.kernel.org/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.23-2build1_amd64.deb" -download_deb_pkg libusb-amd64 "http://mirrors.kernel.org/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.23-2build1_amd64.deb" -download_deb_pkg libusb-i386 "http://mirrors.kernel.org/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.23-2build1_i386.deb" -download_deb_pkg libusb-dev-i386 "http://mirrors.kernel.org/ubuntu/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.23-2build1_i386.deb" -download_deb_pkg libusb-armhf "http://mirrordirector.raspbian.org/raspbian/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.23-2_armhf.deb" -download_deb_pkg libusb-dev-armhf "http://mirrordirector.raspbian.org/raspbian/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.23-2_armhf.deb" -download_deb_pkg libstdc++-linux-armhf "http://mirrors.kernel.org/ubuntu/pool/universe/g/gcc-10-cross/libstdc++-10-dev-armhf-cross_10-20200411-0ubuntu1cross1_all.deb" - -mkdir -p "third_party/libusb-windows" -pushd "third_party/libusb-windows" > /dev/null -if [ ! -f libusb-1.0.23.7z ]; then - wget "https://github.com/libusb/libusb/releases/download/v1.0.23/libusb-1.0.23.7z" -fi -if [ ! -f "libusb-1.0.23/libusb-1.0.def" ]; then - 7z x -o"libusb-1.0.23" "libusb-1.0.23.7z" -fi -popd > /dev/null - - -### compile libusb for macOS - -# Link are broken: -# …ions/Current/Headers $ ls -l IOReturn.h -# lrwxrwxrwx 1 root root 189 Dec 26 2019 IOReturn.h -> Users/phracker/Documents/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/IOKit/IOReturn.h -# Fix with: -# sudo ln -sf /opt/osxcross/SDK/MacOSX10.13.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/IOKit/IOReturn.h IOReturn.h - -oldprefix="Users/phracker/Documents/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" -newprefix="/opt/osxcross/SDK/MacOSX10.13.sdk" -while IFS= read -r link; do - destination="$(readlink "$link")" - pruned_destination="${destination#"$oldprefix"}" - if [ "${oldprefix}${pruned_destination}" == "${destination}" ]; then - sudo mv -T "${newprefix}${pruned_destination}" "$link" - fi -done <<< "$(find /opt/osxcross/SDK/MacOSX10.13.sdk/System/Library/Frameworks/IOKit.framework -xtype l)" - -echo "building libusb for macOS..." - -CC='/opt/osxcross/bin/o64-clang' \ -LD_LIBRARY_PATH="/opt/osxcross/lib" \ -PATH="/opt/osxcross/bin:$PATH" \ -CFLAGS='-I/opt/osxcross/SDK/MacOSX10.13.sdk/usr/include -arch i386 -arch x86_64' \ -MACOSX_DEPLOYMENT_TARGET='10.9' \ - compile_libusb 'macos-amd64' 'x86_64-apple-darwin17' - - - - -### Prepare tup.config files - -mkdir -p build-linux-amd64 -cat < build-linux-amd64/tup.config -CONFIG_DEBUG=false -CONFIG_CC="clang++" -CONFIG_CFLAGS="-I./third_party/libusb-dev-armhf/usr/include/libusb-1.0" -CONFIG_LDFLAGS="./third_party/libusb-amd64/lib/x86_64-linux-gnu/libusb-1.0.so.0.2.0" -CONFIG_USE_PKGCONF=false -EOF - -mkdir -p build-linux-armhf -cat < build-linux-armhf/tup.config -CONFIG_DEBUG=false -CONFIG_CC="arm-linux-gnueabihf-g++" -CONFIG_CFLAGS="-I./third_party/libusb-dev-armhf/usr/include/libusb-1.0" -CONFIG_LDFLAGS="-L./third_party/libstdc++-linux-armhf/usr/lib/gcc-cross/arm-linux-gnueabihf/10 third_party/libusb-armhf/lib/arm-linux-gnueabihf/libusb-1.0.so.0.2.0" -CONFIG_USE_PKGCONF=false -EOF - -mkdir -p build-windows-amd64 -cat < build-windows-amd64/tup.config -CONFIG_DEBUG=false -CONFIG_CC="x86_64-w64-mingw32-g++" -CONFIG_CFLAGS="-I./third_party/libusb-windows/libusb-1.0.23/include/libusb-1.0" -CONFIG_LDFLAGS="-static-libgcc ./third_party/libusb-windows/libusb-1.0.23/MinGW64/static/libusb-1.0.a" -CONFIG_USE_PKGCONF=false -EOF - -mkdir -p build-macos-x86 -cat < build-macos-x86/tup.config -CONFIG_DEBUG=false -CONFIG_CC="LD_LIBRARY_PATH=/opt/osxcross/lib MACOSX_DEPLOYMENT_TARGET=10.9 /opt/osxcross/bin/o64-clang++" -CONFIG_CFLAGS="-I./third_party/libusb-1.0.23/libusb -arch i386 -arch x86_64" -CONFIG_LDFLAGS="./third_party/libusb-1.0.23/build-macos-amd64/libusb/.libs/libusb-1.0.a -framework CoreFoundation -framework IOKit" -CONFIG_USE_PKGCONF=false -EOF - -# Uncomment this to generate the WebAssembly build target. If you do this you -# have to finish a compile without tup before tup works. This is because -# emscripten generates some cache files which tup is unhappy about. -#mkdir -p build-wasm -#cat < build-wasm/tup.config -#CONFIG_DEBUG=true -#CONFIG_CC=/usr/lib/emscripten/em++ -#CONFIG_CFLAGS=-include emscripten.h -DFIBRE_PUBLIC=EMSCRIPTEN_KEEPALIVE -s RESERVED_FUNCTION_POINTERS=1 -#CONFIG_LDFLAGS=-s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0 -s 'EXTRA_EXPORTED_RUNTIME_METHODS=[addFunction, stringToUTF8Array, UTF8ArrayToString, ENV]' -#CONFIG_USE_PKGCONF=false -#CONFIG_ENABLE_LIBUSB=false -#EOF - -mkdir -p build-local -echo "" > build-local/tup.config - - -### Invoke tup for all configs - -tup --no-environ-check - - -### Copy to other locations - -function copy_to() { - cp build-linux-amd64/libfibre-linux-amd64.so "$1/" - cp build-linux-armhf/libfibre-linux-armhf.so "$1/" - cp build-windows-amd64/libfibre-windows-amd64.dll "$1/" - cp /usr/x86_64-w64-mingw32/bin/libwinpthread-1.dll "$1/" - cp build-macos-x86/libfibre-macos-x86.dylib "$1/" -} - -[ -d ../python/fibre ] && copy_to ../python/fibre -[ -d ../js ] && cp build-wasm/libfibre-* ../js diff --git a/Firmware/fibre-cpp/configs/linux-amd64.config b/Firmware/fibre-cpp/configs/linux-amd64.config new file mode 100644 index 00000000..7bb868ed --- /dev/null +++ b/Firmware/fibre-cpp/configs/linux-amd64.config @@ -0,0 +1,5 @@ +CONFIG_DEBUG=false +CONFIG_CC="clang++" +CONFIG_CFLAGS="-I$THIRD_PARTY./third_party/libusb-dev-armhf/usr/include/libusb-1.0" +CONFIG_LDFLAGS="-L$THIRD_PARTY./third_party/libusb-amd64/lib/x86_64-linux-gnu/libusb-1.0.so.0.2.0" +CONFIG_USE_PKGCONF=false diff --git a/Firmware/fibre-cpp/configs/linux-armhf.config b/Firmware/fibre-cpp/configs/linux-armhf.config new file mode 100644 index 00000000..04c427ba --- /dev/null +++ b/Firmware/fibre-cpp/configs/linux-armhf.config @@ -0,0 +1,5 @@ +CONFIG_DEBUG=false +CONFIG_CC="arm-linux-gnueabihf-g++" +CONFIG_CFLAGS="-I$THIRD_PARTY./third_party/libusb-dev-armhf/usr/include/libusb-1.0" +CONFIG_LDFLAGS="-L$THIRD_PARTY./third_party/libstdc++-linux-armhf/usr/lib/gcc-cross/arm-linux-gnueabihf/10 $THIRD_PARTY./third_party/libusb-armhf/usr/lib/arm-linux-gnueabihf/libusb-1.0.so.0" +CONFIG_USE_PKGCONF=false diff --git a/Firmware/fibre-cpp/configs/macos-x86.config b/Firmware/fibre-cpp/configs/macos-x86.config new file mode 100644 index 00000000..9e930262 --- /dev/null +++ b/Firmware/fibre-cpp/configs/macos-x86.config @@ -0,0 +1,9 @@ +CONFIG_DEBUG=false +CONFIG_CC="LD_LIBRARY_PATH=/opt/osxcross/lib MACOSX_DEPLOYMENT_TARGET=10.9 /opt/osxcross/bin/o64-clang++" +CONFIG_CFLAGS="-I$THIRD_PARTY./third_party/libusb-1.0.23/libusb -arch i386 -arch x86_64" +CONFIG_LDFLAGS="$THIRD_PARTY./third_party/libusb-1.0.23/build-macos-amd64/libusb/.libs/libusb-1.0.a -framework CoreFoundation -framework IOKit" +# not supported yet +CONFIG_ENABLE_TCP_SERVER_BACKEND=false +# not supported yet +CONFIG_ENABLE_TCP_CLIENT_BACKEND=false +CONFIG_USE_PKGCONF=false diff --git a/Firmware/fibre-cpp/configs/wasm.config b/Firmware/fibre-cpp/configs/wasm.config new file mode 100644 index 00000000..948a0e7e --- /dev/null +++ b/Firmware/fibre-cpp/configs/wasm.config @@ -0,0 +1,8 @@ +CONFIG_DEBUG=true +CONFIG_CC=/usr/lib/emscripten/em++ +CONFIG_CFLAGS=-include emscripten.h -DFIBRE_PUBLIC=EMSCRIPTEN_KEEPALIVE -s RESERVED_FUNCTION_POINTERS=1 +CONFIG_LDFLAGS=-s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0 -s 'EXTRA_EXPORTED_RUNTIME_METHODS=[addFunction, stringToUTF8Array, UTF8ArrayToString, ENV]' +CONFIG_USE_PKGCONF=false +CONFIG_ENABLE_LIBUSB_BACKEND=false +CONFIG_ENABLE_TCP_SERVER_BACKEND=false +CONFIG_ENABLE_TCP_CLIENT_BACKEND=false diff --git a/Firmware/fibre-cpp/configs/windows-amd64.config b/Firmware/fibre-cpp/configs/windows-amd64.config new file mode 100644 index 00000000..423cfcb1 --- /dev/null +++ b/Firmware/fibre-cpp/configs/windows-amd64.config @@ -0,0 +1,9 @@ +CONFIG_DEBUG=false +CONFIG_CC="x86_64-w64-mingw32-g++" +CONFIG_CFLAGS="-I$THIRD_PARTY./third_party/libusb-windows/libusb-1.0.23/include/libusb-1.0" +CONFIG_LDFLAGS="-static-libgcc $THIRD_PARTY./third_party/libusb-windows/libusb-1.0.23/MinGW64/static/libusb-1.0.a" +# not supported yet +CONFIG_ENABLE_TCP_SERVER_BACKEND=false +# not supported yet +CONFIG_ENABLE_TCP_CLIENT_BACKEND=false +CONFIG_USE_PKGCONF=false diff --git a/Firmware/fibre-cpp/include/fibre/crc.hpp b/Firmware/fibre-cpp/crc.hpp similarity index 100% rename from Firmware/fibre-cpp/include/fibre/crc.hpp rename to Firmware/fibre-cpp/crc.hpp diff --git a/Firmware/fibre-cpp/event_loop.hpp b/Firmware/fibre-cpp/event_loop.hpp deleted file mode 100644 index e4e6a144..00000000 --- a/Firmware/fibre-cpp/event_loop.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef __FIBRE_EVENT_LOOP_HPP -#define __FIBRE_EVENT_LOOP_HPP - -#include - -struct EventLoopTimer; - -/** - * @brief Base class for event loops. - * - * Thread-safety: The functions of this class must not be assumed to be thread-safe. - * Generally the functions of an event loop are only safe to be called from the - * event loop's thread itself. - */ -class EventLoop { -public: - /** - * @brief Registers a callback for immediate execution on the event loop thread. - */ - virtual int post(void (*callback)(void*), void *ctx) = 0; - - virtual int register_event(int event_fd, uint32_t events, void (*callback)(void*), void* ctx) = 0; - virtual int deregister_event(int event_fd) = 0; - - /** - * @brief Registers a callback to be called at a later point in time. - * - * This returns an opaque handler which can be used to cancel the timer. - * - * @param delay: The delay from now in seconds. - * TOOD: specify if OS sleep time is counted in. - */ - virtual struct EventLoopTimer* call_later(float delay, void (*callback)(void*), void *ctx) = 0; - - /** - * @brief Cancels a timer which was previously started by call_later(). - * - * Must not be called after invokation of the callback has started. - * This also means that cancel_timer() must not be called from within the - * callback of the timer itself. - */ - virtual int cancel_timer(EventLoopTimer* timer) = 0; -}; - -#endif // __FIBRE_EVENT_LOOP_HPP \ No newline at end of file diff --git a/Firmware/fibre-cpp/fibre.cpp b/Firmware/fibre-cpp/fibre.cpp new file mode 100644 index 00000000..81a15f4b --- /dev/null +++ b/Firmware/fibre-cpp/fibre.cpp @@ -0,0 +1,251 @@ + +#include +#include "logging.hpp" +#include +#include "legacy_protocol.hpp" +#include "print_utils.hpp" +#include +#include + +#if FIBRE_ALLOW_HEAP +#include +#include +#endif + +DEFINE_LOG_TOPIC(FIBRE); +USE_LOG_TOPIC(FIBRE); + +#if FIBRE_ENABLE_EVENT_LOOP +# ifdef __linux__ +# include "platform_support/epoll_event_loop.hpp" +using EventLoopImpl = fibre::EpollEventLoop; +# else +# error "No event loop implementation available for this operating system." +# endif +#endif + +using namespace fibre; + +struct DiscoveryContext { +}; + +#if FIBRE_ALLOW_HEAP + +template +T* my_alloc() { + return new T{}; +} + +template +void my_free(T* ctx) { + delete ctx; +} + +#else + +template +struct TheInstance { + static T instance; + static bool in_use; +}; + +template T TheInstance::instance{}; +template bool TheInstance::in_use = false; + +template +T* my_alloc() { + if (!TheInstance::in_use) { + TheInstance::in_use = true; + return &TheInstance::instance; + } else { + return nullptr; + } +} + +template +void my_free(T* ctx) { + if (ctx == &TheInstance::instance) { + TheInstance::in_use = false; + } else { + FIBRE_LOG(E) << "bad instance"; + } +} + +#endif + +bool fibre::launch_event_loop(Callback on_started) { +#if FIBRE_ENABLE_EVENT_LOOP + EventLoopImpl* event_loop = my_alloc(); // TODO: free + return event_loop->start([&](){ on_started.invoke(event_loop); }); +#else + return false; +#endif +} + +struct BackendInitializer { + template + bool operator()(T& backend) { + if (!backend.init(ctx->event_loop)) { + return false; + } + ctx->register_backend(backend.get_name(), &backend); + return true; + } + Context* ctx; +}; +struct BackendDeinitializer { + template + bool operator()(T& backend) { + ctx->deregister_backend(backend.get_name()); + return backend.deinit(); + } + Context* ctx; +}; + +Context* fibre::open(EventLoop* event_loop) { + Context* ctx = my_alloc(); + if (!ctx) { + FIBRE_LOG(E) << "already opened"; + return nullptr; + } + + ctx->event_loop = event_loop; + auto static_backends_good = for_each_in_tuple(BackendInitializer{ctx}, + ctx->static_backends); + + // TODO: check static_backends_good + + //if (std::all(static_backends_good)) { + // return nullptr; + //} + + return ctx; +} + +void fibre::close(Context* ctx) { + if (ctx->n_domains) { + FIBRE_LOG(W) <n_domains << " domains are still open"; + } + + for_each_in_tuple(BackendDeinitializer{ctx}, + ctx->static_backends); + + my_free(ctx); +} + +Domain* Context::create_domain(std::string specs) { + FIBRE_LOG(D) << "creating domain with path \"" << specs << "\""; + + Domain* domain = new Domain(); // deleted in close_domain + domain->ctx = this; + + std::string::iterator prev_delim = specs.begin(); + while (prev_delim < specs.end()) { + auto next_delim = std::find(prev_delim, specs.end(), ';'); + auto colon = std::find(prev_delim, next_delim, ':'); + auto colon_end = std::min(colon + 1, next_delim); + + std::string name{prev_delim, colon}; + auto it = discoverers.find(name); + + if (it == discoverers.end()) { + FIBRE_LOG(W) << "transport layer \"" << name << "\" not implemented"; + } else { + domain->channel_discovery_handles[name] = nullptr; + it->second->start_channel_discovery(&*colon_end, next_delim - colon_end, + &domain->channel_discovery_handles[name], + MEMBER_CB(domain, on_found_channels)); + } + + prev_delim = std::min(next_delim + 1, specs.end()); + } + + n_domains++; + return domain; +} + +void Context::close_domain(Domain* domain) { + for (auto& it: domain->channel_discovery_handles) { + discoverers[it.first]->stop_channel_discovery(it.second); + } + domain->channel_discovery_handles.clear(); + delete domain; + n_domains--; +} + +void Context::register_backend(std::string name, ChannelDiscoverer* backend) { + if (discoverers.find(name) != discoverers.end()) { + FIBRE_LOG(W) << "Discoverer " << name << " already registered"; + return; // TODO: report status + } + + discoverers[name] = backend; +} + +void Context::deregister_backend(std::string name) { + auto it = discoverers.find(name); + if (it == discoverers.end()) { + FIBRE_LOG(W) << "Discoverer " << name << " not registered"; + return; // TODO: report status + } + + discoverers.erase(it); +} + +#if FIBRE_ENABLE_CLIENT +void Domain::start_discovery(Callback on_found_object, Callback on_lost_object) { + on_found_object_ = on_found_object; + on_lost_object_ = on_lost_object; + if (root_object_) { + on_found_object_.invoke(root_object_, root_intf_); + } +} + +void Domain::stop_discovery() { + on_found_object_ = nullptr; + on_lost_object_ = nullptr; +} +#endif + +void Domain::on_found_channels(ChannelDiscoveryResult result) { + FIBRE_LOG(D) << "found channels!"; + + if (result.status != kFibreOk) { + FIBRE_LOG(W) << "discoverer stopped"; + return; + } + + if (!result.rx_channel || !result.tx_channel) { + FIBRE_LOG(W) << "unidirectional operation not supported yet"; + return; + } + +#if FIBRE_ENABLE_CLIENT || FIBRE_ENABLE_SERVER + // Deleted during on_stopped() + auto protocol = new fibre::LegacyProtocolPacketBased(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)); +#else + protocol->start(MEMBER_CB(this, on_stopped)); +#endif +#endif +} + +#if FIBRE_ENABLE_CLIENT +void Domain::on_found_root_object(LegacyObjectClient* obj_client, std::shared_ptr obj) { + root_object_ = reinterpret_cast(obj.get()); + root_intf_ = reinterpret_cast(obj->intf.get()); + on_found_object_.invoke(reinterpret_cast(obj.get()), + reinterpret_cast(obj->intf.get())); +} + +void Domain::on_lost_root_object(LegacyObjectClient* obj_client) { + root_object_ = nullptr; + root_intf_ = nullptr; + on_lost_object_.invoke(reinterpret_cast(obj_client->root_obj_.get())); +} +#endif + +void Domain::on_stopped(LegacyProtocolPacketBased* protocol, StreamStatus status) { + delete protocol; +} diff --git a/Firmware/fibre-cpp/function_stubs_template.j2 b/Firmware/fibre-cpp/function_stubs_template.j2 index fb44fdaa..ac25923f 100644 --- a/Firmware/fibre-cpp/function_stubs_template.j2 +++ b/Firmware/fibre-cpp/function_stubs_template.j2 @@ -24,9 +24,9 @@ static inline bool [[func.fullname | to_snake_case]]([% for arg in func.in.value 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 %].value()[% endif %][[', ' if not loop.last]][% endfor %]); + [% 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]].value()->[[func.name]]([% for arg in func.in.values() | skip_first %]in_[[arg.name]][% if not arg.optional %].value()[% endif %][[', ' if not loop.last]][% endfor %]); + [% 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 %] diff --git a/Firmware/fibre-cpp/get_dependencies.sh b/Firmware/fibre-cpp/get_dependencies.sh new file mode 100755 index 00000000..fe05ca16 --- /dev/null +++ b/Firmware/fibre-cpp/get_dependencies.sh @@ -0,0 +1,77 @@ +#!/bin/bash +set -euo pipefail + +# Usage: download_deb_pkg destination-dir url +function download_deb_pkg() { + mkdir -p third_party + dir="$1" + url="$2" + file="$(sed 's|^.*/\([^/]*\)$|\1|' <<< "$url")" + + pushd third_party > /dev/null + if ! [ -f "${file}" ]; then + wget "${url}" + fi + if ! [ -d "${dir}/usr" ]; then + ar x "${file}" "data.tar.xz" + mkdir -p "${dir}" + tar -xvf "data.tar.xz" -C "${dir}" + fi + popd > /dev/null +} + +# Usage: compile_libusb arch-name arch +function compile_libusb() { + arch_name="$1" + arch="$2" + libusb_version=1.0.23 + + pushd third_party > /dev/null + if ! [ -f "libusb-${libusb_version}.tar.bz2" ]; then + wget "https://github.com/libusb/libusb/releases/download/v${libusb_version}/libusb-${libusb_version}.tar.bz2" + fi + if ! [ -d "libusb-${libusb_version}" ]; then + tar -xvf "libusb-${libusb_version}.tar.bz2" + fi + + mkdir -p "libusb-${libusb_version}/build-${arch_name}" + pushd "libusb-${libusb_version}/build-${arch_name}" > /dev/null + unset LDFLAGS + if ! [ -f "libusb/.libs/libusb-1.0.a" ]; then + ../configure --host="$arch" \ + --enable-static \ + --prefix=/opt/osxcross/ \ + --disable-dependency-tracking + # They broke parallel building in libusb 1.20 + make + fi + popd > /dev/null + popd > /dev/null +} + +function patch_macos_sdk() { + # Link are broken: + # …ions/Current/Headers $ ls -l IOReturn.h + # lrwxrwxrwx 1 root root 189 Dec 26 2019 IOReturn.h -> Users/phracker/Documents/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/IOKit/IOReturn.h + # Fix with: + # sudo ln -sf /opt/osxcross/SDK/MacOSX10.13.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/IOKit/IOReturn.h IOReturn.h + + oldprefix="Users/phracker/Documents/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" + newprefix="/opt/osxcross/SDK/MacOSX10.13.sdk" + while IFS= read -r link; do + destination="$(readlink "$link")" + pruned_destination="${destination#"$oldprefix"}" + if [ "${oldprefix}${pruned_destination}" == "${destination}" ]; then + sudo mv -T "${newprefix}${pruned_destination}" "$link" + fi + done <<< "$(find /opt/osxcross/SDK/MacOSX10.13.sdk/System/Library/Frameworks/IOKit.framework -xtype l)" +} + +cmd="$1" +shift +case "$cmd" in + download_deb_pkg) download_deb_pkg $@ ;; + compile_libusb) compile_libusb $@ ;; + patch_macos_sdk) patch_macos_sdk $@ ;; + *) echo "unknown command" && false ;; +esac diff --git a/Firmware/fibre-cpp/async_stream.hpp b/Firmware/fibre-cpp/include/fibre/async_stream.hpp similarity index 80% rename from Firmware/fibre-cpp/async_stream.hpp rename to Firmware/fibre-cpp/include/fibre/async_stream.hpp index bb233e00..c2efc039 100644 --- a/Firmware/fibre-cpp/async_stream.hpp +++ b/Firmware/fibre-cpp/include/fibre/async_stream.hpp @@ -1,7 +1,8 @@ #ifndef __FIBRE_ASYNC_STREAM_HPP #define __FIBRE_ASYNC_STREAM_HPP -#include "include/fibre/bufptr.hpp" // TODO: move this header +#include +#include #include namespace fibre { @@ -13,34 +14,6 @@ enum StreamStatus { kStreamError }; -template -class Completer { -public: - virtual void complete(TResults ... result) = 0; - - static Completer& get_dummy() { - static struct DummyCompleter : Completer { - void complete(TResults ... result) {} - } dummy; - return dummy; - } -}; - -/** - * @brief Safe wrapper around Completer::complete. - * - * This function takes a reference to a completer pointer and only invokes the - * completer if it's not null. Before invoking the completer, the pointer is - * cleared. - */ -template -static void safe_complete(Completer*& completer, TResults ... results) { - Completer* tmp = completer; - completer = nullptr; - if (tmp) { - tmp->complete(results...); - } -} struct ReadResult { StreamStatus status; @@ -70,22 +43,6 @@ struct WriteResult { const unsigned char* end; }; -struct WriteCompleter : Completer { - virtual void on_write_finished(WriteResult result) = 0; - - void complete(WriteResult result) final { - on_write_finished(result); - } -}; - -struct ReadCompleter : Completer { - virtual void on_read_finished(ReadResult result) = 0; - - void complete(ReadResult result) final { - on_read_finished(result); - } -}; - using TransferHandle = uintptr_t; @@ -114,7 +71,7 @@ public: * finishes, whether successful or not. * Must remain valid until it is satisfied. */ - virtual void start_read(bufptr_t buffer, TransferHandle* handle, Completer& completer) = 0; + virtual void start_read(bufptr_t buffer, TransferHandle* handle, Callback completer) = 0; /** * @brief Cancels an operation that was previously started with start_read(). @@ -163,7 +120,7 @@ public: * finishes, whether successful or not. * Must remain valid until it is satisfied. */ - virtual void start_write(cbufptr_t buffer, TransferHandle* handle, Completer& completer) = 0; + virtual void start_write(cbufptr_t buffer, TransferHandle* handle, Callback completer) = 0; /** * @brief Cancels an operation that was previously started with start_write(). diff --git a/Firmware/fibre-cpp/include/fibre/callback.hpp b/Firmware/fibre-cpp/include/fibre/callback.hpp new file mode 100644 index 00000000..dc2f5d8a --- /dev/null +++ b/Firmware/fibre-cpp/include/fibre/callback.hpp @@ -0,0 +1,126 @@ +#ifndef __CALLBACK_HPP +#define __CALLBACK_HPP + +#include +#include +#include +#include +#include + +namespace fibre { + +namespace detail { +template struct get_default { static T val() { return {}; } }; +template<> struct get_default { static void val() {} }; +} + +template +class Callback { +public: + Callback() : cb_(nullptr), ctx_(nullptr) {} + Callback(std::nullptr_t) : cb_(nullptr), ctx_(nullptr) {} + Callback(TRet(*callback)(void*, TArgs...), void* ctx) : + cb_(callback), ctx_(ctx) {} + + /** + * @brief Creates a copy of another Callback instance. + * + * This is only works it the other callback has identical template parameters. + * This constructor is templated so that construction from an incompatible + * callback gives a useful error message. + */ + //template + //Callback(const Callback& other) : cb_(other.cb_), ctx_(other.ctx_) { + // static_assert(std::is_same, Callback>::value, "incompatible callback type"); + //} + + Callback(const Callback& other) : cb_(other.cb_), ctx_(other.ctx_) {} + + // If you get a compile error "[...] invokes a deleted function" that points + // here then you're probably trying to assign a Callback with incompatible + // template arguments to another Callback. + template + Callback(const Callback& other) = delete; + + /** + * @brief Constructs a callback object from a functor. The functor must + * remain allocated throughout the lifetime of the Callback. + */ + template + Callback(const TFunc& func) : + cb_([](void* ctx, TArgs...args){ + return (*(const TFunc*)ctx)(args...); + }), ctx_((void*)&func) {} + + operator bool() { + return cb_; + } + + TRet invoke(TArgs ... result) const { + if (cb_) { + return (*cb_)(ctx_, result...); + } + return detail::get_default::val(); + } + + TRet invoke_and_clear(TArgs ... result) { + void* ctx = ctx_; + auto cb = cb_; + ctx_ = nullptr; + cb_ = nullptr; + if (cb) { + return (*cb)(ctx, result...); + } + return detail::get_default::val(); + } + + typedef TRet(*cb_t)(void*, TArgs...); + cb_t get_ptr() { return cb_; } + void* get_ctx() { return ctx_; } + +private: + TRet(*cb_)(void*, TArgs...); + void* ctx_; +}; + +template +struct function_traits { + using TRet = _TRet; + using TArgs = std::tuple<_TArgs...>; + using TObj = _TObj; +}; + +template +function_traits<_TRet, _TObj, _TArgs...> make_function_traits(_TRet (_TObj::*)(_TArgs...)) { + return {}; +} + +template +struct MemberCallback; + +template +struct MemberCallback> { + using cb_t = Callback; + static cb_t with(TObj* obj) { + return cb_t{[](void* obj, TArgs... arg) { + return (((TObj*)obj)->*func)(arg...); + }, obj}; + } +}; + +template> +typename MemCb::cb_t make_callback(typename TTraits::TObj* obj) { + return MemCb::with(obj); +} + +#define MEMBER_CB(obj, func) \ + fibre::make_callback< \ + decltype(&std::remove_reference_t::func), \ + &std::remove_reference_t::func \ + >(obj) + +} + +#endif // __CALLBACK_HPP \ No newline at end of file diff --git a/Firmware/fibre-cpp/include/fibre/channel_discoverer.hpp b/Firmware/fibre-cpp/include/fibre/channel_discoverer.hpp new file mode 100644 index 00000000..03ce91d1 --- /dev/null +++ b/Firmware/fibre-cpp/include/fibre/channel_discoverer.hpp @@ -0,0 +1,34 @@ +#ifndef __FIBRE_CHANNEL_DISCOVERER +#define __FIBRE_CHANNEL_DISCOVERER + +#include "async_stream.hpp" +#include +#include + +namespace fibre { + +struct ChannelDiscoveryResult { + Status status; + AsyncStreamSource* rx_channel; + AsyncStreamSink* tx_channel; + size_t mtu; +}; + +struct ChannelDiscoveryContext {}; + +class ChannelDiscoverer { +public: + virtual void start_channel_discovery( + const char* specs, size_t specs_len, + ChannelDiscoveryContext** handle, + Callback on_found_channels) = 0; + virtual int stop_channel_discovery(ChannelDiscoveryContext* handle) = 0; + +protected: + bool try_parse_key(const char* begin, const char* end, const char* key, const char** val_begin, const char** val_end); + bool try_parse_key(const char* begin, const char* end, const char* key, int* val); +}; + +} + +#endif // __FIBRE_CHANNEL_DISCOVERER \ No newline at end of file diff --git a/Firmware/fibre-cpp/include/fibre/cpp_utils.hpp b/Firmware/fibre-cpp/include/fibre/cpp_utils.hpp index 285af103..81aad20b 100644 --- a/Firmware/fibre-cpp/include/fibre/cpp_utils.hpp +++ b/Firmware/fibre-cpp/include/fibre/cpp_utils.hpp @@ -489,7 +489,7 @@ public: } inline optional& operator=(const optional & other) { - ~*this(); + (**this).~T(); new (this) optional{other}; return *this; } @@ -506,8 +506,8 @@ public: return *(T*)content_; } - inline T& operator->() { - return *(T*)content_; + inline T* operator->() { + return (T*)content_; } storage_t content_; @@ -521,6 +521,11 @@ optional make_optional(T&& val) { return optional{std::forward(val)}; } +template +optional make_optional(T& val) { + return optional{val}; +} + } // namespace std #endif diff --git a/Firmware/fibre-cpp/include/fibre/decoders.hpp b/Firmware/fibre-cpp/include/fibre/decoders.hpp deleted file mode 100644 index 6d5d69fb..00000000 --- a/Firmware/fibre-cpp/include/fibre/decoders.hpp +++ /dev/null @@ -1,336 +0,0 @@ - -#ifndef __DECODERS_HPP -#define __DECODERS_HPP - -#include "protocol.hpp" -#include "crc.hpp" -#include "cpp_utils.hpp" -#include - - -/* Base classes --------------------------------------------------------------*/ - -// @brief Base class for stream based decoders. -// A stream based decoder is a decoder that processes arbitrary length data blocks. -class StreamDecoder : public StreamSink { -public: - // @brief Returns 0 if no error ocurred, otherwise a non-zero error code. - // Once process_bytes returned an error, subsequent calls to get_status must return the same error. - // If the decoder is in an error state, the behavior of get_expected_bytes and process_bytes is undefined. - virtual int get_status() = 0; - - // @brief Returns the minimum number of bytes that are still needed to complete this decoder. - // If 0, the decoder is considered complete and any subsequent call to process_bytes must process - // exactly 0 bytes. - // process_bytes() must always process all provided bytes unless the decoder expects no more bytes - // afterwards - virtual size_t get_expected_bytes() = 0; -}; - -// @brief Base class for a decoder that is fed in a block-wise fashion. -// This base class is provided for convenience when implementing certain types of decoders. -// A StreamDecoder can be obtained from a BlockDecoder by using StreamDecoder_from_BlockDecoder. -template -class BlockDecoder { -public: - typedef std::integral_constant block_size; - - virtual int get_status() = 0; - virtual size_t get_expected_blocks() = 0; - virtual int process_block(const uint8_t block[BLOCKSIZE]) = 0; -private: -}; - -// @brief Base class for a decoder that is fed in a byte-wise fashion -// This base class is provided for convenience when implementing certain types of decoders. -// A StreamDecoder can be obtained from a ByteDecoder by using StreamDecoder_from_ByteDecoder. -class ByteDecoder { -public: - virtual int get_status() = 0; - virtual size_t get_expected_bytes() = 0; - virtual int process_byte(uint8_t byte) = 0; -}; - -/* Converter classes ---------------------------------------------------------*/ - -// @brief Encapsulates a BlockDecoder to make it look like a StreamDecoder -// @tparam T The encapsulated BlockDecoder type. -// Must inherit from BlockDecoder. -template::template all_are>())> -class StreamDecoder_from_BlockDecoder : public StreamDecoder { -public: - // @brief Imitates the constructor signature of the encapsulated type. - template::template first_is_not())> - explicit StreamDecoder_from_BlockDecoder(Args&& ... args) - : block_decoder_(std::forward(args)...) { - EXPECT_TYPE(T, BlockDecoder); - } - - inline int get_status() final { - return block_decoder_.get_status(); - } - - inline size_t get_expected_bytes() final { - size_t expected_bytes = block_decoder_.get_expected_blocks() * T::block_size::value; - return expected_bytes - std::min(expected_bytes, buffer_pos_); - } - - inline int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) final { - while (!get_status() && get_expected_bytes() && length) { - // use the incoming bytes to fill internal buffer to get a complete block - size_t n_copy = std::min(length, T::block_size::value - buffer_pos_); - memcpy(buffer_ + buffer_pos_, buffer, n_copy); - buffer += n_copy; - length -= n_copy; - if (processed_bytes) (*processed_bytes) += n_copy; - buffer_pos_ += n_copy; - - // if we have a full block, process it - if (buffer_pos_ == T::block_size::value) { - block_decoder_.process_block(buffer_); - buffer_pos_ = 0; - } - } - return get_status(); - } - - size_t get_free_space() { return SIZE_MAX; } // TODO: deprecate -private: - T block_decoder_; - size_t buffer_pos_ = 0; - uint8_t buffer_[T::block_size::value]; -}; - -// @brief Encapsulates a ByteDecoder to make it look like a BlockDecoder -// @tparam T The encapsulated ByteDecoder type. -// Must inherit from ByteDecoder. -template::template all_are())> -class BlockDecoder_from_ByteDecoder : public BlockDecoder<1> { -public: - // @brief Imitates the constructor signature of the encapsulated type. - template::template first_is_not())> - BlockDecoder_from_ByteDecoder(Args&& ... args) - : byte_decoder_(std::forward(args)...) { - EXPECT_TYPE(T, ByteDecoder); - } - - inline int get_status() final { - return byte_decoder_.get_status(); - } - inline size_t get_expected_blocks() final { - return byte_decoder_.get_expected_bytes(); - } - inline int process_block(const uint8_t block[1]) final { - int status = byte_decoder_.process_byte(*block); - return status; - } -private: - T byte_decoder_; -}; - -// @brief Encapsulates a ByteDecoder to make it look like a StreamDecoder -// @tparam T The encapsulated ByteDecoder type. -// Must inherit from ByteDecoder. -template::template all_are())> -class StreamDecoder_from_ByteDecoder : public StreamDecoder { -public: - // @brief Imitates the constructor signature of the encapsulated type. - template::template first_is_not())> - StreamDecoder_from_ByteDecoder(Args&& ... args) - : byte_decoder_(std::forward(args)...) { - EXPECT_TYPE(T, ByteDecoder); - } - - inline int get_status() final { - return byte_decoder_.get_status(); - } - inline size_t get_expected_bytes() final { - return byte_decoder_.get_expected_bytes(); - } - inline size_t get_free_space() { return SIZE_MAX; } - inline int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) final { - while (!byte_decoder_.get_status() && byte_decoder_.get_expected_bytes() && length) { - length--; - if (processed_bytes) (*processed_bytes)++; - byte_decoder_.process_byte(*(buffer++)); - } - return byte_decoder_.get_status(); - } -private: - T byte_decoder_; -}; - -/* Decoder implementations ---------------------------------------------------*/ - -template -class VarintByteDecoder : public ByteDecoder { -public: - static constexpr T BIT_WIDTH = (CHAR_BIT * sizeof(T)); - - VarintByteDecoder(T& state_variable) : - state_variable_(state_variable) - { - } - - size_t get_expected_bytes() final { - return done_ ? 0 : 1; - } - - int get_status() final { - return status_; - } - - int process_byte(uint8_t input_byte) final { - if (bit_pos_ == 0) { - LOG_FIBRE("start decoding varint, with 0x%02x => %zx\n", input_byte, (uintptr_t)&state_variable_); - state_variable_ = 0; - } - LOG_FIBRE("varint: decode %02x << %zu at %zx\n", input_byte, bit_pos_, &bit_pos_); - // we assume bit_pos_ < BIT_WIDTH - state_variable_ |= (static_cast(input_byte & 0x7f) << bit_pos_); - if (((state_variable_ >> bit_pos_) & 0x7f) != static_cast(input_byte & 0x7f)) { - LOG_FIBRE("varint overflow: tried to add %02x << %zu\n", input_byte, bit_pos_); - return (status_ = -1); // overflow - } - bit_pos_ += 7; - done_ = !(input_byte & 0x80); - return (status_ = (done_ || bit_pos_ < BIT_WIDTH) ? 0 : -1); - } - -private: - T& state_variable_; - // At all times where status_ != 0 the following statement holds: - // (done_ || bit_pos_ < BIT_WIDTH) - //size_t bit_pos_ = 0; // bit position - size_t bit_pos_ = 0; // bit position - int status_ = 0; - bool done_ = false; - int data[1024] = {0}; -}; - -template -using VarintStreamDecoder = StreamDecoder_from_ByteDecoder>; - -// This double nested type should work identically but makes it way harder for the compiler to optimize -//template -//using VarintBlockDecoder = BlockDecoder_from_ByteDecoder>; -//template -//using VarintStreamDecoder = StreamDecoder_from_BlockDecoder>; - -template -inline VarintStreamDecoder make_varint_decoder(T& variable) { - return VarintStreamDecoder(variable); -} - -inline VarintStreamDecoder make_endpoint_id_decoder(ReceiverState& state) { - return make_varint_decoder(state.endpoint_id); -} -inline VarintStreamDecoder make_length_decoder(ReceiverState& state) { - return make_varint_decoder(state.length); -} - - - -template::template all_are())> -class CRC8BlockDecoder : public BlockDecoder { -public: - CRC8BlockDecoder(TDecoder&& inner_decoder) : - inner_decoder_(std::forward(inner_decoder)) { - } - - int get_status() final { - return status_; - } - - size_t get_expected_blocks() final { - return (inner_decoder_.get_expected_bytes() + CRC8_BLOCKSIZE - 2) / (CRC8_BLOCKSIZE - 1); - } - - int process_block(const uint8_t input_block[4]) final { - current_crc_ = calc_crc8(current_crc_, input_block, CRC8_BLOCKSIZE - 1); - if (current_crc_ != input_block[CRC8_BLOCKSIZE - 1]) - return status_ = -1; - return status_ = inner_decoder_.process_bytes(input_block, CRC8_BLOCKSIZE - 1, nullptr); - } -private: - TDecoder inner_decoder_; - int status_ = 0; - uint8_t current_crc_ = INIT; -}; - -template -using CRC8StreamDecoder = StreamDecoder_from_BlockDecoder>; - -template -inline CRC8StreamDecoder make_crc8_decoder(TDecoder&& decoder) { - return CRC8StreamDecoder(std::forward(decoder)); -} - -// TODO: ENABLE_IF(TypeChecker::template all_are()) -template -class DecoderChain; - -template<> -class DecoderChain<> : public StreamDecoder { -public: - size_t get_expected_bytes() { return 0; } - int get_status() { return 0; } - int process_bytes(const uint8_t *input, size_t length, size_t* processed_bytes) { return 0; } - size_t get_free_space() { return SIZE_MAX; } // TODO: deprecate -}; - -template -class DecoderChain : public StreamDecoder { -public: - DecoderChain(TDecoder&& this_decoder, TDecoders&& ... subsequent_decoders) : - this_decoder_(std::forward(this_decoder)), - subsequent_decoders_(std::forward(subsequent_decoders)...) - { - EXPECT_TYPE(TDecoder, StreamDecoder); - } - - int get_status() final { - // If this decoder or any of the subsequent decoders failed, return error code. - int this_status = this_decoder_.get_status(); - int subsequent_status = subsequent_decoders_.get_status(); - if (this_status) - return this_status; - else if (subsequent_status) - return subsequent_status; - else - return 0; - } - - size_t get_expected_bytes() final { - return this_decoder_.get_expected_bytes() + subsequent_decoders_.get_expected_bytes(); - } - - int process_bytes(const uint8_t *input, size_t length, size_t* processed_bytes) final { - if (this_decoder_.get_expected_bytes()) { - LOG_FIBRE("decoder chain: process %zu bytes in segment %s\n", length, typeid(TDecoder).name()); - size_t chunk = 0; - int status = this_decoder_.process_bytes(input, length, &chunk); - input += chunk; - length -= chunk; - if (processed_bytes) (*processed_bytes) += chunk; - if (status) - return status; - if (!length) - return 0; - } - return subsequent_decoders_.process_bytes(input, length, processed_bytes); - } - - size_t get_free_space() { return SIZE_MAX; } // TODO: deprecate -private: - TDecoder this_decoder_; - DecoderChain subsequent_decoders_; -}; - -template -inline DecoderChain make_decoder_chain(TDecoders&& ... decoders) { - return DecoderChain(std::forward(decoders)...); -} - -#endif // __DECODERS_HPP diff --git a/Firmware/fibre-cpp/include/fibre/encoders.hpp b/Firmware/fibre-cpp/include/fibre/encoders.hpp deleted file mode 100644 index e296eeb0..00000000 --- a/Firmware/fibre-cpp/include/fibre/encoders.hpp +++ /dev/null @@ -1,323 +0,0 @@ - -#ifndef __ENCODERS_HPP -#define __ENCODERS_HPP - -#include "protocol.hpp" -#include "crc.hpp" -#include "cpp_utils.hpp" -#include - -struct Request { - endpoint_id_t endpoint_id; - size_t length; -}; - -/* Base classes --------------------------------------------------------------*/ - -// @brief Base class for all stream encoders -// A stream based encoder is an encoder that generates arbitrary length data blocks. -class StreamEncoder : public StreamSource { -public: - // @brief Returns 0 if no error ocurred, otherwise a non-zero error code. - // Once get_bytes returned an error, subsequent calls to get_status must return the same error. - // If the encoder is in an error state, the behavior of get_available_bytes and get_bytes is undefined. - virtual int get_status() = 0; - - // @brief Returns the minimum number of bytes that will still be generated by this encoder. - // If 0, the encoder is considered complete and any subsequent call to get_bytes must generate - // exactly 0 bytes. - // get_bytes() must always generate as many bytes as requested unless the encoder generates no more bytes - // afterwards - virtual size_t get_available_bytes() = 0; -}; - -// @brief Base class for an encoder that is fed in a block-wise fashion. -// This base class is provided for convenience when implementing certain types of encoders. -// A StreamEncoder can be obtained from a BlockEncoder by using StreamEncoder_from_BlockEncoder. -template -class BlockEncoder { -public: - typedef std::integral_constant block_size; - - virtual int get_status() = 0; - virtual size_t get_available_blocks() = 0; - virtual int get_block(uint8_t block[BLOCKSIZE]) = 0; -private: -}; - -// @brief Base class for an encoder that is fed in a byte-wise fashion -// This base class is provided for convenience when implementing certain types of encoders. -// A StreamEncoder can be obtained from a ByteEncoder by using StreamEncoder_from_ByteEncoder. -class ByteEncoder { -public: - virtual int get_status() = 0; - virtual size_t get_available_bytes() = 0; - virtual int get_byte(uint8_t *output_byte) = 0; -}; - -/* Converter classes ---------------------------------------------------------*/ - -// @brief Encapsulates a BlockEncoder to make it look like a StreamEncoder -// @tparam T The encapsulated BlockEncoder type. -// Must inherit from to BlockEncoder. -template::template all_are>())> -class StreamEncoder_from_BlockEncoder : public StreamEncoder { -public: - // @brief Imitates the constructor signature of the encapsulated type. - template::template first_is_not())> - explicit StreamEncoder_from_BlockEncoder(Args&& ... args) - : block_encoder_(std::forward(args)...) { - EXPECT_TYPE(T, BlockEncoder); - } - - inline int get_status() final { - return buffered_bytes_ ? 0 : block_encoder_.get_status(); - } - - inline size_t get_available_bytes() final { - size_t available_bytes = block_encoder_.get_available_blocks() * T::block_size::value; - return available_bytes + buffered_bytes_; - } - - inline int get_bytes(uint8_t* buffer, size_t length, size_t* generated_bytes) final { - while (!get_status() && get_available_bytes() && length) { - // if the buffer is empty, retrieve a new block from the encode - if (!buffered_bytes_) { - block_encoder_.get_block(buffer_); - buffered_bytes_ = T::block_size::value; - } - - // hand the buffered bytes to the encoder - size_t n_copy = std::min(buffered_bytes_, length); - memcpy(buffer, buffer_ + T::block_size::value - n_copy, n_copy); - length -= n_copy; - buffer += n_copy; - if (generated_bytes) (*generated_bytes) += n_copy; - buffered_bytes_ -= n_copy; - } - return get_status(); - } -private: - T block_encoder_; - size_t buffered_bytes_ = 0; - uint8_t buffer_[T::block_size::value]; -}; - -// @brief Encapsulates a ByteEncoder to make it look like a BlockEncoder -// @tparam T The encapsulated ByteEncoder type. -// Must inherit from ByteEncoder. -template::template all_are())> -class BlockEncoder_from_ByteEncoder : public BlockEncoder<1> { -public: - // @brief Imitates the constructor signature of the encapsulated type. - template::template first_is_not())> - BlockEncoder_from_ByteEncoder(Args&& ... args) - : byte_encoder_(std::forward(args)...) { - EXPECT_TYPE(T, ByteEncoder); - } - - inline int get_status() final { - return byte_encoder_.get_status(); - } - inline size_t get_available_blocks() final { - return byte_encoder_.get_available_bytes(); - } - inline int get_block(uint8_t block[1]) final { - int status = byte_encoder_.get_byte(*block); - return status; - } -private: - T byte_encoder_; -}; - -// @brief Encapsulates a ByteEncoder to make it look like a StreamEncoder -// @tparam T The encapsulated ByteEncoder type. -// Must inherit from ByteEncoder. -template::template all_are())> -class StreamEncoder_from_ByteEncoder : public StreamEncoder { -public: - // @brief Imitates the constructor signature of the encapsulated type. - template::template first_is_not())> - StreamEncoder_from_ByteEncoder(Args&& ... args) - : byte_encoder_(std::forward(args)...) { - EXPECT_TYPE(T, ByteEncoder); - } - - inline int get_status() final { - return byte_encoder_.get_status(); - } - inline size_t get_available_bytes() final { - return byte_encoder_.get_available_bytes(); - } - inline int get_bytes(uint8_t* buffer, size_t length, size_t* generated_bytes) final { - while (!byte_encoder_.get_status() && byte_encoder_.get_available_bytes() && length) { - length--; - if (generated_bytes) (*generated_bytes)++; - byte_encoder_.get_byte(buffer++); - } - return byte_encoder_.get_status(); - } -private: - T byte_encoder_; -}; - -/* Encoder implementations ---------------------------------------------------*/ - -template -class VarintByteEncoder : public ByteEncoder { -public: - static constexpr T BIT_WIDTH = (CHAR_BIT * sizeof(T)); - - VarintByteEncoder(const T& state_variable) : - state_variable_(state_variable) - {} - - size_t get_available_bytes() final { - return done_ ? 0 : 1; - } - - int get_status() final { - return 0; - } - - int get_byte(uint8_t *output_byte) final { - if (bit_pos_ == 0) - LOG_FIBRE("start encoding varint, from pos %d\n", bit_pos_); - *output_byte = (state_variable_ >> bit_pos_) & 0x7f; - bit_pos_ += 7; - if (bit_pos_ < BIT_WIDTH && (state_variable_ >> bit_pos_)) { - LOG_FIBRE("remainder: %x\n", state_variable_ >> bit_pos_); - *output_byte |= 0x80; - }else - done_ = true; - return 0; - } - -private: - const T& state_variable_; - size_t bit_pos_ = 0; // bit position - int status_ = 0; - bool done_ = false; -}; - -template -using VarintStreamEncoder = StreamEncoder_from_ByteEncoder>; - -template -VarintStreamEncoder make_varint_encoder(const T& variable) { - return VarintStreamEncoder(variable); -} - -VarintStreamEncoder make_endpoint_id_encoder(const Request& request) { - return make_varint_encoder(request.endpoint_id); -} -VarintStreamEncoder make_length_encoder(const Request& request) { - return make_varint_encoder(request.length); -} - -template::template all_are())> -class CRC8BlockEncoder : public BlockEncoder { -public: - CRC8BlockEncoder(TEncoder&& inner_encoder) - : inner_encoder_(std::forward(inner_encoder)) {} - - int get_status() final { - return status_; - } - - size_t get_available_blocks() final { - return (inner_encoder_.get_available_bytes() + CRC8_BLOCKSIZE - 2) / (CRC8_BLOCKSIZE - 1); - } - - int get_block(uint8_t block[4]) final { - size_t generated_bytes = 0; - status_ = inner_encoder_.get_bytes(block, CRC8_BLOCKSIZE - 1, &generated_bytes); - if (status_) - return status_; - - // zero out unused end of the block - while (generated_bytes < CRC8_BLOCKSIZE) - block[generated_bytes++] = 0; - - block[CRC8_BLOCKSIZE - 1] = current_crc_ = calc_crc8(current_crc_, block, CRC8_BLOCKSIZE - 1); - return 0; - } -private: - TEncoder inner_encoder_; - int status_ = 0; - uint8_t current_crc_ = INIT; -}; - -template -using CRC8StreamEncoder = StreamEncoder_from_BlockEncoder>; - -template -CRC8StreamEncoder make_crc8_encoder(TEncoder&& encoder) { - return CRC8StreamEncoder(std::forward(encoder)); -} - -template -class EncoderChain; - -template<> -class EncoderChain<> : public StreamEncoder { -public: - size_t get_available_bytes() final { return 0; } - int get_status() final { return 0; } - int get_bytes(uint8_t *output, size_t length, size_t* generated_bytes) final { return 0; } -}; - -template -class EncoderChain : public StreamEncoder { -public: - EncoderChain(TEncoder&& this_encoder, TEncoders&& ... subsequent_encoders) : - this_encoder_(std::forward(this_encoder)), - subsequent_encoders_(std::forward(subsequent_encoders)...) - { - EXPECT_TYPE(TEncoder, StreamEncoder); - } - - size_t get_available_bytes() final { - return this_encoder_.get_available_bytes() + subsequent_encoders_.get_available_bytes(); - } - - int get_status() final { - // If this encoder or any of the subsequent encoders failed, return error code. - int this_status = this_encoder_.get_status(); - int subsequent_status = subsequent_encoders_.get_status(); - if (this_status) - return this_status; - else if (subsequent_status) - return subsequent_status; - else - return 0; - } - - int get_bytes(uint8_t *output, size_t length, size_t* generated_bytes) final { - if (this_encoder_.get_available_bytes()) { - LOG_FIBRE("encoder chain: generate %zu bytes in segment %s\n", length, typeid(TEncoder).name()); - size_t chunk = 0; - int status = this_encoder_.get_bytes(output, length, &chunk); - if (status) - return status; - output += chunk; - length -= chunk; - if (generated_bytes) *generated_bytes += chunk; - if (!length) - return 0; - } - return subsequent_encoders_.get_bytes(output, length, generated_bytes); - } - -private: - TEncoder this_encoder_; - EncoderChain subsequent_encoders_; -}; - -template -EncoderChain make_encoder_chain(TEncoders&& ... encoders) { - return EncoderChain(std::forward(encoders)...); -} - -#endif // __ENCODERS_HPP diff --git a/Firmware/fibre-cpp/include/fibre/event_loop.hpp b/Firmware/fibre-cpp/include/fibre/event_loop.hpp new file mode 100644 index 00000000..8b94c0f4 --- /dev/null +++ b/Firmware/fibre-cpp/include/fibre/event_loop.hpp @@ -0,0 +1,74 @@ +#ifndef __FIBRE_EVENT_LOOP_HPP +#define __FIBRE_EVENT_LOOP_HPP + +#include "callback.hpp" +#include + +namespace fibre { + +struct EventLoopTimer; + +/** + * @brief Base class for event loops. + * + * Thread-safety: The public functions of this class except for post() must not + * be assumed to be thread-safe. + * Generally the functions of an event loop are only safe to be called from the + * event loop's thread itself. + */ +class EventLoop { +public: + /** + * @brief Registers a callback for immediate execution on the event loop + * thread. + * + * This function must be thread-safe. + */ + virtual bool post(Callback callback) = 0; + + /** + * @brief Registers the given file descriptor on this event loop. + * + * This function is only implemented on Unix-like systems. + * + * @param fd: A waitable Unix file descriptor on which to listen for events. + * @param events: A bitfield that specifies the events to listen for. + * For instance EPOLLIN or EPOLLOUT. + * @param callback: The callback to invoke every time the event triggers. + * A bitfield is passed to the callback to indicate which events were + * triggered. This callback must remain valid until + * deregister_event() is called for the same file descriptor. + */ + virtual bool register_event(int fd, uint32_t events, Callback callback) = 0; + + /** + * @brief Deregisters the given event. + * + * Once this function returns, the associated callback will no longer be + * invoked and its resources can be freed. + */ + virtual bool deregister_event(int fd) = 0; + + /** + * @brief Registers a callback to be called at a later point in time. + * + * This returns an opaque handler which can be used to cancel the timer. + * + * @param delay: The delay from now in seconds. + * TOOD: specify if OS sleep time is counted in. + */ + virtual struct EventLoopTimer* call_later(float delay, Callback callback) = 0; + + /** + * @brief Cancels a timer which was previously started by call_later(). + * + * Must not be called after invokation of the callback has started. + * This also means that cancel_timer() must not be called from within the + * callback of the timer itself. + */ + virtual bool cancel_timer(EventLoopTimer* timer) = 0; +}; + +} + +#endif // __FIBRE_EVENT_LOOP_HPP \ No newline at end of file diff --git a/Firmware/fibre-cpp/include/fibre/fibre.hpp b/Firmware/fibre-cpp/include/fibre/fibre.hpp new file mode 100644 index 00000000..1aefd94f --- /dev/null +++ b/Firmware/fibre-cpp/include/fibre/fibre.hpp @@ -0,0 +1,156 @@ +#ifndef __FIBRE_HPP +#define __FIBRE_HPP + +#include +#include +#include +#include +#include +#include +#include + +#if FIBRE_ENABLE_LIBUSB_BACKEND +#include "../../platform_support/libusb_transport.hpp" +#endif + +#if FIBRE_ENABLE_TCP_CLIENT_BACKEND +#include "../../platform_support/posix_tcp_backend.hpp" +#endif + +namespace fibre { + +struct CallBuffers { + Status status; + cbufptr_t tx_buf; + bufptr_t rx_buf; +}; + +struct CallBufferRelease { + Status status; + const uint8_t* tx_end; + uint8_t* rx_end; +}; + +struct Function { + virtual std::optional + call(void**, CallBuffers, Callback, CallBufferRelease>) = 0; +}; + +struct Object; +struct Interface; +struct Domain; + +template +struct StaticBackend { + std::string name; + T impl; +}; + +struct Context { + size_t n_domains = 0; + EventLoop* event_loop; + + std::tuple< +#if FIBRE_ENABLE_LIBUSB_BACKEND + LibusbDiscoverer +#endif +#if FIBRE_ENABLE_LIBUSB_BACKEND && FIBRE_ENABLE_TCP_CLIENT_BACKEND + , // TODO: find a less awkward way to do this +#endif +#if FIBRE_ENABLE_TCP_CLIENT_BACKEND + PosixTcpClientBackend +#endif +#if FIBRE_ENABLE_TCP_CLIENT_BACKEND && FIBRE_ENABLE_TCP_SERVER_BACKEND + , // TODO: find a less awkward way to do this +#endif +#if FIBRE_ENABLE_TCP_SERVER_BACKEND + PosixTcpServerBackend +#endif + > static_backends; + +#if FIBRE_ALLOW_HEAP + std::unordered_map discoverers; +#endif + + /** + * @brief Creates a domain on which objects can subsequently be published + * and discovered. + * + * This potentially starts looking for channels on this domain. + */ + Domain* create_domain(std::string specs); + void close_domain(Domain* domain); + + void register_backend(std::string name, ChannelDiscoverer* backend); + void deregister_backend(std::string name); +}; + +// TODO: don't declare these types here +struct LegacyProtocolPacketBased; +struct LegacyObjectClient; +struct LegacyObject; + +class Domain { + friend class Context; +public: +#if FIBRE_ENABLE_CLIENT + // TODO: add interface argument + // TODO: support multiple discovery instances + void start_discovery(Callback on_found_object, Callback on_lost_object); + void stop_discovery(); +#endif + + void on_found_channels(ChannelDiscoveryResult result); + + Context* ctx; +private: +#if FIBRE_ENABLE_CLIENT + void on_found_root_object(LegacyObjectClient* obj_client, std::shared_ptr obj); + void on_lost_root_object(LegacyObjectClient* obj_client); +#endif + void on_stopped(LegacyProtocolPacketBased* protocol, StreamStatus status); + +#if FIBRE_ALLOW_HEAP + std::unordered_map channel_discovery_handles; +#endif +#if FIBRE_ENABLE_CLIENT + Callback on_found_object_; + Callback on_lost_object_; + Object* root_object_ = nullptr; + Interface* root_intf_ = nullptr; +#endif +}; + +/** + * @brief Opens and initializes a Fibre context. + * + * If FIBRE_ALLOW_HEAP=0 only one Fibre context can be open at a time. + * + * @returns: A non-null pointer on success, null otherwise. + */ +Context* open(EventLoop* event_loop); + +void close(Context*); + +/** + * @brief Launches an event loop on the current thread. + * + * This function returns when the event loop becomes empty. + * + * If FIBRE_ALLOW_HEAP=0 only one event loop can be running at a time. + * + * This function returns false if Fibre was compiled with + * FIBRE_ENABLE_EVENT_LOOP=0. + * + * @param on_started: This function is the first event that is placed on the + * event loop. This function usually creates further events, for instance + * by calling open(). + * @returns: true if the event loop ran to completion. False if this function is + * not implemented on this operating system or if another error + * occurred. + */ +bool launch_event_loop(Callback on_started); + +} + +#endif // __FIBRE_HPP \ No newline at end of file diff --git a/Firmware/fibre-cpp/include/fibre/introspection.hpp b/Firmware/fibre-cpp/include/fibre/introspection.hpp index 8fbe3545..aae15ff0 100644 --- a/Firmware/fibre-cpp/include/fibre/introspection.hpp +++ b/Firmware/fibre-cpp/include/fibre/introspection.hpp @@ -10,7 +10,7 @@ class TypeInfo; class Introspectable; -using introspectable_storage_t = std::aligned_storage<16, 4>::type; +using introspectable_storage_t = std::aligned_storage<4 * sizeof(uintptr_t), sizeof(uintptr_t)>::type; struct PropertyInfo { const char * name; @@ -116,11 +116,11 @@ public: // these should technically be protected but are public for optimization }; template T& TypeInfo::as(Introspectable& obj) { - static_assert(sizeof(T) <= sizeof(obj.storage_)); + static_assert(sizeof(T) <= sizeof(obj.storage_), "invalid size"); return *(T*)&obj.storage_; } template const T& TypeInfo::as(const Introspectable& obj) { - static_assert(sizeof(T) <= sizeof(obj.storage_)); + static_assert(sizeof(T) <= sizeof(obj.storage_), "invalid size"); return *(const T*)&obj.storage_; } template Introspectable TypeInfo::make_introspectable(T obj, const TypeInfo* type_info) { diff --git a/Firmware/fibre-cpp/include/fibre/libfibre.h b/Firmware/fibre-cpp/include/fibre/libfibre.h index 03e0aa06..d291334e 100644 --- a/Firmware/fibre-cpp/include/fibre/libfibre.h +++ b/Firmware/fibre-cpp/include/fibre/libfibre.h @@ -65,13 +65,19 @@ struct LibFibreFunction; struct LibFibreAttribute; struct LibFibreTxStream; struct LibFibreRxStream; +struct LibFibreDomain; -enum FibreStatus { +// This enum must remain identical to fibre::Status. +enum LibFibreStatus { kFibreOk, - kFibreCancelled, - kFibreClosed, - kFibreInvalidArgument, - kFibreInternalError + kFibreBusy, // libfibre + * Client Application <==== (tx_end, rx_end, status) ===== libfibre * - * The operation must be considered in progress until on_completed is invoked. - * This usually happens directly after both the tx_stream and rx_stream are - * closed (or failed), either by the server or due to a call to - * libfibre_cancel_call(). + * These tuples are exchanged through the input/output arguments of + * libfibre_call() or libfibre_call()'s callback. + * + * If during an ongoing call either of the two parties is unable to respond + * immediately it responds with kFibreBusy and will thus get the responsibility + * to resume the call when able. kFibreCancelled can be issued by either party + * at any time. + * + * Each party must make progress during every control transfer to the other + * party. + * + * For the application this means for every call to libfibre_call() and every + * return from libfibre_call()'s callback the arguments passed from application + * to libfibre must satisfy at least one of the following: + * + * - The call handle is NULL + * - tx_len is non-zero + * - rx_len is non-zero + * - The status is different from kFibreOk + * + * For libfibre this means every for return from libfibre_call() and every + * call to libfibre_call()'s callback the arguments passed from libfibre to + * application satisfy at least one of the following: + * + * - tx_end is larger than the corresponding tx_buf + * - rx_end is larger than the corresponding rx_buf + * - The status is different from kFibreOk * - * @param obj: An object handle that was obtained in the callback of - * libfibre_start_discovery() or from a call to libfibre_get_attribute(). * @param func: A function handle that was obtained in the on_function_added() * callback of libfibre_subscribe_to_interface(). - * @param handle: The variable being pointed to by this argument is set to a - * handle that can be passed to libfibre_cancel_call() to cancel the - * started call. If on_complete() is invoked directly during - * libfibre_start_call() then the handle variable is not updated later - * than that invokation. - * @param tx_stream: The variable being pointed to by this argument is set to - * a TX stream handle that can be used to send data on this call. - * This handle remains valid until the application calls - * libfibre_end_call(). - * @param rx_stream: The variable being pointed to by this argument is set to - * an RX stream handle that can be used to receive data on this call. - * This handle remains valid until the application calls - * libfibre_end_call(). - * @param on_completed: Called when the call completes, whether successful or - * not. - * @param cb_ctx: An opaque handle which will be passed to on_completed(). + * @param handle: The variable being pointed to by this argument identifies the + * coroutine call. If the variable is NULL it will be set to a new opaque + * handle. If the variable is not NULL the active function call is + * continued or cancelled (depending on status). + * @param tx_buf: The buffer to transmit. If libfibre_call() returns kFibreBusy + * then this buffer must remain valid until `callback` is invoked. + * Otherwise it can be freed immediately after this call. + * @param tx_len: Length of tx_buf. Must be zero if tx_buf is NULL. + * @param rx_buf: The buffer into which the received data should be written. If + * libfibre_call() returns kFibreBusy then this buffer must remain + * allocated until `callback` is invoked. Otherwise it can be freed + * immediately after this call. + * @param rx_len: Length of rx_buf. Must be zero if rx_buf is NULL. + * @param tx_end: End of the range of data that was accepted by libfibre. This + * is always in the interval [tx_buf, tx_buf + tx_len] unless + * libfibre_call() returns kFibreBusy, in which case this is NULL. + * This value does not give any delivery guarantees. + * @param rx_end: End of the range of data that was returned by libfibre. This + * is always in the interval [rx_buf, rx_buf + rx_len] unless + * libfibre_call() returns kFibreBusy, in which case this is NULL. + * @param callback: Will be invoked eventually if and only if libfibre_call() + * returns kFibreBusy. This callback is never invoked from inside + * libfibre_call(). + * @param cb_ctx: An opaque application-defined handle that gets passed to + * `callback`. + * + * @retval kFibreOk: libfibre accepted some or all of the tx_buf or filled some + * or all of the rx_buf with data and can immediately accept more TX + * data or provide more RX data. + * @retval kFibreBusy: libfibre will complete the request asynchronously by + * calling `callback`. If this value is returned, then the application + * must not invoke libfibre_call() on the same call handle again until + * `callback` is invoked except for cancelling the call with a status + * of `kFibreCancelled`. + * @retval kFibreClosed: the remote server completed the call and will not + * accept or return any more data on this call. The application must not + * pass the closed call context handle to libfibre_call() anymore. + * @retval kFibreCancelled: the application's cancellation request was honored + * or the remote server cancelled the call. The application must not + * pass the cancelled call context handle to libfibre_call() anymore. */ -FIBRE_PUBLIC void libfibre_start_call(LibFibreObject* obj, LibFibreFunction* func, LibFibreCallContext** handle, LibFibreTxStream** tx_stream, LibFibreRxStream** rx_stream, on_call_completed_cb_t on_completed, void* cb_ctx); - -/** - * @brief Ends an ongoing function call. - * - * Note that this does not request semantic cancellation (or reversal) of - * actions triggered by this call. - * - * The application must still wait for the on_complete callback to be called - * before the call can be considered finished. - * In the meantime if the TX and/or RX stream of this call is still open then - * it is closed. - * libfibre_end_call must not be called twice for the same call. - * The completion callback may be called with kFibreCancelled or any other - * status. - * - * @param handle: The function call handle that was obtained by a call to - * libfibre_start_call(). - */ -FIBRE_PUBLIC void libfibre_end_call(LibFibreCallContext* handle); +FIBRE_PUBLIC LibFibreStatus libfibre_call(LibFibreFunction* func, LibFibreCallContext** handle, + LibFibreStatus status, + const unsigned char* tx_buf, size_t tx_len, + unsigned char* rx_buf, size_t rx_len, + const unsigned char** tx_end, + unsigned char** rx_end, + libfibre_call_cb_t callback, void* cb_ctx); /** * @brief Starts sending data on the specified TX stream. @@ -476,7 +558,7 @@ FIBRE_PUBLIC void libfibre_cancel_tx(LibFibreTxStream* tx_stream); * * Must not be called while a transfer is ongoing. */ -FIBRE_PUBLIC void libfibre_close_tx(LibFibreTxStream* tx_stream, FibreStatus status); +FIBRE_PUBLIC void libfibre_close_tx(LibFibreTxStream* tx_stream, LibFibreStatus status); /** * @brief Starts receiving data on the specified RX stream. @@ -516,7 +598,7 @@ FIBRE_PUBLIC void libfibre_cancel_rx(LibFibreRxStream* rx_stream); * * Must not be called while a transfer is ongoing. */ -FIBRE_PUBLIC void libfibre_close_rx(LibFibreRxStream* rx_stream, FibreStatus status); +FIBRE_PUBLIC void libfibre_close_rx(LibFibreRxStream* rx_stream, LibFibreStatus status); #ifdef __cplusplus } diff --git a/Firmware/fibre-cpp/include/fibre/posix_tcp.hpp b/Firmware/fibre-cpp/include/fibre/posix_tcp.hpp deleted file mode 100644 index 3f6a7a07..00000000 --- a/Firmware/fibre-cpp/include/fibre/posix_tcp.hpp +++ /dev/null @@ -1,4 +0,0 @@ - -#include "protocol.hpp" - -int serve_on_tcp(unsigned int port); diff --git a/Firmware/fibre-cpp/include/fibre/posix_udp.hpp b/Firmware/fibre-cpp/include/fibre/posix_udp.hpp deleted file mode 100644 index 7022f9de..00000000 --- a/Firmware/fibre-cpp/include/fibre/posix_udp.hpp +++ /dev/null @@ -1,4 +0,0 @@ - -#include "protocol.hpp" - -int serve_on_udp(unsigned int port); diff --git a/Firmware/fibre-cpp/include/fibre/simple_serdes.hpp b/Firmware/fibre-cpp/include/fibre/simple_serdes.hpp index 2fc11077..c3abec60 100644 --- a/Firmware/fibre-cpp/include/fibre/simple_serdes.hpp +++ b/Firmware/fibre-cpp/include/fibre/simple_serdes.hpp @@ -5,6 +5,7 @@ #include "limits.h" #include // TODO: make C++11 backport of this #include +#include template struct SimpleSerializer; diff --git a/Firmware/fibre-cpp/include/fibre/status.hpp b/Firmware/fibre-cpp/include/fibre/status.hpp new file mode 100644 index 00000000..c6a61bd7 --- /dev/null +++ b/Firmware/fibre-cpp/include/fibre/status.hpp @@ -0,0 +1,20 @@ +#ifndef __FIBRE_STATUS_HPP +#define __FIBRE_STATUS_HPP + +namespace fibre { + +enum Status { + kFibreOk, + kFibreBusy, // +#include #pragma GCC push_options #pragma GCC optimize ("s") @@ -25,12 +23,12 @@ void [%- elif func.out | length == 1 -%] [[(func.out.values() | first).type.c_name]] [%- else -%] -[% for arg in func.out.values() %][[arg.type]][[', ' if not loop.last]][% endfor %] +std::tuple<[% for arg in func.out.values() %][[arg.type.c_name]][[', ' if not loop.last]][% endfor %]> [%- endif -%] [%- endmacro %] [%- macro render_interface(intf) %] -class [[intf.name | to_pascal_case]]Intf[% if intf.implements %] :[%- for base_intf in intf.implements %] public [[base_intf.c_name]][% endfor %][% endif %] { +class [[intf.name | to_pascal_case]]Intf { public: [%- for intf in intf.interfaces -%] [[render_interface(intf) | indent(4)]] @@ -38,7 +36,7 @@ public: [%- for enum in intf.enums %] enum [[enum.name | to_pascal_case]] { [%- for k, value in enum['values'].items() %] - [[((enum.name | to_macro_case) + "_" + (k | to_macro_case)).ljust(32)]] = [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %], + [[((enum.name + k) | to_macro_case).ljust(32)]] = [% if enum.is_flags %]0x[['%08x' | format(value.value)]][% else %][[value.value]][% endif %], [%- endfor %] }; [%- endfor %] diff --git a/Firmware/fibre-cpp/legacy_object_client.cpp b/Firmware/fibre-cpp/legacy_object_client.cpp index 4ea9c16c..af6bd6ce 100644 --- a/Firmware/fibre-cpp/legacy_object_client.cpp +++ b/Firmware/fibre-cpp/legacy_object_client.cpp @@ -1,10 +1,10 @@ #include "legacy_object_client.hpp" #include "legacy_protocol.hpp" -#include "include/fibre/simple_serdes.hpp" +#include #include "logging.hpp" #include "print_utils.hpp" -#include "include/fibre/crc.hpp" +#include "crc.hpp" #include #include @@ -169,6 +169,16 @@ json_value json_dict_find(json_dict dict, std::string key) { return (it == dict.end()) ? json_make_error(nullptr, "key not found") : *it->second; } +// not sure if this function exists in the STL +template()(*std::declval()))> +TNum calc_sum(TIt begin, TIt end, TFunc func) { + TNum s = {}; + for (TIt it = begin; it != end; ++it) { + s += func(*it); + } + return s; +} + std::unordered_map codecs = { {"bool", 1}, {"int8", 1}, @@ -207,46 +217,28 @@ std::vector parse_arglist(const json_value& list_val) { continue; } + arglist.push_back({ json_as_str(name_val), json_as_str(type_val), + (json_as_str(type_val) == "endpoint_ref") ? "object_ref" : json_as_str(type_val), + get_codec_size(json_as_str(type_val)), + (json_as_str(type_val) == "endpoint_ref") ? sizeof(uintptr_t) : get_codec_size(json_as_str(type_val)), (size_t)json_as_int(id_val), - get_codec_size(json_as_str(type_val)) }); } return arglist; } -void LegacyObjectClient::start(Completer>& on_found_root_object, Completer& on_lost_root_object) { +void LegacyObjectClient::start(Callback> on_found_root_object, Callback on_lost_root_object) { FIBRE_LOG(D) << "start"; - on_found_root_object_ = &on_found_root_object; - on_lost_root_object_ = &on_lost_root_object; + on_found_root_object_ = on_found_root_object; + on_lost_root_object_ = on_lost_root_object; json_.clear(); receive_more_json(); } -void LegacyObjectClient::start_call(size_t ep_num, LegacyFibreFunction* func, CallContext** handle, Completer& completer) { - CallContext* call = new CallContext(); - call->ep_num = ep_num; - call->func = func; - call->protocol_ = protocol_; - call->completer_ = &completer; - - if (handle) { - *handle = call; - } -} - -void LegacyObjectClient::cancel_call(CallContext* handle) { - if (handle->op_handle_) { - handle->protocol_->cancel_endpoint_operation(op_handle_); - handle->cancelling_ = true; - } else { - handle->complete_call(kFibreCancelled); - } -} - std::shared_ptr LegacyObjectClient::get_property_interfaces(std::string codec, bool write) { auto& dict = write ? rw_property_interfaces : ro_property_interfaces; @@ -255,30 +247,43 @@ std::shared_ptr LegacyObjectClient::get_property_interfaces(std: return it->second; } - FibreInterface intf; + auto intf_ptr = std::make_shared(); + dict[codec] = intf_ptr; + FibreInterface& intf = *intf_ptr; size_t size = get_codec_size(codec); + std::string app_codec = codec == "endpoint_ref" ? "object_ref" : codec; + size_t app_codec_size = codec == "endpoint_ref" ? sizeof(uintptr_t) : size; - if (!size) { + if (!size || !app_codec_size) { FIBRE_LOG(W) << "unknown size for codec " << codec; } intf.name = std::string{} + "fibre.Property<" + (write ? "readwrite" : "readonly") + " " + codec + ">"; - intf.functions["read"] = {0, {}, {{"value", codec, 0, size}}}; + intf.functions.emplace("read", LegacyFunction{0, nullptr, {}, {{"value", codec, app_codec, size, app_codec_size, 0}}}); if (write) { - intf.functions["exchange"] = {0, {{"newval", codec, 0, size}}, {{"oldval", codec, 0, size}}}; + intf.functions.emplace("exchange", LegacyFunction{0, nullptr, {{"newval", codec, app_codec, size, app_codec_size, 0}}, {{"oldval", codec, app_codec, size, app_codec_size, 0}}}); } - return dict[codec] = std::make_shared(intf); + return intf_ptr; } std::shared_ptr LegacyObjectClient::load_object(json_value list_val) { - FibreInterface intf; + if (!json_is_list(list_val)) { FIBRE_LOG(W) << "interface members must be a list"; return nullptr; } + LegacyObject obj{ + .client = this, + .ep_num = 0, + .intf = std::make_shared(), + .known_to_application = false + }; + auto obj_ptr = std::make_shared(obj); + FibreInterface& intf = *obj_ptr->intf; + for (auto& item: json_as_list(list_val)) { if (!json_is_dict(*item)) { FIBRE_LOG(W) << "expected dict"; @@ -299,11 +304,12 @@ std::shared_ptr LegacyObjectClient::load_object(json_value list_va if (!json_is_int(id) || ((int)(size_t)json_as_int(id) != json_as_int(id))) { continue; } - intf.functions[name] = { + intf.functions.emplace(name, LegacyFunction{ (size_t)json_as_int(id), + obj_ptr.get(), parse_arglist(json_dict_find(dict, "inputs")), - parse_arglist(json_dict_find(dict, "outputs")), - }; + parse_arglist(json_dict_find(dict, "outputs")) + }); } else if (json_is_str(type) && json_as_str(type) == "json") { // Ignore @@ -334,13 +340,6 @@ std::shared_ptr LegacyObjectClient::load_object(json_value list_va } } - LegacyObject obj{ - .client = this, - .ep_num = 0, - .intf = std::make_shared(intf), - .known_to_application = false - }; - auto obj_ptr = std::make_shared(obj); objects_.push_back(obj_ptr); return obj_ptr; } @@ -349,10 +348,10 @@ void LegacyObjectClient::receive_more_json() { write_le(json_.size(), tx_buf_); json_.resize(json_.size() + 1024); bufptr_t rx_buf = {json_.data() + json_.size() - 1024, json_.data() + json_.size()}; - protocol_->start_endpoint_operation(0, tx_buf_, rx_buf, &op_handle_, *this); + protocol_->start_endpoint_operation(0, tx_buf_, rx_buf, &op_handle_, MEMBER_CB(this, on_received_json)); } -void LegacyObjectClient::complete(EndpointOperationResult result) { +void LegacyObjectClient::on_received_json(EndpointOperationResult result) { // The JSON read operation completed op_handle_ = 0; @@ -393,174 +392,292 @@ void LegacyObjectClient::complete(EndpointOperationResult result) { root_obj_ = load_object(val); json_crc_ = calc_crc16(PROTOCOL_VERSION, json_.data(), json_.size()); if (root_obj_) { - safe_complete(on_found_root_object_, this, root_obj_); + on_found_root_object_.invoke_and_clear(this, root_obj_); } } } -void LegacyObjectClient::CallContext::start_write(cbufptr_t buffer, TransferHandle* handle, Completer& completer) { - if (tx_completer_) { - FIBRE_LOG(W) << "TX operation already in progress"; - completer.complete({kStreamError, buffer.begin()}); + +std::optional LegacyFunction::call(void** call_handle, + CallBuffers buffers, + Callback, CallBufferRelease> callback) { + + LegacyCallContext* ctx; + + if (!*call_handle) { + // Instantiate new call + ctx = new LegacyCallContext(); + ctx->func_ = this; + + size_t total_tx_decoded_size = sizeof(uintptr_t); + for (auto& arg: inputs) { + total_tx_decoded_size += arg.app_size; + } + + size_t total_rx_encoded_size = 0; + for (auto& arg: outputs) { + total_rx_encoded_size += arg.protocol_size; + } + + ctx->tx_buf_.resize(total_tx_decoded_size); + ctx->rx_buf_.resize(total_rx_encoded_size); + + *call_handle = ctx; + } else { + // Resume call + ctx = reinterpret_cast(*call_handle); + } + + + std::variant result = buffers; + + // Run endpoint operations for as long as we can do this synchronously. + for (;;) { + auto continuation = ctx->get_next_task(result); + if (continuation.index() == 0) { + return std::get<0>(continuation); + } else if (continuation.index() == 1) { + auto proto_continuation = std::get<1>(continuation); + proto_continuation.client->start_endpoint_operation( + proto_continuation.ep_num, proto_continuation.tx_buf, + proto_continuation.rx_buf, &ctx->op_handle_, + MEMBER_CB(ctx, resume_from_protocol)); + + if (!ctx->ep_result.has_value()) { + ctx->callback = callback; + return std::nullopt; // protocol will resume asynchronously + } + + result = *ctx->ep_result; + ctx->ep_result = std::nullopt; + + // TODO: ensure progress + } else { + return CallBufferRelease{kFibreInternalError, ctx->app_tx_end_, ctx->app_rx_buf_.begin()}; + } + } + + return ctx->resume_from_app(buffers, callback); +} + + +void LegacyCallContext::resume_from_protocol(EndpointOperationResult result) { + if (!callback) { + // No callback configured. This means that this function is being executed + // synchronously from inside LegacyFunction::call(). Set result and return. + ep_result = result; return; } - tx_completer_ = &completer; + std::variant res = result; - if (handle) { - *handle = reinterpret_cast(this); - } - - if (ep_num) { - // Single-endpoint function - if (progress == 0) { - protocol_->start_endpoint_operation(ep_num, buffer, rx_buf_, &op_handle_, *this); + for (;;) { + auto continuation = get_next_task(res); + if (continuation.index() == 0) { + auto app_result = callback.invoke(std::get<0>(continuation)); + if (!app_result.has_value()) { + return; // app will resume asynchronously + } else { + res = *app_result; + } + } else if (continuation.index() == 1) { + auto proto_continuation = std::get<1>(continuation); + proto_continuation.client->start_endpoint_operation( + proto_continuation.ep_num, proto_continuation.tx_buf, + proto_continuation.rx_buf, &op_handle_, + MEMBER_CB(this, resume_from_protocol)); + return; // protocol will return asynchronously } else { - safe_complete(tx_completer_, {kStreamClosed, buffer.begin()}); + callback.invoke({kFibreInternalError, app_tx_end_, app_rx_buf_.begin()}); } + } +} + +bool LegacyObjectClient::transcode(cbufptr_t src, bufptr_t dst, std::string src_codec, std::string dst_codec) { + if (src_codec == "object_ref" && dst_codec == "endpoint_ref") { + if (src.size() < sizeof(uintptr_t) || dst.size() < 4) { + return false; + } + + uintptr_t val = *reinterpret_cast(src.begin()); + LegacyObject* obj = reinterpret_cast(val); + write_le(obj ? obj->ep_num : 0, &dst); + write_le(obj ? obj->client->json_crc_ : 0, &dst); + + } else if (src_codec == "endpoint_ref" && dst_codec == "object_ref") { + if (src.size() < 4 || dst.size() < sizeof(uintptr_t)) { + return false; + } + + uint16_t ep_num = *read_le(&src); + uint16_t json_crc = *read_le(&src); + + LegacyObject* obj_ptr = nullptr; + + if (ep_num && json_crc == json_crc_) { + for (auto& known_obj: objects_) { + if (known_obj->ep_num == ep_num) { + obj_ptr = known_obj.get(); + } + } + } + + FIBRE_LOG(D) << "placing transcoded ptr " << reinterpret_cast(obj_ptr); + *reinterpret_cast(dst.begin()) = reinterpret_cast(obj_ptr); } else { - // Multi-endpoint function (deprecated) - if (progress < func->inputs.size()) { - // Write input arg - size_t argnum = progress; - if (buffer.size() < func->inputs[argnum].size) { - FIBRE_LOG(W) << "TX granularity too small: " << buffer.size() << " < " << func->inputs[argnum].size; - safe_complete(tx_completer_, {kStreamError, buffer.begin()}); - } else { - protocol_->start_endpoint_operation(func->inputs[argnum].ep_num, - buffer.take(func->inputs[argnum].size), {}, &op_handle_, *this); + if (src.size() != dst.size()) { + return false; + } + + memcpy(dst.begin(), src.begin(), src.size()); + } + + return true; +} + + +std::variant LegacyCallContext::get_next_task(std::variant continue_from) { + if (progress == 0) { + if (continue_from.index() != 0) { + FIBRE_LOG(E) << "expected continuation from app"; + return InternalError{}; + } + + ResultFromApp result_from_app = std::get<0>(continue_from); + + size_t n_copy = std::min(tx_buf_.size() - tx_pos_, result_from_app.tx_buf.size()); + std::copy_n(result_from_app.tx_buf.begin(), n_copy, tx_buf_.begin() + tx_pos_); + result_from_app.tx_buf = result_from_app.tx_buf.skip(n_copy); + tx_pos_ += n_copy; + + app_tx_end_ = result_from_app.tx_buf.begin(); + app_rx_buf_ = result_from_app.rx_buf; + + if (tx_pos_ < tx_buf_.size()) { + // application specified kFibreOk? => return kFibreOk + // application specified kFibreClosed? => return kFibreClosed + return ContinueWithApp{result_from_app.status, app_tx_end_, app_rx_buf_.begin()}; + } + + } else if (progress <= func_->inputs.size() + 1 + func_->outputs.size()) { + if (continue_from.index() != 1) { + FIBRE_LOG(E) << "expected continuation from protocol"; + return InternalError{}; + } + + ResultFromProtocol result_from_protocol = std::get<1>(continue_from); + + if (result_from_protocol.status == kStreamClosed) { + return ContinueWithApp{kFibreHostUnreachable, app_tx_end_, app_rx_buf_.begin()}; + } else if (result_from_protocol.status != kStreamOk) { + FIBRE_LOG(W) << "protocol failed with " << result_from_protocol.status << " - propagating error to application"; + return ContinueWithApp{kFibreInternalError, app_tx_end_, app_rx_buf_.begin()}; + } + + tx_pos_ = result_from_protocol.tx_end - tx_buf_.data(); + if (result_from_protocol.rx_end) { + rx_pos_ = result_from_protocol.rx_end - rx_buf_.data(); + } + + } else if (progress == func_->inputs.size() + 2 + func_->outputs.size()) { + if (continue_from.index() != 0) { + FIBRE_LOG(E) << "expected continuation from app"; + return InternalError{}; + } + + ResultFromApp result_from_app = std::get<0>(continue_from); + + if (result_from_app.status) { + FIBRE_LOG(W) << "application failed with " << result_from_app.status << " - dropping this call"; + return InternalError{}; + } + + app_tx_end_ = result_from_app.tx_buf.begin(); + app_rx_buf_ = result_from_app.rx_buf; + } + + if (progress == 0) { + // Transcode from application codec to protocol codec + + obj_ = *reinterpret_cast(tx_buf_.data()); + FIBRE_LOG(T) << "object is " << as_hex(reinterpret_cast(obj_)); + FIBRE_LOG(T) << "tx buf is " << as_hex(cbufptr_t{tx_buf_}); + + std::vector transcoded; + size_t transcoded_size = calc_sum(func_->inputs.begin(), func_->inputs.end(), + [](LegacyFibreArg& arg) { return arg.protocol_size; }); + FIBRE_LOG(T) << "transcoding " << func_->inputs.size() << " inputs from " << tx_buf_.size() << " B to " << transcoded_size << " B"; + transcoded.resize(transcoded_size); + + tx_pos_ = sizeof(uintptr_t); + size_t transcoded_pos = 0; + for (auto& arg: func_->inputs) { + if (!obj_->client->transcode({tx_buf_.data() + tx_pos_, arg.app_size}, + {transcoded.data() + transcoded_pos, arg.protocol_size}, + arg.app_codec, arg.protocol_codec)) { + return ContinueWithApp{kFibreInternalError, app_tx_end_, app_rx_buf_.begin()}; } - } else if (progress == func->inputs.size()) { - // Trigger function - protocol_->start_endpoint_operation(func->ep_num, buffer.take(0), {}, &op_handle_, *this); - } else { - safe_complete(tx_completer_, {kStreamClosed, buffer.begin()}); - } - } -} - -void LegacyObjectClient::CallContext::cancel_write(TransferHandle transfer_handle) { - // not implemented -} - -void LegacyObjectClient::CallContext::start_read(bufptr_t buffer, TransferHandle* handle, Completer& completer) { - if (rx_completer_) { - FIBRE_LOG(W) << "RX operation already in progress"; - completer.complete({kStreamError, buffer.begin()}); - return; - } - - rx_completer_ = &completer; - - if (handle) { - *handle = reinterpret_cast(this); - } - - if (ep_num) { - // Single-endpoint function - if (progress == 0 && !op_handle_) { - // Transfer has not yet started. Prepare RX buffer for when it starts. - rx_buf_ = buffer; - } else { - // Transfer has already started or completed. Cannot start RX anymore. - safe_complete(rx_completer_, {kStreamClosed, buffer.begin()}); + transcoded_pos += arg.protocol_size; } - } else { - // Multi-endpoint function (deprecated) - if (progress <= func->inputs.size()) { - // Not yet in the receive phase. Store the RX pointer for later use. - rx_buf_ = buffer; - } else if (progress < func->inputs.size() + 1 + func->outputs.size()) { - // Read output arg - size_t argnum = progress - func->inputs.size() - 1; - if (buffer.size() < func->outputs[argnum].size) { - FIBRE_LOG(W) << "RX granularity too small"; - safe_complete(rx_completer_, {kStreamError, buffer.begin()}); - } else { - protocol_->start_endpoint_operation(func->outputs[argnum].ep_num, - {}, buffer.take(func->outputs[argnum].size), &op_handle_, *this); + tx_buf_ = transcoded; + tx_pos_ = 0; + + } else if (progress == func_->inputs.size() + 1 + func_->outputs.size()) { + // Transcode from protocol codec to application codec + + std::vector transcoded; + for (auto& arg: func_->outputs) { + FIBRE_LOG(T) << "arg size " << arg.app_size; + } + size_t transcoded_size = calc_sum(func_->outputs.begin(), func_->outputs.end(), + [](LegacyFibreArg& arg) { return arg.app_size; }); + FIBRE_LOG(T) << "transcoding " << func_->outputs.size() << " outputs from " << rx_buf_.size() << " B to " << transcoded_size << " B"; + transcoded.resize(transcoded_size); + + rx_pos_ = 0; + size_t transcoded_pos = 0; + for (auto& arg: func_->outputs) { + if (!obj_->client->transcode({rx_buf_.data() + rx_pos_, arg.protocol_size}, + {transcoded.data() + transcoded_pos, arg.app_size}, + arg.protocol_codec, arg.app_codec)) { + return ContinueWithApp{kFibreInternalError, app_tx_end_, app_rx_buf_.begin()}; } - } else { - safe_complete(rx_completer_, {kStreamClosed, buffer.begin()}); + transcoded_pos += arg.app_size; } - } -} -void LegacyObjectClient::CallContext::cancel_read(TransferHandle transfer_handle) { - // not implemented -} - -void LegacyObjectClient::CallContext::complete(EndpointOperationResult result) { - op_handle_ = 0; - - if (result.status == kStreamCancelled || (result.status == kStreamOk && cancelling_)) { - safe_complete(tx_completer_, {kStreamCancelled, result.tx_end}); - safe_complete(rx_completer_, {kStreamCancelled, result.rx_end}); - complete_call(kFibreCancelled); - return; - } else if (result.status == kStreamClosed) { - safe_complete(tx_completer_, {kStreamClosed, result.tx_end}); - safe_complete(rx_completer_, {kStreamClosed, result.rx_end}); - complete_call(kFibreClosed); - return; - } else if (result.status != kStreamOk) { - FIBRE_LOG(W) << "endpoint operation failed"; // TODO: add retry logic - safe_complete(tx_completer_, {kStreamError, result.tx_end}); - safe_complete(rx_completer_, {kStreamError, result.rx_end}); - complete_call(kFibreInternalError); - return; + rx_buf_ = transcoded; + rx_pos_ = 0; } progress++; - if (ep_num) { - // Single-endpoint function - if (progress == 1) { - safe_complete(tx_completer_, {kStreamClosed, result.tx_end}); - safe_complete(rx_completer_, {kStreamClosed, result.rx_end}); - complete_call(kFibreOk); - } + if (progress == 1 && obj_->ep_num) { + // Single Endpoint Function - exchange everything in one go + progress = func_->inputs.size() + 1 + func_->outputs.size(); + return ContinueWithProtocol{obj_->client->protocol_, obj_->ep_num, tx_buf_, rx_buf_}; - } else { - // Multi-endpoint function (deprecated) - - if (progress < func->inputs.size()) { - safe_complete(tx_completer_, {kStreamOk, result.tx_end}); - } else if (progress == func->inputs.size()) { - // Last input argument transferred. Start write again with an empty - // buffer to run the trigger operation. - auto tx_completer = tx_completer_; - tx_completer_ = nullptr; - start_write({result.tx_end, result.tx_end}, nullptr, *tx_completer); - } else if (progress == func->inputs.size() + 1) { - safe_complete(tx_completer_, {kStreamClosed, result.tx_end}); - - // If the application has an RX operation enqueued and it was not - // already started (that would happen if it got enqueued during the - // callback above) then we start it now. - if (rx_completer_ && !op_handle_) { - auto rx_completer = rx_completer_; - rx_completer_ = nullptr; - start_read(rx_buf_, nullptr, *rx_completer); // If case there are zero outputs this will close the RX stream - } - - if (!func->outputs.size()) { - complete_call(kFibreOk); - } - } else if (progress < func->inputs.size() + 1 + func->outputs.size()) { - safe_complete(rx_completer_, {kStreamOk, result.rx_end}); - } else if (progress == func->inputs.size() + 1 + func->outputs.size()) { - safe_complete(rx_completer_, {kStreamClosed, result.rx_end}); - complete_call(kFibreOk); - } else { - FIBRE_LOG(W) << "progress is further than expected"; - } + } else if (progress <= func_->inputs.size()) { + // send arg + auto arg = func_->inputs[progress - 1]; + return ContinueWithProtocol{obj_->client->protocol_, arg.ep_num, {tx_buf_.data() + tx_pos_, arg.protocol_size}, {}}; + } else if (progress == func_->inputs.size() + 1) { + // send trigger + return ContinueWithProtocol{obj_->client->protocol_, func_->ep_num, {}, {}}; + } else if (progress <= func_->inputs.size() + 1 + func_->outputs.size()) { + // receive arg + auto arg = func_->outputs[progress - 2 - func_->inputs.size()]; + return ContinueWithProtocol{obj_->client->protocol_, arg.ep_num, {}, {rx_buf_.data() + rx_pos_, arg.protocol_size}}; + } else if (progress == func_->inputs.size() + 2 + func_->outputs.size()) { + // return data to application + size_t n_copy = std::min(rx_buf_.size() - rx_pos_, app_rx_buf_.size()); + std::copy_n(rx_buf_.data() + rx_pos_, n_copy, app_rx_buf_.begin()); + app_rx_buf_ = app_rx_buf_.skip(n_copy); + rx_pos_ += n_copy; + return ContinueWithApp{rx_pos_ == rx_buf_.size() ? kFibreClosed : kFibreOk, app_tx_end_, app_rx_buf_.begin()}; } -} -void LegacyObjectClient::CallContext::complete_call(FibreStatus result) { - safe_complete(completer_, result); - delete this; + return InternalError{}; } diff --git a/Firmware/fibre-cpp/legacy_object_client.hpp b/Firmware/fibre-cpp/legacy_object_client.hpp index 157c09a3..cf54c5a4 100644 --- a/Firmware/fibre-cpp/legacy_object_client.hpp +++ b/Firmware/fibre-cpp/legacy_object_client.hpp @@ -1,13 +1,14 @@ #ifndef __FIBRE_LEGACY_OBJECT_MODEL_HPP #define __FIBRE_LEGACY_OBJECT_MODEL_HPP -//#include "legacy_protocol.hpp" -#include "async_stream.hpp" -#include +#include #include #include #include #include +#include +#include // std::variant and std::optional C++ backport +#include struct json_value; @@ -19,28 +20,38 @@ struct EndpointOperationResult { uint8_t* rx_end; }; -// Lower 16 bits are the seqno. Upper 16 bits are all 1 for valid handles -// (such that seqno 0 doesn't cause the handle to be 0) using EndpointOperationHandle = uint32_t; struct LegacyProtocolPacketBased; struct LegacyFibreArg { std::string name; - std::string codec; + std::string protocol_codec; + std::string app_codec; + size_t protocol_size; + size_t app_size; size_t ep_num; - size_t size; }; -struct LegacyFibreFunction { +struct LegacyObject; + +struct LegacyFunction : Function { + LegacyFunction(std::vector inputs, std::vector outputs) + : ep_num(0), obj_(nullptr), inputs(inputs), outputs(outputs) {} + LegacyFunction(size_t ep_num, LegacyObject* obj, std::vector inputs, std::vector outputs) + : ep_num(ep_num), obj_(obj), inputs(inputs), outputs(outputs) {} + + std::optional + call(void**, CallBuffers, Callback, CallBufferRelease>) final; + size_t ep_num; // 0 for property read/write/exchange functions + LegacyObject* obj_; // null for property read/write/exchange functions (all other functions are associated with one object only) std::vector inputs; std::vector outputs; }; struct FibreInterface; struct LegacyObjectClient; -struct LegacyObject; struct LegacyFibreAttribute { std::shared_ptr object; @@ -48,7 +59,7 @@ struct LegacyFibreAttribute { struct FibreInterface { std::string name; - std::unordered_map functions; + std::unordered_map functions; std::unordered_map attributes; }; @@ -59,55 +70,77 @@ struct LegacyObject { bool known_to_application; }; -class LegacyObjectClient : Completer { -public: - struct CallContext : AsyncStreamSink, AsyncStreamSource, Completer { - size_t progress = 0; - size_t ep_num = 0; - bufptr_t rx_buf_ = {}; - LegacyFibreFunction* func = nullptr; - Completer* tx_completer_ = nullptr; - Completer* rx_completer_ = nullptr; - Completer* completer_ = nullptr; - EndpointOperationHandle op_handle_ = 0; - LegacyProtocolPacketBased* protocol_ = nullptr; - bool cancelling_ = false; +struct LegacyCallContext { + LegacyFunction* func_; - void start_write(cbufptr_t buffer, TransferHandle* handle, Completer& completer) final; - void cancel_write(TransferHandle transfer_handle) final; - void start_read(bufptr_t buffer, TransferHandle* handle, Completer& completer) final; - void cancel_read(TransferHandle transfer_handle) final; + size_t progress = 0; //!< 0: expecting more tx data + //!< [1...n_inputs]: endpoint operations for sending inputs + //!< n_inputs + 1: trigger endpoint operation + //!< [n_inputs + 2, n_inputs + 2 + n_outputs]: endpoint operations for receiving outputs + //!< n_inputs + 3 + n_outputs: reporting outputs to application + + EndpointOperationHandle op_handle_ = 0; - void complete(EndpointOperationResult result); - void complete_call(FibreStatus result); + std::vector tx_buf_; + size_t tx_pos_ = 0; + std::vector rx_buf_; + size_t rx_pos_ = 0; + + const uint8_t* app_tx_end_; + bufptr_t app_rx_buf_; + + Callback, CallBufferRelease> callback; + std::optional ep_result; + + LegacyObject* obj_; + + std::optional + resume_from_app(CallBuffers, Callback, CallBufferRelease>); + + void resume_from_protocol(EndpointOperationResult result); + + struct ContinueWithProtocol { + LegacyProtocolPacketBased* client; + size_t ep_num; + cbufptr_t tx_buf; + bufptr_t rx_buf; }; + using ContinueWithApp = CallBufferRelease; + using ResultFromProtocol = EndpointOperationResult; + using ResultFromApp = CallBuffers; + struct InternalError {}; + + // Returns control either to the application or to the next endpoint operation + std::variant get_next_task(std::variant continue_from); +}; + +class LegacyObjectClient { +public: LegacyObjectClient(LegacyProtocolPacketBased* protocol) : protocol_(protocol) {} - void start(Completer>& on_found_root_object, Completer& on_lost_root_object); - - void start_call(size_t ep_num, LegacyFibreFunction* func, CallContext** handle, Completer& completer); - void cancel_call(CallContext* handle); + void start(Callback> on_found_root_object, Callback on_lost_root_object); + bool transcode(cbufptr_t src, bufptr_t dst, std::string src_codec, std::string dst_codec); // For direct access by LegacyProtocolPacketBased and libfibre.cpp uint16_t json_crc_ = 0; - Completer* on_lost_root_object_; + Callback on_lost_root_object_; std::shared_ptr root_obj_; std::vector> objects_; void* user_data_; // used by libfibre to store the libfibre context pointer + LegacyProtocolPacketBased* protocol_; private: std::shared_ptr get_property_interfaces(std::string codec, bool write); std::shared_ptr load_object(json_value list_val); void receive_more_json(); - void complete(EndpointOperationResult result); + void on_received_json(EndpointOperationResult result); - LegacyProtocolPacketBased* protocol_; - Completer>* on_found_root_object_; + Callback> on_found_root_object_; uint8_t tx_buf_[4] = {0xff, 0xff, 0xff, 0xff}; EndpointOperationHandle op_handle_ = 0; std::vector json_; - std::vector pending_calls_; + //std::vector pending_calls_; std::unordered_map> rw_property_interfaces; std::unordered_map> ro_property_interfaces; }; diff --git a/Firmware/fibre-cpp/legacy_protocol.cpp b/Firmware/fibre-cpp/legacy_protocol.cpp index 4f0ea1d2..27575704 100644 --- a/Firmware/fibre-cpp/legacy_protocol.cpp +++ b/Firmware/fibre-cpp/legacy_protocol.cpp @@ -2,11 +2,11 @@ #include "legacy_protocol.hpp" -#include -#include +#include "protocol.hpp" +#include "crc.hpp" #include "logging.hpp" #include "print_utils.hpp" -#include "async_stream.hpp" +#include #include #include @@ -18,21 +18,21 @@ using namespace fibre; /* PacketWrapper -------------------------------------------------------------*/ -void PacketWrapper::start_write(cbufptr_t buffer, TransferHandle* handle, Completer& completer) { +void PacketWrapper::start_write(cbufptr_t buffer, TransferHandle* handle, Callback completer) { if (handle) { *handle = reinterpret_cast(this); } if (state_ != kStateIdle) { - completer.complete({kStreamError, buffer.begin()}); + completer.invoke({kStreamError, buffer.begin()}); } // TODO: support buffer size >= 128 if (buffer.size() >= 128) { - completer.complete({kStreamError, buffer.begin()}); + completer.invoke({kStreamError, buffer.begin()}); } - completer_ = &completer; + completer_ = completer; header_buf_[0] = CANONICAL_PREFIX; header_buf_[1] = static_cast(buffer.size()); @@ -46,7 +46,7 @@ void PacketWrapper::start_write(cbufptr_t buffer, TransferHandle* handle, Comple state_ = kStateSendingHeader; expected_tx_end_ = header_buf_ + 3; - tx_channel_->start_write(header_buf_, &inner_transfer_handle_, *this); + tx_channel_->start_write(header_buf_, &inner_transfer_handle_, MEMBER_CB(this, complete)); } void PacketWrapper::cancel_write(TransferHandle transfer_handle) { @@ -57,55 +57,55 @@ void PacketWrapper::cancel_write(TransferHandle transfer_handle) { void PacketWrapper::complete(WriteResult result) { if (state_ == kStateCancelling) { state_ = kStateIdle; - safe_complete(completer_, {kStreamCancelled, payload_buf_.begin()}); + completer_.invoke_and_clear({kStreamCancelled, payload_buf_.begin()}); return; } if (result.status != kStreamOk) { state_ = kStateIdle; - safe_complete(completer_, {result.status, payload_buf_.begin()}); + completer_.invoke_and_clear({result.status, payload_buf_.begin()}); return; } if (result.end < expected_tx_end_) { - tx_channel_->start_write({result.end, expected_tx_end_}, &inner_transfer_handle_, *this); + tx_channel_->start_write({result.end, expected_tx_end_}, &inner_transfer_handle_, MEMBER_CB(this, complete)); return; } if (state_ == kStateSendingHeader) { state_ = kStateSendingPayload; expected_tx_end_ = payload_buf_.end(); - tx_channel_->start_write(payload_buf_, &inner_transfer_handle_, *this); + tx_channel_->start_write(payload_buf_, &inner_transfer_handle_, MEMBER_CB(this, complete)); } else if (state_ == kStateSendingPayload) { state_ = kStateSendingTrailer; expected_tx_end_ = trailer_buf_ + 2; - tx_channel_->start_write(trailer_buf_, &inner_transfer_handle_, *this); + tx_channel_->start_write(trailer_buf_, &inner_transfer_handle_, MEMBER_CB(this, complete)); } else if (state_ == kStateSendingTrailer) { state_ = kStateIdle; - safe_complete(completer_, {kStreamOk, payload_buf_.end()}); + completer_.invoke_and_clear({kStreamOk, payload_buf_.end()}); } } /* PacketUnwrapper -----------------------------------------------------------*/ -void PacketUnwrapper::start_read(bufptr_t buffer, TransferHandle* handle, Completer& completer) { +void PacketUnwrapper::start_read(bufptr_t buffer, TransferHandle* handle, Callback completer) { if (handle) { *handle = reinterpret_cast(this); } if (state_ != kStateIdle) { - completer.complete({kStreamError, buffer.begin()}); + completer.invoke({kStreamError, buffer.begin()}); } - completer_ = &completer; + completer_ = completer; payload_buf_ = buffer; state_ = kStateReceivingHeader; expected_rx_end_ = rx_buf_ + 3; - rx_channel_->start_read({rx_buf_, expected_rx_end_}, &inner_transfer_handle_, *this); + rx_channel_->start_read({rx_buf_, expected_rx_end_}, &inner_transfer_handle_, MEMBER_CB(this, complete)); } void PacketUnwrapper::cancel_read(TransferHandle transfer_handle) { @@ -120,18 +120,18 @@ void PacketUnwrapper::complete(ReadResult result) { if (state_ == kStateCancelling) { state_ = kStateIdle; - safe_complete(completer_, {kStreamCancelled, payload_buf_.begin()}); + completer_.invoke_and_clear({kStreamCancelled, payload_buf_.begin()}); return; } if (result.status != kStreamOk) { state_ = kStateIdle; - safe_complete(completer_, {result.status, payload_buf_.begin()}); + completer_.invoke_and_clear({result.status, payload_buf_.begin()}); return; } if (result.end < expected_rx_end_) { - rx_channel_->start_read({result.end, expected_rx_end_}, &inner_transfer_handle_, *this); + rx_channel_->start_read({result.end, expected_rx_end_}, &inner_transfer_handle_, MEMBER_CB(this, complete)); return; } @@ -149,18 +149,18 @@ void PacketUnwrapper::complete(ReadResult result) { state_ = kStateReceivingPayload; payload_length_ = std::min(payload_buf_.size(), (size_t)rx_buf_[1]); expected_rx_end_ = payload_buf_.begin() + payload_length_; - rx_channel_->start_read(payload_buf_.take(payload_length_), &inner_transfer_handle_, *this); + rx_channel_->start_read(payload_buf_.take(payload_length_), &inner_transfer_handle_, MEMBER_CB(this, complete)); return; } // Header was bad: discard the bad header bytes and receive more memmove(rx_buf_, rx_buf_ + n_discard, sizeof(rx_buf_) - n_discard); - rx_channel_->start_read(bufptr_t{rx_buf_}.skip(3 - n_discard), &inner_transfer_handle_, *this); + rx_channel_->start_read(bufptr_t{rx_buf_}.skip(3 - n_discard), &inner_transfer_handle_, MEMBER_CB(this, complete)); } else if (state_ == kStateReceivingPayload) { expected_rx_end_ = rx_buf_ + 2; state_ = kStateReceivingTrailer; - rx_channel_->start_read({rx_buf_, expected_rx_end_}, &inner_transfer_handle_, *this); + rx_channel_->start_read({rx_buf_, expected_rx_end_}, &inner_transfer_handle_, MEMBER_CB(this, complete)); } else if (state_ == kStateReceivingTrailer) { uint16_t crc = calc_crc16(CANONICAL_CRC16_INIT, payload_buf_.begin(), payload_length_); @@ -168,11 +168,11 @@ void PacketUnwrapper::complete(ReadResult result) { if (!crc) { state_ = kStateIdle; - safe_complete(completer_, {kStreamOk, payload_buf_.begin() + payload_length_}); + completer_.invoke_and_clear({kStreamOk, payload_buf_.begin() + payload_length_}); } else { state_ = kStateReceivingHeader; expected_rx_end_ = rx_buf_ + 3; - rx_channel_->start_read({rx_buf_, expected_rx_end_}, &inner_transfer_handle_, *this); + rx_channel_->start_read({rx_buf_, expected_rx_end_}, &inner_transfer_handle_, MEMBER_CB(this, complete)); } } } @@ -180,7 +180,7 @@ void PacketUnwrapper::complete(ReadResult result) { /* LegacyProtocolPacketBased -------------------------------------------------*/ -#ifdef FIBRE_ENABLE_CLIENT +#if FIBRE_ENABLE_CLIENT /** * @brief Starts a remote endpoint operation. @@ -200,7 +200,7 @@ void PacketUnwrapper::complete(ReadResult result) { * this function then the handle is not set later than invoking the * completer. */ -void LegacyProtocolPacketBased::start_endpoint_operation(uint16_t endpoint_id, cbufptr_t tx_buf, bufptr_t rx_buf, EndpointOperationHandle* handle, Completer& completer) { +void LegacyProtocolPacketBased::start_endpoint_operation(uint16_t endpoint_id, cbufptr_t tx_buf, bufptr_t rx_buf, EndpointOperationHandle* handle, Callback callback) { outbound_seq_no_ = ((outbound_seq_no_ + 1) & 0x7fff); EndpointOperation op = { @@ -208,7 +208,7 @@ void LegacyProtocolPacketBased::start_endpoint_operation(uint16_t endpoint_id, c .endpoint_id = endpoint_id, .tx_buf = tx_buf, .rx_buf = rx_buf, - .completer = &completer + .callback = callback }; if (handle) { @@ -219,11 +219,11 @@ void LegacyProtocolPacketBased::start_endpoint_operation(uint16_t endpoint_id, c FIBRE_LOG(D) << "Endpoint operation already in progress. Enqueuing this one."; // A TX operation is already in progress - if (pending_operation_.completer) { + if (pending_operation_.has_value()) { // Previous endpoint operation was not yet sent. We don't support // enqueuing multiple endpoint operations while the first didn't send yet. FIBRE_LOG(E) << "previous endpoint operation still not sent"; - completer.complete({kStreamError, tx_buf.begin(), rx_buf.begin()}); + callback.invoke_and_clear({kStreamError, tx_buf.begin(), rx_buf.begin()}); } else { // Control is returned to start_endpoint_operation once TX completes pending_operation_ = op; @@ -251,7 +251,7 @@ void LegacyProtocolPacketBased::start_endpoint_operation(EndpointOperation op) { expected_acks_[op.seqno] = op; transmitting_op_ = op.seqno | 0xffff0000; - tx_channel_->start_write(cbufptr_t{tx_buf_}.take(8 + n_payload), &tx_handle_, *static_cast(this)); + tx_channel_->start_write(cbufptr_t{tx_buf_}.take(8 + n_payload), &tx_handle_, MEMBER_CB(this, on_write_finished)); } @@ -262,21 +262,21 @@ void LegacyProtocolPacketBased::cancel_endpoint_operation(EndpointOperationHandl uint16_t seqno = static_cast(handle & 0xffff); - Completer* completer; + Callback callback; const uint8_t* tx_end = nullptr; uint8_t* rx_end = nullptr; - if (pending_operation_.seqno == seqno) { - completer = pending_operation_.completer; - tx_end = pending_operation_.tx_buf.begin(); - rx_end = pending_operation_.rx_buf.begin(); - pending_operation_ = {}; + if (pending_operation_.has_value() && pending_operation_->seqno == seqno) { + callback = pending_operation_->callback; + tx_end = pending_operation_->tx_buf.begin(); + rx_end = pending_operation_->rx_buf.begin(); + pending_operation_ = std::nullopt; } - auto it = expected_acks_.find(seqno); + auto it = expected_acks_.find(handle); if (it != expected_acks_.end()) { - completer = it->second.completer; + callback = it->second.callback; tx_end = it->second.tx_buf.begin(); rx_end = it->second.rx_buf.begin(); expected_acks_.erase(it); @@ -289,13 +289,13 @@ void LegacyProtocolPacketBased::cancel_endpoint_operation(EndpointOperationHandl } else { // Either we're waiting for an ack on this operation or it has not yet // been sent. In both cases we can just complete immediately. - safe_complete(completer, {kStreamCancelled, tx_end, rx_end}); + callback.invoke_and_clear({kStreamCancelled, tx_end, rx_end}); } } #endif -#ifdef FIBRE_ENABLE_SERVER +#if FIBRE_ENABLE_SERVER // Returns part of the JSON interface definition. bool fibre::endpoint0_handler(fibre::cbufptr_t* input_buffer, fibre::bufptr_t* output_buffer) { @@ -336,28 +336,17 @@ void LegacyProtocolPacketBased::on_write_finished(WriteResult result) { transmitting_op_ = 0; auto it = expected_acks_.find(seqno); - size_t n_sent = std::max((size_t)(result.end - tx_buf_), (size_t)8) - 8; it->second.tx_buf = it->second.tx_buf.skip(n_sent); - it->second.tx_done = true; - if (it->second.rx_done) { - // It's possible that the RX operation completes before the TX operation - auto op = it->second; - expected_acks_.erase(it); - safe_complete(op.completer, {kStreamOk, op.tx_buf.begin(), op.rx_buf.begin()}); - } else if (result.status != kStreamOk) { - // If the TX task was a remote endpoint operation but didn't succeed - // we terminate that operation - auto completer = it->second.completer; + // If the TX task was a remote endpoint operation but didn't succeed + // we terminate that operation + if (result.status != kStreamOk) { + auto callback = it->second.callback; auto tx_end = it->second.tx_buf.begin(); auto rx_end = it->second.rx_buf.begin(); expected_acks_.erase(it); - safe_complete(completer, {result.status, result.end, rx_end}); - } - - if (transmitting_op_) { - return; + callback.invoke_and_clear({result.status, result.end, rx_end}); } } #endif @@ -371,24 +360,18 @@ void LegacyProtocolPacketBased::on_write_finished(WriteResult result) { uint8_t* rx_end = rx_end_; rx_end_ = nullptr; on_read_finished({kStreamOk, rx_end}); -#if FIBRE_ENABLE_CLIENT - if (transmitting_op_) { - return; - } -#endif + return; } #endif #if FIBRE_ENABLE_CLIENT - if (pending_operation_.completer) { + if (pending_operation_.has_value()) { // There is a write operation pending from the client side (i.e. an // outgoing remote endpoint operation). - EndpointOperation op = pending_operation_; - pending_operation_ = {}; + EndpointOperation op = *pending_operation_; + pending_operation_ = std::nullopt; start_endpoint_operation(op); - if (transmitting_op_) { - return; - } + return; } #endif } @@ -423,7 +406,7 @@ void LegacyProtocolPacketBased::on_read_finished(ReadResult result) { } else if (*seq_no & 0x8000) { -#ifdef FIBRE_ENABLE_CLIENT +#if FIBRE_ENABLE_CLIENT auto it = expected_acks_.find(*seq_no & 0x7fff); @@ -432,16 +415,11 @@ void LegacyProtocolPacketBased::on_read_finished(ReadResult result) { } else { size_t n_copy = std::min((size_t)(result.end - rx_buf.begin()), it->second.rx_buf.size()); memcpy(it->second.rx_buf.begin(), rx_buf.begin(), n_copy); - it->second.rx_buf = it->second.rx_buf.skip(n_copy); - it->second.rx_done = true; - FIBRE_LOG(T) << "received ACK: " << (*seq_no & 0x7fff); - - // It's possible that the RX operation completes before the TX operation - if (it->second.tx_done) { - auto op = it->second; - expected_acks_.erase(it); - safe_complete(op.completer, {kStreamOk, op.tx_buf.begin(), op.rx_buf.begin()}); - } + const uint8_t* tx_end = it->second.tx_buf.begin(); + uint8_t* rx_end = it->second.rx_buf.begin() + n_copy; + auto callback = it->second.callback; + expected_acks_.erase(it); + callback.invoke_and_clear({kStreamOk, tx_end, rx_end}); } #else @@ -450,10 +428,10 @@ void LegacyProtocolPacketBased::on_read_finished(ReadResult result) { } else { -#ifdef FIBRE_ENABLE_SERVER +#if FIBRE_ENABLE_SERVER if (rx_buf.size() < 6) { FIBRE_LOG(W) << "packet too short"; - rx_channel_->start_read(rx_buf_, &dummy, *static_cast(this)); + rx_channel_->start_read(rx_buf_, &dummy, MEMBER_CB(this, on_read_finished)); return; } @@ -479,7 +457,7 @@ void LegacyProtocolPacketBased::on_read_finished(ReadResult result) { uint16_t actual_trailer = *(rx_buf.end() - 2) | (*(rx_buf.end() - 1) << 8); if (expected_trailer != actual_trailer) { FIBRE_LOG(D) << "trailer mismatch for endpoint " << endpoint_id << ": expected " << as_hex(expected_trailer) << ", got " << as_hex(actual_trailer); - rx_channel_->start_read(rx_buf_, &dummy, *static_cast(this)); + rx_channel_->start_read(rx_buf_, &dummy, MEMBER_CB(this, on_read_finished)); return; } FIBRE_LOG(D) << "trailer ok for endpoint " << endpoint_id; @@ -502,14 +480,14 @@ void LegacyProtocolPacketBased::on_read_finished(ReadResult result) { write_le(*seq_no | 0x8000, tx_buf_); FIBRE_LOG(D) << "send packet: " << as_hex(cbufptr_t{tx_buf_, actual_response_length}); - tx_channel_->start_write({tx_buf_, actual_response_length}, &tx_handle_, *static_cast(this)); + tx_channel_->start_write({tx_buf_, actual_response_length}, &tx_handle_, MEMBER_CB(this, on_write_finished)); } #else FIBRE_LOG(W) << "received request but server support is not compiled in"; #endif } - rx_channel_->start_read(rx_buf_, &dummy, *static_cast(this)); + rx_channel_->start_read(rx_buf_, &dummy, MEMBER_CB(this, on_read_finished)); } void LegacyProtocolPacketBased::on_rx_closed(StreamStatus status) { @@ -526,17 +504,17 @@ void LegacyProtocolPacketBased::on_rx_closed(StreamStatus status) { void LegacyProtocolPacketBased::on_rx_tx_closed(StreamStatus status) { -#ifdef FIBRE_ENABLE_CLIENT +#if FIBRE_ENABLE_CLIENT // Cancel pending endpoint operation - if (pending_operation_.completer) { - pending_operation_.completer->complete({status, pending_operation_.tx_buf.begin(), pending_operation_.rx_buf.begin()}); - pending_operation_ = {}; + if (pending_operation_.has_value()) { + pending_operation_->callback.invoke_and_clear({status, pending_operation_->tx_buf.begin(), pending_operation_->rx_buf.begin()}); + pending_operation_ = std::nullopt; } // Cancel all ongoing endpoint operations for (auto& item: expected_acks_) { - if (item.second.completer) { - (*item.second.completer).complete({status, item.second.tx_buf.begin(), item.second.rx_buf.begin()}); + if (item.second.callback) { + item.second.callback.invoke_and_clear({status, item.second.tx_buf.begin(), item.second.rx_buf.begin()}); } } expected_acks_.clear(); @@ -544,20 +522,20 @@ void LegacyProtocolPacketBased::on_rx_tx_closed(StreamStatus status) { // Report that the root object was lost if (client_.on_lost_root_object_ && client_.root_obj_) { client_.root_obj_ = nullptr; - client_.on_lost_root_object_->complete(&client_); + client_.on_lost_root_object_.invoke(&client_); } #endif - safe_complete(on_stopped_, this, status); + on_stopped_.invoke_and_clear(this, status); } #if FIBRE_ENABLE_CLIENT -void LegacyProtocolPacketBased::start(Completer>& on_found_root_object, Completer& on_lost_root_object, Completer& on_stopped) { +void LegacyProtocolPacketBased::start(Callback> on_found_root_object, Callback on_lost_root_object, Callback on_stopped) { #else -void LegacyProtocolPacketBased::start(Completer& on_stopped) { +void LegacyProtocolPacketBased::start(Callback on_stopped) { #endif - on_stopped_ = &on_stopped; + on_stopped_ = on_stopped; TransferHandle dummy; - rx_channel_->start_read(rx_buf_, &dummy, *static_cast(this)); + rx_channel_->start_read(rx_buf_, &dummy, MEMBER_CB(this, on_read_finished)); #if FIBRE_ENABLE_CLIENT if (on_stopped_) { diff --git a/Firmware/fibre-cpp/legacy_protocol.hpp b/Firmware/fibre-cpp/legacy_protocol.hpp index 87c048fd..d87223f3 100644 --- a/Firmware/fibre-cpp/legacy_protocol.hpp +++ b/Firmware/fibre-cpp/legacy_protocol.hpp @@ -1,11 +1,12 @@ #ifndef __FIBRE_LEGACY_PROTOCOL_HPP #define __FIBRE_LEGACY_PROTOCOL_HPP -#include "async_stream.hpp" +#include #ifdef FIBRE_ENABLE_CLIENT #include "legacy_object_client.hpp" #include +#include #endif namespace fibre { @@ -28,12 +29,12 @@ constexpr uint8_t CANONICAL_PREFIX = 0xAA; constexpr uint16_t PROTOCOL_VERSION = 1; -class PacketWrapper : public AsyncStreamSink, Completer { +class PacketWrapper : public AsyncStreamSink { public: PacketWrapper(AsyncStreamSink* tx_channel) : tx_channel_(tx_channel) {} - void start_write(cbufptr_t buffer, TransferHandle* handle, Completer& completer) final; + void start_write(cbufptr_t buffer, TransferHandle* handle, Callback completer) final; void cancel_write(TransferHandle transfer_handle) final; private: @@ -45,7 +46,7 @@ private: uint8_t trailer_buf_[2]; const uint8_t* expected_tx_end_; cbufptr_t payload_buf_ = {nullptr, nullptr}; - Completer* completer_; + Callback completer_; enum { kStateIdle, @@ -57,12 +58,12 @@ private: }; -class PacketUnwrapper : public AsyncStreamSource, Completer { +class PacketUnwrapper : public AsyncStreamSource { public: PacketUnwrapper(AsyncStreamSource* rx_channel) : rx_channel_(rx_channel) {} - void start_read(bufptr_t buffer, TransferHandle* handle, Completer& completer) final; + void start_read(bufptr_t buffer, TransferHandle* handle, Callback completer) final; void cancel_read(TransferHandle transfer_handle) final; private: @@ -74,7 +75,7 @@ private: uint8_t* expected_rx_end_; size_t payload_length_ = 0; bufptr_t payload_buf_ = {nullptr, nullptr}; - Completer* completer_; + Callback completer_; enum { kStateIdle, @@ -86,7 +87,7 @@ private: }; -struct LegacyProtocolPacketBased : ReadCompleter, WriteCompleter { +struct LegacyProtocolPacketBased { public: LegacyProtocolPacketBased(AsyncStreamSource* rx_channel, AsyncStreamSink* tx_channel, size_t tx_mtu) : rx_channel_(rx_channel), tx_channel_(tx_channel), tx_mtu_(std::min(tx_mtu, sizeof(tx_buf_))) {} @@ -103,38 +104,36 @@ public: // This signals to the TX process that it should close // the protocol instance at the next possible instant. - Completer* on_stopped_ = nullptr; + Callback on_stopped_ = nullptr; -#ifdef FIBRE_ENABLE_CLIENT - void start_endpoint_operation(uint16_t endpoint_id, cbufptr_t tx_buf, bufptr_t rx_buf, EndpointOperationHandle* handle, Completer& completer); +#if FIBRE_ENABLE_CLIENT + void start_endpoint_operation(uint16_t endpoint_id, cbufptr_t tx_buf, bufptr_t rx_buf, EndpointOperationHandle* handle, Callback callback); void cancel_endpoint_operation(EndpointOperationHandle handle); LegacyObjectClient client_{this}; #endif -#ifdef FIBRE_ENABLE_CLIENT - void start(Completer>& on_found_root_object, Completer& on_lost_root_object, Completer& on_stopped); +#if FIBRE_ENABLE_CLIENT + void start(Callback> on_found_root_object, Callback on_lost_root_object, Callback on_stopped); #else - void start(Completer& on_stopped); + void start(Callback on_stopped); #endif private: -#ifdef FIBRE_ENABLE_CLIENT +#if FIBRE_ENABLE_CLIENT struct EndpointOperation { uint16_t seqno; uint16_t endpoint_id; cbufptr_t tx_buf; - bool tx_done; bufptr_t rx_buf; - bool rx_done; - Completer* completer; + Callback callback; }; void start_endpoint_operation(EndpointOperation op); uint16_t outbound_seq_no_ = 0; - EndpointOperation pending_operation_{.completer = nullptr}; // operation that is waiting for TX + std::optional pending_operation_ = std::nullopt; // operation that is waiting for TX EndpointOperationHandle transmitting_op_ = 0; // operation that is in TX std::unordered_map expected_acks_; // operations that are waiting for RX #endif @@ -152,12 +151,12 @@ public: : unwrapper_(rx_channel), wrapper_(tx_channel) {} -#ifdef FIBRE_ENABLE_CLIENT - void start(Completer>& on_found_root_object, Completer& on_lost_root_object, Completer& on_stopped) { +#if FIBRE_ENABLE_CLIENT + void start(Callback> on_found_root_object, Callback on_lost_root_object, Callback on_stopped) { inner_protocol_.start(on_found_root_object, on_lost_root_object, on_stopped); } #else - void start(Completer& on_stopped) { inner_protocol_.start(on_stopped); } + void start(Callback on_stopped) { inner_protocol_.start(on_stopped); } #endif private: diff --git a/Firmware/fibre-cpp/libfibre.cpp b/Firmware/fibre-cpp/libfibre.cpp index b569a639..9cb9a1e9 100644 --- a/Firmware/fibre-cpp/libfibre.cpp +++ b/Firmware/fibre-cpp/libfibre.cpp @@ -1,343 +1,53 @@ #include +#include #include "logging.hpp" #include "print_utils.hpp" -#include "legacy_protocol.hpp" -#include "legacy_object_client.hpp" -#include "event_loop.hpp" -#include "channel_discoverer.hpp" -#include "string.h" +#include "legacy_protocol.hpp" // TODO: remove this include +#include "legacy_object_client.hpp" // TODO: remove this include #include -#include "fibre/simple_serdes.hpp" - -#ifdef FIBRE_ENABLE_LIBUSB -#include "platform_support/libusb_transport.hpp" -#endif DEFINE_LOG_TOPIC(LIBFIBRE); USE_LOG_TOPIC(LIBFIBRE); static const struct LibFibreVersion libfibre_version = { 0, 1, 0 }; -class FIBRE_PRIVATE ExternalEventLoop : public EventLoop { +class FIBRE_PRIVATE ExternalEventLoop : public fibre::EventLoop { public: - ExternalEventLoop(post_cb_t post, - register_event_cb_t register_event, - deregister_event_cb_t deregister_event, - call_later_cb_t call_later, - cancel_timer_cb_t cancel_timer) : - post_(post), - register_event_(register_event), - deregister_event_(deregister_event), - call_later_(call_later), - cancel_timer_(cancel_timer) {} + ExternalEventLoop(LibFibreEventLoop impl) : impl_(impl) {} - int post(void (*callback)(void*), void *ctx) final { - return (*post_)(callback, ctx); + bool post(fibre::Callback callback) final { + return impl_.post && ((*impl_.post)(callback.get_ptr(), callback.get_ctx()) == 0); } - int register_event(int event_fd, uint32_t events, void (*callback)(void*), void* ctx) final { - return (*register_event_)(event_fd, events, callback, ctx); + bool register_event(int event_fd, uint32_t events, fibre::Callback callback) final { + return impl_.register_event && ((*impl_.register_event)(event_fd, events, callback.get_ptr(), callback.get_ctx()) == 0); } - int deregister_event(int event_fd) final { - return (*deregister_event_)(event_fd); + bool deregister_event(int event_fd) final { + return impl_.deregister_event && ((*impl_.deregister_event)(event_fd) == 0); } - struct EventLoopTimer* call_later(float delay, void (*callback)(void*), void *ctx) final { - return (*call_later_)(delay, callback, ctx); + struct fibre::EventLoopTimer* call_later(float delay, fibre::Callback callback) final { + if (!impl_.call_later) { + return nullptr; + } + return (fibre::EventLoopTimer*)(*impl_.call_later)(delay, callback.get_ptr(), callback.get_ctx()); } - int cancel_timer(struct EventLoopTimer* timer) final { - return (*cancel_timer_)(timer); + bool cancel_timer(struct fibre::EventLoopTimer* timer) final { + return impl_.cancel_timer && ((*impl_.cancel_timer)((EventLoopTimer*)timer) == 0); } private: - post_cb_t post_; - register_event_cb_t register_event_; - deregister_event_cb_t deregister_event_; - call_later_cb_t call_later_; - cancel_timer_cb_t cancel_timer_; -}; - -namespace fibre { - -class AsyncStreamLink : public AsyncStreamSink, public AsyncStreamSource { -public: - void start_write(cbufptr_t buffer, TransferHandle* handle, Completer& completer) final; - void cancel_write(TransferHandle transfer_handle) final; - void start_read(bufptr_t buffer, TransferHandle* handle, Completer& completer) final; - void cancel_read(TransferHandle transfer_handle) final; - void close(StreamStatus status); - - Completer* read_completer_ = nullptr; - bufptr_t read_buf_; - Completer* write_completer_ = nullptr; - cbufptr_t write_buf_; -}; - -void AsyncStreamLink::start_write(cbufptr_t buffer, TransferHandle* handle, Completer& completer) { - if (read_completer_) { - size_t n_copy = std::min(read_buf_.size(), buffer.size()); - memcpy(read_buf_.begin(), buffer.begin(), n_copy); - safe_complete(read_completer_, {kStreamOk, read_buf_.begin() + n_copy}); - completer.complete({kStreamOk, buffer.begin() + n_copy}); - } else { - if (handle) { - *handle = reinterpret_cast(this); - } - write_buf_ = buffer; - write_completer_ = &completer; - } -} - -void AsyncStreamLink::cancel_write(TransferHandle transfer_handle) { - safe_complete(write_completer_, {kStreamCancelled, write_buf_.begin()}); -} - -void AsyncStreamLink::start_read(bufptr_t buffer, TransferHandle* handle, Completer& completer) { - if (write_completer_) { - FIBRE_LOG(W) << "start_read: completing writer"; - size_t n_copy = std::min(buffer.size(), write_buf_.size()); - memcpy(buffer.begin(), write_buf_.begin(), n_copy); - safe_complete(write_completer_, {kStreamOk, write_buf_.begin() + n_copy}); - completer.complete({kStreamOk, buffer.begin() + n_copy}); - } else { - //FIBRE_LOG(W) << "start_read: waiting for writer"; - if (handle) { - *handle = reinterpret_cast(this); - } - read_buf_ = buffer; - read_completer_ = &completer; - } -} - -void AsyncStreamLink::cancel_read(TransferHandle transfer_handle) { - safe_complete(read_completer_, {kStreamCancelled, read_buf_.begin()}); -} - -void AsyncStreamLink::close(StreamStatus status) { - safe_complete(write_completer_, {status, write_buf_.begin()}); - safe_complete(read_completer_, {status, read_buf_.begin()}); -} - -} - -FibreStatus convert_status(fibre::StreamStatus status) { - switch (status) { - case fibre::kStreamOk: return kFibreOk; - case fibre::kStreamCancelled: return kFibreCancelled; - case fibre::kStreamClosed: return kFibreClosed; - default: return kFibreInternalError; // TODO: this may not always be appropriate - } -} - -fibre::StreamStatus convert_status(FibreStatus status) { - switch (status) { - case kFibreOk: return fibre::kStreamOk; - case kFibreCancelled: return fibre::kStreamCancelled; - case kFibreClosed: return fibre::kStreamClosed; - default: return fibre::kStreamError; // TODO: this may not always be appropriate - } -} - -struct FIBRE_PRIVATE LibFibreCtx { - ExternalEventLoop* event_loop; - construct_object_cb_t on_construct_object; - destroy_object_cb_t on_destroy_object; - void* cb_ctx; - size_t n_discoveries = 0; - - std::unordered_map> discoverers; -}; - -struct FIBRE_PRIVATE LibFibreDiscoveryCtx : - fibre::Completer, - fibre::Completer>, - fibre::Completer, - fibre::Completer -{ - void complete(fibre::ChannelDiscoveryResult result) final; - void complete(fibre::LegacyObjectClient* obj_client, std::shared_ptr intf) final; - void complete(fibre::LegacyObjectClient* obj_client) final; - void complete(fibre::LegacyProtocolPacketBased* protocol, fibre::StreamStatus status) final; - - std::unordered_map context_handles; - - on_found_object_cb_t on_found_object; - void* cb_ctx; - LibFibreCtx* ctx; - - // A LibFibreDiscoveryCtx is created when the application starts discovery - // and is deleted when the application stopped discovery _and_ all protocol - // instances that arose from this discovery instance were also stopped. - size_t use_count = 1; -}; - -struct LibFibreTxStream : fibre::Completer { - void complete(fibre::WriteResult result) { - if (on_completed) { - (*on_completed)(ctx, this, convert_status(result.status), result.end); - } - } - - fibre::AsyncStreamSink* sink; - fibre::TransferHandle handle; - on_tx_completed_cb_t on_completed; - void* ctx; - void (*on_closed)(LibFibreTxStream*, void*, fibre::StreamStatus); - void* on_closed_ctx; -}; - -struct LibFibreRxStream : fibre::Completer { - void complete(fibre::ReadResult result) { - if (on_completed) { - (*on_completed)(ctx, this, convert_status(result.status), result.end); - } - } - - fibre::AsyncStreamSource* source; - fibre::TransferHandle handle; - on_rx_completed_cb_t on_completed; - void* ctx; - void (*on_closed)(LibFibreRxStream*, void*, fibre::StreamStatus); - void* on_closed_ctx; -}; - - -// Callback for start_channel_discovery() -void LibFibreDiscoveryCtx::complete(fibre::ChannelDiscoveryResult result) { - FIBRE_LOG(D) << "found channels!"; - - if (result.status != kFibreOk) { - FIBRE_LOG(W) << "discoverer stopped"; - return; - } - - if (!result.rx_channel || !result.tx_channel) { - FIBRE_LOG(W) << "unidirectional operation not supported yet"; - return; - } - - use_count++; - - auto protocol = new fibre::LegacyProtocolPacketBased(result.rx_channel, result.tx_channel, result.mtu); - protocol->client_.user_data_ = ctx; - protocol->start(*this, *this, *this); -} - -// on_found_root_object callback for LegacyProtocolPacketBased::start() -void LibFibreDiscoveryCtx::complete(fibre::LegacyObjectClient* obj_client, std::shared_ptr obj) { - auto obj_cast = reinterpret_cast(obj.get()); // corresponding reverse cast in libfibre_get_attribute() - auto intf_cast = reinterpret_cast(obj->intf.get()); // corresponding reverse cast in libfibre_subscribe_to_interface() - - for (auto& obj: obj_client->objects_) { - // If the callback handler calls libfibre_get_attribute() before - // all objects were announced to the application then it's possible - // that during that function call some objects are already announced - // on-demand. - if (!obj->known_to_application) { - obj->known_to_application = true; - //FIBRE_LOG(D) << "constructing root object " << fibre::as_hex(reinterpret_cast(obj.get())); - if (ctx->on_construct_object) { - (*ctx->on_construct_object)(ctx->cb_ctx, - reinterpret_cast(obj.get()), - reinterpret_cast(obj->intf.get()), - obj->intf->name.size() ? obj->intf->name.data() : nullptr, obj->intf->name.size()); - } - } - } - - if (on_found_object) { - FIBRE_LOG(D) << "announcing root object " << fibre::as_hex(reinterpret_cast(obj_cast)); - (*on_found_object)(cb_ctx, obj_cast); - } -} - -// on_lost_root_object for LegacyProtocolPacketBased::start() -void LibFibreDiscoveryCtx::complete(fibre::LegacyObjectClient* obj_client) { - if (ctx->on_destroy_object) { - for (auto obj: obj_client->objects_) { - auto obj_cast = reinterpret_cast(obj.get()); - //FIBRE_LOG(D) << "destroying subobject " << fibre::as_hex(reinterpret_cast(obj_cast)); - (*ctx->on_destroy_object)(ctx->cb_ctx, obj_cast); - } - - obj_client->objects_.clear(); - } -} - -// on_stopped callback for LegacyProtocolPacketBased::start() -void LibFibreDiscoveryCtx::complete(fibre::LegacyProtocolPacketBased* protocol, fibre::StreamStatus status) { - delete protocol; - - if (--use_count == 0) { - FIBRE_LOG(D) << "deleting discovery context"; - delete this; - } -} - -const struct LibFibreVersion* libfibre_get_version() { - return &libfibre_version; -} - -LibFibreCtx* libfibre_open( - post_cb_t post, - register_event_cb_t register_event, - deregister_event_cb_t deregister_event, - call_later_cb_t call_later, - cancel_timer_cb_t cancel_timer, - construct_object_cb_t construct_object, - destroy_object_cb_t destroy_object, - void* cb_ctx) -{ - //if (!register_event || !deregister_event) { - // FIBRE_LOG(E) << "invalid argument"; - // return nullptr; - //} - FIBRE_LOG(D) << "object constructor: " << reinterpret_cast(construct_object); - LibFibreCtx* ctx = new LibFibreCtx(); - ctx->event_loop = new ExternalEventLoop(post, register_event, deregister_event, call_later, cancel_timer); - ctx->on_construct_object = construct_object; - ctx->on_destroy_object = destroy_object; - ctx->cb_ctx = cb_ctx; - -#ifdef FIBRE_ENABLE_LIBUSB - auto libusb_discoverer = std::make_shared(); - if (libusb_discoverer->init(ctx->event_loop) != 0) { - delete ctx; - FIBRE_LOG(E) << "failed to init libusb transport layer"; - return nullptr; - } - ctx->discoverers["usb"] = libusb_discoverer; -#endif - - FIBRE_LOG(D) << "opened (" << fibre::as_hex((uintptr_t)ctx) << ")"; - return ctx; -} - -void libfibre_close(LibFibreCtx* ctx) { - if (ctx->n_discoveries) { - FIBRE_LOG(W) << "there are still discovery processes ongoing"; - } - - ctx->discoverers.clear(); - - delete ctx->event_loop; - delete ctx; - - FIBRE_LOG(D) << "closed (" << fibre::as_hex((uintptr_t)ctx) << ")"; -} - -struct LibFibreChannelDiscoveryCtx : fibre::ChannelDiscoveryContext { - fibre::Completer* completer; + LibFibreEventLoop impl_; }; class ExternalDiscoverer : public fibre::ChannelDiscoverer { void start_channel_discovery( const char* specs, size_t specs_len, fibre::ChannelDiscoveryContext** handle, - fibre::Completer& on_found_channels) final; + fibre::Callback on_found_channels) final; int stop_channel_discovery(fibre::ChannelDiscoveryContext* handle) final; public: on_start_discovery_cb_t on_start_discovery; @@ -345,9 +55,13 @@ public: void* cb_ctx; }; -void ExternalDiscoverer::start_channel_discovery(const char* specs, size_t specs_len, fibre::ChannelDiscoveryContext** handle, fibre::Completer& on_found_channels) { +struct LibFibreChannelDiscoveryCtx : fibre::ChannelDiscoveryContext { + fibre::Callback completer; +}; + +void ExternalDiscoverer::start_channel_discovery(const char* specs, size_t specs_len, fibre::ChannelDiscoveryContext** handle, fibre::Callback on_found_channels) { LibFibreChannelDiscoveryCtx* ctx = new LibFibreChannelDiscoveryCtx{}; - ctx->completer = &on_found_channels; + ctx->completer = on_found_channels; if (handle) { *handle = ctx; } @@ -365,21 +79,246 @@ int ExternalDiscoverer::stop_channel_discovery(fibre::ChannelDiscoveryContext* h return 0; } -void libfibre_register_discoverer(LibFibreCtx* ctx, const char* name, size_t name_length, on_start_discovery_cb_t on_start_discovery, on_stop_discovery_cb_t on_stop_discovery, void* cb_ctx) { - std::string name_str = {name, name + name_length}; - if (ctx->discoverers.find(name_str) != ctx->discoverers.end()) { - FIBRE_LOG(W) << "Discoverer " << name_str << " already registered"; - return; // TODO: report status +namespace fibre { + +class AsyncStreamLink : public AsyncStreamSink, public AsyncStreamSource { +public: + void start_write(cbufptr_t buffer, TransferHandle* handle, Callback completer) final; + void cancel_write(TransferHandle transfer_handle) final; + void start_read(bufptr_t buffer, TransferHandle* handle, Callback completer) final; + void cancel_read(TransferHandle transfer_handle) final; + void close(StreamStatus status); + + Callback read_completer_; + bufptr_t read_buf_; + Callback write_completer_; + cbufptr_t write_buf_; +}; + +void AsyncStreamLink::start_write(cbufptr_t buffer, TransferHandle* handle, Callback completer) { + if (read_completer_) { + size_t n_copy = std::min(read_buf_.size(), buffer.size()); + memcpy(read_buf_.begin(), buffer.begin(), n_copy); + read_completer_.invoke_and_clear({kStreamOk, read_buf_.begin() + n_copy}); + completer.invoke({kStreamOk, buffer.begin() + n_copy}); + } else { + if (handle) { + *handle = reinterpret_cast(this); + } + write_buf_ = buffer; + write_completer_ = completer; + } +} + +void AsyncStreamLink::cancel_write(TransferHandle transfer_handle) { + write_completer_.invoke_and_clear({kStreamCancelled, write_buf_.begin()}); +} + +void AsyncStreamLink::start_read(bufptr_t buffer, TransferHandle* handle, Callback completer) { + if (write_completer_) { + FIBRE_LOG(W) << "start_read: completing writer"; + size_t n_copy = std::min(buffer.size(), write_buf_.size()); + memcpy(buffer.begin(), write_buf_.begin(), n_copy); + write_completer_.invoke_and_clear({kStreamOk, write_buf_.begin() + n_copy}); + completer.invoke({kStreamOk, buffer.begin() + n_copy}); + } else { + //FIBRE_LOG(W) << "start_read: waiting for writer"; + if (handle) { + *handle = reinterpret_cast(this); + } + read_buf_ = buffer; + read_completer_ = completer; + } +} + +void AsyncStreamLink::cancel_read(TransferHandle transfer_handle) { + read_completer_.invoke_and_clear({kStreamCancelled, read_buf_.begin()}); +} + +void AsyncStreamLink::close(StreamStatus status) { + write_completer_.invoke_and_clear({status, write_buf_.begin()}); + read_completer_.invoke_and_clear({status, read_buf_.begin()}); +} + +} + +LibFibreStatus convert_status(fibre::StreamStatus status) { + switch (status) { + case fibre::kStreamOk: return kFibreOk; + case fibre::kStreamCancelled: return kFibreCancelled; + case fibre::kStreamClosed: return kFibreClosed; + default: return kFibreInternalError; // TODO: this may not always be appropriate + } +} + +fibre::StreamStatus convert_status(LibFibreStatus status) { + switch (status) { + case kFibreOk: return fibre::kStreamOk; + case kFibreCancelled: return fibre::kStreamCancelled; + case kFibreClosed: return fibre::kStreamClosed; + default: return fibre::kStreamError; // TODO: this may not always be appropriate + } +} + +struct FIBRE_PRIVATE LibFibreCtx { + ExternalEventLoop* event_loop; + //size_t n_discoveries = 0; + fibre::Context* fibre_ctx; + //std::unordered_map> discoverers; +}; + +struct FIBRE_PRIVATE LibFibreDiscoveryCtx { + void on_found_object(fibre::Object* obj, fibre::Interface* intf); + void on_lost_object(fibre::Object* obj); + + on_found_object_cb_t on_found_object_; + on_lost_object_cb_t on_lost_object_; + void* cb_ctx_; + fibre::Domain* domain_; +}; + +struct LibFibreTxStream { + void on_tx_done(fibre::WriteResult result) { + if (on_completed) { + (*on_completed)(ctx, this, convert_status(result.status), result.end); + } } - auto disc = std::make_shared(); + fibre::AsyncStreamSink* sink; + fibre::TransferHandle handle; + on_tx_completed_cb_t on_completed; + void* ctx; + void (*on_closed)(LibFibreTxStream*, void*, fibre::StreamStatus); + void* on_closed_ctx; +}; + +struct LibFibreRxStream { + void on_rx_done(fibre::ReadResult result) { + if (on_completed) { + (*on_completed)(ctx, this, convert_status(result.status), result.end); + } + } + + fibre::AsyncStreamSource* source; + fibre::TransferHandle handle; + on_rx_completed_cb_t on_completed; + void* ctx; + void (*on_closed)(LibFibreRxStream*, void*, fibre::StreamStatus); + void* on_closed_ctx; +}; + +LibFibreFunction* to_c(fibre::Function* ptr) { + return reinterpret_cast(ptr); +} +fibre::Function* from_c(LibFibreFunction* ptr) { + return reinterpret_cast(ptr); +} +void** from_c(LibFibreCallContext** ptr) { + return reinterpret_cast(ptr); +} +LibFibreDomain* to_c(fibre::Domain* ptr) { + return reinterpret_cast(ptr); +} +fibre::Domain* from_c(LibFibreDomain* ptr) { + return reinterpret_cast(ptr); +} +LibFibreObject* to_c(fibre::Object* ptr) { + return reinterpret_cast(ptr); +} +fibre::Object* from_c(LibFibreObject* ptr) { + return reinterpret_cast(ptr); +} +LibFibreInterface* to_c(fibre::Interface* ptr) { + return reinterpret_cast(ptr); +} +fibre::Interface* from_c(LibFibreInterface* ptr) { + return reinterpret_cast(ptr); +} +LibFibreStatus to_c(fibre::Status status) { + return static_cast(status); +} +fibre::Status from_c(LibFibreStatus status) { + return static_cast(status); +} + +void LibFibreDiscoveryCtx::on_found_object(fibre::Object* obj, fibre::Interface* intf) { + if (on_found_object_) { + FIBRE_LOG(D) << "discovered object " << fibre::as_hex(reinterpret_cast(obj)); + (*on_found_object_)(cb_ctx_, to_c(obj), to_c(intf)); + } +} + +void LibFibreDiscoveryCtx::on_lost_object(fibre::Object* obj) { + if (on_lost_object_) { + FIBRE_LOG(D) << "lost object " << fibre::as_hex(reinterpret_cast(obj)); + (*on_lost_object_)(cb_ctx_, to_c(obj)); + } +} + +const struct LibFibreVersion* libfibre_get_version() { + return &libfibre_version; +} + +LibFibreCtx* libfibre_open(LibFibreEventLoop event_loop) { + LibFibreCtx* ctx = new LibFibreCtx(); + ctx->event_loop = new ExternalEventLoop(event_loop); + ctx->fibre_ctx = fibre::open(ctx->event_loop); + + if (!ctx->fibre_ctx) { + delete ctx->event_loop; + delete ctx; + return nullptr; + } + + return ctx; +} + +void libfibre_close(LibFibreCtx* ctx) { + if (!ctx) { + FIBRE_LOG(E) << "invalid argument"; + return; + } + + fibre::close(ctx->fibre_ctx); + ctx->fibre_ctx = nullptr; + + delete ctx->event_loop; + delete ctx; + + FIBRE_LOG(D) << "closed (" << fibre::as_hex((uintptr_t)ctx) << ")"; +} + + +void libfibre_register_backend(LibFibreCtx* ctx, const char* name, size_t name_length, on_start_discovery_cb_t on_start_discovery, on_stop_discovery_cb_t on_stop_discovery, void* cb_ctx) { + auto disc = new ExternalDiscoverer(); disc->on_start_discovery = on_start_discovery; disc->on_stop_discovery = on_stop_discovery; disc->cb_ctx = cb_ctx; - ctx->discoverers[name_str] = disc; + ctx->fibre_ctx->register_backend({name, name + name_length}, disc); } -void libfibre_add_channels(LibFibreCtx* ctx, LibFibreChannelDiscoveryCtx* discovery_ctx, LibFibreRxStream** tx_channel, LibFibreTxStream** rx_channel, size_t mtu) { +FIBRE_PUBLIC LibFibreDomain* libfibre_open_domain(LibFibreCtx* ctx, + const char* specs, size_t specs_len) { + if (!ctx) { + FIBRE_LOG(E) << "invalid context"; + return nullptr; + } else { + FIBRE_LOG(D) << "opening domain"; + return to_c(ctx->fibre_ctx->create_domain({specs, specs_len})); + } +} + +void libfibre_close_domain(LibFibreDomain* domain) { + if (!domain) { + FIBRE_LOG(E) << "invalid domain"; + return; + } + FIBRE_LOG(D) << "closing 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) { fibre::AsyncStreamLink* tx_link = new fibre::AsyncStreamLink(); fibre::AsyncStreamLink* rx_link = new fibre::AsyncStreamLink(); LibFibreRxStream* tx = new LibFibreRxStream(); @@ -410,13 +349,14 @@ void libfibre_add_channels(LibFibreCtx* ctx, LibFibreChannelDiscoveryCtx* discov *rx_channel = rx; } - fibre::ChannelDiscoveryResult result = {kFibreOk, rx_link, tx_link, mtu}; - discovery_ctx->completer->complete(result); + fibre::ChannelDiscoveryResult result = {fibre::kFibreOk, rx_link, tx_link, mtu}; + from_c(domain)->on_found_channels(result); } -void libfibre_start_discovery(LibFibreCtx* ctx, const char* specs, size_t specs_len, struct LibFibreDiscoveryCtx** handle, - on_found_object_cb_t on_found_object, on_stopped_cb_t on_stopped, void* cb_ctx) { - if (!ctx) { +void libfibre_start_discovery(LibFibreDomain* domain, LibFibreDiscoveryCtx** handle, + on_found_object_cb_t on_found_object, on_lost_object_cb_t on_lost_object, + on_stopped_cb_t on_stopped, void* cb_ctx) { + if (!domain) { FIBRE_LOG(E) << "invalid argument"; if (on_stopped) { (*on_stopped)(cb_ctx, kFibreInvalidArgument); @@ -424,66 +364,31 @@ void libfibre_start_discovery(LibFibreCtx* ctx, const char* specs, size_t specs_ return; } - const char* prev_delim = specs; - - FIBRE_LOG(D) << "starting discovery with path \"" << std::string(specs, specs_len) << "\""; - + // deleted in libfibre_stop_discovery() LibFibreDiscoveryCtx* discovery_ctx = new LibFibreDiscoveryCtx(); - discovery_ctx->on_found_object = on_found_object; - discovery_ctx->cb_ctx = cb_ctx; - discovery_ctx->ctx = ctx; + discovery_ctx->on_found_object_ = on_found_object; + discovery_ctx->on_lost_object_ = on_lost_object; + discovery_ctx->cb_ctx_ = cb_ctx; + discovery_ctx->domain_ = from_c(domain); if (handle) { *handle = discovery_ctx; } - while (prev_delim < specs + specs_len) { - const char* next_delim = std::find(prev_delim, specs + specs_len, ';'); - const char* colon = std::find(prev_delim, next_delim, ':'); - const char* colon_end = std::min(colon + 1, next_delim); - - std::string name{prev_delim, colon}; - auto it = ctx->discoverers.find(name); - - if (it == ctx->discoverers.end()) { - FIBRE_LOG(W) << "transport layer \"" << name << "\" not implemented"; - } else { - discovery_ctx->context_handles[name] = nullptr; - it->second->start_channel_discovery(colon_end, next_delim - colon_end, - &discovery_ctx->context_handles[name], *discovery_ctx); - } - - prev_delim = std::min(next_delim + 1, specs + specs_len); - } - - ctx->n_discoveries++; + from_c(domain)->start_discovery(MEMBER_CB(discovery_ctx, on_found_object), + MEMBER_CB(discovery_ctx, on_lost_object)); } -void libfibre_stop_discovery(LibFibreCtx* ctx, LibFibreDiscoveryCtx* discovery_ctx) { - if (!ctx->n_discoveries) { - FIBRE_LOG(W) << "stopping a discovery process but none is active"; - } else { - ctx->n_discoveries--; +void libfibre_stop_discovery(LibFibreDiscoveryCtx* handle) { + if (!handle) { + FIBRE_LOG(E) << "bad handle"; + return; } - for (auto& it: discovery_ctx->context_handles) { - ctx->discoverers[it.first]->stop_channel_discovery(it.second); - } - discovery_ctx->context_handles.clear(); - - if (--discovery_ctx->use_count == 0) { - FIBRE_LOG(D) << "deleting discovery context"; - delete discovery_ctx; - } + handle->domain_->stop_discovery(); + delete handle; } -const char* transform_codec(std::string& codec) { - if (codec == "endpoint_ref") { - return "object_ref"; - } else { - return codec.data(); - } -} void libfibre_subscribe_to_interface(LibFibreInterface* interface, on_attribute_added_cb_t on_attribute_added, @@ -495,17 +400,17 @@ void libfibre_subscribe_to_interface(LibFibreInterface* interface, auto intf = reinterpret_cast(interface); // corresponding reverse cast in LibFibreDiscoveryCtx::complete() and libfibre_subscribe_to_interface() for (auto& func: intf->functions) { - std::vector input_names; - std::vector input_codecs; + std::vector input_names = {"obj"}; + std::vector input_codecs = {"object_ref"}; std::vector output_names; std::vector output_codecs; for (auto& arg: func.second.inputs) { input_names.push_back(arg.name.data()); - input_codecs.push_back(transform_codec(arg.codec)); + input_codecs.push_back(arg.app_codec.data()); } for (auto& arg: func.second.outputs) { output_names.push_back(arg.name.data()); - output_codecs.push_back(transform_codec(arg.codec)); + output_codecs.push_back(arg.app_codec.data()); } input_names.push_back(nullptr); input_codecs.push_back(nullptr); @@ -514,7 +419,7 @@ void libfibre_subscribe_to_interface(LibFibreInterface* interface, if (on_function_added) { (*on_function_added)(cb_ctx, - reinterpret_cast(&func.second), // corresponding reverse cast in libfibre_start_call() + to_c(&func.second), func.first.data(), func.first.size(), input_names.data(), input_codecs.data(), output_names.data(), output_codecs.data()); @@ -533,7 +438,7 @@ void libfibre_subscribe_to_interface(LibFibreInterface* interface, } } -FibreStatus libfibre_get_attribute(LibFibreObject* parent_obj, LibFibreAttribute* attr, LibFibreObject** child_obj_ptr) { +LibFibreStatus libfibre_get_attribute(LibFibreObject* parent_obj, LibFibreAttribute* attr, LibFibreObject** child_obj_ptr) { if (!parent_obj || !attr) { return kFibreInvalidArgument; } @@ -558,13 +463,13 @@ FibreStatus libfibre_get_attribute(LibFibreObject* parent_obj, LibFibreAttribute if (!attr_cast->object->known_to_application) { attr_cast->object->known_to_application = true; - if (libfibre_ctx->on_construct_object) { - //FIBRE_LOG(D) << "constructing subobject " << fibre::as_hex(reinterpret_cast(child_obj)); - (*libfibre_ctx->on_construct_object)(libfibre_ctx->cb_ctx, - reinterpret_cast(child_obj), - reinterpret_cast(child_obj->intf.get()), - child_obj->intf->name.size() ? child_obj->intf->name.data() : nullptr, child_obj->intf->name.size()); - } + //if (libfibre_ctx->on_construct_object) { + // //FIBRE_LOG(D) << "constructing subobject " << fibre::as_hex(reinterpret_cast(child_obj)); + // (*libfibre_ctx->on_construct_object)(libfibre_ctx->cb_ctx, + // reinterpret_cast(child_obj), + // reinterpret_cast(child_obj->intf.get()), + // child_obj->intf->name.size() ? child_obj->intf->name.data() : nullptr, child_obj->intf->name.size()); + //} } if (child_obj_ptr) { @@ -587,227 +492,53 @@ void resize_at(std::vector& vec, size_t pos, ssize_t delta) { } } -class ArgEncoder : public fibre::AsyncStreamSink, fibre::Completer { - void start_write(fibre::cbufptr_t buffer, fibre::TransferHandle* handle, Completer& completer) final; - void cancel_write(fibre::TransferHandle transfer_handle) final; - void complete(fibre::WriteResult result) final; - -public: - LibFibreCallContext* call_ = nullptr; - fibre::AsyncStreamSink* encoded_stream_ = nullptr; - fibre::cbufptr_t decoded_buf_; // application-owned buffer - std::vector encoded_buf_; // libfibre-owned buffer used after transcoding from application buffer - size_t encoded_offset_ = 0; // offset in the TX stream after transcoding from application-facing format - fibre::TransferHandle transfer_handle_ = 0; - fibre::Completer* completer_ = nullptr; -}; - -class ArgDecoder : public fibre::AsyncStreamSource, fibre::Completer { - void start_read(fibre::bufptr_t buffer, fibre::TransferHandle* handle, Completer& completer) final; - void cancel_read(fibre::TransferHandle transfer_handle) final; - void complete(fibre::ReadResult result) final; - -public: - LibFibreCallContext* call_ = nullptr; - fibre::AsyncStreamSource* encoded_stream_ = nullptr; - fibre::bufptr_t decoded_buf_; // application-owned buffer - std::vector encoded_buf_; // libfibre-owned buffer used before transcoding to application buffer - size_t encoded_offset_ = 0; // offset in the RX stream before transcoding to application-facing format - fibre::TransferHandle transfer_handle_ = 0; - fibre::Completer* completer_ = nullptr; -}; - -struct FIBRE_PRIVATE LibFibreCallContext : fibre::Completer { - void complete(FibreStatus status) final; - - template - bool iterate_over_args_at(std::vector& args, size_t encoded_offset, size_t max_encoded_length, size_t max_decoded_length, Func visitor); - - uint8_t n_active_transfers = 0; - fibre::LegacyObject* obj = nullptr; - fibre::LegacyFibreFunction* func = nullptr; - on_call_completed_cb_t on_call_completed_ = nullptr; - void* call_cb_ctx_ = nullptr; - fibre::LegacyObjectClient::CallContext* handle_ = nullptr; - LibFibreTxStream tx_stream_; - LibFibreRxStream rx_stream_; - ArgDecoder decoder_; - ArgEncoder encoder_; -}; - -void libfibre_start_call(LibFibreObject* obj, LibFibreFunction* func, - LibFibreCallContext** handle, - LibFibreTxStream** tx_stream, - LibFibreRxStream** rx_stream, - on_call_completed_cb_t on_completed, void* cb_ctx) { - if (!obj || !func) { - if (on_completed) { - (*on_completed)(cb_ctx, kFibreInvalidArgument); - } - return; +LibFibreStatus libfibre_call(LibFibreFunction* func, LibFibreCallContext** handle, + LibFibreStatus status, + const unsigned char* tx_buf, size_t tx_len, + unsigned char* rx_buf, size_t rx_len, + const unsigned char** tx_end, + unsigned char** rx_end, + libfibre_call_cb_t callback, void* cb_ctx) { + bool valid_args = func && handle + && (!tx_len || tx_buf) // tx_buf valid + && (!rx_len || rx_buf) // rx_buf valid + && tx_end && rx_end // tx_end, rx_end valid + && ((status != kFibreOk) || tx_len || rx_len || !handle); // progress + if (!valid_args) { + FIBRE_LOG(E) << "invalid argument"; + return kFibreInvalidArgument; } - fibre::LegacyObject* obj_cast = reinterpret_cast(obj); - fibre::LegacyFibreFunction* func_cast = reinterpret_cast(func); + struct Ctx { libfibre_call_cb_t callback; void* ctx; }; + struct Ctx* ctx = new Ctx{callback, cb_ctx}; - bool is_member = std::find_if(obj_cast->intf->functions.begin(), obj_cast->intf->functions.end(), - [&](std::pair& kv) { - return &kv.second == func_cast; - }) != obj_cast->intf->functions.end(); - - if (!is_member) { - FIBRE_LOG(W) << "attempt to invoke function on an object that does not implement it"; - if (on_completed) { - (*on_completed)(cb_ctx, kFibreInvalidArgument); - } - return; - } + fibre::Callback, fibre::CallBufferRelease> cb{ + [](void* ctx_, fibre::CallBufferRelease result) -> std::optional { + auto ctx = reinterpret_cast(ctx_); + const unsigned char* tx_buf; + size_t tx_len; + unsigned char* rx_buf; + size_t rx_len; + auto status = ctx->callback(ctx->ctx, to_c(result.status), result.tx_end, result.rx_end, &tx_buf, &tx_len, &rx_buf, &rx_len); + if (status == kFibreBusy) { + delete ctx; + return std::nullopt; + } else { + return fibre::CallBuffers{from_c(status), {tx_buf, tx_len}, {rx_buf, rx_len}}; + } + }, ctx}; - auto completer = new LibFibreCallContext(); - completer->obj = obj_cast; - completer->func = func_cast; - completer->on_call_completed_ = on_completed; - completer->call_cb_ctx_ = cb_ctx; - completer->encoder_.call_ = completer; - completer->decoder_.call_ = completer; - completer->tx_stream_.sink = &completer->encoder_; - completer->rx_stream_.source = &completer->decoder_; + auto response = from_c(func)->call(from_c(handle), {from_c(status), {tx_buf, tx_len}, {rx_buf, rx_len}}, cb); - if (handle) { - *handle = completer; - } - if (tx_stream) { - *tx_stream = &completer->tx_stream_; - } - if (rx_stream) { - *rx_stream = &completer->rx_stream_; - } - - obj_cast->client->start_call(obj_cast->ep_num, func_cast, - &completer->handle_, *completer); - - completer->encoder_.encoded_stream_ = completer->handle_; - completer->decoder_.encoded_stream_ = completer->handle_; -} - -void libfibre_end_call(LibFibreCallContext* handle) { - if (handle) { - handle->obj->client->cancel_call(handle->handle_); - } -} - -void LibFibreCallContext::complete(FibreStatus status) { - if (on_call_completed_) { - (*on_call_completed_)(call_cb_ctx_, status); - } - delete this; -} - -bool encode_for_transport(fibre::LegacyObjectClient* client, fibre::cbufptr_t src, fibre::bufptr_t dst, const fibre::LegacyFibreArg& arg) { - if (arg.codec == "endpoint_ref") { - if (src.size() < sizeof(uintptr_t) || dst.size() < 4) { - return false; - } - - uintptr_t val = *reinterpret_cast(src.begin()); - auto obj = reinterpret_cast(val); - write_le(obj ? obj->ep_num : 0, &dst); - write_le(obj ? obj->client->json_crc_ : 0, &dst); + if (!response.has_value()) { + return kFibreBusy; } else { - if (src.size() < arg.size || dst.size() < arg.size) { - return false; - } - - memcpy(dst.begin(), src.begin(), arg.size); + delete ctx; + *tx_end = response->tx_end; + *rx_end = response->rx_end; + return to_c(response->status); } - - return true; -} - -bool decode_from_transport(fibre::LegacyObjectClient* client, fibre::cbufptr_t src, fibre::bufptr_t dst, const fibre::LegacyFibreArg& arg) { - if (arg.codec == "endpoint_ref") { - if (src.size() < 4 || dst.size() < sizeof(uintptr_t)) { - return false; - } - - uint16_t ep_num = *read_le(&src); - uint16_t json_crc = *read_le(&src); - - fibre::LegacyObject* obj_ptr = nullptr; - - if (ep_num && json_crc == client->json_crc_) { - for (auto& known_obj: client->objects_) { - if (known_obj->ep_num == ep_num) { - obj_ptr = known_obj.get(); - } - } - } - - FIBRE_LOG(D) << "placing transcoded ptr " << reinterpret_cast(obj_ptr); - *reinterpret_cast(dst.begin()) = reinterpret_cast(obj_ptr); - - } else { - if (src.size() < arg.size || dst.size() < arg.size) { - return false; - } - - memcpy(dst.begin(), src.begin(), arg.size); - } - - return true; -} - -/** - * @brief func: A functor that takes these arguments: - * - size_t rel_encoded_offset (relative to the starting pos described by encoded_offset) - * - size_t rel_decoded_offset (relative to the starting pos described by encoded_offset) - * - size_t encoded_length - * - size_t decoded_length - */ -template -bool LibFibreCallContext::iterate_over_args_at(std::vector& args, size_t encoded_offset, size_t max_encoded_length, size_t max_decoded_length, Func visitor) { - ssize_t arg_offset = 0; - ssize_t len_diff = 0; - - for (auto& arg: args) { - if (arg.size >= SIZE_MAX || arg_offset + arg.size > encoded_offset) { - ssize_t rel_encoded_offset = arg_offset - (ssize_t)encoded_offset; - ssize_t rel_decoded_offset = arg_offset - (ssize_t)encoded_offset - len_diff; - - if (rel_decoded_offset >= max_decoded_length || rel_encoded_offset >= max_encoded_length) { - break; - } - - size_t encoded_arg_size = arg.size; - size_t decoded_arg_size = arg.codec == "endpoint_ref" ? sizeof(uintptr_t) : arg.size; - len_diff += encoded_arg_size - decoded_arg_size; - - if (rel_encoded_offset < 0) { - encoded_arg_size += rel_encoded_offset; - } - - if (rel_decoded_offset < 0) { - decoded_arg_size += rel_decoded_offset; - } - - if (encoded_arg_size < SIZE_MAX && rel_encoded_offset + encoded_arg_size > max_encoded_length) { - encoded_arg_size -= rel_encoded_offset + encoded_arg_size - max_encoded_length; - } - - if (decoded_arg_size < SIZE_MAX && rel_decoded_offset + decoded_arg_size > max_decoded_length) { - decoded_arg_size -= rel_decoded_offset + decoded_arg_size - max_decoded_length; - } - - if (!visitor(arg, rel_encoded_offset, rel_decoded_offset, encoded_arg_size, decoded_arg_size)) { - return false; - } - } - - arg_offset += arg.size; - } - - return true; } void libfibre_start_tx(LibFibreTxStream* tx_stream, @@ -815,14 +546,14 @@ void libfibre_start_tx(LibFibreTxStream* tx_stream, void* ctx) { tx_stream->on_completed = on_completed; tx_stream->ctx = ctx; - tx_stream->sink->start_write({tx_buf, tx_len}, &tx_stream->handle, *tx_stream); + tx_stream->sink->start_write({tx_buf, tx_len}, &tx_stream->handle, MEMBER_CB(tx_stream, on_tx_done)); } void libfibre_cancel_tx(LibFibreTxStream* tx_stream) { tx_stream->sink->cancel_write(tx_stream->handle); } -void libfibre_close_tx(LibFibreTxStream* tx_stream, FibreStatus status) { +void libfibre_close_tx(LibFibreTxStream* tx_stream, LibFibreStatus status) { if (tx_stream->on_closed) { (tx_stream->on_closed)(tx_stream, tx_stream->on_closed_ctx, convert_status(status)); } @@ -833,141 +564,15 @@ void libfibre_start_rx(LibFibreRxStream* rx_stream, void* ctx) { rx_stream->on_completed = on_completed; rx_stream->ctx = ctx; - rx_stream->source->start_read({rx_buf, rx_len}, &rx_stream->handle, *rx_stream); + rx_stream->source->start_read({rx_buf, rx_len}, &rx_stream->handle, MEMBER_CB(rx_stream, on_rx_done)); } void libfibre_cancel_rx(LibFibreRxStream* rx_stream) { rx_stream->source->cancel_read(rx_stream->handle); } -void libfibre_close_rx(LibFibreRxStream* rx_stream, FibreStatus status) { +void libfibre_close_rx(LibFibreRxStream* rx_stream, LibFibreStatus status) { if (rx_stream->on_closed) { (rx_stream->on_closed)(rx_stream, rx_stream->on_closed_ctx, convert_status(status)); } } - -void ArgEncoder::start_write(fibre::cbufptr_t buffer, fibre::TransferHandle* handle, Completer& completer) { - // Allocate libfibre-internal TX buffer into which the application buffer - // will be encoded. The size can still change during transcoding. - encoded_buf_ = std::vector{}; - encoded_buf_.reserve(buffer.size()); - - // Transcode application buffer to stream buffer - bool ok = call_->iterate_over_args_at(call_->func->inputs, encoded_offset_, SIZE_MAX, buffer.size(), [&]( - const fibre::LegacyFibreArg& arg, - ssize_t rel_encoded_offset, ssize_t rel_decoded_offset, - size_t encoded_arg_size, size_t decoded_arg_size) { - encoded_buf_.resize(rel_encoded_offset + encoded_arg_size); - fibre::bufptr_t encoded_buf = {encoded_buf_.data() + rel_encoded_offset, encoded_arg_size}; - fibre::cbufptr_t decoded_buf = {buffer.begin() + rel_decoded_offset, decoded_arg_size}; - return encode_for_transport(call_->obj->client, decoded_buf, encoded_buf, arg); - }); - - if (!ok) { - FIBRE_LOG(W) << "Transcoding before TX failed. Note that partial transcoding of arguments is not supported."; - completer.complete({fibre::kStreamError, buffer.begin()}); - return; - } - - decoded_buf_ = buffer; - call_->n_active_transfers++; - completer_ = &completer; - - encoded_stream_->start_write(encoded_buf_, &transfer_handle_, *this); -} - -void ArgEncoder::cancel_write(fibre::TransferHandle transfer_handle) { - encoded_stream_->cancel_write(transfer_handle_); -} - -void ArgEncoder::complete(fibre::WriteResult result) { - size_t n_sent = (result.end - encoded_buf_.data()); - FIBRE_LOG(D) << "sent " << n_sent << " bytes with status " << result.status; - - if (n_sent > encoded_buf_.size()) { - FIBRE_LOG(E) << "internal error: sent more bytes than expected"; - } - - ssize_t len_diff = encoded_buf_.size() - decoded_buf_.size(); - const uint8_t* tx_end = decoded_buf_.begin() + n_sent - len_diff; - - decoded_buf_ = {}; - encoded_buf_ = {}; - encoded_offset_ += n_sent; - call_->n_active_transfers--; - - safe_complete(completer_, {result.status, tx_end}); -} - -void ArgDecoder::start_read(fibre::bufptr_t buffer, fibre::TransferHandle* handle, Completer& completer) { - size_t encoded_size = 0; - - bool ok = call_->iterate_over_args_at(call_->func->outputs, encoded_offset_, SIZE_MAX, buffer.size(), [&]( - const fibre::LegacyFibreArg& arg, - ssize_t rel_encoded_offset, ssize_t rel_decoded_offset, - size_t encoded_arg_size, size_t decoded_arg_size) { - encoded_size += encoded_arg_size; - return true; - }); - - if (!ok) { - FIBRE_LOG(W) << "Transcoding preparation before RX failed. Note that partial transcoding of arguments is not supported."; - completer.complete({fibre::kStreamError, buffer.begin()}); - return; - } - - encoded_buf_ = {}; - encoded_buf_.resize(encoded_size); - decoded_buf_ = buffer; - completer_ = &completer; - call_->n_active_transfers++; - - encoded_stream_->start_read(encoded_buf_, &transfer_handle_, *this); -} - -void ArgDecoder::cancel_read(fibre::TransferHandle transfer_handle) { - encoded_stream_->cancel_read(transfer_handle_); -} - -void ArgDecoder::complete(fibre::ReadResult result) { - transfer_handle_ = 0; - - size_t n_recv = result.end - encoded_buf_.data(); - FIBRE_LOG(D) << "received " << n_recv << " bytes with status " << result.status; - - if (n_recv > encoded_buf_.size()) { - FIBRE_LOG(E) << "internal error: received more bytes than expected"; - } - - ssize_t len_diff = encoded_buf_.size() - decoded_buf_.size(); - uint8_t* rx_end = decoded_buf_.begin() + n_recv - len_diff; - - size_t arg_offset = 0; - - // Transcode stream buffer to application buffer - bool ok = call_->iterate_over_args_at(call_->func->outputs, encoded_offset_, n_recv, decoded_buf_.size(), [&]( - const fibre::LegacyFibreArg& arg, - ssize_t rel_encoded_offset, ssize_t rel_decoded_offset, - size_t encoded_arg_size, size_t decoded_arg_size) { - fibre::cbufptr_t encoded_buf = {encoded_buf_.data() + rel_encoded_offset, encoded_arg_size}; - fibre::bufptr_t decoded_buf = {decoded_buf_.begin() + rel_decoded_offset, decoded_arg_size}; - return decode_from_transport(call_->obj->client, encoded_buf, decoded_buf, arg); - }); - - if (!ok) { - FIBRE_LOG(W) << "Transcoding after RX failed. Partial transcoding of arguments is not supported."; - rx_end = decoded_buf_.begin(); - result.status = fibre::kStreamError; - } else if (rx_end > decoded_buf_.end()) { - FIBRE_LOG(E) << "miscalculated pointer: beyond buffer end"; - rx_end = decoded_buf_.end(); - result.status = fibre::kStreamError; - } - - decoded_buf_ = {}; - encoded_buf_ = {}; - encoded_offset_ += n_recv; - call_->n_active_transfers--; - - safe_complete(completer_, {result.status, rx_end}); -} diff --git a/Firmware/fibre-cpp/logging.hpp b/Firmware/fibre-cpp/logging.hpp index 05751e97..32fe97db 100644 --- a/Firmware/fibre-cpp/logging.hpp +++ b/Firmware/fibre-cpp/logging.hpp @@ -55,8 +55,18 @@ #ifndef __FIBRE_LOGGING_HPP #define __FIBRE_LOGGING_HPP +/** + * @brief Tag type to print the last system error + * + * The statement `std::out << sys_err();` will print the last system error + * in the following format: "error description (errno)". + * This is based on `GetLastError()` (Windows) or `errno` (all other systems). + */ +struct sys_err {}; + + // TODO: support lite-version of logging on embedded systems -#if defined(_WIN32) || defined(_WIN64) || defined(__linux__) || defined(__APPLE__) || defined(EMSCRIPTEN) +#if FIBRE_MAX_LOG_VERBOSITY #include @@ -235,10 +245,10 @@ public: template -constexpr log_level_t get_default_log_verbosity() { return FIBRE_DEFAULT_LOG_VERBOSITY; } +constexpr log_level_t get_default_log_verbosity() { return (log_level_t)FIBRE_DEFAULT_LOG_VERBOSITY; } template -constexpr log_level_t get_max_log_verbosity() { return FIBRE_MAX_LOG_VERBOSITY; } +constexpr log_level_t get_max_log_verbosity() { return (log_level_t)FIBRE_MAX_LOG_VERBOSITY; } /** * @brief Resolves the currently active log verbosity for the given topic. @@ -302,14 +312,6 @@ constexpr const char * get_file_name(TFilepath file_path) { } -/** - * @brief Tag type to print the last system error - * - * The statement `std::out << sys_err();` will print the last system error - * in the following format: "error description (errno)". - * This is based on `GetLastError()` (Windows) or `errno` (all other systems). - */ -struct sys_err {}; namespace std { static inline std::ostream& operator<<(std::ostream& stream, const sys_err&) { @@ -334,6 +336,6 @@ struct NullStream { #define FIBRE_LOG(level) NullStream() -#endif +#endif // FIBRE_MAX_LOG_VERBOSITY #endif // __FIBRE_LOGGING_HPP diff --git a/Firmware/fibre-cpp/package.lua b/Firmware/fibre-cpp/package.lua index 41bfafc1..5bc98e98 100644 --- a/Firmware/fibre-cpp/package.lua +++ b/Firmware/fibre-cpp/package.lua @@ -1,12 +1,139 @@ -fibre_package = { - core_files = { - 'libfibre.cpp', - 'legacy_protocol.cpp', - 'legacy_object_client.cpp', - }, - features = { - LIBUSB={'platform_support/libusb_transport.cpp'}, - LOGGING={'logging.cpp'}, + +-- Returns a table that contains the Fibre code files and the flags required to +-- compile and link those files. +-- +-- args: A dictionary containing the fibre options. Refer to the Compile Options +-- in README.md for a list of available options. For example the option +-- `FIBRE_ENABLE_SERVER` maps to the argument `args.enable_server`. +-- In addition: +-- args.pkgconf: Controls the use of the pkgconf or pkg-config utility that +-- shall be used to locate build dependencies. Can be one of the following: +-- - A string: Use the binary provided by the string. Fail if it doesn't +-- exist. +-- - true: Use "pkgconf" and fall back to "pkg-config" if "pkgconf" +-- doesn't exist. Fail if both don't exist. +-- - false: Don't use pkg-config. The user is responsible of determining +-- the required compile and link flags. +-- - nil: Try both "pkgconf" and "pkg-config". If both don't exist fall +-- back to a hardcoded list of well-known settings. +-- +-- Returns: A dictionary with the following items: +-- code_files: A list of strings that name the C++ code files to be compiled. +-- The names are relative to package.lua. +-- include_dirs: A list of directories that must be added to the include path +-- when compiling the code files. The paths are relative to +-- package.lua. +-- cflags: A list of flags that should be passed to the compiler/linker when +-- compiling and linking the code files. +-- ldflags: A list of linker flags that should be passed to the linker when +-- linking the object files. +function get_fibre_package(args) + pkg = { + code_files = { + 'fibre.cpp', + 'channel_discoverer.cpp', + }, + include_dirs = {'include'}, + cflags = {}, + ldflags = {}, } -} + + -- Select a pkgconf function + if args.pkgconf == true or args.pkgconf == nil then + -- Autodetect pkgconf + if test_pkgconf('pkgconf') then + print("using pkgconf") + pkgconf_file = 'pkgconf' + pkgconf = real_pkgconf + elseif test_pkgconf('pkg-config') then + print("using pkg-config") + pkgconf_file = 'pkg-config' + pkgconf = real_pkgconf + elseif args.pkgconf == nil then + print("using hardcoded pkgconf") + pkgconf = hardcoded_pkgconf + else + error("couldn't find pkgconf nor pkg-config") + end + + elseif args.pkgconf == false then + pkgconf_file = nil + pkgconf = null_pkgconf + else + pkgconf_file = args.pkgconf + pkgconf = real_pkgconf + end + + pkg.cflags += '-DFIBRE_ENABLE_SERVER='..(args.enable_server and '1' or '0') + pkg.cflags += '-DFIBRE_ENABLE_CLIENT='..(args.enable_client and '1' or '0') + pkg.cflags += '-DFIBRE_ENABLE_EVENT_LOOP='..(args.enable_event_loop and '1' or '0') + pkg.cflags += '-DFIBRE_ALLOW_HEAP='..(args.allow_heap and '1' or '0') + pkg.cflags += '-DFIBRE_MAX_LOG_VERBOSITY='..(args.max_log_verbosity or '5') + pkg.cflags += '-DFIBRE_DEFAULT_LOG_VERBOSITY='..(args.default_log_verbosity or '2') + pkg.cflags += '-DFIBRE_ENABLE_LIBUSB_BACKEND='..(args.enable_libusb_backend and '1' or '0') + pkg.cflags += '-DFIBRE_ENABLE_TCP_SERVER_BACKEND='..(args.enable_tcp_server_backend and '1' or '0') + pkg.cflags += '-DFIBRE_ENABLE_TCP_CLIENT_BACKEND='..(args.enable_tcp_client_backend and '1' or '0') + + if args.enable_libusb_backend then + pkg.code_files += 'platform_support/libusb_transport.cpp' + pkgconf(pkg, "libusb-1.0") + + -- TODO: only add pthread on linux and windows + pkg.ldflags += '-lpthread' + end + if args.max_log_verbosity == nil or (args.max_log_verbosity > 0) then + pkg.code_files += 'logging.cpp' + end + if args.enable_client then + pkg.code_files += 'legacy_object_client.cpp' + end + if args.enable_client or args.enable_server then + pkg.code_files += 'legacy_protocol.cpp' + end + if args.enable_event_loop then + pkg.code_files += 'platform_support/epoll_event_loop.cpp' + end + if args.enable_tcp_client_backend or args.enable_tcp_server_backend then + -- TODO: chose between windows and posix backend + pkg.code_files += 'platform_support/posix_tcp_backend.cpp' + pkg.code_files += 'platform_support/posix_socket.cpp' + pkg.ldflags += '-lanl' + end + + return pkg +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 +-- line removed. +function run_now(command) + local handle + handle = io.popen(command) + local output = handle:read("*a") + local rc = {handle:close()} + return string.sub(output, 0, -2), rc[1] +end + +function test_pkgconf(name) + local str, rc = run_now(name.." --version 2>&1 >/dev/null") + return rc +end + +function real_pkgconf(pkg, lib) + pkg.cflags += run_now(pkgconf_file..' '..lib..' --cflags') + pkg.ldflags += run_now(pkgconf_file..' '..lib..' --libs') +end + +function null_pkgconf(pkg, lib) + -- don't do anything +end + +function hardcoded_pkgconf(pkg, lib) + libs = { + ['libusb-1.0'] = {cflags = {}, ldflags = {}}, + } + tup.append_table(pkg.cflags, libs[lib].cflags) + tup.append_table(pkg.ldflags, libs[lib].ldflags) +end \ No newline at end of file diff --git a/Firmware/fibre-cpp/platform_support/epoll_event_loop.cpp b/Firmware/fibre-cpp/platform_support/epoll_event_loop.cpp new file mode 100644 index 00000000..dcb36eba --- /dev/null +++ b/Firmware/fibre-cpp/platform_support/epoll_event_loop.cpp @@ -0,0 +1,204 @@ + +#include "epoll_event_loop.hpp" +#include "../logging.hpp" + +#include +#include +#include +#include +#include + +using namespace fibre; + +DEFINE_LOG_TOPIC(EVENT_LOOP); +USE_LOG_TOPIC(EVENT_LOOP); + + +bool EpollEventLoop::start(Callback on_started) { + if (epoll_fd_ >= 0) { + FIBRE_LOG(E) << "already started"; + return false; + } + + epoll_fd_ = epoll_create1(0); + if (epoll_fd_ < 0) { + FIBRE_LOG(E) << "epoll_create1() failed"; + return false; + } + + bool ok = true; + + post_fd_ = eventfd(0, 0); + + bool post_fd_ok = (post_fd_ >= 0) + && register_event(post_fd_, EPOLLIN, MEMBER_CB(this, run_callbacks)) + && post(on_started); + + if (!post_fd_ok) { + FIBRE_LOG(E) << "failed to create an event for posting callbacks onto the event loop"; + ok = false; + } + + // Run for as long as there are callbacks pending posted or there's at least + // one file descriptor other than post_fd_ registerd. + while (pending_callbacks_.size() || (context_map_.size() > 1)) { + iterations_++; + + do { + FIBRE_LOG(D) << "epoll_wait..."; + n_triggered_events_ = epoll_wait(epoll_fd_, triggered_events_, max_triggered_events_, -1); + FIBRE_LOG(D) << "epoll_wait unblocked by " << n_triggered_events_ << " events"; + if (errno == EINTR) { + FIBRE_LOG(D) << "interrupted"; + } + } while (n_triggered_events_ < 0 && errno == EINTR); // ignore syscall interruptions. This happens for instance during suspend. + + if (n_triggered_events_ <= 0) { + FIBRE_LOG(E) << "epoll_wait() failed with " << n_triggered_events_ << ": " << sys_err() << " - Terminating worker thread."; + ok = false; + break; + } + + // Handle events + for (int i = 0; i < n_triggered_events_; ++i) { + EventContext* ctx = (EventContext*)triggered_events_[i].data.ptr; + if (ctx) { + try { // TODO: not sure if using "try" without throwing exceptions will do unwanted things with the stack + ctx->callback.invoke(triggered_events_[i].events); + } catch (...) { + FIBRE_LOG(E) << "worker callback threw an exception."; + } + } + } + } + + FIBRE_LOG(D) << "epoll loop exited"; + + if ((post_fd_ >= 0) && !deregister_event(post_fd_)) { + FIBRE_LOG(E) << "deregister_event() failed"; + ok = false; + } + + if ((post_fd_ >= 0) && close(post_fd_) != 0) { + FIBRE_LOG(E) << "close() failed: " << sys_err(); + ok = false; + } + post_fd_ = -1; + + if (close(epoll_fd_) != 0) { + FIBRE_LOG(E) << "close() failed: " << sys_err(); + ok = false; + } + epoll_fd_ = -1; + + return ok; +} + +bool EpollEventLoop::post(Callback callback) { + if (epoll_fd_ < 0) { + FIBRE_LOG(E) << "not started"; + return false; + } + + { + std::unique_lock lock(pending_callbacks_mutex_); + pending_callbacks_.push_back(callback); + } + + const uint64_t val = 1; + if (write(post_fd_, &val, sizeof(val)) != sizeof(val)) { + FIBRE_LOG(E) << "write() failed" << sys_err(); + return false; + } + return true; +} + +bool EpollEventLoop::register_event(int event_fd, uint32_t events, Callback callback) { + if (epoll_fd_ < 0) { + FIBRE_LOG(E) << "not initialized"; + return false; + } + + if (event_fd < 0) { + FIBRE_LOG(E) << "invalid argument"; + return false; + } + + EventContext* ctx = new EventContext{callback}; + struct epoll_event ev = { + .events = events, + .data = { .ptr = ctx } + }; + context_map_[event_fd] = ctx; + + if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, event_fd, &ev) != 0) { + FIBRE_LOG(E) << "epoll_ctl(" << event_fd << "...) failed: " << sys_err(); + delete ctx; + return false; + } + + FIBRE_LOG(D) << "registered epoll event " << event_fd; + + return true; +} + +bool EpollEventLoop::deregister_event(int event_fd) { + if (epoll_fd_ < 0) { + FIBRE_LOG(E) << "not running"; + return false; + } + + int result = true; + + if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, event_fd, nullptr) != 0) { + FIBRE_LOG(E) << "epoll_ctl() failed: " << sys_err(); + result = false; + } + + EventContext* callback = context_map_[event_fd]; + + auto it = context_map_.find(event_fd); + if (it == context_map_.end()) { + FIBRE_LOG(E) << "event context not found"; + return false; + } + + for (int i = 0; i < n_triggered_events_; ++i) { + if ((EventContext*)(triggered_events_[i].data.ptr) == it->second) { + triggered_events_[i].data.ptr = nullptr; + } + } + + context_map_.erase(it); + + return result; +} + +struct EventLoopTimer* EpollEventLoop::call_later(float delay, Callback callback) { + FIBRE_LOG(E) << "not implemented"; // TODO: implement + return nullptr; +} + +bool EpollEventLoop::cancel_timer(EventLoopTimer* timer) { + FIBRE_LOG(E) << "not implemented"; // TODO: implement + return false; +} + +void EpollEventLoop::run_callbacks(uint32_t) { + // TODO: warn if read fails + uint64_t val; + if (read(post_fd_, &val, sizeof(val)) != sizeof(val)) { + FIBRE_LOG(E) << "failed to read from post file descriptor"; + } + + std::vector> pending_callbacks; + + { + std::unique_lock lock(pending_callbacks_mutex_); + std::swap(pending_callbacks, pending_callbacks_); + } + + for (auto& cb: pending_callbacks) { + cb.invoke(); + } +} diff --git a/Firmware/fibre-cpp/platform_support/epoll_event_loop.hpp b/Firmware/fibre-cpp/platform_support/epoll_event_loop.hpp new file mode 100644 index 00000000..b900c98f --- /dev/null +++ b/Firmware/fibre-cpp/platform_support/epoll_event_loop.hpp @@ -0,0 +1,69 @@ +#ifndef __FIBRE_LINUX_EVENT_LOOP_HPP +#define __FIBRE_LINUX_EVENT_LOOP_HPP + +//#include +#include +#include +#include +#include +//#include + +#include + +namespace fibre { + +/** + * @brief Event loop based on the Linux-specific `epoll()` infrastructure. + * + * Thread safety: None of the public functions are thread-safe with respect to + * each other. However they are thread safe with respect to the internal event + * loop, that means register_event() and deregister_event() can be called from + * within an event callback (which executes on the event loop thread), provided + * those calls are properly synchronized with calls from other threads. + */ +class EpollEventLoop : public EventLoop { +public: + + /** + * @brief Starts the event loop on the current thread and places the + * specified start callback on the event queue. + * + * The function returns when the event loop becomes empty or if a platform + * error occurs. + */ + bool start(Callback on_started); + + bool post(Callback callback) final; + bool register_event(int fd, uint32_t events, Callback callback) final; + bool deregister_event(int fd) final; + struct EventLoopTimer* call_later(float delay, Callback callback) final; + bool cancel_timer(EventLoopTimer* timer) final; + +private: + struct EventContext { + //int fd; + Callback callback; + }; + + void run_callbacks(uint32_t); + + int epoll_fd_ = -1; + int post_fd_ = -1; + unsigned int iterations_ = 0; + + std::unordered_map context_map_; // required to deregister callbacks + + static const size_t max_triggered_events_ = 16; // max number of events that can be handled per iteration + int n_triggered_events_ = 0; + struct epoll_event triggered_events_[max_triggered_events_]; + + // List of callbacks that were submitted through post(). + std::vector> pending_callbacks_; + + // Mutex to protect pending_callbacks_ + std::mutex pending_callbacks_mutex_; +}; + +} + +#endif // __FIBRE_LINUX_EVENT_LOOP_HPP \ No newline at end of file diff --git a/Firmware/fibre-cpp/platform_support/libusb_transport.cpp b/Firmware/fibre-cpp/platform_support/libusb_transport.cpp index 31de2bf1..2e5488a1 100644 --- a/Firmware/fibre-cpp/platform_support/libusb_transport.cpp +++ b/Firmware/fibre-cpp/platform_support/libusb_transport.cpp @@ -9,6 +9,11 @@ #include "../print_utils.hpp" #include +#include + +#if !FIBRE_ALLOW_HEAP +# error "The libusb backend requires heap allocation." +#endif using namespace fibre; @@ -34,14 +39,14 @@ constexpr unsigned int kPollingIntervalMs = 1000; * pointer must be non-null and initialized when this function is called. * It must remain initialized until deinit() of this discoverer was called. */ -int LibusbDiscoverer::init(EventLoop* event_loop) { +bool LibusbDiscoverer::init(EventLoop* event_loop) { if (!event_loop) - return -1; + return false; event_loop_ = event_loop; if (libusb_init(&libusb_ctx_) != LIBUSB_SUCCESS) { FIBRE_LOG(E) << "libusb_init() failed: " << sys_err(); - return deinit(0), -1; + return deinit(0), false; } // Fetch initial list of file-descriptors we have to monitor. @@ -79,7 +84,7 @@ int LibusbDiscoverer::init(EventLoop* event_loop) { // different approach for Windows anyway. const struct libusb_pollfd** pollfds = libusb_get_pollfds(libusb_ctx_); if (!pollfds) { - return deinit(2), -1; + return deinit(2), false; } for (size_t i = 0; pollfds[i]; ++i) { @@ -113,7 +118,7 @@ int LibusbDiscoverer::init(EventLoop* event_loop) { if (LIBUSB_SUCCESS != result) { FIBRE_LOG(E) << "Error subscribing to hotplug events"; hotplug_callback_handle_ = 0; - return deinit(3), -1; + return deinit(3), false; } } else { @@ -129,10 +134,10 @@ int LibusbDiscoverer::init(EventLoop* event_loop) { FIBRE_LOG(E) << "Hotplug detection with separate libusb thread will cause trouble."; } - return 0; + return true; } -int LibusbDiscoverer::deinit(int stage) { +bool LibusbDiscoverer::deinit(int stage) { // TODO: verify that all devices are closed and hotplug detection is disabled if (stage > 3 && libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { @@ -188,24 +193,7 @@ int LibusbDiscoverer::deinit(int stage) { event_loop_ = nullptr; - return 0; -} - -bool try_parse_key(const char* begin, const char* end, const char* key, int* val) { - char buf[end - begin + 1]; - memcpy(buf, begin, end - begin); - buf[end - begin] = 0; - - char fmt1[strlen(key) + 6]; - memcpy(fmt1, key, strlen(key)); - memcpy(fmt1 + strlen(key), "=0x%x", 6); - - char fmt2[strlen(key) + 4]; - memcpy(fmt2, key, strlen(key)); - memcpy(fmt2 + strlen(key), "=%d", 4); - - return sscanf(buf, fmt1, val) == 1 - || sscanf(buf, fmt2, val) == 1; + return true; } /** @@ -218,50 +206,31 @@ bool try_parse_key(const char* begin, const char* end, const char* key, int* val * If the function succeeds, an opaque context pointer is returned which must be * passed to stop_channel_discovery() to terminate this particular request. * - * @param specs: Specifies the constraints to consider. Must be either empty or - * of the format "key1=val1,key2=val2" where the available keys are: - * - * bus, address, idProduct, idVendor, bInterfaceClass, - * bInterfaceSubClass, bInterfaceProtocol - * - * The value can be either a integer in decimal or hexadecimal notation - * (0x1234). - * Omitted keys are ignored during filtering. + * @param specs: See README of the main Fibre repository for details. + * (https://github.com/samuelsadok/fibre/tree/devel). * * @param on_found_channels: Invoked when a matching pair of RX/TX channels is found. * This callback will also be called for any matching channels that already exist when * the discovery is started. */ -void LibusbDiscoverer::start_channel_discovery(const char* specs, size_t specs_len, ChannelDiscoveryContext** handle, Completer& on_found_channels) { +void LibusbDiscoverer::start_channel_discovery(const char* specs, size_t specs_len, ChannelDiscoveryContext** handle, Callback on_found_channels) { FIBRE_LOG(D) << "starting discovery with filter \"" << std::string(specs, specs_len) << "\""; const char* prev_delim = specs; InterfaceSpecs interface_specs; - while (prev_delim < specs + specs_len) { - const char* next_delim = std::find(prev_delim, specs + specs_len, ','); - - bool success = try_parse_key(prev_delim, next_delim, "bus", &interface_specs.bus) - || try_parse_key(prev_delim, next_delim, "address", &interface_specs.address) - || try_parse_key(prev_delim, next_delim, "idVendor", &interface_specs.vendor_id) - || try_parse_key(prev_delim, next_delim, "idProduct", &interface_specs.product_id) - || try_parse_key(prev_delim, next_delim, "bInterfaceClass", &interface_specs.interface_class) - || try_parse_key(prev_delim, next_delim, "bInterfaceSubClass", &interface_specs.interface_subclass) - || try_parse_key(prev_delim, next_delim, "bInterfaceProtocol", &interface_specs.interface_protocol); - - if (!success) { - FIBRE_LOG(E) << "could not interpret channel discovery specs"; - on_found_channels.complete({kFibreInvalidArgument, nullptr, nullptr, 0}); - return; - } - - prev_delim = std::min(next_delim + 1, specs + specs_len); - } + try_parse_key(specs, specs + specs_len, "bus", &interface_specs.bus); + try_parse_key(specs, specs + specs_len, "address", &interface_specs.address); + try_parse_key(specs, specs + specs_len, "idVendor", &interface_specs.vendor_id); + try_parse_key(specs, specs + specs_len, "idProduct", &interface_specs.product_id); + try_parse_key(specs, specs + specs_len, "bInterfaceClass", &interface_specs.interface_class); + try_parse_key(specs, specs + specs_len, "bInterfaceSubClass", &interface_specs.interface_subclass); + try_parse_key(specs, specs + specs_len, "bInterfaceProtocol", &interface_specs.interface_protocol); MyChannelDiscoveryContext* subscription = new MyChannelDiscoveryContext{}; subscription->interface_specs = interface_specs; - subscription->on_found_channels = &on_found_channels; + subscription->on_found_channels = on_found_channels; subscriptions_.push_back(subscription); for (auto& dev: known_devices_) { @@ -324,9 +293,8 @@ void LibusbDiscoverer::on_event_loop_iteration() { if (libusb_get_next_timeout(libusb_ctx_, &timeout)) { float timeout_sec = (float)timeout.tv_sec + (float)timeout.tv_usec * 1e-6; FIBRE_LOG(D) << "setting event loop timeout to " << timeout_sec << " s"; - event_loop_timer_ = event_loop_->call_later(timeout_sec, [](void* ctx) { - ((LibusbDiscoverer*)ctx)->on_event_loop_iteration(); - }, this); + event_loop_timer_ = event_loop_->call_later(timeout_sec, + MEMBER_CB(this, on_event_loop_iteration)); } } @@ -334,9 +302,8 @@ void LibusbDiscoverer::on_event_loop_iteration() { * @brief Called when libusb wants to add a file descriptor to our event loop. */ void LibusbDiscoverer::on_add_pollfd(int fd, short events) { - event_loop_->register_event(fd, events, [](void* ctx) { - ((LibusbDiscoverer*)ctx)->on_event_loop_iteration(); - }, this); + event_loop_->register_event(fd, events, + MEMBER_CB(this, on_event_loop_iteration2)); } /** @@ -452,9 +419,8 @@ void LibusbDiscoverer::poll_devices_now() { // It's possible that the discoverer was deinited during this function. if (event_loop_) { - device_polling_timer_ = event_loop_->call_later(kPollingIntervalMs * 0.001f, [](void* ctx) { - ((LibusbDiscoverer*)ctx)->poll_devices_now(); - }, this); + device_polling_timer_ = event_loop_->call_later(kPollingIntervalMs * 0.001f, + MEMBER_CB(this, poll_devices_now)); } } @@ -554,9 +520,7 @@ void LibusbDiscoverer::consider_device(struct libusb_device *device, MyChannelDi ep_out = nullptr; } - if (subscription->on_found_channels) { - subscription->on_found_channels->complete({kFibreOk, ep_in, ep_out, mtu}); - } + subscription->on_found_channels.invoke({kFibreOk, ep_in, ep_out, mtu}); } } @@ -589,20 +553,20 @@ bool LibusbBulkEndpoint::deinit() { } template -void LibusbBulkEndpoint::start_transfer(bufptr_t buffer, TransferHandle* handle, Completer& completer) { +void LibusbBulkEndpoint::start_transfer(bufptr_t buffer, TransferHandle* handle, Callback completer) { if (handle) { *handle = reinterpret_cast(this); } if (completer_) { FIBRE_LOG(E) << "transfer already in progress"; - completer.complete({kStreamError, nullptr}); + completer.invoke({kStreamError, nullptr}); return; } if (!handle_) { FIBRE_LOG(E) << "device not open"; - completer.complete({kStreamError, nullptr}); + completer.invoke({kStreamError, nullptr}); return; } @@ -613,11 +577,8 @@ void LibusbBulkEndpoint::start_transfer(bufptr_t buffer, TransferHandle* h // This callback is used if we start our own libusb thread // separate from the application's event loop thread auto indirect_callback = [](struct libusb_transfer* transfer){ - ((LibusbBulkEndpoint*)transfer->user_data)->parent_->event_loop_->post( - [](void* ctx) { - ((LibusbBulkEndpoint*)ctx)->on_transfer_finished(); - }, transfer->user_data - ); + auto ep = (LibusbBulkEndpoint*)transfer->user_data; + ep->parent_->event_loop_->post(MEMBER_CB(ep, on_transfer_finished)); }; //FIBRE_LOG(D) << "transfer of size " << buffer.size(); @@ -626,7 +587,7 @@ void LibusbBulkEndpoint::start_transfer(bufptr_t buffer, TransferHandle* h parent_->using_sparate_libusb_thread_ ? indirect_callback : direct_callback, this, kBulkTimeoutMs); - completer_ = &completer; + completer_ = completer; submit_transfer(); } @@ -648,10 +609,10 @@ void LibusbBulkEndpoint::submit_transfer() { FIBRE_LOG(T) << "started USB transfer on EP " << as_hex(endpoint_id_); } else if (LIBUSB_ERROR_NO_DEVICE == result) { FIBRE_LOG(W) << "couldn't start USB transfer on EP " << as_hex(endpoint_id_) << ": " << libusb_error_name(result); - safe_complete(completer_, {kStreamClosed, nullptr}); + completer_.invoke_and_clear({kStreamClosed, nullptr}); } else { FIBRE_LOG(W) << "couldn't start USB transfer on EP " << as_hex(endpoint_id_) << ": " << libusb_error_name(result); - safe_complete(completer_, {kStreamError, nullptr}); + completer_.invoke_and_clear({kStreamError, nullptr}); } } @@ -708,7 +669,7 @@ void LibusbBulkEndpoint::on_transfer_finished() { } uint8_t* end = std::max(transfer_->buffer + transfer_->actual_length, transfer_->buffer); - safe_complete(completer_, {status, end}); + completer_.invoke_and_clear({status, end}); // If libusb does hotplug detection itself then we don't need to handle // device removal here. Libusb will call the corresponding hotplug callback. diff --git a/Firmware/fibre-cpp/platform_support/libusb_transport.hpp b/Firmware/fibre-cpp/platform_support/libusb_transport.hpp index 33e1a2db..42416508 100644 --- a/Firmware/fibre-cpp/platform_support/libusb_transport.hpp +++ b/Firmware/fibre-cpp/platform_support/libusb_transport.hpp @@ -1,9 +1,9 @@ #ifndef __FIBRE_USB_DISCOVERER_HPP #define __FIBRE_USB_DISCOVERER_HPP -#include "../event_loop.hpp" -#include "../async_stream.hpp" -#include "../channel_discoverer.hpp" +#include +#include +#include #include #include @@ -15,9 +15,9 @@ namespace fibre { class LibusbBulkInEndpoint; class LibusbBulkOutEndpoint; -template class FIBRE_PRIVATE LibusbBulkEndpoint; +template class LibusbBulkEndpoint; -class FIBRE_PRIVATE LibusbDiscoverer : public ChannelDiscoverer { +class LibusbDiscoverer : public ChannelDiscoverer { public: struct InterfaceSpecs { @@ -32,14 +32,13 @@ public: struct MyChannelDiscoveryContext : ChannelDiscoveryContext { InterfaceSpecs interface_specs; - Completer* on_found_channels; + Callback on_found_channels; }; - ~LibusbDiscoverer() { deinit(); } - - int init(EventLoop* event_loop); - int deinit() { return deinit(INT_MAX); } - void start_channel_discovery(const char* specs, size_t specs_len, ChannelDiscoveryContext** handle, Completer& on_found_channels) final; + constexpr static const char* get_name() { return "usb"; } + bool init(EventLoop* event_loop); + bool deinit() { return deinit(INT_MAX); } + void start_channel_discovery(const char* specs, size_t specs_len, ChannelDiscoveryContext** handle, Callback on_found_channels) final; int stop_channel_discovery(ChannelDiscoveryContext* handle) final; private: @@ -53,9 +52,10 @@ private: std::vector ep_out; }; - int deinit(int stage); + bool deinit(int stage); void internal_event_loop(); void on_event_loop_iteration(); + void on_event_loop_iteration2(uint32_t) { on_event_loop_iteration(); } void on_add_pollfd(int fd, short events); void on_remove_pollfd(int fd); int on_hotplug(struct libusb_device *dev, libusb_hotplug_event event); @@ -75,13 +75,13 @@ private: }; template -class FIBRE_PRIVATE LibusbBulkEndpoint { +class LibusbBulkEndpoint { public: bool init(LibusbDiscoverer* parent, struct libusb_device_handle* handle, uint8_t endpoint_id); bool deinit(); protected: - void start_transfer(bufptr_t buffer, TransferHandle* handle, Completer& completer); + void start_transfer(bufptr_t buffer, TransferHandle* handle, Callback completer); void cancel_transfer(TransferHandle transfer_handle); private: @@ -92,12 +92,12 @@ private: struct libusb_device_handle* handle_ = nullptr; uint8_t endpoint_id_ = 0; struct libusb_transfer* transfer_ = nullptr; - Completer* completer_ = nullptr; + Callback completer_ = nullptr; }; -class FIBRE_PRIVATE LibusbBulkInEndpoint : public LibusbBulkEndpoint, public AsyncStreamSource { +class LibusbBulkInEndpoint : public LibusbBulkEndpoint, public AsyncStreamSource { public: - void start_read(bufptr_t buffer, TransferHandle* handle, Completer& completer) final { + void start_read(bufptr_t buffer, TransferHandle* handle, Callback completer) final { start_transfer(buffer, handle, completer); } @@ -106,9 +106,9 @@ public: } }; -class FIBRE_PRIVATE LibusbBulkOutEndpoint : public LibusbBulkEndpoint, public AsyncStreamSink { +class LibusbBulkOutEndpoint : public LibusbBulkEndpoint, public AsyncStreamSink { public: - void start_write(cbufptr_t buffer, TransferHandle* handle, Completer& completer) final { + void start_write(cbufptr_t buffer, TransferHandle* handle, Callback completer) final { start_transfer({ (unsigned char*)buffer.begin(), buffer.size() diff --git a/Firmware/fibre-cpp/platform_support/posix_socket.cpp b/Firmware/fibre-cpp/platform_support/posix_socket.cpp new file mode 100644 index 00000000..21d691e5 --- /dev/null +++ b/Firmware/fibre-cpp/platform_support/posix_socket.cpp @@ -0,0 +1,505 @@ + +#include "posix_socket.hpp" +#include "../logging.hpp" +#include "../print_utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DEFINE_LOG_TOPIC(SOCKET); +USE_LOG_TOPIC(SOCKET); + +#define MAX_CONCURRENT_CONNECTIONS 128 + +using namespace fibre; + +namespace fibre { +/** + * @brief Tag type to print the last socket error. + * + * This is very similar to sys_err(), except that on Windows it uses + * WSAGetLastError() instead of `errno` to fetch the last error code. + */ +struct sock_err { + sock_err() : +#if defined(_WIN32) || defined(_WIN64) + error_number(WSAGetLastError()) {} +#else + error_number(errno) {} +#endif + + sock_err(int error_number) : error_number(error_number) {} + + int error_number; +}; +} + +namespace std { +std::ostream& operator<<(std::ostream& stream, const struct sockaddr_storage& val) { + char buf[128]; + + if ((val.ss_family == AF_INET) && (inet_ntop(val.ss_family, ((struct sockaddr*)&val)->sa_data+2, buf, sizeof(buf)))) { + return stream << buf; + } else if ((val.ss_family == AF_INET6) && (inet_ntop(val.ss_family, ((struct sockaddr*)&val)->sa_data+6, buf, sizeof(buf)))) { + return stream << buf; + } else { + return stream << "(invalid address)"; + } +} + +std::ostream& operator<<(std::ostream& stream, const fibre::sock_err& err) { + return stream << strerror(err.error_number) << " (" << err.error_number << ")"; +} +} + +struct fibre::AddressResolutionContext { + struct addrinfo hints{}; + std::string address_str; + std::string port_str; + EventLoop* event_loop; + Callback> callback; + int cmpl_fd; + struct gaicb gaicb{}; + struct gaicb* list[1]; + + void on_gai_completed(); +}; + +bool fibre::start_resolving_address(EventLoop* event_loop, std::tuple address, bool passive, AddressResolutionContext** handle, Callback> callback) { + // deleted in on_gai_completed() + AddressResolutionContext* ctx = new AddressResolutionContext(); + + ctx->address_str = std::get<0>(address); + ctx->port_str = std::to_string(std::get<1>(address)); + ctx->event_loop = event_loop; + ctx->callback = callback; + + ctx->hints = { + .ai_flags = (passive ? AI_PASSIVE : 0), + .ai_family = AF_UNSPEC, + .ai_socktype = 0, // this makes apparently no difference for numerical addresses + }; + + ctx->gaicb = { + .ar_name = ctx->address_str.c_str(), + .ar_service = ctx->port_str.c_str(), + .ar_request = &ctx->hints + }; + ctx->list[0] = &ctx->gaicb; + + // An extra thread will be created once getaddrinfo_a() completes. This + // thread will post a callback onto the original event loop to do the actual + // handling of the result. This is of course exceedingly stupid but it's + // less bad than throwing around with actual signals that could hit threads + // that don't expect it. + + struct sigevent sig = { + .sigev_value = { .sival_ptr = ctx }, + //.sigev_signo = SIGRTMIN, + .sigev_notify = SIGEV_THREAD, + }; + sig.sigev_notify_function = [](union sigval sigval) { + auto ctx = ((AddressResolutionContext*)sigval.sival_ptr); + ctx->event_loop->post(MEMBER_CB(ctx, on_gai_completed)); + }; + + FIBRE_LOG(D) << "starting address resolution for " << ctx->address_str; + if (getaddrinfo_a(GAI_NOWAIT, ctx->list, 1, &sig) != 0) { + FIBRE_LOG(E) << "getaddrinfo_a() failed"; + delete ctx; + return false; + } + + return true; +} + +void fibre::cancel_resolving_address(AddressResolutionContext* handle) { + gai_cancel(&handle->gaicb); +} + +void AddressResolutionContext::on_gai_completed() { + FIBRE_LOG(D) << "address resolution complete"; + if (gai_error(&gaicb) != 0) { + FIBRE_LOG(W) << "failed to resolve " << address_str << ": " << sys_err(); + } else { + // this returns multiple addresses + for (struct addrinfo* addr = gaicb.ar_result; addr; addr = addr->ai_next) { + FIBRE_LOG(D) << "resolved IP: " << *(struct sockaddr_storage*)addr->ai_addr; + cbufptr_t buf = {(const uint8_t*)addr->ai_addr, (size_t)addr->ai_addrlen}; + callback.invoke(buf); + } + } + freeaddrinfo(gaicb.ar_result); + callback.invoke(std::nullopt); // Announce completion of the request + delete this; +} + +struct fibre::ConnectionContext { + EventLoop* event_loop; + socket_id_t socket_id; + Callback> callback; + + void on_connection_complete(uint32_t mask); + void on_accept(uint32_t mask); +}; + +bool fibre::start_connecting(EventLoop* event_loop, cbufptr_t addr, int type, int protocol, ConnectionContext** ctx, Callback> on_connected) { + auto the_addr = reinterpret_cast(addr.begin()); + + ConnectionContext* context = new ConnectionContext(); + context->event_loop = event_loop; + context->socket_id = socket(the_addr->sa_family, type | SOCK_NONBLOCK, protocol); + context->callback = on_connected; + + if (IS_INVALID_SOCKET(context->socket_id)) { + FIBRE_LOG(E) << "failed to open socket: " << sock_err(); + goto fail0; + } + + if (connect(context->socket_id, the_addr, addr.size()) == 0) { + if (errno != EINPROGRESS) { + FIBRE_LOG(E) << "connect() failed: " << sock_err(); + goto fail1; + } + } + + if (!event_loop->register_event(context->socket_id, EPOLLOUT, MEMBER_CB(context, on_connection_complete))) { + FIBRE_LOG(E) << "failed to register event: " << sock_err(); + goto fail1; + } + + if (ctx) { + *ctx = context; + } + + return true; + +fail1: + close(context->socket_id); +fail0: + delete context; + return false; +} + +void fibre::stop_connecting(ConnectionContext* ctx) { + if (!ctx->event_loop->deregister_event(ctx->socket_id)) { + FIBRE_LOG(W) << "failed to deregister event"; + } + if (close(ctx->socket_id) != 0) { + FIBRE_LOG(W) << "failed to close socket"; + } + ctx->socket_id = INVALID_SOCKET; + ctx->callback.invoke_and_clear(std::nullopt); + delete ctx; +} + +void fibre::ConnectionContext::on_connection_complete(uint32_t mask) { + bool failed; + int error_code; + socklen_t error_code_size = sizeof(error_code); + if (getsockopt(socket_id, SOL_SOCKET, SO_ERROR, &error_code, &error_code_size) != 0) { + FIBRE_LOG(W) << "connection failed (unknown error)"; + failed = true; + } else if (error_code != 0) { + FIBRE_LOG(W) << "connection failed: " << sock_err{error_code}; + failed = true; + } else { + failed = false; + } + + event_loop->deregister_event(socket_id); + callback.invoke(failed ? std::nullopt : std::make_optional(socket_id)); + close(socket_id); // The callback must duplicate the socket id if it intends + // to keep using it. + delete this; +} + +bool fibre::start_listening(EventLoop* event_loop, cbufptr_t addr, int type, int protocol, ConnectionContext** ctx, Callback> on_connected) { + auto the_addr = reinterpret_cast(addr.begin()); + int flag = 1; + + ConnectionContext* context = new ConnectionContext(); + context->event_loop = event_loop; + context->socket_id = socket(the_addr->sa_family, type | SOCK_NONBLOCK, protocol); + context->callback = on_connected; + + if (IS_INVALID_SOCKET(context->socket_id)) { + FIBRE_LOG(E) << "failed to open socket: " << sock_err(); + goto fail0; + } + + // Reuse local address. + // This helps reusing ports that were previously not closed cleanly and + // are therefore still lingering in the TIME_WAIT state. + if (setsockopt(context->socket_id, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag))) { + FIBRE_LOG(E) << "failed to make socket reuse addresses: " << sock_err(); + goto fail1; + } + + if (bind(context->socket_id, the_addr, addr.size())) { + FIBRE_LOG(E) << "failed to bind socket: " << sock_err(); + goto fail1; + } + + // make this socket a passive socket + if (listen(context->socket_id, MAX_CONCURRENT_CONNECTIONS) != 0) { + FIBRE_LOG(E) << "failed to listen on TCP: " << sys_err(); + goto fail1; + } + + if (!event_loop->register_event(context->socket_id, EPOLLIN, MEMBER_CB(context, on_accept))) { + FIBRE_LOG(E) << "failed to register event: " << sock_err(); + goto fail1; + } + + return true; + +fail1: + close(context->socket_id); +fail0: + delete context; + return false; +} + +void fibre::stop_listening(ConnectionContext* ctx) { + stop_connecting(ctx); // same implementation +} + +void fibre::ConnectionContext::on_accept(uint32_t mask) { + struct sockaddr_storage remote_addr; + socklen_t slen = sizeof(remote_addr); + + FIBRE_LOG(D) << "incoming TCP connection"; + int new_socket_id = accept(socket_id, reinterpret_cast(&remote_addr), &slen); + if (IS_INVALID_SOCKET(new_socket_id)) { + FIBRE_LOG(E) << "accept() returned invalid socket: " << sock_err(); + return; // ignore and wait for next incoming connection + } + + callback.invoke(std::make_optional(new_socket_id)); + close(new_socket_id); // The callback must duplicate the socket id if it intends + // to keep using it. +} + +bool PosixSocket::init(EventLoop* event_loop, socket_id_t socket_id) { + if (!IS_INVALID_SOCKET(socket_id_)) { + FIBRE_LOG(E) << "already initialized"; + return false; + } + + socket_id = dup(socket_id); + if (IS_INVALID_SOCKET(socket_id)) { + FIBRE_LOG(E) << "failed to duplicate socket: " << sock_err(); + return false; + } + + //if (!event_loop->register_event(socket_id, 0, MEMBER_CB(this, on_event))) { + // FIBRE_LOG(E) << "failed to register socket event"; + // close(socket_id); + // return false; + //} + + event_loop_ = event_loop; + socket_id_ = socket_id; + return true; +} + +bool PosixSocket::deinit() { + if (IS_INVALID_SOCKET(socket_id_)) { + FIBRE_LOG(E) << "not initialized"; + return false; + } + + bool result = true; + if (::close(socket_id_)) { + FIBRE_LOG(E) << "close() failed: " << sock_err(); + result = false; + } + + socket_id_ = INVALID_SOCKET; + return result; +} + +void PosixSocket::start_read(bufptr_t buffer, TransferHandle* handle, Callback completer) { + if (rx_callback_) { + FIBRE_LOG(E) << "RX request already pending"; + completer.invoke({kStreamError}); + return; + } + + if (handle) { + *handle = reinterpret_cast(this); + } + + auto result = read_sync(buffer); + if (result.has_value()) { + completer.invoke(*result); + } else { + rx_buf_ = buffer; + rx_callback_ = completer; + update_subscription(); + } +} + +void PosixSocket::cancel_read(TransferHandle transfer_handle) { + if (transfer_handle != reinterpret_cast(this)) { + FIBRE_LOG(E) << "invalid handle"; + } else if (!rx_callback_) { + FIBRE_LOG(E) << "no RX pending"; + } else { + rx_callback_.invoke_and_clear({kStreamCancelled, rx_buf_.begin()}); + } +} + +void PosixSocket::start_write(cbufptr_t buffer, TransferHandle* handle, Callback completer) { + if (tx_callback_) { + FIBRE_LOG(E) << "TX request already pending"; + completer.invoke({kStreamError}); + return; + } + + if (handle) { + *handle = reinterpret_cast(this); + } + + auto result = write_sync(buffer); + if (result.has_value()) { + completer.invoke(*result); + } else { + tx_buf_ = buffer; + tx_callback_ = completer; + update_subscription(); + } +} + +void PosixSocket::cancel_write(TransferHandle transfer_handle) { + if (transfer_handle != reinterpret_cast(this)) { + FIBRE_LOG(E) << "invalid handle"; + } else if (!tx_callback_) { + FIBRE_LOG(E) << "no TX pending"; + } else { + tx_callback_.invoke_and_clear({kStreamCancelled, tx_buf_.begin()}); + } +} + +std::optional PosixSocket::read_sync(bufptr_t buffer) { + if (buffer.size() == 0) { + // Empty buffers mess with our socket-close detection + FIBRE_LOG(W) << "empty buffer not permitted"; + } + + socklen_t slen = sizeof(remote_addr_); + ssize_t n_received = recvfrom(socket_id_, buffer.begin(), buffer.size(), + MSG_DONTWAIT, reinterpret_cast(&remote_addr_), &slen); + + if (n_received < 0) { + // If recvfrom returns -1 an errno is set to indicate the error. + auto err = sock_err{}; + if (err.error_number == EAGAIN || err.error_number == EWOULDBLOCK) { + return std::nullopt; + } else { + FIBRE_LOG(E) << "Socket read failed: " << err; + return {{kStreamError, buffer.end()}}; // the function might have written to the buffer + } + + } else if (n_received > buffer.size()) { + FIBRE_LOG(E) << "received too many bytes"; + return {{kStreamError, buffer.end()}}; + + } else if (n_received == 0) { + FIBRE_LOG(D) << "socket closed (RX half)"; + return {{kStreamClosed, buffer.begin()}}; + + } else { + FIBRE_LOG(D) << "Received " << n_received << " bytes from " << remote_addr_; + return {{kStreamOk, buffer.begin() + n_received}}; + } +} + +std::optional PosixSocket::write_sync(cbufptr_t buffer) { + if (buffer.size() == 0) { + // Empty buffers mess with our socket-close detection + FIBRE_LOG(W) << "empty buffer not permitted"; + } + + int n_sent = sendto(socket_id_, buffer.begin(), buffer.size(), MSG_DONTWAIT, + reinterpret_cast(&remote_addr_), sizeof(remote_addr_)); + if (n_sent < 0) { + // If sendto returns -1 an errno is set to indicate the error. + auto err = sock_err{}; + if (err.error_number == EAGAIN || err.error_number == EWOULDBLOCK) { + return std::nullopt; + } else { + FIBRE_LOG(E) << "Socket write failed: " << err; + return {{kStreamError, buffer.end()}}; // the function might have written to the buffer + } + + } else if (n_sent > buffer.size()) { + FIBRE_LOG(E) << "sent too many bytes"; + return {{kStreamError, buffer.end()}}; + + } else if (n_sent == 0) { + FIBRE_LOG(D) << "socket closed (TX half)"; + return {{kStreamClosed, buffer.begin()}}; + + } else { + FIBRE_LOG(D) << "Sent " << n_sent << " bytes to " << remote_addr_; + return {{kStreamOk, buffer.begin() + n_sent}}; + } +} + +void PosixSocket::update_subscription() { + uint32_t new_mask = (tx_callback_ ? EPOLLOUT : 0) + | (rx_callback_ ? EPOLLIN : 0); + if (new_mask != mask_) { + if (mask_) { + event_loop_->deregister_event(socket_id_); + } + mask_ = new_mask; + if (new_mask) { + event_loop_->register_event(socket_id_, new_mask, MEMBER_CB(this, on_event)); + } + } +} + +void PosixSocket::on_event(uint32_t mask) { + + if (mask & EPOLLIN) { + // The socket is ready for RX. If an RX request is pending, handle it + // here, otherwise ignore the event. + + if (rx_callback_) { + auto result = read_sync(rx_buf_); + rx_buf_ = {}; + if (result.has_value()) { + rx_callback_.invoke_and_clear(*result); + } + } + } + + if (mask & EPOLLOUT) { + // The socket is ready for RX. If an RX request is pending, handle it + // here, otherwise ignore the event. + + if (tx_callback_) { + auto result = write_sync(tx_buf_); + tx_buf_ = {}; + if (result.has_value()) { + tx_callback_.invoke_and_clear(*result); + } + } + } + + if (mask & ~(EPOLLIN | EPOLLOUT)) { + FIBRE_LOG(E) << "unknown event mask: " << as_hex(mask); + } + + update_subscription(); +} diff --git a/Firmware/fibre-cpp/platform_support/posix_socket.hpp b/Firmware/fibre-cpp/platform_support/posix_socket.hpp new file mode 100644 index 00000000..ed7151f2 --- /dev/null +++ b/Firmware/fibre-cpp/platform_support/posix_socket.hpp @@ -0,0 +1,173 @@ +#ifndef __FIBRE_POSIX_SOCKET_HPP +#define __FIBRE_POSIX_SOCKET_HPP + +#include +#include +#include +#include +#include +#include + +namespace fibre { + + +#if defined(__linux__) +//using PosixSocketWorker = LinuxWorker; // TODO: rename to EPollWorker or LinuxEPollWorker +using socket_id_t = int; +#elif defined(_WIN32) || defined(_WIN64) +//using PosixSocketWorker = PosixPollWorker; +using socket_id_t = SOCKET; +#else +//using PosixSocketWorker = KQueueWorker; +using socket_id_t = int; +#endif + +#if defined(_Win32) || defined(_Win64) +#define IS_INVALID_SOCKET(socket_id) (socket_id == INVALID_SOCKET) +#else +#define INVALID_SOCKET (-1) +#define IS_INVALID_SOCKET(socket_id) (socket_id < 0) +#endif + +struct AddressResolutionContext; +struct ConnectionContext; + +/** + * @brief Starts resolving a hostname (such as www.google.com) to one or + * multiple IP addresses. + * + * If available, both IPv4 and IPv6 addresses are returned. + * + * @param passive: If false, the returned address will be suitable for use with + * connect(2), sendto(2), or sendmsg(2). + * @param callback: Invoked for every address that is found. Invoked with null + * if no more addresses are available, including in case of an error or + * cancellation. + * + * @returns: false if the lookup could not be started. `callback` will not be + * called. + */ +bool start_resolving_address(EventLoop* event_loop, + std::tuple address, bool passive, + AddressResolutionContext** handle, + Callback> callback); + +/** + * @brief Cancels the ongoing address resolution. + * + * The cancellation is complete once the associated callback is invoked with + * null. + */ +void cancel_resolving_address(AddressResolutionContext* handle); + +/** + * @brief Starts connecting to the specified address + * + * @param addr: The address to connect to. Usually this buffer contains an + * address of the type `struct sockaddr`. The family parameter of this + * address will be passed as 1st argument to socket(). + * @param type: Will be passed as 2nd argument to socket(). Can be for + * instance SOCK_DGRAM or SOCKET_STREAM. + * @param protocol: Will be passed as 3rd argument to socket(). Can be for + * instance IPPROTO_UDP or IPPROTO_TCP. + * @param on_connected: Called when the connection attempt succeeds or fails. + * If the connection was established, the socket ID is passed to the + * callback. This socket ID will only be valid for the duration of the + * callback and must be duplicated (dup) if the application intends to + * keep using it. + * If the connection failed, std::nullopt is passed. + */ +bool start_connecting(EventLoop* event_loop, cbufptr_t addr, int type, int protocol, ConnectionContext** ctx, Callback> on_connected); +void stop_connecting(ConnectionContext* ctx); + +/** + * @brief Starts listening and accepting connections on the specified local + * address. + * + * @param addr: The local address to listen on. Usually this buffer contains an + * address of the type `struct sockaddr`. The family parameter of this + * address will be passed as 1st argument to socket(). + * @param type: Will be passed as 2nd argument to socket(). Can be for + * instance SOCK_DGRAM or SOCKET_STREAM. + * @param protocol: Will be passed as 3rd argument to socket(). Can be for + * instance IPPROTO_UDP or IPPROTO_TCP. + * @param on_connected: Called for every connection that is accepted. The new + * socket ID is passed to the callback. This socket ID will only be valid + * for the duration of the callback and must be duplicated (dup) if the + * application intends to keep using it. + * If the attempt to listen fails permanently or is cancelled, + * std::nullopt is passed. + */ +bool start_listening(EventLoop* event_loop, cbufptr_t addr, int type, int protocol, ConnectionContext** ctx, Callback> on_connected); +void stop_listening(ConnectionContext* ctx); + +/** + * @brief AsyncStreamSource and AsyncStreamSink based on a Posix or WinSock + * socket ID. + * + * Note: To make this work on Windows, a "poll"-based worker must be implemented. + */ +class PosixSocket : public AsyncStreamSource, public AsyncStreamSink { +public: + /** + * @brief Initializes the object with the given socket ID. + * + * The socket must be bound to a local address before this function is + * called. + * + * @param socket_id: For Unix-like systems this should be a file descriptor, + * for Windows this should be a Windows Socket ID (as returned by + * socket()). + * The socket must be in non-blocking mode (opened with O_NONBLOCK). + * The socket will internally be duplicated using dup() so it can be + * closed after this call. + */ + bool init(EventLoop* event_loop, socket_id_t socket_id); + + /** + * @brief Deinits a socket that was initialized with init(). + */ + bool deinit(); + + void start_read(bufptr_t buffer, TransferHandle* handle, Callback completer) final; + void cancel_read(TransferHandle transfer_handle) final; + + void start_write(cbufptr_t buffer, TransferHandle* handle, Callback completer) final; + void cancel_write(TransferHandle transfer_handle) final; + + /** + * @brief Returns the remote address of this socket. + * + * For connectionless sockets this is origin of the most recently received + * data and it is only valid from the moment something was actually received. + * + * For connection-oriented sockets this address is valid as soon as the + * socket is initialized. + */ + struct sockaddr_storage get_remote_address() const { return remote_addr_; } + +private: + std::optional read_sync(bufptr_t buffer); + std::optional write_sync(cbufptr_t buffer); + void update_subscription(); + void on_event(uint32_t mask); + + int socket_id_ = INVALID_SOCKET; + EventLoop* event_loop_ = nullptr; + struct sockaddr_storage remote_addr_ = {0}; // updated after each RX event + uint32_t mask_ = 0; // current event subscription mask + bufptr_t rx_buf_{}; // valid while there is an RX request pending + cbufptr_t tx_buf_{}; // valid while there is a TX request pending + Callback rx_callback_; // valid while there is an RX request pending + Callback tx_callback_; // valid while there is a TX request pending +}; + +} + +#include + +namespace std { +std::ostream& operator<<(std::ostream& stream, const struct sockaddr_storage& val); +} + +#endif // __FIBRE_POSIX_SOCKET_HPP \ No newline at end of file diff --git a/Firmware/fibre-cpp/platform_support/posix_tcp_backend.cpp b/Firmware/fibre-cpp/platform_support/posix_tcp_backend.cpp new file mode 100644 index 00000000..8971701d --- /dev/null +++ b/Firmware/fibre-cpp/platform_support/posix_tcp_backend.cpp @@ -0,0 +1,137 @@ + +#include "posix_tcp_backend.hpp" +#include "posix_socket.hpp" +#include "../logging.hpp" +#include +#include +#include +#include + +DEFINE_LOG_TOPIC(TCP); +USE_LOG_TOPIC(TCP); + +using namespace fibre; + +bool PosixTcpBackend::init(EventLoop* event_loop) { + if (event_loop_) { + FIBRE_LOG(E) << "already initialized"; + return false; + } + event_loop_ = event_loop; + return true; +} + +bool PosixTcpBackend::deinit() { + if (!event_loop_) { + FIBRE_LOG(E) << "not initialized"; + return false; + } + if (n_discoveries_) { + FIBRE_LOG(W) << "some discoveries still ongoing"; + } + event_loop_ = nullptr; + return true; +} + +void PosixTcpBackend::start_channel_discovery(const char* specs, size_t specs_len, ChannelDiscoveryContext** handle, Callback on_found_channels) { + const char* address_begin; + const char* address_end; + int port; + + if (!event_loop_) { + FIBRE_LOG(E) << "not initialized"; + on_found_channels.invoke({kFibreInvalidArgument, nullptr, nullptr, 0}); + return; + } + + if (!try_parse_key(specs, specs + specs_len, "address", &address_begin, &address_end)) { + FIBRE_LOG(E) << "no address specified"; + on_found_channels.invoke({kFibreInvalidArgument, nullptr, nullptr, 0}); + return; + } + + if (!try_parse_key(specs, specs + specs_len, "port", &port)) { + FIBRE_LOG(E) << "no port specified"; + on_found_channels.invoke({kFibreInvalidArgument, nullptr, nullptr, 0}); + return; + } + + n_discoveries_++; + + TcpChannelDiscoveryContext* ctx = new TcpChannelDiscoveryContext(); // TODO: free + ctx->parent = this; + ctx->address = {{address_begin, address_end}, port}; + ctx->on_found_channels = on_found_channels; + ctx->resolve_address(); +} + +int PosixTcpBackend::stop_channel_discovery(ChannelDiscoveryContext* handle) { + // TODO + n_discoveries_--; + return 0; +} + +void PosixTcpBackend::TcpChannelDiscoveryContext::resolve_address() { + if (addr_resolution_ctx) { + FIBRE_LOG(E) << "already resolving"; + return; + } + + if (!start_resolving_address(parent->event_loop_, address, false, &addr_resolution_ctx, MEMBER_CB(this, on_found_address))) { + FIBRE_LOG(E) << "cannot start address resolution"; + return; + } +} + +void PosixTcpBackend::TcpChannelDiscoveryContext::on_found_address(std::optional addr) { + FIBRE_LOG(D) << "found address"; + + if (addr.has_value()) { + // Resolved an address. If it wasn't already known, try to connect to it. + std::vector vec{addr->begin(), addr->end()}; + bool is_known = std::find_if(known_addresses.begin(), known_addresses.end(), + [&](AddrContext& val){ return val.addr == vec; }) != known_addresses.end(); + + if (!is_known) { + AddrContext ctx = {.addr = vec}; + if (parent->start_opening_connections(parent->event_loop_, *addr, SOCK_STREAM, IPPROTO_TCP, &ctx.connection_ctx, MEMBER_CB(this, on_connected))) { + known_addresses.push_back(ctx); + } else { + // TODO + } + } + } else { + // No more addresses. + addr_resolution_ctx = nullptr; + if (known_addresses.size() == 0) { + // No addresses could be found. Try again using exponential backoff. + parent->event_loop_->call_later(lookup_period, MEMBER_CB(this, resolve_address)); + lookup_period = std::min(lookup_period * 3.0f, 3600.0f); // exponential backoff with at most 1h period + } else { + // Some addresses are known from this lookup or from a previous + // lookup. Resolve addresses again in 1h. + parent->event_loop_->call_later(3600.0, MEMBER_CB(this, resolve_address)); + } + } +} + +void PosixTcpBackend::TcpChannelDiscoveryContext::on_connected(std::optional socket_id) { + if (socket_id.has_value()) { + auto socket = new PosixSocket{}; // TODO: free + if (socket->init(parent->event_loop_, *socket_id)) { + on_found_channels.invoke({kFibreOk, socket, socket, SIZE_MAX}); + return; + } + delete socket; + } + + FIBRE_LOG(D) << "not connected"; + // Try to reconnect soon + lookup_period = 1.0f; + resolve_address(); +} + +void PosixTcpBackend::TcpChannelDiscoveryContext::on_disconnected() { + lookup_period = 1.0f; // reset exponential backoff + resolve_address(); +} diff --git a/Firmware/fibre-cpp/platform_support/posix_tcp_backend.hpp b/Firmware/fibre-cpp/platform_support/posix_tcp_backend.hpp new file mode 100644 index 00000000..7e09d5df --- /dev/null +++ b/Firmware/fibre-cpp/platform_support/posix_tcp_backend.hpp @@ -0,0 +1,80 @@ +#ifndef __FIBRE_POSIX_TCP_BACKEND_HPP +#define __FIBRE_POSIX_TCP_BACKEND_HPP + +#include +#include "posix_socket.hpp" +#include +#include +#include + +namespace fibre { + +/** + * TCP client and TCP server implementations are identical up to the function + * that is used to convert an address to one or more connected socket IDs. + * The client uses the posix function `connect` to do so, while the server uses + * the posix functions `listen` and `accept`. + */ +class PosixTcpBackend : public ChannelDiscoverer { +public: + bool init(EventLoop* event_loop); + bool deinit(); + + void start_channel_discovery(const char* specs, size_t specs_len, ChannelDiscoveryContext** handle, Callback on_found_channels) final; + int stop_channel_discovery(ChannelDiscoveryContext* handle) final; + +private: + struct TcpChannelDiscoveryContext { + PosixTcpBackend* parent; + std::tuple address; + Callback on_found_channels; + AddressResolutionContext* addr_resolution_ctx; + ConnectionContext* connection_ctx; + float lookup_period = 1.0f; // wait 1s for next address resolution + + struct AddrContext { + std::vector addr; + ConnectionContext* connection_ctx; + }; + + std::vector known_addresses; + void resolve_address(); + void on_found_address(std::optional addr); + void on_connected(std::optional socket_id); + void on_disconnected(); + }; + + virtual bool start_opening_connections(EventLoop* event_loop, cbufptr_t addr, int type, int protocol, ConnectionContext** ctx, Callback> on_connected) = 0; + virtual void cancel_opening_connections(ConnectionContext* ctx) = 0; + + EventLoop* event_loop_ = nullptr; + size_t n_discoveries_ = 0; +}; + +class PosixTcpClientBackend : public PosixTcpBackend { +public: + constexpr static const char* get_name() { return "tcp-client"; } + + bool start_opening_connections(EventLoop* event_loop, cbufptr_t addr, int type, int protocol, ConnectionContext** ctx, Callback> on_connected) final { + return start_connecting(event_loop, addr, type, protocol, ctx, on_connected); + } + void cancel_opening_connections(ConnectionContext* ctx) final { + stop_connecting(ctx); + } +}; + +class PosixTcpServerBackend : public PosixTcpBackend { +public: + constexpr static const char* get_name() { return "tcp-server"; } + + bool start_opening_connections(EventLoop* event_loop, cbufptr_t addr, int type, int protocol, ConnectionContext** ctx, Callback> on_connected) final { + return start_listening(event_loop, addr, type, protocol, ctx, on_connected); + } + void cancel_opening_connections(ConnectionContext* ctx) final { + stop_listening(ctx); + } +}; + +} + +#endif // __FIBRE_POSIX_TCP_BACKEND_HPP \ No newline at end of file diff --git a/Firmware/fibre-cpp/posix_tcp.cpp b/Firmware/fibre-cpp/posix_tcp.cpp deleted file mode 100644 index 57ce0df9..00000000 --- a/Firmware/fibre-cpp/posix_tcp.cpp +++ /dev/null @@ -1,108 +0,0 @@ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - - -#define TCP_RX_BUF_LEN 512 - -class TCPStreamSink : public StreamSink { -public: - TCPStreamSink(int socket_fd) : - socket_fd_(socket_fd) - {} - - int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) { - int bytes_sent = send(socket_fd_, buffer, length, 0); - if (processed_bytes) - *processed_bytes = (bytes_sent == -1) ? 0 : bytes_sent; - return (bytes_sent == -1) ? -1 : 0; - } - - size_t get_free_space() { return SIZE_MAX; } - -private: - int socket_fd_; -}; - - -int serve_client(int sock_fd) { - uint8_t buf[TCP_RX_BUF_LEN]; - - // initialize output stack for this client - TCPStreamSink tcp_packet_output(sock_fd); - StreamBasedPacketSink packet2stream(tcp_packet_output); - BidirectionalPacketBasedChannel channel(packet2stream); - - StreamToPacketSegmenter stream2packet(channel); - - // now listen for it - for (;;) { - memset(buf, 0, sizeof(buf)); - // returns as soon as there is some data - ssize_t n_received = recv(sock_fd, buf, sizeof(buf), 0); - - // -1 indicates error and 0 means that the client gracefully terminated - if (n_received == -1 || n_received == 0) { - close(sock_fd); - return n_received; - } - - // input processing stack - size_t processed = 0; - stream2packet.process_bytes(buf, n_received, &processed); - } -} - -// function to check if a worker thread handling a single client is done -template -bool future_is_ready(std::future& t){ - return t.wait_for(std::chrono::seconds(0)) == std::future_status::ready; -} - -int serve_on_tcp(unsigned int port) { - struct sockaddr_in6 si_me, si_other; - int s; - - - if ((s=socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) == -1) { - return -1; - } - - memset((char *) &si_me, 0, sizeof(si_me)); - si_me.sin6_family = AF_INET6; - si_me.sin6_port = htons(port); - si_me.sin6_flowinfo = 0; - si_me.sin6_addr = in6addr_any; - if (bind(s, reinterpret_cast(&si_me), sizeof(si_me)) == -1) { - return -1; - } - - listen(s, 128); // make this socket a passive socket - std::vector> serv_pool; - for (;;) { - memset(&si_other, 0, sizeof(si_other)); - - socklen_t silen = sizeof(si_other); - // TODO: Add a limit on accepting connections - int client_portal_fd = accept(s, reinterpret_cast(&si_other), &silen); // blocking call - serv_pool.push_back(std::async(std::launch::async, serve_client, client_portal_fd)); - // do a little clean up on the pool - for (std::vector>::iterator it = serv_pool.end()-1; it >= serv_pool.begin(); --it) { - if (future_is_ready(*it)) { - // we can erase this thread - serv_pool.erase(it); - } - } - } - - close(s); -} - diff --git a/Firmware/fibre-cpp/posix_udp.cpp b/Firmware/fibre-cpp/posix_udp.cpp deleted file mode 100644 index 0cc9d09c..00000000 --- a/Firmware/fibre-cpp/posix_udp.cpp +++ /dev/null @@ -1,70 +0,0 @@ - -#include -#include -#include -#include -#include - -#include - -#define UDP_RX_BUF_LEN 512 -#define UDP_TX_BUF_LEN 512 - - -class UDPPacketSender : public PacketSink { -public: - UDPPacketSender(int socket_fd, struct sockaddr_in6 *si_other) : - _socket_fd(socket_fd), - _si_other(si_other) - {} - - size_t get_mtu() { return UDP_TX_BUF_LEN; } - - int process_packet(const uint8_t* buffer, size_t length) { - // cannot send partial packets - if (length > get_mtu()) - return -1; - - int status = sendto(_socket_fd, buffer, length, 0, reinterpret_cast(_si_other), sizeof(*_si_other)); - return (status == -1) ? -1 : 0; - } - -private: - int _socket_fd; - struct sockaddr_in6 *_si_other; -}; - - - -int serve_on_udp(unsigned int port) { - struct sockaddr_in6 si_me, si_other; - int s; - socklen_t slen = sizeof(si_other); - uint8_t buf[UDP_RX_BUF_LEN]; - - if ((s=socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP)) == -1) - return -1; - - memset((char *) &si_me, 0, sizeof(si_me)); - si_me.sin6_family = AF_INET6; - si_me.sin6_port = htons(port); - si_me.sin6_flowinfo = 0; - si_me.sin6_addr= in6addr_any; - if (bind(s, reinterpret_cast(&si_me), sizeof(si_me)) == -1) - return -1; - - for (;;) { - ssize_t n_received = recvfrom(s, buf, sizeof(buf), 0, reinterpret_cast(&si_other), &slen); - if (n_received == -1) - return -1; - //printf("Received packet from %s:%d\nData: %s\n\n", - // inet_ntoa(si_other.sin_addr), ntohs(si_other.sin_port), buf); - - UDPPacketSender udp_packet_output(s, &si_other); - BidirectionalPacketBasedChannel udp_channel(udp_packet_output); - udp_channel.process_packet(buf, n_received); - } - - close(s); -} - diff --git a/Firmware/fibre-cpp/include/fibre/protocol.hpp b/Firmware/fibre-cpp/protocol.hpp similarity index 99% rename from Firmware/fibre-cpp/include/fibre/protocol.hpp rename to Firmware/fibre-cpp/protocol.hpp index 9c039e7d..4d1ab1fc 100644 --- a/Firmware/fibre-cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre-cpp/protocol.hpp @@ -13,10 +13,9 @@ see protocol.md for the protocol specification #include #include #include -#include "crc.hpp" -#include "cpp_utils.hpp" -#include "bufptr.hpp" -#include "simple_serdes.hpp" +#include +#include +#include typedef struct { diff --git a/Firmware/fibre-cpp/stream_utils.hpp b/Firmware/fibre-cpp/stream_utils.hpp index bc137bbb..42f99740 100644 --- a/Firmware/fibre-cpp/stream_utils.hpp +++ b/Firmware/fibre-cpp/stream_utils.hpp @@ -97,7 +97,7 @@ class AsyncStreamSinkMultiplexer : public AsyncStreamSink, Completer& completer) final { + void start_write(cbufptr_t buffer, TransferHandle* handle, Callback completer) final { for (size_t i = 0; i < NSlots; ++i) { auto& [slot_in_use, slot_buf, slot_completer] = slots_[i]; if (!__atomic_exchange_n(&slot_in_use, true, __ATOMIC_SEQ_CST)) {