Merge commit 'e9a9a4d4f3483bddee1864de28adb506bcf10dfa' into libfibre

This commit is contained in:
Samuel Sadok
2021-01-07 20:40:43 +01:00
49 changed files with 3416 additions and 2546 deletions
+62
View File
@@ -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"]
+59 -12
View File
@@ -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
+28 -46
View File
@@ -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
+47
View File
@@ -0,0 +1,47 @@
#include <fibre/channel_discoverer.hpp>
#include <string.h>
#include <stdio.h>
#include <algorithm>
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;
}
@@ -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 <<EOF > 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 <<EOF > 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 <<EOF > 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 <<EOF > 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 <<EOF > 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
@@ -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
@@ -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
@@ -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
+8
View File
@@ -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
@@ -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
-45
View File
@@ -1,45 +0,0 @@
#ifndef __FIBRE_EVENT_LOOP_HPP
#define __FIBRE_EVENT_LOOP_HPP
#include <stdint.h>
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
+251
View File
@@ -0,0 +1,251 @@
#include <fibre/fibre.hpp>
#include "logging.hpp"
#include <fibre/channel_discoverer.hpp>
#include "legacy_protocol.hpp"
#include "print_utils.hpp"
#include <memory>
#include <algorithm>
#if FIBRE_ALLOW_HEAP
#include <unordered_map>
#include <string>
#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<typename T>
T* my_alloc() {
return new T{};
}
template<typename T>
void my_free(T* ctx) {
delete ctx;
}
#else
template<typename T>
struct TheInstance {
static T instance;
static bool in_use;
};
template<typename T> T TheInstance<T>::instance{};
template<typename T> bool TheInstance<T>::in_use = false;
template<typename T>
T* my_alloc() {
if (!TheInstance<T>::in_use) {
TheInstance<T>::in_use = true;
return &TheInstance<T>::instance;
} else {
return nullptr;
}
}
template<typename T>
void my_free(T* ctx) {
if (ctx == &TheInstance<T>::instance) {
TheInstance<T>::in_use = false;
} else {
FIBRE_LOG(E) << "bad instance";
}
}
#endif
bool fibre::launch_event_loop(Callback<void, EventLoop*> on_started) {
#if FIBRE_ENABLE_EVENT_LOOP
EventLoopImpl* event_loop = my_alloc<EventLoopImpl>(); // TODO: free
return event_loop->start([&](){ on_started.invoke(event_loop); });
#else
return false;
#endif
}
struct BackendInitializer {
template<typename T>
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<typename T>
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<Context>();
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) <<ctx->n_domains << " domains are still open";
}
for_each_in_tuple(BackendDeinitializer{ctx},
ctx->static_backends);
my_free<Context>(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<void, Object*, Interface*> on_found_object, Callback<void, Object*> 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<LegacyObject> obj) {
root_object_ = reinterpret_cast<Object*>(obj.get());
root_intf_ = reinterpret_cast<Interface*>(obj->intf.get());
on_found_object_.invoke(reinterpret_cast<Object*>(obj.get()),
reinterpret_cast<Interface*>(obj->intf.get()));
}
void Domain::on_lost_root_object(LegacyObjectClient* obj_client) {
root_object_ = nullptr;
root_intf_ = nullptr;
on_lost_object_.invoke(reinterpret_cast<Object*>(obj_client->root_obj_.get()));
}
#endif
void Domain::on_stopped(LegacyProtocolPacketBased* protocol, StreamStatus status) {
delete protocol;
}
@@ -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 %]
+77
View File
@@ -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
@@ -1,7 +1,8 @@
#ifndef __FIBRE_ASYNC_STREAM_HPP
#define __FIBRE_ASYNC_STREAM_HPP
#include "include/fibre/bufptr.hpp" // TODO: move this header
#include <fibre/bufptr.hpp>
#include <fibre/callback.hpp>
#include <stdint.h>
namespace fibre {
@@ -13,34 +14,6 @@ enum StreamStatus {
kStreamError
};
template<typename ... TResults>
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<typename ... TResults>
static void safe_complete(Completer<TResults...>*& completer, TResults ... results) {
Completer<TResults...>* 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<WriteResult> {
virtual void on_write_finished(WriteResult result) = 0;
void complete(WriteResult result) final {
on_write_finished(result);
}
};
struct ReadCompleter : Completer<ReadResult> {
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<ReadResult>& completer) = 0;
virtual void start_read(bufptr_t buffer, TransferHandle* handle, Callback<void, ReadResult> 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<WriteResult>& completer) = 0;
virtual void start_write(cbufptr_t buffer, TransferHandle* handle, Callback<void, WriteResult> completer) = 0;
/**
* @brief Cancels an operation that was previously started with start_write().
@@ -0,0 +1,126 @@
#ifndef __CALLBACK_HPP
#define __CALLBACK_HPP
#include <stdlib.h>
#include <typeinfo>
#include <tuple>
#include <functional>
#include <type_traits>
namespace fibre {
namespace detail {
template<typename T> struct get_default { static T val() { return {}; } };
template<> struct get_default<void> { static void val() {} };
}
template<typename TRet, typename ... TArgs>
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<typename TRetOther, typename ... TArgsOther>
//Callback(const Callback<TRetOther, TArgsOther...>& other) : cb_(other.cb_), ctx_(other.ctx_) {
// static_assert(std::is_same<Callback<TRetOther, TArgsOther...>, 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<typename TRetOther, typename ... TArgsOther>
Callback(const Callback<TRetOther, TArgsOther...>& other) = delete;
/**
* @brief Constructs a callback object from a functor. The functor must
* remain allocated throughout the lifetime of the Callback.
*/
template<typename TFunc>
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<TRet>::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<TRet>::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<typename _TRet, typename _TObj, typename ... _TArgs>
struct function_traits {
using TRet = _TRet;
using TArgs = std::tuple<_TArgs...>;
using TObj = _TObj;
};
template<typename _TRet, typename _TObj, typename ... _TArgs>
function_traits<_TRet, _TObj, _TArgs...> make_function_traits(_TRet (_TObj::*)(_TArgs...)) {
return {};
}
template<typename T1, T1 T2, typename T3, typename T4, typename T5>
struct MemberCallback;
template<typename T, T func, typename TObj, typename TRes, typename ... TArgs>
struct MemberCallback<T, func, TObj, TRes, std::tuple<TArgs...>> {
using cb_t = Callback<TRes, TArgs...>;
static cb_t with(TObj* obj) {
return cb_t{[](void* obj, TArgs... arg) {
return (((TObj*)obj)->*func)(arg...);
}, obj};
}
};
template<typename T, T func,
typename TTraits = decltype(make_function_traits(func)),
typename MemCb = MemberCallback<T, func, typename TTraits::TObj, typename TTraits::TRet, typename TTraits::TArgs>>
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<decltype(*obj)>::func), \
&std::remove_reference_t<decltype(*obj)>::func \
>(obj)
}
#endif // __CALLBACK_HPP
@@ -0,0 +1,34 @@
#ifndef __FIBRE_CHANNEL_DISCOVERER
#define __FIBRE_CHANNEL_DISCOVERER
#include "async_stream.hpp"
#include <fibre/callback.hpp>
#include <fibre/status.hpp>
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<void, ChannelDiscoveryResult> 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
@@ -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<T> make_optional(T&& val) {
return optional<T>{std::forward<T>(val)};
}
template<typename T>
optional<T> make_optional(T& val) {
return optional<T>{val};
}
} // namespace std
#endif
@@ -1,336 +0,0 @@
#ifndef __DECODERS_HPP
#define __DECODERS_HPP
#include "protocol.hpp"
#include "crc.hpp"
#include "cpp_utils.hpp"
#include <utility>
/* 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<unsigned BLOCKSIZE>
class BlockDecoder {
public:
typedef std::integral_constant<size_t, BLOCKSIZE> 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<typename T, ENABLE_IF(TypeChecker<T>::template all_are<BlockDecoder<T::block_size::value>>())>
class StreamDecoder_from_BlockDecoder : public StreamDecoder {
public:
// @brief Imitates the constructor signature of the encapsulated type.
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<StreamDecoder_from_BlockDecoder>())>
explicit StreamDecoder_from_BlockDecoder(Args&& ... args)
: block_decoder_(std::forward<Args>(args)...) {
EXPECT_TYPE(T, BlockDecoder<T::block_size::value>);
}
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<typename T, ENABLE_IF(TypeChecker<T>::template all_are<ByteDecoder>())>
class BlockDecoder_from_ByteDecoder : public BlockDecoder<1> {
public:
// @brief Imitates the constructor signature of the encapsulated type.
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<BlockDecoder_from_ByteDecoder>())>
BlockDecoder_from_ByteDecoder(Args&& ... args)
: byte_decoder_(std::forward<Args>(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<typename T, ENABLE_IF(TypeChecker<T>::template all_are<ByteDecoder>())>
class StreamDecoder_from_ByteDecoder : public StreamDecoder {
public:
// @brief Imitates the constructor signature of the encapsulated type.
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<StreamDecoder_from_ByteDecoder>())>
StreamDecoder_from_ByteDecoder(Args&& ... args)
: byte_decoder_(std::forward<Args>(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<typename T>
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<T>(input_byte & 0x7f) << bit_pos_);
if (((state_variable_ >> bit_pos_) & 0x7f) != static_cast<T>(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<typename T>
using VarintStreamDecoder = StreamDecoder_from_ByteDecoder<VarintByteDecoder<T>>;
// This double nested type should work identically but makes it way harder for the compiler to optimize
//template<typename T>
//using VarintBlockDecoder = BlockDecoder_from_ByteDecoder<VarintByteDecoder<T>>;
//template<typename T>
//using VarintStreamDecoder = StreamDecoder_from_BlockDecoder<VarintBlockDecoder<T>>;
template<typename T>
inline VarintStreamDecoder<T> make_varint_decoder(T& variable) {
return VarintStreamDecoder<T>(variable);
}
inline VarintStreamDecoder<GET_TYPE_OF(&ReceiverState::endpoint_id)> make_endpoint_id_decoder(ReceiverState& state) {
return make_varint_decoder(state.endpoint_id);
}
inline VarintStreamDecoder<GET_TYPE_OF(&ReceiverState::length)> make_length_decoder(ReceiverState& state) {
return make_varint_decoder(state.length);
}
template<uint8_t INIT, uint8_t POLYNOMIAL, typename TDecoder,
ENABLE_IF(TypeChecker<TDecoder>::template all_are<StreamDecoder>())>
class CRC8BlockDecoder : public BlockDecoder<CRC8_BLOCKSIZE> {
public:
CRC8BlockDecoder(TDecoder&& inner_decoder) :
inner_decoder_(std::forward<TDecoder>(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<POLYNOMIAL>(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<unsigned INIT, unsigned POLYNOMIAL, typename TDecoder>
using CRC8StreamDecoder = StreamDecoder_from_BlockDecoder<CRC8BlockDecoder<INIT, POLYNOMIAL, TDecoder>>;
template<unsigned INIT, unsigned POLYNOMIAL, typename TDecoder>
inline CRC8StreamDecoder<INIT, POLYNOMIAL, TDecoder> make_crc8_decoder(TDecoder&& decoder) {
return CRC8StreamDecoder<INIT, POLYNOMIAL, TDecoder>(std::forward<TDecoder>(decoder));
}
// TODO: ENABLE_IF(TypeChecker<TDecoders...>::template all_are<StreamDecoder>())
template<typename ... TDecoders>
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<typename TDecoder, typename ... TDecoders>
class DecoderChain<TDecoder, TDecoders...> : public StreamDecoder {
public:
DecoderChain(TDecoder&& this_decoder, TDecoders&& ... subsequent_decoders) :
this_decoder_(std::forward<TDecoder>(this_decoder)),
subsequent_decoders_(std::forward<TDecoders>(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<TDecoders...> subsequent_decoders_;
};
template<typename ... TDecoders>
inline DecoderChain<TDecoders...> make_decoder_chain(TDecoders&& ... decoders) {
return DecoderChain<TDecoders...>(std::forward<TDecoders>(decoders)...);
}
#endif // __DECODERS_HPP
@@ -1,323 +0,0 @@
#ifndef __ENCODERS_HPP
#define __ENCODERS_HPP
#include "protocol.hpp"
#include "crc.hpp"
#include "cpp_utils.hpp"
#include <utility>
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<unsigned BLOCKSIZE>
class BlockEncoder {
public:
typedef std::integral_constant<size_t, BLOCKSIZE> 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<typename T, ENABLE_IF(TypeChecker<T>::template all_are<BlockEncoder<T::block_size::value>>())>
class StreamEncoder_from_BlockEncoder : public StreamEncoder {
public:
// @brief Imitates the constructor signature of the encapsulated type.
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<StreamEncoder_from_BlockEncoder>())>
explicit StreamEncoder_from_BlockEncoder(Args&& ... args)
: block_encoder_(std::forward<Args>(args)...) {
EXPECT_TYPE(T, BlockEncoder<T::block_size::value>);
}
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<typename T, ENABLE_IF(TypeChecker<T>::template all_are<ByteEncoder>())>
class BlockEncoder_from_ByteEncoder : public BlockEncoder<1> {
public:
// @brief Imitates the constructor signature of the encapsulated type.
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<BlockEncoder_from_ByteEncoder>())>
BlockEncoder_from_ByteEncoder(Args&& ... args)
: byte_encoder_(std::forward<Args>(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<typename T, ENABLE_IF(TypeChecker<T>::template all_are<ByteEncoder>())>
class StreamEncoder_from_ByteEncoder : public StreamEncoder {
public:
// @brief Imitates the constructor signature of the encapsulated type.
template<typename ... Args, ENABLE_IF(TypeChecker<Args...>::template first_is_not<StreamEncoder_from_ByteEncoder>())>
StreamEncoder_from_ByteEncoder(Args&& ... args)
: byte_encoder_(std::forward<Args>(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<typename T>
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<typename T>
using VarintStreamEncoder = StreamEncoder_from_ByteEncoder<VarintByteEncoder<T>>;
template<typename T>
VarintStreamEncoder<T> make_varint_encoder(const T& variable) {
return VarintStreamEncoder<T>(variable);
}
VarintStreamEncoder<GET_TYPE_OF(&Request::endpoint_id)> make_endpoint_id_encoder(const Request& request) {
return make_varint_encoder(request.endpoint_id);
}
VarintStreamEncoder<GET_TYPE_OF(&Request::length)> make_length_encoder(const Request& request) {
return make_varint_encoder(request.length);
}
template<uint8_t INIT, uint8_t POLYNOMIAL, typename TEncoder,
ENABLE_IF(TypeChecker<TEncoder>::template all_are<StreamEncoder>())>
class CRC8BlockEncoder : public BlockEncoder<CRC8_BLOCKSIZE> {
public:
CRC8BlockEncoder(TEncoder&& inner_encoder)
: inner_encoder_(std::forward<TEncoder>(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<POLYNOMIAL>(current_crc_, block, CRC8_BLOCKSIZE - 1);
return 0;
}
private:
TEncoder inner_encoder_;
int status_ = 0;
uint8_t current_crc_ = INIT;
};
template<unsigned INIT, unsigned POLYNOMIAL, typename TEncoder>
using CRC8StreamEncoder = StreamEncoder_from_BlockEncoder<CRC8BlockEncoder<INIT, POLYNOMIAL, TEncoder>>;
template<unsigned INIT, unsigned POLYNOMIAL, typename TEncoder>
CRC8StreamEncoder<INIT, POLYNOMIAL, TEncoder> make_crc8_encoder(TEncoder&& encoder) {
return CRC8StreamEncoder<INIT, POLYNOMIAL, TEncoder>(std::forward<TEncoder>(encoder));
}
template<typename ... TEncoders>
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<typename TEncoder, typename ... TEncoders>
class EncoderChain<TEncoder, TEncoders...> : public StreamEncoder {
public:
EncoderChain(TEncoder&& this_encoder, TEncoders&& ... subsequent_encoders) :
this_encoder_(std::forward<TEncoder>(this_encoder)),
subsequent_encoders_(std::forward<TEncoders>(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<TEncoders...> subsequent_encoders_;
};
template<typename ... TEncoders>
EncoderChain<TEncoders...> make_encoder_chain(TEncoders&& ... encoders) {
return EncoderChain<TEncoders...>(std::forward<TEncoders>(encoders)...);
}
#endif // __ENCODERS_HPP
@@ -0,0 +1,74 @@
#ifndef __FIBRE_EVENT_LOOP_HPP
#define __FIBRE_EVENT_LOOP_HPP
#include "callback.hpp"
#include <stdint.h>
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<void> 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<void, uint32_t> 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<void> 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
+156
View File
@@ -0,0 +1,156 @@
#ifndef __FIBRE_HPP
#define __FIBRE_HPP
#include <fibre/callback.hpp>
#include <fibre/bufptr.hpp>
#include <fibre/cpp_utils.hpp>
#include <fibre/event_loop.hpp>
#include <fibre/channel_discoverer.hpp>
#include <string>
#include <memory>
#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<CallBufferRelease>
call(void**, CallBuffers, Callback<std::optional<CallBuffers>, CallBufferRelease>) = 0;
};
struct Object;
struct Interface;
struct Domain;
template<typename T>
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<std::string, ChannelDiscoverer*> 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<void, Object*, Interface*> on_found_object, Callback<void, Object*> 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<LegacyObject> obj);
void on_lost_root_object(LegacyObjectClient* obj_client);
#endif
void on_stopped(LegacyProtocolPacketBased* protocol, StreamStatus status);
#if FIBRE_ALLOW_HEAP
std::unordered_map<std::string, fibre::ChannelDiscoveryContext*> channel_discovery_handles;
#endif
#if FIBRE_ENABLE_CLIENT
Callback<void, Object*, Interface*> on_found_object_;
Callback<void, Object*> 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<void, EventLoop*> on_started);
}
#endif // __FIBRE_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<typename T> 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<typename T> 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<typename T> Introspectable TypeInfo::make_introspectable(T obj, const TypeInfo* type_info) {
+234 -152
View File
@@ -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, //<! The request will complete asynchronously
kFibreCancelled, //!< The operation was cancelled due to a request by the application or the remote peer
kFibreClosed, //!< The operation has finished orderly or shall be finished orderly
kFibreInvalidArgument, //!< Bug in the application
kFibreInternalError, //!< Bug in the local fibre implementation
kFibreProtocolError, //!< A remote peer is misbehaving (indicates bug in the remote peer)
kFibreHostUnreachable, //!< The remote peer can no longer be reached
//kFibreInsufficientData, // maybe we will introduce this to tell the caller that the granularity of the data is too small
};
struct LibFibreVersion {
@@ -80,34 +86,54 @@ struct LibFibreVersion {
uint16_t patch;
};
typedef int (*post_cb_t)(void (*callback)(void*), void* cb_ctx);
typedef int (*register_event_cb_t)(int fd, uint32_t events, void (*callback)(void*), void* cb_ctx);
typedef int (*register_event_cb_t)(int fd, uint32_t events, void (*callback)(void*, uint32_t), void* cb_ctx);
typedef int (*deregister_event_cb_t)(int fd);
typedef struct EventLoopTimer* (*call_later_cb_t)(float delay, void (*callback)(void*), void* cb_ctx);
typedef int (*cancel_timer_cb_t)(struct EventLoopTimer* timer);
/**
* @brief construct_object callback type for libfibre_open().
*
* @param ctx: The user data that was passed to libfibre_open().
* @param obj: An object handle. This handle is valid until the invokation of
* destroy_object(). It is unique at any point in time but can be reused
* after destroy_object().
* @param intf: A handle for the interface that this object implements.
* The handle may be identical to an interface handle announced for a
* previous object.
* The interface handle is valid until the last object that implements it
* is destroyed.
* @param intf_name: The ASCII-encoded name of the interface. Can be NULL for
* anonymous interfaces. If not NULL, it is only valid for the duration
* of the callback and must not be freed by the application.
*/
typedef void (*construct_object_cb_t)(void* ctx, LibFibreObject* obj, LibFibreInterface* intf, const char* intf_name, size_t intf_name_length);
typedef void (*destroy_object_cb_t)(void* ctx, LibFibreObject* obj);
struct LibFibreEventLoop {
/**
* @brief Called by libfibre when it wants the application to run a callback
* on the application's event loop.
*
* This is the only callback that libfibre can invoke from a different
* thread than the event loop thread itself. The application must ensure
* that this callback is thread-safe.
* This allows libfibre to run other threads internally while keeping
* threading promises made to the application.
*/
post_cb_t post;
/**
* @brief TODO: this is a Unix specific callback. Need to use IOCP on Windows.
*/
register_event_cb_t register_event;
/**
* @brief TODO: this is a Unix specific callback. Need to use IOCP on Windows.
*/
deregister_event_cb_t deregister_event;
/**
* @brief Called by libfibre to ask the application to call a certain
* callback after a certain amount of time.
*
* The callback must be invoked on the same thread on which libfibre_open()
* was called. The application should return an opaque handle that
* libfibre can use to cancel the timer.
*/
call_later_cb_t call_later;
/**
* @brief Called by libfibre to ask the application to cancel a callback
* timer previously enqueued with call_later().
*/
cancel_timer_cb_t cancel_timer;
};
/**
* @brief on_start_discovery callback type for libfibre_register_discoverer().
* @brief on_start_discovery callback type for libfibre_register_backend().
*
* For every channel pair that the application finds that matches the filter of
* this discoverer the application should call libfibre_add_channels().
@@ -120,8 +146,20 @@ typedef void (*destroy_object_cb_t)(void* ctx, LibFibreObject* obj);
typedef void (*on_start_discovery_cb_t)(void* ctx, LibFibreChannelDiscoveryCtx* discovery_ctx, const char* specs, size_t specs_length);
typedef void (*on_stop_discovery_cb_t)(void* ctx, LibFibreChannelDiscoveryCtx* discovery_ctx);
typedef void (*on_found_object_cb_t)(void*, LibFibreObject*);
typedef void (*on_stopped_cb_t)(void*, FibreStatus);
/**
* @brief on_found_object callback type for libfibre_start_discovery().
* @param obj: The object handle.
* @param intf: The interface handle. Valid for as long as any handle of an
* object that implements it is valid.
*/
typedef void (*on_found_object_cb_t)(void*, LibFibreObject* obj, LibFibreInterface* intf);
/**
* @brief on_lost_object callback type for libfibre_start_discovery().
*/
typedef void (*on_lost_object_cb_t)(void*, LibFibreObject* obj);
typedef void (*on_stopped_cb_t)(void*, LibFibreStatus);
typedef void (*on_attribute_added_cb_t)(void*, LibFibreAttribute*, const char* name, size_t name_length, LibFibreInterface*, const char* intf_name, size_t intf_name_length);
typedef void (*on_attribute_removed_cb_t)(void*, LibFibreAttribute*);
@@ -148,7 +186,46 @@ typedef void (*on_attribute_removed_cb_t)(void*, LibFibreAttribute*);
typedef void (*on_function_added_cb_t)(void* ctx, LibFibreFunction* func, const char* name, size_t name_length, const char** input_names, const char** input_codecs, const char** output_names, const char** output_codecs);
typedef void (*on_function_removed_cb_t)(void*, LibFibreFunction*);
typedef void (*on_call_completed_cb_t)(void*, FibreStatus);
/**
* @brief Callback type for libfibre_call().
*
* For an overview of the coroutine call control flow see libfibre_call().
*
* @param ctx: The context pointer that was passed to libfibre_call().
* @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] where `tx_buf` and
* `tx_len` are the arguments of the corresponding libfibre_call() call.
* @param tx_end: End of the range of data that was returned by libfibre. This
* is always in the interval [rx_buf, rx_buf + rx_len] where `rx_buf` and
* `rx_len` are the arguments of the corresponding libfibre_call() call.
* @param tx_buf: The application should set this to the next buffer to
* transmit. The buffer must remain valid until the next callback
* invokation.
* @param tx_len: The length of tx_buf. Must be zero if tx_buf is NULL.
* @param rx_buf: The application should set this to the buffer into which data
* should be written. The buffer must remain allocated until the next
* callback invokation.
* @param rx_len: The length of rx_buf. Must be zero if rx_buf is NULL.
*
* @retval kFibreOk: The application set tx_buf and rx_buf to valid or empty
* buffers and libfibre should invoke the callback again when it has
* made progress.
* @retval kFibreBusy: The application cannot provide a new tx_buf or rx_buf at
* the moment. The application will eventually call libfibre_call() for
* this coroutine call again.
* @retval kFibreClosed: The application may have returned non-empty buffers and
* if libfibre manages to fully handle these buffers it shall consider
* the call ended.
* @retval kFibreCancelled: The application did not set valid tx and rx buffers
* and libfibre should consider the call cancelled. Libfibre will not
* invoke the callback anymore.
*/
typedef LibFibreStatus (*libfibre_call_cb_t)(void* ctx,
LibFibreStatus status,
const unsigned char* tx_end, unsigned char* rx_end,
const unsigned char** tx_buf, size_t* tx_len,
unsigned char** rx_buf, size_t* rx_len);
/**
* @brief TX completion callback type for libfibre_start_tx().
@@ -169,7 +246,7 @@ typedef void (*on_call_completed_cb_t)(void*, FibreStatus);
* kFibreClosed then the pointer may not precisely indicate the
* transmitted data range.
*/
typedef void (*on_tx_completed_cb_t)(void* ctx, LibFibreTxStream* tx_stream, FibreStatus status, const uint8_t* tx_end);
typedef void (*on_tx_completed_cb_t)(void* ctx, LibFibreTxStream* tx_stream, LibFibreStatus status, const uint8_t* tx_end);
/**
* @brief RX completion callback type for libfibre_start_rx().
@@ -190,7 +267,7 @@ typedef void (*on_tx_completed_cb_t)(void* ctx, LibFibreTxStream* tx_stream, Fib
* kFibreClosed then the pointer may not precisely indicate the
* received data range.
*/
typedef void (*on_rx_completed_cb_t)(void* ctx, LibFibreRxStream* rx_stream, FibreStatus status, uint8_t* rx_end);
typedef void (*on_rx_completed_cb_t)(void* ctx, LibFibreRxStream* rx_stream, LibFibreStatus status, uint8_t* rx_end);
/**
* @brief Returns the version of the libfibre library.
@@ -209,45 +286,13 @@ FIBRE_PUBLIC const struct LibFibreVersion* libfibre_get_version();
/**
* @brief Opens and initializes a Fibre context.
*
* @param post: Called by libfibre when it wants the application to run
* a callback on the application's event loop.
* This is the only callback that libfibre can invoke from a different
* thread than the event loop thread itself. The application must
* ensure that this callback is thread-safe.
* This allows libfibre to run other threads internally while keeping
* threading promises made to the application.
* @param register_event: TODO: this is a Linux specific callback. Need to use
* IOCP on Windows.
* @param deregister_event: TODO: this is a Linux specific callback. Need to use
* IOCP on Windows.
* @param call_later: Called by libfibre to ask the application to call a
* certain callback after a certain amount of time on the same thread
* on which libfibre_open() was called. The application should return
* an opaque handle that libfibre can use to cancel the timer.
* @param cancel_timer: Called by libfibre to ask the application to cancel a
* callback timer previously enqueued with call_later().
* @param construct_object: Called by libfibre for every remote object that is
* allocated. This can be a root object that was just discovered or an
* object that was created as a result of operations like
* libfibre_get_attribute(). The application can use this to construct
* a corresponding object in the application's environment.
* @param destroy_object: Called by libfibre when a remote object is lost, for
* instance because all channels that provided connection to the object
* broke down.
* An object pointer must no longer be used during or after the call to
* destroy_object().
* @param cb_ctx: Arbitrary user data passed to construct_object() and
* destroy_object().
* @param event_loop: The event loop on which libfibre will run. Some function
of the event loop can be left unimplemented (set to NULL) depending on
the platform and the backends used (TODO: elaborate).
The event loop must be single threaded and all calls to libfibre must
happen on the event loop thread.
*/
FIBRE_PUBLIC struct 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);
FIBRE_PUBLIC struct LibFibreCtx* libfibre_open(LibFibreEventLoop event_loop);
/**
* @brief Closes a context that was previously opened with libfibre_open().
@@ -258,55 +303,63 @@ FIBRE_PUBLIC struct LibFibreCtx* libfibre_open(
FIBRE_PUBLIC void libfibre_close(struct LibFibreCtx* ctx);
/**
* @brief Registers an external channel discovery provider.
* @brief Registers an external channel provider.
*
* Libfibre starts and stops the discoverer on demand as a result of calls
* to libfibre_start_discovery() and libfibre_stop_discovery().
* This can be used by applications to implement transport providers which are
* not supported natively in libfibre.
*/
FIBRE_PUBLIC 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);
FIBRE_PUBLIC 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);
/**
* @brief Registers new TX and RX channels as part of an ongoing discovery
* operation.
* @brief Creates a communication domain from the specified spec string.
*
* @param ctx: The libfibre context that was obtained from libfibre_open().
* @param specs: Pointer to an ASCII string encoding the channel specifications.
* Must remain valid for the life time of the discovery.
* See README of the main Fibre repository for details.
* (https://github.com/samuelsadok/fibre/tree/devel).
* @returns: An opaque handle which can be passed to libfibre_start_discovery().
*/
FIBRE_PUBLIC LibFibreDomain* libfibre_open_domain(LibFibreCtx* ctx,
const char* specs, size_t specs_len);
/**
* @brief Closes a domain that was previously opened with libfibre_open_domain().
*/
FIBRE_PUBLIC void libfibre_close_domain(LibFibreDomain* domain);
/**
* @brief Adds new TX and RX channels to a domain.
*
* The channels can be closed with libfibre_close_tx() and libfibre_close_rx().
*/
FIBRE_PUBLIC void libfibre_add_channels(LibFibreCtx* ctx, LibFibreChannelDiscoveryCtx* discovery_ctx, LibFibreRxStream** tx_channel, LibFibreTxStream** rx_channel, size_t mtu);
FIBRE_PUBLIC void libfibre_add_channels(LibFibreDomain* domain, LibFibreRxStream** tx_channel, LibFibreTxStream** rx_channel, size_t mtu);
/**
* @brief Starts looking for Fibre objects that match the specifications.
*
* TODO: specify if specs needs to remain valid for the duration of discovery.
*
* @param ctx: The libfibre context that was obtained from libfibre_open().
* @param specs: Pointer to an ASCII string encoding the channel specifications.
* Must remain valid for the duration of the discovery.
*
* The specification has the format:
* "transport_provider1:args1;transport_provider2:args2"
* Transport providers are for example "usb", "serial", etc.
* Refer to the transport provider's documentation to see what arguments
* it takes.
*
* Example:
* "usb:idVendor=0x1209,idVendor=0x0d32;serial:path=/dev/ttyACM0"
* This will look for channels on USB devices with VID:PID 1209:0d32 and
* on the serial port /dev/ttyACM0.
* @param on_found_object: Invoked for every object that is found. Objects are
* first passed to the construct_object() callback of libfibre_open()
* before they are passed to this callback. An application should use
* the destroy_object() callback of the libfibre_open() function to
* detect the loss of objects.
* @param domain: The domain obtained from libfibre_open_domain() on which to
* discover objects.
* @param on_found_object: Invoked for every matching object that is found.
* The application must expect the same object handle to appear more than
* once.
* libfibre increments the internal reference count of the object before
* this call and decrements it after the corresponding call to
* on_lost_object. When the reference count reaches zero the application
* must no longer use it. The reference count is always non-negative.
* @param on_lost_object: Invoked when an object is lost.
* @param on_stopped: Invoked when the discovery stops for any reason, including
* a corresponding call to libfibre_stop_discovery().
* @param cb_ctx: Arbitrary user data passed to the callbacks.
* @returns: An opaque handle which should be passed to libfibre_stop_discovery().
*/
FIBRE_PUBLIC void libfibre_start_discovery(LibFibreCtx* ctx,
const char* specs, size_t specs_len, LibFibreDiscoveryCtx** handle,
on_found_object_cb_t on_found_object,
FIBRE_PUBLIC 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);
/**
@@ -318,7 +371,7 @@ FIBRE_PUBLIC void libfibre_start_discovery(LibFibreCtx* ctx,
* libfibre_start_discovery() is invoked. Once this callback is invoked,
* libfibre_stop_discovery() must no longer be called.
*/
FIBRE_PUBLIC void libfibre_stop_discovery(LibFibreCtx* ctx, LibFibreDiscoveryCtx* discovery_ctx);
FIBRE_PUBLIC void libfibre_stop_discovery(LibFibreDiscoveryCtx* handle);
/**
* @brief Subscribes to changes on the interface.
@@ -377,66 +430,95 @@ FIBRE_PUBLIC void libfibre_subscribe_to_interface(LibFibreInterface* interface,
* libfibre_ref_obj() immediately.
* @returns: kFibreOk or kFibreInvalidArgument
*/
FIBRE_PUBLIC FibreStatus libfibre_get_attribute(LibFibreObject* parent_obj, LibFibreAttribute* attr, LibFibreObject** child_obj_ptr);
FIBRE_PUBLIC LibFibreStatus libfibre_get_attribute(LibFibreObject* parent_obj, LibFibreAttribute* attr, LibFibreObject** child_obj_ptr);
/**
* @brief Starts a remote procedure call.
* @brief Starts a remote coroutine call or continues or cancels an ongoing call.
*
* This function returns a TX/RX pair of streams that can be used by the
* application to send inputs to the remote procedure and receive outputs from
* it.
* A remote coroutine call can be considered a continuous exchange of the
* following tuples:
*
* libfibre is not required to send anything on the underlying transport
* layer(s) until a TX operation is started on the call's tx_stream. This means
* that for functions with no input the application must start a zero-length
* operation for the call to be started.
* Client Application ===== (tx_buf, rx_buf, status) ====> 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
}
@@ -1,4 +0,0 @@
#include "protocol.hpp"
int serve_on_tcp(unsigned int port);
@@ -1,4 +0,0 @@
#include "protocol.hpp"
int serve_on_udp(unsigned int port);
@@ -5,6 +5,7 @@
#include "limits.h"
#include <optional> // TODO: make C++11 backport of this
#include <cstring>
#include <stdint.h>
template<typename T, bool BigEndian, typename = void>
struct SimpleSerializer;
@@ -0,0 +1,20 @@
#ifndef __FIBRE_STATUS_HPP
#define __FIBRE_STATUS_HPP
namespace fibre {
enum Status {
kFibreOk,
kFibreBusy, //<! The request will complete asynchronously
kFibreCancelled, //!< The operation was cancelled due to a request by the application or the remote peer
kFibreClosed, //!< The operation has finished orderly or shall be finished orderly
kFibreInvalidArgument, //!< Bug in the application
kFibreInternalError, //!< Bug in the local fibre implementation
kFibreProtocolError, //!< A remote peer is misbehaving (indicates bug in the remote peer)
kFibreHostUnreachable, //!< The remote peer can no longer be reached
//kFibreInsufficientData, // maybe we will introduce this to tell the caller that the granularity of the data is too small
};
}
#endif // __FIBRE_STATUS_HPP
+4 -6
View File
@@ -12,9 +12,7 @@
#ifndef __FIBRE_INTERFACES_HPP
#define __FIBRE_INTERFACES_HPP
[[userdata.c_preamble]]
#include <fibre/protocol.hpp>
#include <fibre/../../protocol.hpp>
#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 %]
File diff suppressed because it is too large Load Diff
+71 -38
View File
@@ -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 <fibre/libfibre.h>
#include <fibre/async_stream.hpp>
#include <unordered_map>
#include <vector>
#include <memory>
#include <string>
#include <fibre/callback.hpp>
#include <fibre/cpp_utils.hpp> // std::variant and std::optional C++ backport
#include <fibre/fibre.hpp>
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<LegacyFibreArg> inputs, std::vector<LegacyFibreArg> outputs)
: ep_num(0), obj_(nullptr), inputs(inputs), outputs(outputs) {}
LegacyFunction(size_t ep_num, LegacyObject* obj, std::vector<LegacyFibreArg> inputs, std::vector<LegacyFibreArg> outputs)
: ep_num(ep_num), obj_(obj), inputs(inputs), outputs(outputs) {}
std::optional<CallBufferRelease>
call(void**, CallBuffers, Callback<std::optional<CallBuffers>, 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<LegacyFibreArg> inputs;
std::vector<LegacyFibreArg> outputs;
};
struct FibreInterface;
struct LegacyObjectClient;
struct LegacyObject;
struct LegacyFibreAttribute {
std::shared_ptr<LegacyObject> object;
@@ -48,7 +59,7 @@ struct LegacyFibreAttribute {
struct FibreInterface {
std::string name;
std::unordered_map<std::string, LegacyFibreFunction> functions;
std::unordered_map<std::string, LegacyFunction> functions;
std::unordered_map<std::string, LegacyFibreAttribute> attributes;
};
@@ -59,55 +70,77 @@ struct LegacyObject {
bool known_to_application;
};
class LegacyObjectClient : Completer<EndpointOperationResult> {
public:
struct CallContext : AsyncStreamSink, AsyncStreamSource, Completer<EndpointOperationResult> {
size_t progress = 0;
size_t ep_num = 0;
bufptr_t rx_buf_ = {};
LegacyFibreFunction* func = nullptr;
Completer<WriteResult>* tx_completer_ = nullptr;
Completer<ReadResult>* rx_completer_ = nullptr;
Completer<FibreStatus>* 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<WriteResult>& completer) final;
void cancel_write(TransferHandle transfer_handle) final;
void start_read(bufptr_t buffer, TransferHandle* handle, Completer<ReadResult>& 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<uint8_t> tx_buf_;
size_t tx_pos_ = 0;
std::vector<uint8_t> rx_buf_;
size_t rx_pos_ = 0;
const uint8_t* app_tx_end_;
bufptr_t app_rx_buf_;
Callback<std::optional<CallBuffers>, CallBufferRelease> callback;
std::optional<EndpointOperationResult> ep_result;
LegacyObject* obj_;
std::optional<CallBufferRelease>
resume_from_app(CallBuffers, Callback<std::optional<CallBuffers>, 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<ContinueWithApp, ContinueWithProtocol, InternalError> get_next_task(std::variant<ResultFromApp, ResultFromProtocol> continue_from);
};
class LegacyObjectClient {
public:
LegacyObjectClient(LegacyProtocolPacketBased* protocol) : protocol_(protocol) {}
void start(Completer<LegacyObjectClient*, std::shared_ptr<LegacyObject>>& on_found_root_object, Completer<LegacyObjectClient*>& on_lost_root_object);
void start_call(size_t ep_num, LegacyFibreFunction* func, CallContext** handle, Completer<FibreStatus>& completer);
void cancel_call(CallContext* handle);
void start(Callback<void, LegacyObjectClient*, std::shared_ptr<LegacyObject>> on_found_root_object, Callback<void, LegacyObjectClient*> 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<LegacyObjectClient*>* on_lost_root_object_;
Callback<void, LegacyObjectClient*> on_lost_root_object_;
std::shared_ptr<LegacyObject> root_obj_;
std::vector<std::shared_ptr<LegacyObject>> objects_;
void* user_data_; // used by libfibre to store the libfibre context pointer
LegacyProtocolPacketBased* protocol_;
private:
std::shared_ptr<FibreInterface> get_property_interfaces(std::string codec, bool write);
std::shared_ptr<LegacyObject> load_object(json_value list_val);
void receive_more_json();
void complete(EndpointOperationResult result);
void on_received_json(EndpointOperationResult result);
LegacyProtocolPacketBased* protocol_;
Completer<LegacyObjectClient*, std::shared_ptr<LegacyObject>>* on_found_root_object_;
Callback<void, LegacyObjectClient*, std::shared_ptr<LegacyObject>> on_found_root_object_;
uint8_t tx_buf_[4] = {0xff, 0xff, 0xff, 0xff};
EndpointOperationHandle op_handle_ = 0;
std::vector<uint8_t> json_;
std::vector<CallContext*> pending_calls_;
//std::vector<LegacyCallContext*> pending_calls_;
std::unordered_map<std::string, std::shared_ptr<FibreInterface>> rw_property_interfaces;
std::unordered_map<std::string, std::shared_ptr<FibreInterface>> ro_property_interfaces;
};
+75 -97
View File
@@ -2,11 +2,11 @@
#include "legacy_protocol.hpp"
#include <fibre/protocol.hpp>
#include <fibre/crc.hpp>
#include "protocol.hpp"
#include "crc.hpp"
#include "logging.hpp"
#include "print_utils.hpp"
#include "async_stream.hpp"
#include <fibre/async_stream.hpp>
#include <memory>
#include <stdlib.h>
@@ -18,21 +18,21 @@ using namespace fibre;
/* PacketWrapper -------------------------------------------------------------*/
void PacketWrapper::start_write(cbufptr_t buffer, TransferHandle* handle, Completer<WriteResult>& completer) {
void PacketWrapper::start_write(cbufptr_t buffer, TransferHandle* handle, Callback<void, WriteResult> completer) {
if (handle) {
*handle = reinterpret_cast<TransferHandle>(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<uint8_t>(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<ReadResult>& completer) {
void PacketUnwrapper::start_read(bufptr_t buffer, TransferHandle* handle, Callback<void, ReadResult> completer) {
if (handle) {
*handle = reinterpret_cast<TransferHandle>(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_POLYNOMIAL>(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<EndpointOperationResult>& completer) {
void LegacyProtocolPacketBased::start_endpoint_operation(uint16_t endpoint_id, cbufptr_t tx_buf, bufptr_t rx_buf, EndpointOperationHandle* handle, Callback<void, EndpointOperationResult> 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<WriteCompleter*>(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<uint16_t>(handle & 0xffff);
Completer<EndpointOperationResult>* completer;
Callback<void, EndpointOperationResult> 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<ReadCompleter*>(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<ReadCompleter*>(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<uint16_t>(*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<WriteCompleter*>(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<ReadCompleter*>(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<LegacyObjectClient*, std::shared_ptr<LegacyObject>>& on_found_root_object, Completer<LegacyObjectClient*>& on_lost_root_object, Completer<LegacyProtocolPacketBased*, StreamStatus>& on_stopped) {
void LegacyProtocolPacketBased::start(Callback<void, LegacyObjectClient*, std::shared_ptr<LegacyObject>> on_found_root_object, Callback<void, LegacyObjectClient*> on_lost_root_object, Callback<void, LegacyProtocolPacketBased*, StreamStatus> on_stopped) {
#else
void LegacyProtocolPacketBased::start(Completer<LegacyProtocolPacketBased*, StreamStatus>& on_stopped) {
void LegacyProtocolPacketBased::start(Callback<void, LegacyProtocolPacketBased*, StreamStatus> on_stopped) {
#endif
on_stopped_ = &on_stopped;
on_stopped_ = on_stopped;
TransferHandle dummy;
rx_channel_->start_read(rx_buf_, &dummy, *static_cast<ReadCompleter*>(this));
rx_channel_->start_read(rx_buf_, &dummy, MEMBER_CB(this, on_read_finished));
#if FIBRE_ENABLE_CLIENT
if (on_stopped_) {
+21 -22
View File
@@ -1,11 +1,12 @@
#ifndef __FIBRE_LEGACY_PROTOCOL_HPP
#define __FIBRE_LEGACY_PROTOCOL_HPP
#include "async_stream.hpp"
#include <fibre/async_stream.hpp>
#ifdef FIBRE_ENABLE_CLIENT
#include "legacy_object_client.hpp"
#include <unordered_map>
#include <optional>
#endif
namespace fibre {
@@ -28,12 +29,12 @@ constexpr uint8_t CANONICAL_PREFIX = 0xAA;
constexpr uint16_t PROTOCOL_VERSION = 1;
class PacketWrapper : public AsyncStreamSink, Completer<WriteResult> {
class PacketWrapper : public AsyncStreamSink {
public:
PacketWrapper(AsyncStreamSink* tx_channel)
: tx_channel_(tx_channel) {}
void start_write(cbufptr_t buffer, TransferHandle* handle, Completer<WriteResult>& completer) final;
void start_write(cbufptr_t buffer, TransferHandle* handle, Callback<void, WriteResult> 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<WriteResult>* completer_;
Callback<void, WriteResult> completer_;
enum {
kStateIdle,
@@ -57,12 +58,12 @@ private:
};
class PacketUnwrapper : public AsyncStreamSource, Completer<ReadResult> {
class PacketUnwrapper : public AsyncStreamSource {
public:
PacketUnwrapper(AsyncStreamSource* rx_channel)
: rx_channel_(rx_channel) {}
void start_read(bufptr_t buffer, TransferHandle* handle, Completer<ReadResult>& completer) final;
void start_read(bufptr_t buffer, TransferHandle* handle, Callback<void, ReadResult> 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<ReadResult>* completer_;
Callback<void, ReadResult> 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<LegacyProtocolPacketBased*, StreamStatus>* on_stopped_ = nullptr;
Callback<void, LegacyProtocolPacketBased*, StreamStatus> 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<EndpointOperationResult>& completer);
#if FIBRE_ENABLE_CLIENT
void start_endpoint_operation(uint16_t endpoint_id, cbufptr_t tx_buf, bufptr_t rx_buf, EndpointOperationHandle* handle, Callback<void, EndpointOperationResult> callback);
void cancel_endpoint_operation(EndpointOperationHandle handle);
LegacyObjectClient client_{this};
#endif
#ifdef FIBRE_ENABLE_CLIENT
void start(Completer<LegacyObjectClient*, std::shared_ptr<LegacyObject>>& on_found_root_object, Completer<LegacyObjectClient*>& on_lost_root_object, Completer<LegacyProtocolPacketBased*, StreamStatus>& on_stopped);
#if FIBRE_ENABLE_CLIENT
void start(Callback<void, LegacyObjectClient*, std::shared_ptr<LegacyObject>> on_found_root_object, Callback<void, LegacyObjectClient*> on_lost_root_object, Callback<void, LegacyProtocolPacketBased*, StreamStatus> on_stopped);
#else
void start(Completer<LegacyProtocolPacketBased*, StreamStatus>& on_stopped);
void start(Callback<void, LegacyProtocolPacketBased*, StreamStatus> 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<EndpointOperationResult>* completer;
Callback<void, EndpointOperationResult> 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<EndpointOperation> pending_operation_ = std::nullopt; // operation that is waiting for TX
EndpointOperationHandle transmitting_op_ = 0; // operation that is in TX
std::unordered_map<uint16_t, EndpointOperation> 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<LegacyObjectClient*, std::shared_ptr<LegacyObject>>& on_found_root_object, Completer<LegacyObjectClient*>& on_lost_root_object, Completer<LegacyProtocolPacketBased*, StreamStatus>& on_stopped) {
#if FIBRE_ENABLE_CLIENT
void start(Callback<void, LegacyObjectClient*, std::shared_ptr<LegacyObject>> on_found_root_object, Callback<void, LegacyObjectClient*> on_lost_root_object, Callback<void, LegacyProtocolPacketBased*, StreamStatus> on_stopped) {
inner_protocol_.start(on_found_root_object, on_lost_root_object, on_stopped);
}
#else
void start(Completer<LegacyProtocolPacketBased*, StreamStatus>& on_stopped) { inner_protocol_.start(on_stopped); }
void start(Callback<void, LegacyProtocolPacketBased*, StreamStatus> on_stopped) { inner_protocol_.start(on_stopped); }
#endif
private:
File diff suppressed because it is too large Load Diff
+14 -12
View File
@@ -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 <fibre/cpp_utils.hpp>
@@ -235,10 +245,10 @@ public:
template<typename TOPIC>
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<typename TOPIC>
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
+137 -10
View File
@@ -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
@@ -0,0 +1,204 @@
#include "epoll_event_loop.hpp"
#include "../logging.hpp"
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/eventfd.h>
#include <unistd.h>
#include <string.h>
using namespace fibre;
DEFINE_LOG_TOPIC(EVENT_LOOP);
USE_LOG_TOPIC(EVENT_LOOP);
bool EpollEventLoop::start(Callback<void> 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<void> callback) {
if (epoll_fd_ < 0) {
FIBRE_LOG(E) << "not started";
return false;
}
{
std::unique_lock<std::mutex> 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<void, uint32_t> 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<void> 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<Callback<void>> pending_callbacks;
{
std::unique_lock<std::mutex> lock(pending_callbacks_mutex_);
std::swap(pending_callbacks, pending_callbacks_);
}
for (auto& cb: pending_callbacks) {
cb.invoke();
}
}
@@ -0,0 +1,69 @@
#ifndef __FIBRE_LINUX_EVENT_LOOP_HPP
#define __FIBRE_LINUX_EVENT_LOOP_HPP
//#include <thread>
#include <sys/epoll.h>
#include <unordered_map>
#include <vector>
#include <mutex>
//#include <algorithm>
#include <fibre/event_loop.hpp>
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<void> on_started);
bool post(Callback<void> callback) final;
bool register_event(int fd, uint32_t events, Callback<void, uint32_t> callback) final;
bool deregister_event(int fd) final;
struct EventLoopTimer* call_later(float delay, Callback<void> callback) final;
bool cancel_timer(EventLoopTimer* timer) final;
private:
struct EventContext {
//int fd;
Callback<void, uint32_t> callback;
};
void run_callbacks(uint32_t);
int epoll_fd_ = -1;
int post_fd_ = -1;
unsigned int iterations_ = 0;
std::unordered_map<int, EventContext*> 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<Callback<void>> pending_callbacks_;
// Mutex to protect pending_callbacks_
std::mutex pending_callbacks_mutex_;
};
}
#endif // __FIBRE_LINUX_EVENT_LOOP_HPP
@@ -9,6 +9,11 @@
#include "../print_utils.hpp"
#include <algorithm>
#include <string.h>
#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<ChannelDiscoveryResult>& on_found_channels) {
void LibusbDiscoverer::start_channel_discovery(const char* specs, size_t specs_len, ChannelDiscoveryContext** handle, Callback<void, ChannelDiscoveryResult> 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<TRes>::deinit() {
}
template<typename TRes>
void LibusbBulkEndpoint<TRes>::start_transfer(bufptr_t buffer, TransferHandle* handle, Completer<TRes>& completer) {
void LibusbBulkEndpoint<TRes>::start_transfer(bufptr_t buffer, TransferHandle* handle, Callback<void, TRes> completer) {
if (handle) {
*handle = reinterpret_cast<TransferHandle>(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<TRes>::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<TRes>*)transfer->user_data)->parent_->event_loop_->post(
[](void* ctx) {
((LibusbBulkEndpoint<TRes>*)ctx)->on_transfer_finished();
}, transfer->user_data
);
auto ep = (LibusbBulkEndpoint<TRes>*)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<TRes>::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<TRes>::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<TRes>::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.
@@ -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 <fibre/event_loop.hpp>
#include <fibre/async_stream.hpp>
#include <fibre/channel_discoverer.hpp>
#include <libusb.h>
#include <thread>
@@ -15,9 +15,9 @@ namespace fibre {
class LibusbBulkInEndpoint;
class LibusbBulkOutEndpoint;
template<typename TRes> class FIBRE_PRIVATE LibusbBulkEndpoint;
template<typename TRes> 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<ChannelDiscoveryResult>* on_found_channels;
Callback<void, ChannelDiscoveryResult> 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<ChannelDiscoveryResult>& 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<void, ChannelDiscoveryResult> on_found_channels) final;
int stop_channel_discovery(ChannelDiscoveryContext* handle) final;
private:
@@ -53,9 +52,10 @@ private:
std::vector<LibusbBulkOutEndpoint*> 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<typename TRes>
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<TRes>& completer);
void start_transfer(bufptr_t buffer, TransferHandle* handle, Callback<void, TRes> 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<TRes>* completer_ = nullptr;
Callback<void, TRes> completer_ = nullptr;
};
class FIBRE_PRIVATE LibusbBulkInEndpoint : public LibusbBulkEndpoint<ReadResult>, public AsyncStreamSource {
class LibusbBulkInEndpoint : public LibusbBulkEndpoint<ReadResult>, public AsyncStreamSource {
public:
void start_read(bufptr_t buffer, TransferHandle* handle, Completer<ReadResult>& completer) final {
void start_read(bufptr_t buffer, TransferHandle* handle, Callback<void, ReadResult> completer) final {
start_transfer(buffer, handle, completer);
}
@@ -106,9 +106,9 @@ public:
}
};
class FIBRE_PRIVATE LibusbBulkOutEndpoint : public LibusbBulkEndpoint<WriteResult>, public AsyncStreamSink {
class LibusbBulkOutEndpoint : public LibusbBulkEndpoint<WriteResult>, public AsyncStreamSink {
public:
void start_write(cbufptr_t buffer, TransferHandle* handle, Completer<WriteResult>& completer) final {
void start_write(cbufptr_t buffer, TransferHandle* handle, Callback<void, WriteResult> completer) final {
start_transfer({
(unsigned char*)buffer.begin(),
buffer.size()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,173 @@
#ifndef __FIBRE_POSIX_SOCKET_HPP
#define __FIBRE_POSIX_SOCKET_HPP
#include <fibre/event_loop.hpp>
#include <netinet/in.h>
#include <string>
#include <fibre/cpp_utils.hpp>
#include <fibre/bufptr.hpp>
#include <fibre/async_stream.hpp>
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<std::string, int> address, bool passive,
AddressResolutionContext** handle,
Callback<void, std::optional<cbufptr_t>> 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<void, std::optional<socket_id_t>> 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<void, std::optional<socket_id_t>> 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<void, ReadResult> completer) final;
void cancel_read(TransferHandle transfer_handle) final;
void start_write(cbufptr_t buffer, TransferHandle* handle, Callback<void, WriteResult> 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<ReadResult> read_sync(bufptr_t buffer);
std::optional<WriteResult> 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<void, ReadResult> rx_callback_; // valid while there is an RX request pending
Callback<void, WriteResult> tx_callback_; // valid while there is a TX request pending
};
}
#include <iostream>
namespace std {
std::ostream& operator<<(std::ostream& stream, const struct sockaddr_storage& val);
}
#endif // __FIBRE_POSIX_SOCKET_HPP
@@ -0,0 +1,137 @@
#include "posix_tcp_backend.hpp"
#include "posix_socket.hpp"
#include "../logging.hpp"
#include <signal.h>
#include <unistd.h>
#include <algorithm>
#include <string.h>
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<void, ChannelDiscoveryResult> 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<cbufptr_t> 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<uint8_t> 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_t> 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();
}
@@ -0,0 +1,80 @@
#ifndef __FIBRE_POSIX_TCP_BACKEND_HPP
#define __FIBRE_POSIX_TCP_BACKEND_HPP
#include <fibre/event_loop.hpp>
#include "posix_socket.hpp"
#include <fibre/channel_discoverer.hpp>
#include <string>
#include <netdb.h>
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<void, ChannelDiscoveryResult> on_found_channels) final;
int stop_channel_discovery(ChannelDiscoveryContext* handle) final;
private:
struct TcpChannelDiscoveryContext {
PosixTcpBackend* parent;
std::tuple<std::string, int> address;
Callback<void, ChannelDiscoveryResult> 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<uint8_t> addr;
ConnectionContext* connection_ctx;
};
std::vector<AddrContext> known_addresses;
void resolve_address();
void on_found_address(std::optional<cbufptr_t> addr);
void on_connected(std::optional<socket_id_t> socket_id);
void on_disconnected();
};
virtual bool start_opening_connections(EventLoop* event_loop, cbufptr_t addr, int type, int protocol, ConnectionContext** ctx, Callback<void, std::optional<socket_id_t>> 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<void, std::optional<socket_id_t>> 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<void, std::optional<socket_id_t>> 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
-108
View File
@@ -1,108 +0,0 @@
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <thread>
#include <future>
#include <vector>
#include <fibre/protocol.hpp>
#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<typename T>
bool future_is_ready(std::future<T>& 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<struct sockaddr *>(&si_me), sizeof(si_me)) == -1) {
return -1;
}
listen(s, 128); // make this socket a passive socket
std::vector<std::future<int>> 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<sockaddr *>(&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<std::future<int>>::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);
}
-70
View File
@@ -1,70 +0,0 @@
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fibre/protocol.hpp>
#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<struct sockaddr*>(_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<struct sockaddr *>(&si_me), sizeof(si_me)) == -1)
return -1;
for (;;) {
ssize_t n_received = recvfrom(s, buf, sizeof(buf), 0, reinterpret_cast<struct sockaddr *>(&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);
}
@@ -13,10 +13,9 @@ see protocol.md for the protocol specification
#include <string.h>
#include <unistd.h>
#include <cstring>
#include "crc.hpp"
#include "cpp_utils.hpp"
#include "bufptr.hpp"
#include "simple_serdes.hpp"
#include <fibre/cpp_utils.hpp>
#include <fibre/bufptr.hpp>
#include <fibre/simple_serdes.hpp>
typedef struct {
+1 -1
View File
@@ -97,7 +97,7 @@ class AsyncStreamSinkMultiplexer : public AsyncStreamSink, Completer<WriteResult
public:
AsyncStreamSinkMultiplexer(AsyncStreamSink& sink) : sink_(sink) {}
void start_write(cbufptr_t buffer, TransferHandle* handle, Completer<WriteResult>& completer) final {
void start_write(cbufptr_t buffer, TransferHandle* handle, Callback<void, WriteResult> 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)) {