├── .clang-format ├── .github ├── actions │ └── compile │ │ └── action.yml └── workflows │ ├── tests.yaml │ └── update-satellite-repos.yaml ├── .gitignore ├── .vscode ├── c_cpp_properties.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE.md ├── Makefile ├── README.md ├── Tuprules.lua ├── cpp ├── .gitignore ├── Dockerfile ├── Makefile ├── README.md ├── Tupfile.lua ├── channel_discoverer.cpp ├── codecs.hpp ├── configs │ ├── linux-aarch64.config │ ├── linux-amd64.config │ ├── linux-armhf.config │ ├── macos-x86.config │ ├── wasm.config │ └── windows-amd64.config ├── connection.cpp ├── crc.hpp ├── endpoint_connection.cpp ├── fibre.cpp ├── fibre_config.hpp ├── func_utils.cpp ├── get_dependencies.sh ├── include │ └── fibre │ │ ├── async_stream.hpp │ │ ├── backport │ │ ├── optional.hpp │ │ └── variant.hpp │ │ ├── base_types.hpp │ │ ├── bufchain.hpp │ │ ├── bufptr.hpp │ │ ├── callback.hpp │ │ ├── channel_discoverer.hpp │ │ ├── config.hpp │ │ ├── connection.hpp │ │ ├── cpp_utils.hpp │ │ ├── domain.hpp │ │ ├── endpoint_connection.hpp │ │ ├── event_loop.hpp │ │ ├── fibre.hpp │ │ ├── func_utils.hpp │ │ ├── function.hpp │ │ ├── interface.hpp │ │ ├── introspection.hpp │ │ ├── libfibre.h │ │ ├── logging.hpp │ │ ├── low_level_protocol.hpp │ │ ├── multiplexer.hpp │ │ ├── node.hpp │ │ ├── object_server.hpp │ │ ├── pool.hpp │ │ ├── rich_status.hpp │ │ ├── simple_serdes.hpp │ │ ├── socket.hpp │ │ ├── status.hpp │ │ ├── timer.hpp │ │ └── tx_pipe.hpp ├── interfaces │ ├── canbus.hpp │ └── usb.hpp ├── interfaces_template.j2 ├── json.hpp ├── legacy_endpoints_template.j2 ├── legacy_object_client.cpp ├── legacy_object_client.hpp ├── legacy_object_server.cpp ├── legacy_object_server.hpp ├── legacy_protocol.cpp ├── legacy_protocol.hpp ├── libfibre.cpp ├── libfibre.version ├── mini_rng.hpp ├── multiplexer.cpp ├── package.lua ├── platform_support │ ├── can_adapter.cpp │ ├── can_adapter.hpp │ ├── dom_connector.hpp │ ├── dom_connector.js │ ├── epoll_event_loop.cpp │ ├── epoll_event_loop.hpp │ ├── libusb_backend.cpp │ ├── libusb_backend.hpp │ ├── posix_socket.cpp │ ├── posix_socket.hpp │ ├── posix_tcp_backend.cpp │ ├── posix_tcp_backend.hpp │ ├── socket_can.cpp │ ├── socket_can.hpp │ ├── usb_host_adapter.cpp │ ├── usb_host_adapter.hpp │ ├── webusb_backend.cpp │ └── webusb_backend.hpp ├── print_utils.hpp ├── property.hpp ├── protocol.hpp ├── static_exports.hpp ├── static_exports_template.j2 ├── stream_utils.hpp └── type_info_template.j2 ├── js ├── .gitignore ├── README.md ├── example.gif ├── example.html ├── fibre.js ├── multidevice_example.html └── package.json ├── python ├── .gitignore ├── README.md ├── fibre │ ├── __init__.py │ ├── libfibre.py │ ├── protocol.py │ ├── shell.py │ └── utils.py └── setup.py ├── sim ├── Tupfile.lua ├── fibre_config.hpp ├── mock_can.cpp ├── mock_can.hpp ├── sim_main.cpp ├── simulator.cpp └── simulator.hpp ├── test ├── Tupfile.lua ├── fibre_config.hpp ├── test-interface.yaml ├── test_client.py ├── test_node.cpp └── test_node.hpp └── tools ├── fibre-shell ├── interface-definition-file.md └── interface_generator.py /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | IndentWidth: 4 4 | 5 | AccessModifierOffset: -4 6 | 7 | #AllowShortBlocksOnASingleLine: Never 8 | AllowShortFunctionsOnASingleLine: Empty 9 | DerivePointerAlignment: false 10 | SpaceAfterTemplateKeyword: false 11 | AlwaysBreakTemplateDeclarations: No 12 | 13 | IncludeBlocks: Merge 14 | IncludeCategories: 15 | - Regex: '^"' 16 | Priority: 1 17 | - Regex: '^(<|")fibre/' 18 | Priority: 2 19 | - Regex: '<[[:alnum:].]+>' 20 | Priority: 4 21 | - Regex: '.*' 22 | Priority: 3 23 | -------------------------------------------------------------------------------- /.github/actions/compile/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Compile libfibre' 2 | description: 'Compiles libfibre for the specified target' 3 | 4 | inputs: 5 | target: 6 | description: | 7 | The build target on which the compiled libfibre is supposed to run. 8 | The supported targets are all strings "target" for which a file 9 | cpp/[target].config exists. 10 | required: true 11 | 12 | runs: 13 | using: 'docker' 14 | image: fibreframework/compiler:latest 15 | args: ['cpp/configs/${{ inputs.target }}.config'] 16 | 17 | branding: 18 | icon: terminal 19 | color: green 20 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: Tests 3 | 4 | # new draft: https://godbolt.org/z/qMa4YG 5 | 6 | on: 7 | pull_request: 8 | branches: [master, devel] 9 | push: 10 | branches: [master, devel, test-ci, webusb] 11 | 12 | jobs: 13 | compile: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | target: [linux-aarch64, linux-amd64, linux-armhf, macos-x86, wasm, windows-amd64] 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: ./.github/actions/compile 22 | with: 23 | target: ${{ matrix.target }} 24 | - name: List outputs 25 | run: | 26 | sudo rm -f cpp/build/*.fat 27 | ls -la cpp/build/ 28 | ls -la test/build/ 29 | - uses: actions/upload-artifact@v2 30 | with: 31 | name: libfibre-${{ matrix.target }} 32 | path: cpp/build/libfibre-* 33 | - uses: actions/upload-artifact@v2 34 | with: 35 | name: test-server-${{ matrix.target }} 36 | path: test/build/test_node.elf 37 | 38 | test-pyfibre: 39 | needs: [compile] 40 | strategy: 41 | fail-fast: false 42 | matrix: 43 | #os: [ubuntu-latest, macos-latest, windows-latest] # TODO: add TCP support to windows and macOS to test on CI 44 | os: [ubuntu-latest] 45 | runs-on: ${{ matrix.os }} 46 | steps: 47 | - uses: actions/checkout@v2 48 | - uses: actions/download-artifact@v2 49 | with: 50 | path: artifacts 51 | - name: Display structure of downloaded files 52 | run: ls -R artifacts 53 | 54 | - name: Run test server 55 | run: | 56 | uname -a 57 | ARCH="$(uname -m)" 58 | 59 | if [[ "$OSTYPE" == "msys" ]] && [[ "$ARCH" == "x86_64" ]]; then 60 | ARCH="windows-amd64" 61 | elif [[ "$OSTYPE" == "linux-gnu"* ]] && [[ "$ARCH" == "x86_64" ]]; then 62 | ARCH="linux-amd64" 63 | elif [[ "$OSTYPE" == "darwin"* ]] && [[ "$ARCH" == "x86_64" ]]; then 64 | ARCH="macos-x86" 65 | brew install coreutils # needed for the timeout command to work 66 | else 67 | echo "Unknown platform: $OSTYPE-$ARCH" 68 | false 69 | fi 70 | 71 | python3 --version 72 | 73 | chmod +x ./artifacts/test-server-$ARCH/test_node.elf 74 | ls ./artifacts/test-server-$ARCH/test_node.elf 75 | cp ./artifacts/libfibre-$ARCH/* ./python/fibre/ 76 | 77 | # Launch test server in background 78 | LD_PRELOAD=libSegFault.so FIBRE_LOG=5 ./artifacts/test-server-$ARCH/test_node.elf --server --domain tcp-server:address=localhost,port=14220 >test_server.log 2>&1 & 79 | 80 | # TODO: try launching client/server in reverse order 81 | sleep 1 82 | 83 | if FIBRE_LOG=5 timeout --signal=9 5s python3 ./test/test_client.py; then 84 | echo "Test client succeeded" 85 | CLIENT_STATUS="ok" 86 | elif [ "$?" == "124" ]; then 87 | echo "Test client timed out" 88 | CLIENT_STATUS="timeout" 89 | else 90 | echo "Test client failed" 91 | CLIENT_STATUS="fail" 92 | fi 93 | 94 | # Tell test_node.elf politely to finish (if it's still running) 95 | 96 | echo "terminiating test server" 97 | 98 | # TODO: find out why SIGINT doesn't work 99 | kill -9 $! || true 100 | SERVER_STATUS="ok" 101 | 102 | #timeout 1s kill -2 $! || true 103 | #echo "kill command completed" 104 | 105 | ## If it failed to react, force kill 106 | #sleep 1 107 | #echo "checking if still running" 108 | #if kill -0 $!; then 109 | # echo "Test server did not react to SIGINT. Killing." 110 | # kill -9 $! || true 111 | # SERVER_STATUS="timeout" 112 | #elif ! wait $!; then 113 | # echo "Test server returned error code" 114 | # SERVER_STATUS="fail" 115 | #else 116 | # SERVER_STATUS="ok" 117 | #fi 118 | 119 | echo "Test server log:" 120 | cat test_server.log 121 | 122 | [ $CLIENT_STATUS == "ok" ] && [ $SERVER_STATUS == "ok" ] 123 | shell: bash 124 | 125 | formatting: 126 | strategy: 127 | fail-fast: false 128 | runs-on: ubuntu-20.04 # need a recent clang-format 129 | steps: 130 | - uses: actions/checkout@v2 131 | 132 | - name: Check C++ Formatting 133 | run: | 134 | clang-format --version 135 | 136 | # TODO: we run this only on a few selected files for now until we have it properly configured 137 | files="test/test_node.cpp 138 | test/test_node.hpp 139 | sim/sim_main.cpp" 140 | 141 | NUM_BAD=0 142 | while read file; do 143 | if ! clang-format -style=file --Werror --dry-run "$file"; then 144 | NUM_BAD=$(( $NUM_BAD + 1 )) 145 | fi 146 | done <<< "$files" 147 | 148 | if ! [ "$NUM_BAD" == "0" ]; then 149 | echo "$NUM_BAD files need formatting" 150 | false # fail CI 151 | else 152 | echo "all files good" 153 | fi 154 | 155 | # TODO: check if interface_generator outputs the same thing with Python 3.5 and Python 3.8 156 | -------------------------------------------------------------------------------- /.github/workflows/update-satellite-repos.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: Update Satellite Repos 3 | 4 | on: 5 | push: 6 | branches: [master, devel] 7 | 8 | jobs: 9 | push: 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | include: 14 | - {name: fibre-cpp, dir: cpp} 15 | - {name: fibre-js, dir: js} 16 | - {name: fibre-tools, dir: tools} 17 | - {name: pyfibre, dir: python} 18 | 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Push to ${{ matrix.name }} 26 | run: | 27 | # Unset GITHUB_TOKEN so we can push to another repo (see https://stackoverflow.com/questions/64270867/auth-error-trying-to-copy-a-repo-with-github-actions) 28 | git config -l | grep 'http\..*\.extraheader' | cut -d= -f1 | xargs -L1 git config --unset-all 29 | 30 | git config user.name "${GITHUB_ACTOR}" 31 | git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" 32 | git config credential.helper store 33 | echo "https://samuelsadok:${{ secrets.REPO_ACCESS_TOKEN }}@github.com" > ~/.git-credentials 34 | wc -c ~/.git-credentials 35 | 36 | URL="https://github.com/samuelsadok/${{ matrix.name }}.git" 37 | git remote add ${{ matrix.name }}-origin "$URL" 38 | 39 | echo "Fetching from ${{ matrix.name }}-origin..." 40 | git fetch ${{ matrix.name }}-origin 41 | 42 | BRANCH="${GITHUB_REF##*/}" 43 | echo "Pushing to ${{ matrix.name }}-origin/$BRANCH..." 44 | git subtree push --prefix ${{ matrix.dir }} ${{ matrix.name }}-origin "$BRANCH" 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .Trash* 3 | 4 | # Test build files 5 | /test/autogen 6 | /test/build 7 | /sim/autogen 8 | /sim/build 9 | 10 | # Tup database 11 | /.tup 12 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "/usr/include/libusb-1.0", 8 | "/usr/include/dbus-1.0", 9 | "/usr/lib/dbus-1.0/include" 10 | ], 11 | "defines": [ 12 | "FIBRE_ENABLE_SERVER=1", 13 | "FIBRE_ENABLE_CLIENT=1", 14 | "FIBRE_ENABLE_EVENT_LOOP=1", 15 | "FIBRE_ALLOW_HEAP=1" 16 | ], 17 | "compilerPath": "/usr/bin/clang", 18 | "cStandard": "c11", 19 | "intelliSenseMode": "clang-x64" 20 | } 21 | ], 22 | "version": 4 23 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "fibre-shell (libfibre)", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "/usr/bin/python", 12 | "args": ["${workspaceFolder}/tools/fibre-shell"], 13 | "stopAtEntry": false, 14 | "cwd": "${workspaceFolder}", 15 | "environment": [{"name": "FIBRE_LOG", "value": "5"}], 16 | "externalConsole": false, 17 | "MIMode": "gdb", 18 | "setupCommands": [ 19 | { 20 | "description": "Enable pretty-printing for gdb", 21 | "text": "-enable-pretty-printing", 22 | "ignoreFailures": true 23 | } 24 | ], 25 | "symbolLoadInfo": { 26 | "loadAll": true, 27 | "exceptionList": "" 28 | } 29 | }, 30 | { 31 | "name": "fibre-shell (PyFibre)", 32 | "type": "python", 33 | "request": "launch", 34 | "program": "${workspaceFolder}/tools/fibre-shell", 35 | "console": "integratedTerminal" 36 | }, 37 | { 38 | "name": "Python Test Client (libfibre)", 39 | "type": "cppdbg", 40 | "request": "launch", 41 | "program": "/usr/bin/python", 42 | "args": ["${workspaceFolder}/test/test_client.py"], 43 | "stopAtEntry": false, 44 | "cwd": "${workspaceFolder}", 45 | "environment": [{"name": "FIBRE_LOG", "value": "5"}], 46 | "externalConsole": false, 47 | "MIMode": "gdb", 48 | "setupCommands": [ 49 | { 50 | "description": "Enable pretty-printing for gdb", 51 | "text": "-enable-pretty-printing", 52 | "ignoreFailures": true 53 | } 54 | ], 55 | "symbolLoadInfo": { 56 | "loadAll": true, 57 | "exceptionList": "" 58 | } 59 | }, 60 | { 61 | "name": "Python Test Client (Python)", 62 | "type": "python", 63 | "request": "launch", 64 | "program": "${workspaceFolder}/test/test_client.py", 65 | "env": {"FIBRE_LOG": "5"}, 66 | "console": "integratedTerminal" 67 | }, 68 | { 69 | "name": "C++ Test Server", 70 | "type": "cppdbg", 71 | "request": "launch", 72 | "program": "${workspaceFolder}/test/build/test_node.elf", 73 | "stopAtEntry": false, 74 | "cwd": "${workspaceFolder}/test", 75 | "environment": [{"name": "FIBRE_LOG", "value": "5"}], 76 | "externalConsole": false, 77 | "MIMode": "gdb", 78 | "args": ["--server", "--domain", "tcp-server:address=localhost,port=14220,can:if=vcan0"], 79 | "setupCommands": [ 80 | { 81 | "description": "Enable pretty-printing for gdb", 82 | "text": "-enable-pretty-printing", 83 | "ignoreFailures": true 84 | } 85 | ], 86 | "symbolLoadInfo": { 87 | "loadAll": true, 88 | "exceptionList": "" 89 | } 90 | }, 91 | { 92 | "name": "Simulation", 93 | "type": "cppdbg", 94 | "request": "launch", 95 | "program": "${workspaceFolder}/sim/build/fibre_sim", 96 | "stopAtEntry": false, 97 | "cwd": "${workspaceFolder}/sim", 98 | "environment": [{"name": "FIBRE_LOG", "value": "5"}], 99 | "externalConsole": false, 100 | "MIMode": "gdb", 101 | "setupCommands": [ 102 | { 103 | "description": "Enable pretty-printing for gdb", 104 | "text": "-enable-pretty-printing", 105 | "ignoreFailures": true 106 | } 107 | ], 108 | "symbolLoadInfo": { 109 | "loadAll": true, 110 | "exceptionList": "" 111 | } 112 | } 113 | ] 114 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.autoAddFileAssociations": false 3 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "shell", 6 | "label": "Build Libfibre", 7 | "command": "make", 8 | "args": [], 9 | "options": { 10 | "cwd": "${workspaceFolder}" 11 | }, 12 | "problemMatcher": { 13 | "base": "$gcc", 14 | "fileLocation": ["relative", "${workspaceFolder}/cpp"], 15 | 16 | }, 17 | "group": { 18 | "kind": "build", 19 | "isDefault": true, 20 | }, 21 | "presentation": { 22 | "clear": true 23 | } 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2020 The Fibre Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: 3 | tup --no-environ-check 4 | cp cpp/build/libfibre-* python/fibre/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fibre 2 | 3 | ## Overview 4 | 5 | The goal of Fibre is to provide a framework to make suckless distributed applications easier to program. 6 | 7 | In particular: 8 | 9 | - Nobody likes boiler plate code. Using a remote object should feel almost 10 | exactly as if it was local. No matter if it's in a different process, on 11 | a USB device, connected via Bluetooth, over the Internet or all at the 12 | same time. 13 | All complexity arising from the system being distributed should be taken 14 | care of by Fibre while still allowing the application developer to easily 15 | fine-tune things. 16 | 17 | - Fibre has the ambition to run on most major platforms and provide bindings 18 | for the most popular languages. Even bare metal embedded systems with very 19 | limited resources. See the Compatibility section for the current status. 20 | 21 | - Once you deployed your application and want to change the interface, don't 22 | worry about breaking other applications. With Fibre's object model, the 23 | most common updates like adding methods, properties or arguments won't 24 | break anything. Sometimes you can get away with removing methods if they 25 | weren't used by other programs. **This is not implemented yet.** 26 | 27 | ## Platform Compatibility 28 | 29 | Fibre can be compiled for many of platforms and work on many kinds of transport layers. But to make life simple Fibre already ships with built-in support for a couple of backends which can be enabled/disabled selectively. The [official precompiled binaries](https://github.com/samuelsadok/fibre/releases) (and by extension all language bindings) have all available backends enabled. These backends are available: 30 | 31 | | | Windows | macOS [1] | Linux | Web [2] | 32 | |---------------------------|--------------|--------------|--------------|--------------| 33 | | USB (`usb`) | yes (libusb) | yes (libusb) | yes (libusb) | yes (WebUSB) | 34 | | TCP client (`tcp-server`) | no | no | yes | no | 35 | | TCP server (`tcp-client`) | no | no | yes | no | 36 | 37 | - [1] macOS 10.9 (Mavericks) or later 38 | - [2] see [fibre-js](js/README.md) 39 | 40 | ## Channel Specs 41 | 42 | When discovering objects and publishing objects, the caller usually specifies which backends to discover/publish on. This is specified through a channel spec string. 43 | 44 | The channel spec string has the form `backend1:key1=val1,key2=val2;backend2:key1=val1,key2=val2;backend3`. The following sections describe the available backends and the arguments they support. Integers can be in decimal as well as hexadecimal notation (`0x1234`). 45 | 46 | ### `usb` 47 | 48 | **Compile option:** `FIBRE_ENABLE_LIBUSB_BACKEND` 49 | 50 | **Parameters:** 51 | 52 | - `bus` (int): Only accept devices on this bus number. 53 | - `address` (int): Only accept the USB device with this device address. The device address usually changes when the device is replugged. 54 | - `idVendor` (int): Only accept devices with this Vendor ID. 55 | - `idProduct` (int): Only accept devices with this Product ID. 56 | - `bInterfaceClass` (int): The interface class of the compatible interface or interface association. 57 | - `bInterfaceSubClass` (int): The interface subclass of the compatible interface or interface association. 58 | - `bInterfaceProtocol` (int): The protocol of the compatible interface or interface association. 59 | 60 | Omitted parameters are ignored during filtering. 61 | 62 | **Example:** `usb:idVendor=0x1209,idVendor=0x0d32` looks for channels on USB devices with VID:PID 1209:0d32. 63 | 64 | ### `tcp-client` 65 | 66 | - `address` (string): The IP address or hostname of the remote server to connect to. 67 | - `port` (int): The port on which to connect. 68 | 69 | ### `tcp-server` 70 | 71 | - `address` (string): The IP address of the local server on which to listen. 72 | - `port` (int): The port on which to listen. 73 | 74 | ### `serial` 75 | 76 | - `path` (int): The name or path of the serial port. On Unix systems this is usually something like `/dev/ttyACM0` and on Windows something like `COM1`. 77 | 78 | **Example:** `serial:path=/dev/ttyACM0` looks for channels on the serial port /dev/ttyACM0. 79 | 80 | ## Implementations 81 | 82 | * **C++**: See [fibre-cpp](cpp/README.md). 83 | * **C**: See [fibre-cpp](cpp/README.md), specifically `libfibre.h`. 84 | * **Python**: See [PyFibre](python/README.md). 85 | * **JavaScript**: See [fibre-js](js/README.md). 86 | 87 | Under the hood all language-specific implementations bind to the C++ implementation which we provide as a [precompiled library](https://github.com/samuelsadok/fibre/releases) `libfibre`. 88 | 89 | ## Adding Fibre to your project 90 | 91 | We recommend Git subtrees if you want to include the Fibre source code in another project. 92 | Other contributors don't need to know anything about subtrees. To them the Fibre repo will be like any other normal directory. 93 | 94 | #### Adding the repo 95 | ``` 96 | git remote add fibre-origin git@github.com:samuelsadok/fibre.git 97 | git fetch fibre-origin 98 | git subtree add --prefix=fibre --squash fibre-origin master 99 | ``` 100 | 101 | If you only need support for a specific programming language you can also just include the language-specific repository Instead of the whole main repository. 102 | 103 | #### Pulling updates from upstream 104 | ``` 105 | git subtree pull --prefix=fibre --squash fibre-origin master 106 | ``` 107 | 108 | #### Contributing changes back to upstream 109 | This requires push access to `fibre-origin`. 110 | ``` 111 | git subtree push --prefix=fibre fibre-origin master 112 | ``` 113 | 114 | ## Projects using Fibre ## 115 | 116 | - [ODrive](https://github.com/madcowswe/ODrive): High performance motor control 117 | - [lightd](https://github.com/samuelsadok/lightd): Service that can be run on a Raspberry Pi (or similar) to control RGB LED strips 118 | 119 | ## Contribute ## 120 | 121 | This project losely adheres to the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html). 122 | 123 | ## Credits ## 124 | 125 | A significant portion of the code in this repository was written for and financed by [ODrive Robotics Inc](https://odriverobotics.com/). 126 | -------------------------------------------------------------------------------- /Tuprules.lua: -------------------------------------------------------------------------------- 1 | 2 | function compile(src_file, extra_inputs) 3 | obj_file = 'build/'..tup.file(src_file)..'.o' 4 | tup.frule{ 5 | inputs={src_file, extra_inputs=extra_inputs}, 6 | command='^co^ '..CXX..' -c %f '..tostring(CFLAGS)..' -o %o', 7 | outputs={obj_file} 8 | } 9 | return obj_file 10 | end 11 | -------------------------------------------------------------------------------- /cpp/.gitignore: -------------------------------------------------------------------------------- 1 | /third_party 2 | build/ 3 | build-*/ 4 | /.tup 5 | -------------------------------------------------------------------------------- /cpp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM archlinux:base-devel 2 | 3 | # Set up package manager 4 | RUN echo "[custom]" >> /etc/pacman.conf && \ 5 | echo "SigLevel = Required TrustedOnly" >> /etc/pacman.conf && \ 6 | echo "Server = https://innovation-labs.appinstall.ch/archlinux/\$repo/os/\$arch" >> /etc/pacman.conf && \ 7 | pacman-key --init && \ 8 | pacman-key --recv-keys 0CB4116A1A3A789937D6DEFB506F27823D2B7B33 && \ 9 | pacman-key --lsign-key 0CB4116A1A3A789937D6DEFB506F27823D2B7B33 && \ 10 | pacman -Syu --noconfirm 11 | 12 | # Install prerequisites for the following targets: 13 | # - Linux (AMD64) 14 | # - Linux (ARM 32-bit) 15 | # - Linux (AArch64) 16 | # - Windows (AMD64) 17 | # - macOS (x86_32/AMD64) 18 | # - WebAssembly 19 | RUN pacman -S --noconfirm tup clang gcc binutils wget && \ 20 | pacman -S --noconfirm arm-linux-gnueabihf-gcc arm-linux-gnueabihf-binutils && \ 21 | pacman -S --noconfirm aarch64-linux-gnu-gcc && \ 22 | pacman -S --noconfirm mingw-w64-gcc mingw-w64-binutils p7zip && \ 23 | pacman -S --noconfirm apple-darwin-osxcross && \ 24 | pacman -S --noconfirm emscripten 25 | 26 | # Downgrade binutils (temporary workaround for https://bugs.archlinux.org/task/69567) 27 | RUN wget https://archive.archlinux.org/packages/b/binutils/binutils-2.35.1-1-x86_64.pkg.tar.zst && \ 28 | pacman -U --noconfirm binutils-2.35.1-1-x86_64.pkg.tar.zst 29 | 30 | ENV PATH=${PATH}:/opt/osxcross/bin 31 | ENV PATH=${PATH}:/usr/lib/emscripten 32 | 33 | COPY get_dependencies.sh /get_dependencies.sh 34 | 35 | # Download and compile dependencies 36 | 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" && \ 37 | /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" && \ 38 | /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" && \ 39 | /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" && \ 40 | /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" && \ 41 | /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" && \ 42 | /get_dependencies.sh download_deb_pkg libusb-aarch64 "http://deb.debian.org/debian/pool/main/libu/libusb-1.0/libusb-1.0-0_1.0.24-2_arm64.deb" && \ 43 | /get_dependencies.sh download_deb_pkg libusb-dev-aarch64 "http://deb.debian.org/debian/pool/main/libu/libusb-1.0/libusb-1.0-0-dev_1.0.24-2_arm64.deb" && \ 44 | /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" 45 | 46 | 47 | RUN /get_dependencies.sh patch_macos_sdk && \ 48 | 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' 49 | 50 | RUN mkdir -p "third_party/libusb-windows" && \ 51 | pushd "third_party/libusb-windows" > /dev/null && \ 52 | wget "https://github.com/libusb/libusb/releases/download/v1.0.23/libusb-1.0.23.7z" && \ 53 | 7z x -o"libusb-1.0.23" "libusb-1.0.23.7z" 54 | 55 | # Make Emscripten build its standard libraries for the WebAssembly target 56 | RUN echo "void test() {}" | em++ -x c - -o /tmp/a.out 57 | 58 | # Install dependencies for interface_generator.py 59 | RUN pacman -S --noconfirm python-yaml python-jinja python-jsonschema 60 | 61 | ENV THIRD_PARTY=/ 62 | 63 | # Set up entrypoint 64 | RUN echo "#!/bin/bash" > /entrypoint.sh && \ 65 | echo "set -euo pipefail" >> /entrypoint.sh && \ 66 | echo "rm -rdf build/*" >> /entrypoint.sh && \ 67 | echo "echo building \$@" >> /entrypoint.sh && \ 68 | echo "tup generate --config \$@ /tmp/build.sh" >> /entrypoint.sh && \ 69 | echo "exec /usr/bin/bash -x -e /tmp/build.sh" >> /entrypoint.sh && \ 70 | chmod +x /entrypoint.sh && \ 71 | mkdir /build 72 | 73 | WORKDIR /build 74 | ENTRYPOINT ["/entrypoint.sh"] 75 | -------------------------------------------------------------------------------- /cpp/Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: 3 | tup --no-environ-check build-local 4 | tup --no-environ-check build-wasm 5 | cp build-local/libfibre-* ../python/fibre/ 6 | cp build-wasm/libfibre-* ../js/ 7 | -------------------------------------------------------------------------------- /cpp/Tupfile.lua: -------------------------------------------------------------------------------- 1 | 2 | -- Projects that include fibre-cpp and also use tup can place a Tuprules.lua file 3 | -- into their root directory with the line `no_libfibre = true` to prevent 4 | -- libfibre from building. 5 | if no_libfibre == true then 6 | return 7 | end 8 | 9 | tup.include('package.lua') 10 | 11 | CFLAGS = {'-fPIC -std=c++11 -DFIBRE_COMPILE -Wall -I.'} 12 | LDFLAGS = {'-static-libstdc++'} 13 | 14 | 15 | if tup.getconfig("CC") == "" then 16 | CXX = 'clang++' 17 | LINKER = 'clang++' 18 | else 19 | CXX = tup.getconfig("CC") 20 | LINKER = tup.getconfig("CC") 21 | end 22 | 23 | function get_bool_config(name, default) 24 | if tup.getconfig(name) == "" then 25 | return default 26 | elseif tup.getconfig(name) == "true" then 27 | return true 28 | elseif tup.getconfig(name) == "false" then 29 | return false 30 | else 31 | error(name.." ("..tup.getconfig(name)..") must be 'true' or 'false'.") 32 | end 33 | end 34 | 35 | CFLAGS += tup.getconfig("CFLAGS") 36 | LDFLAGS += tup.getconfig("LDFLAGS") 37 | DEBUG = get_bool_config("DEBUG", true) 38 | STRICT = get_bool_config("STRICT", false) 39 | 40 | machine = fibre_run_now(CXX..' -dumpmachine') -- works with both clang and GCC 41 | 42 | BUILD_TYPE='-shared' 43 | enable_tcp = true 44 | 45 | if string.find(machine, "x86_64.*%-linux%-.*") then 46 | outname = 'libfibre-linux-amd64.so' 47 | LDFLAGS += '-lpthread -Wl,--version-script=libfibre.version -Wl,--gc-sections' 48 | STRIP = not DEBUG 49 | elseif string.find(machine, "arm.*%-linux%-.*") then 50 | outname = 'libfibre-linux-armhf.so' 51 | LDFLAGS += '-lpthread -Wl,--version-script=libfibre.version -Wl,--gc-sections' 52 | STRIP = false 53 | elseif string.find(machine, "aarch64.*%-linux%-.*") then 54 | outname = 'libfibre-linux-aarch64.so' 55 | LDFLAGS += '-lpthread -Wl,--version-script=libfibre.version -Wl,--gc-sections' 56 | STRIP = not DEBUG 57 | elseif string.find(machine, "x86_64.*-mingw.*") then 58 | outname = 'libfibre-windows-amd64.dll' 59 | LDFLAGS += '-lpthread -Wl,--version-script=libfibre.version' 60 | STRIP = not DEBUG 61 | elseif string.find(machine, "x86_64.*-apple-.*") then 62 | outname = 'libfibre-macos-x86.dylib' 63 | STRIP = false 64 | enable_tcp = false 65 | elseif string.find(machine, "arm64.*-apple-.*") then 66 | outname = 'libfibre-macos-arm.dylib' 67 | STRIP = false 68 | enable_tcp = false 69 | elseif string.find(machine, "wasm.*") then 70 | outname = 'libfibre-wasm.js' 71 | STRIP = false 72 | enable_tcp = false 73 | BUILD_TYPE = '' 74 | else 75 | error('unknown machine identifier '..machine) 76 | end 77 | 78 | LDFLAGS += BUILD_TYPE 79 | 80 | if DEBUG then 81 | CFLAGS += '-Os -g' 82 | else 83 | CFLAGS += '-O3' -- TODO: add back -lfto 84 | end 85 | 86 | if STRICT then 87 | CFLAGS += '-Werror' 88 | end 89 | 90 | function compile(src_file) 91 | obj_file = 'build/'..tup.file(src_file)..'.o' 92 | tup.frule{ 93 | inputs={src_file}, 94 | command='^co^ '..CXX..' -c %f '..tostring(CFLAGS)..' -o %o', 95 | outputs={obj_file} 96 | } 97 | return obj_file 98 | end 99 | 100 | pkg = get_fibre_package({ 101 | enable_server=false, 102 | enable_client=true, 103 | enable_tcp_server_backend=get_bool_config("ENABLE_TCP_SERVER_BACKEND", enable_tcp), 104 | enable_tcp_client_backend=get_bool_config("ENABLE_TCP_CLIENT_BACKEND", enable_tcp), 105 | enable_libusb_backend=get_bool_config("ENABLE_LIBUSB_BACKEND", true), 106 | enable_socket_can_backend=get_bool_config("ENABLE_SOCKETCAN_BACKEND", true), 107 | allow_heap=true, 108 | pkgconf=(tup.getconfig("USE_PKGCONF") != "") and tup.getconfig("USE_PKGCONF") or nil 109 | }) 110 | 111 | CFLAGS += pkg.cflags 112 | LDFLAGS += pkg.ldflags 113 | 114 | for _, inc in pairs(pkg.include_dirs) do 115 | CFLAGS += '-I./'..inc 116 | end 117 | 118 | for _, src_file in pairs(pkg.code_files) do 119 | object_files += compile(src_file) 120 | end 121 | object_files += compile('libfibre.cpp') 122 | 123 | outname = 'build/'..outname 124 | 125 | if not STRIP then 126 | compile_outname=outname 127 | else 128 | compile_outname=outname..'.fat' 129 | end 130 | 131 | if tup.ext(outname) == 'js' then 132 | extra_outputs = {'build/'..tup.base(compile_outname)..'.wasm'} 133 | else 134 | extra_outputs = {} 135 | end 136 | 137 | tup.frule{ 138 | inputs=object_files, 139 | command='^c^ '..LINKER..' %f '..tostring(CFLAGS)..' '..tostring(LDFLAGS)..' -o %o', 140 | outputs={compile_outname, extra_outputs=extra_outputs} 141 | } 142 | 143 | if STRIP then 144 | tup.frule{ 145 | inputs={compile_outname}, 146 | command=tup.getconfig("BINUTILS_PREFIX")..'strip --strip-all --discard-all %f -o %o', 147 | outputs={outname} 148 | } 149 | end 150 | 151 | if string.find(machine, "x86_64.*-apple-.*") then 152 | tup.frule{ 153 | inputs=outname, 154 | command='^c^ chmod 644 %f', 155 | } 156 | end 157 | -------------------------------------------------------------------------------- /cpp/channel_discoverer.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace fibre; 9 | 10 | bool ChannelDiscoverer::try_parse_key(const char* begin, const char* end, const char* key, const char** val_begin, const char** val_end) { 11 | ssize_t keylen = strlen(key); 12 | 13 | while (begin != end) { 14 | const char* next_delim = std::find(begin, end, ','); 15 | 16 | if ((next_delim - begin >= keylen) && (memcmp(begin, key, keylen) == 0)) { 17 | if (next_delim - begin == keylen) { 18 | // The key exists but has no value 19 | *val_begin = *val_end = next_delim; 20 | return true; 21 | } else if (begin[keylen] == '=') { 22 | *val_begin = begin + keylen + 1; 23 | *val_end = next_delim; 24 | return true; 25 | } 26 | } 27 | 28 | begin = std::min(next_delim + 1, end); 29 | } 30 | 31 | return false; // key not found 32 | } 33 | 34 | bool ChannelDiscoverer::try_parse_key(const char* begin, const char* end, const char* key, int* val) { 35 | const char* val_begin; 36 | const char* val_end; 37 | if (!try_parse_key(begin, end, key, &val_begin, &val_end)) { 38 | return false; 39 | } 40 | 41 | // Copy value to a null-terminated buffer 42 | char buf[val_end - val_begin + 1]; 43 | memcpy(buf, val_begin, val_end - val_begin); 44 | buf[val_end - val_begin] = 0; 45 | 46 | return sscanf(buf, "0x%x", val) == 1 47 | || sscanf(buf, "%d", val) == 1; 48 | } 49 | 50 | bool ChannelDiscoverer::try_parse_key(const char* begin, const char* end, const char* key, std::string* val) { 51 | const char* val_begin; 52 | const char* val_end; 53 | if (!try_parse_key(begin, end, key, &val_begin, &val_end)) { 54 | return false; 55 | } 56 | *val = std::string{val_begin, val_end}; 57 | return true; 58 | } 59 | 60 | RichStatus ChannelDiscoverer::show_device_dialog() { 61 | return F_MAKE_ERR("not implemented"); 62 | } 63 | -------------------------------------------------------------------------------- /cpp/codecs.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_CODECS_HPP 2 | #define __FIBRE_CODECS_HPP 3 | 4 | #include "static_exports.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "property.hpp" 11 | #include 12 | 13 | namespace fibre { 14 | 15 | template 16 | struct Codec { 17 | static RichStatusOr decode(Domain* domain, cbufptr_t* buffer) { 18 | F_LOG_E(domain->ctx->logger, "unknown decoder for " << typeid(T).name()); 19 | return std::nullopt; } 20 | }; 21 | 22 | template<> struct Codec { 23 | static RichStatusOr decode(Domain* domain, cbufptr_t* buffer) { return (buffer->begin() == buffer->end()) ? RichStatusOr{F_MAKE_ERR("empty buffer")} : RichStatusOr((bool)*(buffer->begin()++)); } 24 | static bool encode(bool value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } 25 | }; 26 | 27 | template 28 | struct Codec::value>> { 29 | static RichStatusOr decode(Domain* domain, cbufptr_t* buffer) { 30 | std::optional val = SimpleSerializer::read(&(buffer->begin()), buffer->end()); 31 | if (val.has_value()) { 32 | return *val; 33 | } else { 34 | return F_MAKE_ERR("decode failed"); 35 | } 36 | } 37 | static bool encode(T value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } 38 | }; 39 | 40 | template<> struct Codec { 41 | static RichStatusOr decode(Domain* domain, cbufptr_t* buffer) { 42 | RichStatusOr int_val = Codec::decode(domain, buffer); 43 | return int_val.has_value() ? RichStatusOr(*reinterpret_cast(&int_val.value())) : int_val.status(); 44 | } 45 | static bool encode(float value, bufptr_t* buffer) { 46 | void* ptr = &value; 47 | return Codec::encode(*reinterpret_cast(ptr), buffer); 48 | } 49 | }; 50 | template 51 | struct Codec::value>> { 52 | using int_type = std::underlying_type_t; 53 | static std::optional decode(Domain* domain, cbufptr_t* buffer) { 54 | std::optional int_val = SimpleSerializer::read(&(buffer->begin()), buffer->end()); 55 | return int_val.has_value() ? std::make_optional(static_cast(*int_val)) : std::nullopt; 56 | } 57 | static bool encode(T value, bufptr_t* buffer) { return SimpleSerializer::write(value, &(buffer->begin()), buffer->end()); } 58 | }; 59 | 60 | 61 | template struct Codec { 62 | static RichStatusOr decode(Domain* domain, cbufptr_t* buffer) { 63 | #if FIBRE_ENABLE_SERVER 64 | uint8_t idx = (*buffer)[0]; // TODO: define actual decoder 65 | 66 | // Check object type 67 | ServerObjectDefinition* obj_entry = domain->get_server_object(idx); 68 | 69 | F_RET_IF(!obj_entry, 70 | "index out of range"); 71 | 72 | F_RET_IF(obj_entry->interface != get_interface_id(), 73 | "incompatile interface: expected " << (int)obj_entry->interface << " but got " << (int)get_interface_id()); 74 | 75 | *buffer = buffer->skip(1); 76 | return (T*)obj_entry->ptr; 77 | #else 78 | return F_MAKE_ERR("no server support compiled in"); 79 | #endif 80 | } 81 | 82 | static bool encode(bool value, bufptr_t* buffer) { return false; } 83 | }; 84 | 85 | } 86 | 87 | #endif // __FIBRE_CODECS_HPP 88 | -------------------------------------------------------------------------------- /cpp/configs/linux-aarch64.config: -------------------------------------------------------------------------------- 1 | CONFIG_DEBUG=false 2 | CONFIG_STRICT=true 3 | CONFIG_CC="aarch64-linux-gnu-g++" 4 | CONFIG_BINUTILS_PREFIX="aarch64-linux-gnu-" 5 | CONFIG_CFLAGS="-I$THIRD_PARTY./third_party/libusb-dev-aarch64/usr/include/libusb-1.0 -Wno-maybe-uninitialized" 6 | CONFIG_LDFLAGS="$THIRD_PARTY./third_party/libusb-aarch64/usr/lib/aarch64-linux-gnu/libusb-1.0.so.0" 7 | CONFIG_USE_PKGCONF=false 8 | -------------------------------------------------------------------------------- /cpp/configs/linux-amd64.config: -------------------------------------------------------------------------------- 1 | CONFIG_DEBUG=false 2 | CONFIG_STRICT=true 3 | CONFIG_CC="clang++" 4 | CONFIG_CFLAGS="-I$THIRD_PARTY./third_party/libusb-dev-armhf/usr/include/libusb-1.0" 5 | CONFIG_LDFLAGS="$THIRD_PARTY./third_party/libusb-amd64/lib/x86_64-linux-gnu/libusb-1.0.so.0.2.0" 6 | CONFIG_USE_PKGCONF=false 7 | -------------------------------------------------------------------------------- /cpp/configs/linux-armhf.config: -------------------------------------------------------------------------------- 1 | CONFIG_DEBUG=false 2 | CONFIG_STRICT=true 3 | CONFIG_CC="arm-linux-gnueabihf-g++" 4 | CONFIG_CFLAGS="-I$THIRD_PARTY./third_party/libusb-dev-armhf/usr/include/libusb-1.0" 5 | 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" 6 | CONFIG_USE_PKGCONF=false 7 | -------------------------------------------------------------------------------- /cpp/configs/macos-x86.config: -------------------------------------------------------------------------------- 1 | CONFIG_DEBUG=false 2 | CONFIG_STRICT=true 3 | CONFIG_CC="LD_LIBRARY_PATH=/opt/osxcross/lib MACOSX_DEPLOYMENT_TARGET=10.9 /opt/osxcross/bin/o64-clang++" 4 | CONFIG_CFLAGS="-I$THIRD_PARTY./third_party/libusb-1.0.23/libusb -arch i386 -arch x86_64" 5 | CONFIG_LDFLAGS="$THIRD_PARTY./third_party/libusb-1.0.23/build-macos-amd64/libusb/.libs/libusb-1.0.a -framework CoreFoundation -framework IOKit" 6 | # not supported yet 7 | CONFIG_ENABLE_TCP_SERVER_BACKEND=false 8 | # not supported yet 9 | CONFIG_ENABLE_TCP_CLIENT_BACKEND=false 10 | CONFIG_USE_PKGCONF=false 11 | -------------------------------------------------------------------------------- /cpp/configs/wasm.config: -------------------------------------------------------------------------------- 1 | CONFIG_DEBUG=true 2 | CONFIG_STRICT=true 3 | CONFIG_CC=/usr/lib/emscripten/em++ 4 | CONFIG_CFLAGS=-include emscripten.h -DFIBRE_PUBLIC=EMSCRIPTEN_KEEPALIVE -fno-exceptions 5 | CONFIG_LDFLAGS=-s EXPORT_ES6=1 -s MODULARIZE=1 -s USE_ES6_IMPORT_META=0 -s RESERVED_FUNCTION_POINTERS=1 -s 'EXPORTED_RUNTIME_METHODS=[addFunction, stringToUTF8, lengthBytesUTF8, UTF8ArrayToString, ENV]' -s 'EXPORTED_FUNCTIONS=[_malloc, _free]' -s ERROR_ON_UNDEFINED_SYMBOLS=1 --js-library platform_support/dom_connector.js -fno-exceptions -s NO_FILESYSTEM=1 -Wno-limited-postlink-optimizations -sINITIAL_MEMORY=33554432 6 | CONFIG_USE_PKGCONF=false 7 | -------------------------------------------------------------------------------- /cpp/configs/windows-amd64.config: -------------------------------------------------------------------------------- 1 | CONFIG_DEBUG=false 2 | CONFIG_STRICT=true 3 | CONFIG_CC="x86_64-w64-mingw32-g++" 4 | CONFIG_CFLAGS="-I$THIRD_PARTY./third_party/libusb-windows/libusb-1.0.23/include/libusb-1.0" 5 | CONFIG_LDFLAGS="-static-libgcc $THIRD_PARTY./third_party/libusb-windows/libusb-1.0.23/MinGW64/static/libusb-1.0.a" 6 | # not supported yet 7 | CONFIG_ENABLE_TCP_SERVER_BACKEND=false 8 | # not supported yet 9 | CONFIG_ENABLE_TCP_CLIENT_BACKEND=false 10 | CONFIG_USE_PKGCONF=false 11 | -------------------------------------------------------------------------------- /cpp/crc.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CRC_HPP 2 | #define __CRC_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // Calculates an arbitrary CRC for one byte. 9 | // Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code 10 | template 11 | static T calc_crc(T remainder, uint8_t value) { 12 | constexpr T BIT_WIDTH = (CHAR_BIT * sizeof(T)); 13 | constexpr T TOPBIT = ((T)1 << (BIT_WIDTH - 1)); 14 | 15 | // Bring the next byte into the remainder. 16 | remainder ^= (value << (BIT_WIDTH - 8)); 17 | 18 | // Perform modulo-2 division, a bit at a time. 19 | for (uint8_t bit = 8; bit; --bit) { 20 | if (remainder & TOPBIT) { 21 | remainder = (remainder << 1) ^ POLYNOMIAL; 22 | } else { 23 | remainder = (remainder << 1); 24 | } 25 | } 26 | 27 | return remainder; 28 | } 29 | 30 | template 31 | static T calc_crc(T remainder, const uint8_t* buffer, size_t length) { 32 | while (length--) 33 | remainder = calc_crc(remainder, *(buffer++)); 34 | return remainder; 35 | } 36 | 37 | template 38 | static uint8_t calc_crc8(uint8_t remainder, uint8_t value) { 39 | return calc_crc(remainder, value); 40 | } 41 | 42 | template 43 | static uint16_t calc_crc16(uint16_t remainder, uint8_t value) { 44 | return calc_crc(remainder, value); 45 | } 46 | 47 | template 48 | static uint8_t calc_crc8(uint8_t remainder, const uint8_t* buffer, size_t length) { 49 | return calc_crc(remainder, buffer, length); 50 | } 51 | 52 | template 53 | static uint16_t calc_crc16(uint16_t remainder, const uint8_t* buffer, size_t length) { 54 | return calc_crc(remainder, buffer, length); 55 | } 56 | 57 | #endif /* __CRC_HPP */ 58 | -------------------------------------------------------------------------------- /cpp/fibre_config.hpp: -------------------------------------------------------------------------------- 1 | 2 | #define FIBRE_ENABLE_SERVER 0 3 | #define FIBRE_ENABLE_CLIENT 1 4 | 5 | #if defined(__linux__) 6 | // event loop currently only implemented on Linux 7 | #define FIBRE_ENABLE_EVENT_LOOP 1 8 | #endif 9 | 10 | #define FIBRE_ALLOW_HEAP 1 11 | #define FIBRE_ENABLE_TEXT_LOGGING 1 12 | 13 | #if defined(__EMSCRIPTEN__) 14 | #define FIBRE_ENABLE_WEBUSB_BACKEND 1 15 | #else 16 | #define FIBRE_ENABLE_LIBUSB_BACKEND 1 17 | #endif 18 | 19 | #if defined(__linux__) 20 | #define FIBRE_ENABLE_TCP_CLIENT_BACKEND 1 21 | #define FIBRE_ENABLE_TCP_SERVER_BACKEND 1 22 | #define FIBRE_ENABLE_SOCKET_CAN_BACKEND 1 23 | #endif 24 | 25 | #if FIBRE_ENABLE_SOCKET_CAN_BACKEND 26 | #define FIBRE_ENABLE_CAN_ADAPTER 1 27 | #endif 28 | -------------------------------------------------------------------------------- /cpp/func_utils.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | using namespace fibre; 5 | 6 | Socket* FuncAsCoro::start_call(Domain* domain, bufptr_t call_frame, 7 | Socket* caller) const { 8 | if (call_frame.size() < sizeof(FuncAsCoroCall)) { 9 | return nullptr; 10 | } 11 | FuncAsCoroCall* call = 12 | new ((FuncAsCoroCall*)call_frame.begin()) FuncAsCoroCall{}; 13 | call->func = this; 14 | call->domain_ = domain; 15 | call->caller_ = caller; 16 | call->buf_end = call_frame.end(); 17 | call->collector_or_emitter_ = ArgCollector{{(uint8_t*)(call + 1)}, 1, 0}; 18 | return call; 19 | } 20 | 21 | WriteResult ArgCollector::write(WriteArgs args, bufptr_t storage) { 22 | while (args.buf.n_chunks()) { 23 | if (n_arg_dividers_ >= 24 | sizeof(arg_dividers_) / sizeof(arg_dividers_[0])) { 25 | return {kFibreOutOfMemory, args.buf.begin()}; 26 | } 27 | 28 | Chunk chunk = args.buf.front(); 29 | 30 | if (chunk.is_buf()) { 31 | // Copy caller's input buffer into call's state buffer 32 | if (chunk.buf().size() > storage.size() - offset_) { 33 | return {kFibreOutOfMemory, args.buf.begin()}; 34 | } 35 | std::copy(chunk.buf().begin(), chunk.buf().end(), 36 | storage.begin() + offset_); 37 | offset_ += chunk.buf().size(); 38 | 39 | } else if (chunk.is_frame_boundary() && chunk.layer() == 0) { 40 | // Switch to next input arg 41 | arg_dividers_[n_arg_dividers_] = storage.begin() + offset_; 42 | n_arg_dividers_++; 43 | } 44 | 45 | args.buf = args.buf.skip_chunks(1); 46 | } 47 | return {args.status, args.buf.begin()}; 48 | } 49 | 50 | void ArgEmitter::start(Status status, const uint8_t** arg_dividers, 51 | size_t n_arg_dividers, Socket* sink) { 52 | // Convert output args to chunks 53 | BufChainBuilder builder{chunks_}; 54 | if (2 * n_arg_dividers > sizeof(chunks_) / sizeof(chunks_[0]) + 2) { 55 | status_ = kFibreOutOfMemory; 56 | } else { 57 | status_ = status; 58 | write_iterator it{builder}; 59 | for (size_t i = 1; i < n_arg_dividers; ++i) { 60 | it = Chunk(0, {arg_dividers[i - 1], arg_dividers[i]}); 61 | it = Chunk::frame_boundary(0); 62 | } 63 | } 64 | 65 | tx_chain_ = builder; 66 | 67 | // Write output arguments until done or connection blocks 68 | WriteResult result = sink->write({tx_chain_, status_}); 69 | while (!result.is_busy()) { 70 | tx_chain_ = tx_chain_.from(result.end); 71 | if (!tx_chain_.n_chunks()) { 72 | break; 73 | } 74 | result = sink->write({tx_chain_, status_}); 75 | } 76 | } 77 | 78 | WriteArgs ArgEmitter::on_write_done(WriteResult result) { 79 | tx_chain_ = tx_chain_.from(result.end); 80 | return {tx_chain_, status_}; 81 | } 82 | 83 | WriteResult FuncAsCoroCall::write(WriteArgs args) { 84 | bufptr_t arg_memory{(uint8_t*)(this + 1), buf_end}; 85 | 86 | if (collector_or_emitter_.index() != 0) { 87 | // Bad source behavior 88 | return {kFibreInternalError, args.buf.end()}; 89 | } 90 | 91 | ArgCollector& collector = std::get<0>(collector_or_emitter_); 92 | WriteResult result = collector.write(args, arg_memory); 93 | 94 | if (result.status == kFibreClosed) { 95 | const uint8_t* arg_dividers[8]; 96 | size_t n_arg_dividers = 8; 97 | 98 | Status status = func->impl_.invoke( 99 | domain_, collector.arg_dividers_, collector.n_arg_dividers_, 100 | arg_dividers, &n_arg_dividers, arg_memory); 101 | 102 | collector_or_emitter_ = ArgEmitter{}; 103 | std::get<1>(collector_or_emitter_) 104 | .start(status, arg_dividers, n_arg_dividers, caller_); 105 | 106 | } else if (result.status != kFibreOk) { 107 | collector_or_emitter_ = ArgEmitter{}; 108 | std::get<1>(collector_or_emitter_) 109 | .start(result.status, nullptr, 0, caller_); 110 | } 111 | 112 | return result; 113 | } 114 | 115 | WriteArgs FuncAsCoroCall::on_write_done(WriteResult result) { 116 | return std::get<1>(collector_or_emitter_).on_write_done(result); 117 | } 118 | 119 | void CoroAsFunc::call(const cbufptr_t* inputs, size_t n_inputs, 120 | Callback 121 | on_call_finished) { 122 | 123 | collector_ = ArgCollector{{rx_buf}, 1, 0}; 124 | 125 | const uint8_t* arg_dividers[8] = {tx_buf}; 126 | size_t n_arg_dividers = n_inputs + 1; 127 | 128 | // Convert input arguments to chunks 129 | size_t tx_buf_pos = 0; 130 | for (size_t i = 0; i < n_inputs; ++i) { 131 | if (inputs[i].size() > sizeof(tx_buf) - tx_buf_pos) { 132 | on_call_finished.invoke(this, kFibreOutOfMemory, nullptr, 0); 133 | return; 134 | } 135 | std::copy(inputs[i].begin(), inputs[i].end(), tx_buf + tx_buf_pos); 136 | arg_dividers[i] = tx_buf + tx_buf_pos; 137 | tx_buf_pos += inputs[i].size(); 138 | } 139 | 140 | on_call_finished_ = on_call_finished; 141 | 142 | Socket* call = func->start_call(nullptr, call_frame, this); 143 | emitter_.start(kFibreClosed, arg_dividers, n_arg_dividers, call); 144 | } 145 | 146 | WriteArgs CoroAsFunc::on_write_done(WriteResult result) { 147 | return emitter_.on_write_done(result); 148 | } 149 | 150 | WriteResult CoroAsFunc::write(WriteArgs args) { 151 | WriteResult result = collector_.write(args, rx_buf); 152 | 153 | if (result.status != kFibreOk) { 154 | cbufptr_t chunks[collector_.n_arg_dividers_ / 2]; 155 | for (size_t i = 1; i < collector_.n_arg_dividers_; ++i) { 156 | chunks[(i - 1) / 2] = {collector_.arg_dividers_[i - 1], 157 | collector_.arg_dividers_[i]}; 158 | } 159 | 160 | on_call_finished_.invoke_and_clear(this, args.status, chunks, 161 | collector_.n_arg_dividers_ / 2); 162 | } 163 | 164 | return result; 165 | } 166 | -------------------------------------------------------------------------------- /cpp/get_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # Usage: download_deb_pkg destination-dir url 5 | function download_deb_pkg() { 6 | mkdir -p third_party 7 | dir="$1" 8 | url="$2" 9 | file="$(sed 's|^.*/\([^/]*\)$|\1|' <<< "$url")" 10 | 11 | pushd third_party > /dev/null 12 | if ! [ -f "${file}" ]; then 13 | wget "${url}" 14 | fi 15 | if ! [ -d "${dir}/usr" ]; then 16 | ar x "${file}" "data.tar.xz" 17 | mkdir -p "${dir}" 18 | tar -xvf "data.tar.xz" -C "${dir}" 19 | fi 20 | popd > /dev/null 21 | } 22 | 23 | # Usage: compile_libusb arch-name arch 24 | function compile_libusb() { 25 | arch_name="$1" 26 | arch="$2" 27 | libusb_version=1.0.23 28 | 29 | pushd third_party > /dev/null 30 | if ! [ -f "libusb-${libusb_version}.tar.bz2" ]; then 31 | wget "https://github.com/libusb/libusb/releases/download/v${libusb_version}/libusb-${libusb_version}.tar.bz2" 32 | fi 33 | if ! [ -d "libusb-${libusb_version}" ]; then 34 | tar -xvf "libusb-${libusb_version}.tar.bz2" 35 | fi 36 | 37 | mkdir -p "libusb-${libusb_version}/build-${arch_name}" 38 | pushd "libusb-${libusb_version}/build-${arch_name}" > /dev/null 39 | unset LDFLAGS 40 | if ! [ -f "libusb/.libs/libusb-1.0.a" ]; then 41 | ../configure --host="$arch" \ 42 | --enable-static \ 43 | --prefix=/opt/osxcross/ \ 44 | --disable-dependency-tracking 45 | # They broke parallel building in libusb 1.20 46 | make 47 | fi 48 | popd > /dev/null 49 | popd > /dev/null 50 | } 51 | 52 | function patch_macos_sdk() { 53 | # Link are broken: 54 | # …ions/Current/Headers $ ls -l IOReturn.h 55 | # 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 56 | # Fix with: 57 | # sudo ln -sf /opt/osxcross/SDK/MacOSX10.13.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/IOKit/IOReturn.h IOReturn.h 58 | 59 | oldprefix="Users/phracker/Documents/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" 60 | newprefix="/opt/osxcross/SDK/MacOSX10.13.sdk" 61 | while IFS= read -r link; do 62 | destination="$(readlink "$link")" 63 | pruned_destination="${destination#"$oldprefix"}" 64 | if [ "${oldprefix}${pruned_destination}" == "${destination}" ]; then 65 | sudo mv -T "${newprefix}${pruned_destination}" "$link" 66 | fi 67 | done <<< "$(find /opt/osxcross/SDK/MacOSX10.13.sdk/System/Library/Frameworks/IOKit.framework -xtype l)" 68 | } 69 | 70 | cmd="$1" 71 | shift 72 | case "$cmd" in 73 | download_deb_pkg) download_deb_pkg $@ ;; 74 | compile_libusb) compile_libusb $@ ;; 75 | patch_macos_sdk) patch_macos_sdk $@ ;; 76 | *) echo "unknown command" && false ;; 77 | esac 78 | -------------------------------------------------------------------------------- /cpp/include/fibre/async_stream.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_ASYNC_STREAM_HPP 2 | #define __FIBRE_ASYNC_STREAM_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace fibre { 9 | 10 | enum StreamStatus { 11 | kStreamOk, 12 | kStreamCancelled, 13 | kStreamClosed, 14 | kStreamError 15 | }; 16 | 17 | 18 | struct ReadResult { 19 | StreamStatus status; 20 | 21 | /** 22 | * @brief The pointer to one position after the last byte that was 23 | * transferred. 24 | * This must always be in [buffer.begin(), buffer.end()], even if the 25 | * transfer was not succesful. 26 | * If the status is kStreamError or kStreamCancelled then the accuracy 27 | * of this field is not guaranteed. 28 | */ 29 | unsigned char* end; 30 | }; 31 | 32 | struct WriteResult0 { 33 | StreamStatus status; 34 | 35 | /** 36 | * @brief The pointer to one position after the last byte that was 37 | * transferred. 38 | * This must always be in [buffer.begin(), buffer.end()], even if the 39 | * transfer was not succesful. 40 | * If the status is kStreamError or kStreamCancelled then the accuracy 41 | * of this field is not guaranteed. 42 | */ 43 | const unsigned char* end; 44 | }; 45 | 46 | 47 | using TransferHandle = uintptr_t; 48 | 49 | /** 50 | * @brief Base class for asynchronous stream sources. 51 | */ 52 | class AsyncStreamSource { 53 | public: 54 | /** 55 | * @brief Starts a read operation. Once the read operation completes, 56 | * on_finished.complete() is called. 57 | * 58 | * Most implementations only allow one transfer to be active at a time. 59 | * 60 | * TODO: specify if `completer` can be called directly within this function. 61 | * 62 | * @param buffer: The buffer where the data to be written shall be fetched from. 63 | * Must remain valid until `completer` is satisfied. 64 | * @param handle: The variable pointed to by this argument is set to an 65 | * opaque transfer handle that can be passed to cancel_read() as 66 | * long as the operation has not yet completed. 67 | * If the completer is invoked directly from start_read() then the 68 | * handle is not modified after this invokation. That means it's safe 69 | * for the completion handler to reuse the handle variable. 70 | * @param completer: The completer that will be completed once the operation 71 | * finishes, whether successful or not. 72 | * Must remain valid until it is satisfied. 73 | */ 74 | virtual void start_read(bufptr_t buffer, TransferHandle* handle, Callback completer) = 0; 75 | 76 | /** 77 | * @brief Cancels an operation that was previously started with start_read(). 78 | * 79 | * The transfer is cancelled asynchronously and the associated completer 80 | * will eventually be completed with kStreamCancelled. Until then the 81 | * transfer must be considered still in progress and associated resources 82 | * must not be freed. 83 | * 84 | * TODO: specify if an implementation is allowed to return something other 85 | * than kStreamCancelled when the transfer was cancelled. 86 | * 87 | * This function must not be called once the stream has started to invoke 88 | * the associated completion handler. It must also not be called twice for 89 | * the same transfer. 90 | */ 91 | virtual void cancel_read(TransferHandle transfer_handle) = 0; 92 | }; 93 | 94 | /** 95 | * @brief Base class for asynchronous stream sources. 96 | * 97 | * Thread-safety: Implementations are generally not required to provide thread 98 | * safety. Users should only call the functions of this class on the same thread 99 | * as the event loop on which the stream runs. 100 | */ 101 | class AsyncStreamSink { 102 | public: 103 | /** 104 | * @brief Starts a write operation. Once the write operation completes, 105 | * on_finished.complete() is called. 106 | * 107 | * Most implementations only allow one transfer to be active at a time. 108 | * 109 | * TODO: specify if `completer` can be called directly within this function. 110 | * 111 | * @param buffer: The buffer where the data to be written shall be fetched from. 112 | * Must remain valid until `completer` is satisfied. 113 | * @param handle: The variable pointed to by this argument is set to an 114 | * opaque transfer handle that can be passed to cancel_write() as 115 | * long as the operation has not yet completed. 116 | * If the completer is invoked directly from start_write() then the 117 | * handle is not modified after this invokation. That means it's safe 118 | * for the completion handler to reuse the handle variable. 119 | * @param completer: The completer that will be completed once the operation 120 | * finishes, whether successful or not. 121 | * Must remain valid until it is satisfied. 122 | */ 123 | virtual void start_write(cbufptr_t buffer, TransferHandle* handle, Callback completer) = 0; 124 | 125 | /** 126 | * @brief Cancels an operation that was previously started with start_write(). 127 | * 128 | * The transfer is cancelled asynchronously and the associated completer 129 | * will eventually be completed with kStreamCancelled. Until then the 130 | * transfer must be considered still in progress and associated resources 131 | * must not be freed. 132 | * 133 | * TODO: specify if an implementation is allowed to return something other 134 | * than kStreamCancelled when the transfer was cancelled. 135 | * 136 | * This function must not be called once the stream has started to invoke 137 | * the associated completion handler. It must also not be called twice for 138 | * the same transfer. 139 | */ 140 | virtual void cancel_write(TransferHandle transfer_handle) = 0; 141 | }; 142 | 143 | } 144 | 145 | #endif // __FIBRE_ASYNC_STREAM_HPP -------------------------------------------------------------------------------- /cpp/include/fibre/backport/optional.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_BACKPORT_OPTIONAL_HPP 2 | #define __FIBRE_BACKPORT_OPTIONAL_HPP 3 | 4 | #if __cplusplus >= 201703L 5 | #include 6 | #else 7 | 8 | namespace std { 9 | 10 | /// Tag type to disengage optional objects. 11 | struct nullopt_t { 12 | // Do not user-declare default constructor at all for 13 | // optional_value = {} syntax to work. 14 | // nullopt_t() = delete; 15 | 16 | // Used for constructing nullopt. 17 | enum class _Construct { _Token }; 18 | 19 | // Must be constexpr for nullopt_t to be literal. 20 | explicit constexpr nullopt_t(_Construct) { } 21 | }; 22 | 23 | constexpr nullopt_t nullopt { nullopt_t::_Construct::_Token }; 24 | 25 | template 26 | class optional { 27 | public: 28 | using storage_t = char[sizeof(T)]; 29 | 30 | optional() : has_value_(false) {} 31 | optional(nullopt_t val) : has_value_(false) {} 32 | 33 | optional(const optional & other) : has_value_(other.has_value_) { 34 | if (has_value_) 35 | new ((T*)content_) T{*(T*)other.content_}; 36 | } 37 | 38 | optional(optional&& other) : has_value_(other.has_value_) { 39 | if (has_value_) 40 | new ((T*)content_) T{*(T*)other.content_}; 41 | } 42 | 43 | optional(T& arg) { 44 | new ((T*)content_) T{arg}; 45 | has_value_ = true; 46 | } 47 | 48 | optional(T&& arg) { 49 | new ((T*)content_) T{std::forward(arg)}; 50 | has_value_ = true; 51 | } 52 | 53 | ~optional() { 54 | if (has_value_) 55 | ((T*)content_)->~T(); 56 | } 57 | 58 | inline optional& operator=(const optional & other) { 59 | (*this).~optional(); 60 | new (this) optional{other}; 61 | return *this; 62 | } 63 | 64 | inline bool operator==(const optional& rhs) const { 65 | return (!has_value_ && !rhs.has_value_) || (*(T*)content_ == *(T*)rhs.content_); 66 | } 67 | 68 | inline bool operator!=(const optional& rhs) const { 69 | return !(*this == rhs); 70 | } 71 | 72 | inline T& operator*() { 73 | return *(T*)content_; 74 | } 75 | 76 | inline T* operator->() { 77 | return (T*)content_; 78 | } 79 | 80 | size_t has_value() const { return has_value_; } 81 | 82 | inline T& value() { 83 | return *(T*)content_; 84 | } 85 | 86 | inline const T& value() const { 87 | return *(T*)content_; 88 | } 89 | 90 | alignas(T) storage_t content_; 91 | size_t has_value_; 92 | }; 93 | 94 | template 95 | optional make_optional(T&& val) { 96 | return optional{std::forward(val)}; 97 | } 98 | 99 | template 100 | optional make_optional(T& val) { 101 | return optional{val}; 102 | } 103 | 104 | } 105 | 106 | #endif 107 | 108 | #endif // __FIBRE_BACKPORT_OPTIONAL_HPP 109 | -------------------------------------------------------------------------------- /cpp/include/fibre/base_types.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_BASE_TYPES_HPP 2 | #define __FIBRE_BASE_TYPES_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace fibre { 9 | 10 | #if FIBRE_ENABLE_SERVER 11 | typedef uint8_t ServerFunctionId; 12 | typedef uint8_t ServerInterfaceId; 13 | 14 | // TODO: Use pointer instead? The codec that decodes the object still needs a table 15 | // to prevent arbitrary memory access. 16 | typedef uint8_t ServerObjectId; 17 | 18 | struct ServerObjectDefinition { 19 | void* ptr; 20 | ServerInterfaceId interface; // TODO: use pointer instead of index? Faster but needs more memory 21 | }; 22 | #endif 23 | 24 | using NodeId = std::array; 25 | 26 | } 27 | 28 | #endif // __FIBRE_BASE_TYPES_HPP 29 | -------------------------------------------------------------------------------- /cpp/include/fibre/bufptr.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_BUFPTR_HPP 2 | #define __FIBRE_BUFPTR_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace fibre { 9 | 10 | static inline bool soft_assert(bool expr) { return expr; } // TODO: implement 11 | 12 | /** 13 | * @brief Holds a reference to a buffer and a length. 14 | * Since this class implements begin() and end(), you can use it with many 15 | * standard algorithms that operate on iterable objects. 16 | */ 17 | template 18 | struct generic_bufptr_t { 19 | using iterator = T*; 20 | using const_iterator = const T*; 21 | 22 | generic_bufptr_t(T* begin, size_t length) : begin_(begin), end_(begin + length) {} 23 | 24 | generic_bufptr_t(T* begin, T* end) : begin_(begin), end_(end) {} 25 | 26 | generic_bufptr_t() : begin_(nullptr), end_(nullptr) {} 27 | 28 | template 29 | generic_bufptr_t(T (&begin)[I]) : generic_bufptr_t(begin, I) {} 30 | 31 | template 32 | generic_bufptr_t(std::array::type, I>& array) 33 | : generic_bufptr_t(array.data(), I) {} 34 | 35 | generic_bufptr_t(std::vector::type>& vector) 36 | : generic_bufptr_t(vector.data(), vector.size()) {} 37 | 38 | generic_bufptr_t(const std::vector::type>& vector) 39 | : generic_bufptr_t(vector.data(), vector.size()) {} 40 | 41 | generic_bufptr_t(const generic_bufptr_t::type>& other) 42 | : generic_bufptr_t(other.begin(), other.end()) {} 43 | 44 | generic_bufptr_t& operator+=(size_t num) { 45 | if (!soft_assert(num <= size())) { 46 | num = size(); 47 | } 48 | begin_ += num; 49 | return *this; 50 | } 51 | 52 | generic_bufptr_t operator++(int) { 53 | generic_bufptr_t result = *this; 54 | *this += 1; 55 | return result; 56 | } 57 | 58 | T& operator*() { 59 | return *begin_; 60 | } 61 | 62 | generic_bufptr_t take(size_t num) const { 63 | if (!soft_assert(num <= size())) { 64 | num = size(); 65 | } 66 | generic_bufptr_t result = {begin_, num}; 67 | return result; 68 | } 69 | 70 | generic_bufptr_t skip(size_t num, size_t* processed_bytes = nullptr) const { 71 | if (!soft_assert(num <= size())) { 72 | num = size(); 73 | } 74 | if (processed_bytes) 75 | (*processed_bytes) += num; 76 | return {begin_ + num, end_}; 77 | } 78 | 79 | size_t size() const { 80 | return end_ - begin_; 81 | } 82 | 83 | bool empty() const { 84 | return size() == 0; 85 | } 86 | 87 | T*& begin() { return begin_; } 88 | T*& end() { return end_; } 89 | T* const & begin() const { return begin_; } 90 | T* const & end() const { return end_; } 91 | T& front() const { return *begin(); } 92 | T& back() const { return *(end() - 1); } 93 | T& operator[](size_t idx) { return *(begin() + idx); } 94 | 95 | private: 96 | T* begin_; 97 | T* end_; 98 | }; 99 | 100 | using cbufptr_t = generic_bufptr_t; 101 | using bufptr_t = generic_bufptr_t; 102 | 103 | } 104 | 105 | #endif // __FIBRE_BUFPTR_HPP 106 | -------------------------------------------------------------------------------- /cpp/include/fibre/callback.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CALLBACK_HPP 2 | #define __CALLBACK_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace fibre { 11 | 12 | namespace detail { 13 | template struct get_default { static T val() { return {}; } }; 14 | template<> struct get_default { static void val() {} }; 15 | } 16 | 17 | template 18 | class Callback { 19 | public: 20 | Callback() : cb_(nullptr), ctx_(nullptr) {} 21 | Callback(std::nullptr_t) : cb_(nullptr), ctx_(nullptr) {} 22 | Callback(TRet(*callback)(void*, TArgs...), void* ctx) : 23 | cb_(callback), ctx_(ctx) {} 24 | 25 | /** 26 | * @brief Creates a copy of another Callback instance. 27 | * 28 | * This is only works it the other callback has identical template parameters. 29 | * This constructor is templated so that construction from an incompatible 30 | * callback gives a useful error message. 31 | */ 32 | //template 33 | //Callback(const Callback& other) : cb_(other.cb_), ctx_(other.ctx_) { 34 | // static_assert(std::is_same, Callback>::value, "incompatible callback type"); 35 | //} 36 | 37 | Callback(const Callback& other) : cb_(other.cb_), ctx_(other.ctx_) {} 38 | 39 | // If you get a compile error "[...] invokes a deleted function" that points 40 | // here then you're probably trying to assign a Callback with incompatible 41 | // template arguments to another Callback. 42 | template 43 | Callback(const Callback& other) = delete; 44 | 45 | /** 46 | * @brief Constructs a callback object from a functor. The functor must 47 | * remain allocated throughout the lifetime of the Callback. 48 | */ 49 | template 50 | Callback(const TFunc& func) : 51 | cb_([](void* ctx, TArgs...args){ 52 | return (*(const TFunc*)ctx)(args...); 53 | }), ctx_((void*)&func) {} 54 | 55 | bool has_value() { 56 | return cb_; 57 | } 58 | 59 | TRet invoke(TArgs ... arg) const { 60 | if (cb_) { 61 | return (*cb_)(ctx_, arg...); 62 | } 63 | return detail::get_default::val(); 64 | } 65 | 66 | TRet invoke_and_clear(TArgs ... arg) { 67 | void* ctx = ctx_; 68 | auto cb = cb_; 69 | ctx_ = nullptr; 70 | cb_ = nullptr; 71 | if (cb) { 72 | return (*cb)(ctx, arg...); 73 | } 74 | return detail::get_default::val(); 75 | } 76 | 77 | void clear() { 78 | ctx_ = nullptr; 79 | cb_ = nullptr; 80 | } 81 | 82 | typedef TRet(*cb_t)(void*, TArgs...); 83 | cb_t get_ptr() { return cb_; } 84 | void* get_ctx() { return ctx_; } 85 | 86 | private: 87 | TRet(*cb_)(void*, TArgs...); 88 | void* ctx_; 89 | }; 90 | 91 | template 92 | struct function_traits { 93 | using TRet = _TRet; 94 | using TArgs = std::tuple<_TArgs...>; 95 | using TObj = _TObj; 96 | }; 97 | 98 | template 99 | function_traits<_TRet, _TObj, _TArgs...> make_function_traits(_TRet (_TObj::*)(_TArgs...)) { 100 | return {}; 101 | } 102 | 103 | template 104 | function_traits<_TRet, _TObj, _TArgs...> make_function_traits(_TRet (_TObj::*)(_TArgs...) const) { 105 | return {}; 106 | } 107 | 108 | template 109 | struct MemberCallback; 110 | 111 | template 112 | struct MemberCallback> { 113 | using cb_t = Callback; 114 | static cb_t with(TObj* obj) { 115 | return cb_t{[](void* obj, TArgs... arg) { 116 | return (((TObj*)obj)->*func)(arg...); 117 | }, obj}; 118 | } 119 | }; 120 | 121 | template> 124 | typename MemCb::cb_t make_callback(typename TTraits::TObj* obj) { 125 | return MemCb::with(obj); 126 | } 127 | 128 | #define MEMBER_CB(obj, func) \ 129 | fibre::make_callback< \ 130 | decltype(&std::remove_reference::type::func), \ 131 | &std::remove_reference::type::func \ 132 | >(obj) 133 | 134 | } 135 | 136 | #endif // __CALLBACK_HPP -------------------------------------------------------------------------------- /cpp/include/fibre/channel_discoverer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_CHANNEL_DISCOVERER 2 | #define __FIBRE_CHANNEL_DISCOVERER 3 | 4 | #include "async_stream.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace fibre { 11 | 12 | class Domain; // defined in domain.hpp 13 | struct Node; // defined in node.hpp 14 | class Logger; // defined in logging.hpp 15 | class EventLoop; // defined in event_loop.hpp 16 | struct RichStatus; // defined in rich_status.hpp 17 | struct TxPipe; 18 | 19 | struct ChannelDiscoveryResult { 20 | Status status; 21 | AsyncStreamSource* rx_channel; 22 | AsyncStreamSink* tx_channel; 23 | size_t mtu; 24 | bool packetized; 25 | }; 26 | 27 | 28 | struct FrameStreamSink { 29 | virtual bool open_output_slot(uintptr_t* p_slot_id, Node* dest) = 0; 30 | virtual bool close_output_slot(uintptr_t slot_id) = 0; 31 | virtual bool start_write(TxTaskChain tasks) = 0; 32 | virtual void cancel_write() = 0; 33 | 34 | Multiplexer multiplexer_{this}; 35 | }; 36 | 37 | struct ChannelDiscoveryContext {}; 38 | 39 | class ChannelDiscoverer { 40 | public: 41 | // TODO: maybe we should remove "handle" because a discovery can also be 42 | // uniquely identified by domain. 43 | virtual void start_channel_discovery( 44 | Domain* domain, 45 | const char* specs, size_t specs_len, 46 | ChannelDiscoveryContext** handle) = 0; 47 | virtual RichStatus stop_channel_discovery(ChannelDiscoveryContext* handle) = 0; 48 | virtual RichStatus show_device_dialog(); 49 | 50 | static bool try_parse_key(const char* begin, const char* end, const char* key, const char** val_begin, const char** val_end); 51 | static bool try_parse_key(const char* begin, const char* end, const char* key, int* val); 52 | static bool try_parse_key(const char* begin, const char* end, const char* key, std::string* val); 53 | }; 54 | 55 | struct Backend : ChannelDiscoverer { 56 | virtual ~Backend() {}; 57 | virtual RichStatus init(EventLoop* event_loop, Logger logger) = 0; 58 | virtual RichStatus deinit() = 0; 59 | }; 60 | 61 | } 62 | 63 | #endif // __FIBRE_CHANNEL_DISCOVERER -------------------------------------------------------------------------------- /cpp/include/fibre/config.hpp: -------------------------------------------------------------------------------- 1 | 2 | // This file must be provided by the application. Make sure its containing 3 | // directory is in the compiler's include search path. 4 | // The file can be empty to accept the default configuration. 5 | #include 6 | 7 | 8 | // Default configuration (keep consistent with README!) 9 | 10 | #ifndef FIBRE_ENABLE_SERVER 11 | #define FIBRE_ENABLE_SERVER 0 12 | #endif 13 | 14 | #ifndef FIBRE_ENABLE_CLIENT 15 | #define FIBRE_ENABLE_CLIENT 0 16 | #endif 17 | 18 | #ifndef FIBRE_ENABLE_EVENT_LOOP 19 | #define FIBRE_ENABLE_EVENT_LOOP 0 20 | #endif 21 | 22 | #ifndef FIBRE_ALLOW_HEAP 23 | #define FIBRE_ALLOW_HEAP 1 24 | #endif 25 | 26 | #ifndef FIBRE_MAX_LOG_VERBOSITY 27 | #define FIBRE_MAX_LOG_VERBOSITY 5 28 | #endif 29 | 30 | #ifndef FIBRE_ENABLE_TEXT_LOGGING 31 | #define FIBRE_ENABLE_TEXT_LOGGING 1 32 | #endif 33 | 34 | #ifndef FIBRE_ENABLE_CAN_ADAPTER 35 | #define FIBRE_ENABLE_CAN_ADAPTER 0 36 | #endif 37 | 38 | #ifndef FIBRE_ENABLE_LIBUSB_BACKEND 39 | #define FIBRE_ENABLE_LIBUSB_BACKEND 0 40 | #endif 41 | 42 | #ifndef FIBRE_ENABLE_TCP_CLIENT_BACKEND 43 | #define FIBRE_ENABLE_TCP_CLIENT_BACKEND 0 44 | #endif 45 | 46 | #ifndef FIBRE_ENABLE_TCP_SERVER_BACKEND 47 | #define FIBRE_ENABLE_TCP_SERVER_BACKEND 0 48 | #endif 49 | 50 | #ifndef FIBRE_ENABLE_SOCKET_CAN_BACKEND 51 | #define FIBRE_ENABLE_SOCKET_CAN_BACKEND 0 52 | #endif 53 | 54 | #define F_RUNTIME_CONFIG 2 55 | 56 | #if FIBRE_ENABLE_CLIENT == 0 57 | #define F_CONFIG_ENABLE_SERVER_T std::integral_constant 58 | #define F_CONFIG_ENABLE_CLIENT_T std::integral_constant 59 | #elif FIBRE_ENABLE_CLIENT == 1 60 | #define F_CONFIG_ENABLE_SERVER_T std::integral_constant 61 | #define F_CONFIG_ENABLE_CLIENT_T std::integral_constant 62 | #else 63 | #define F_CONFIG_ENABLE_SERVER_T bool 64 | #define F_CONFIG_ENABLE_CLIENT_T bool 65 | #endif 66 | -------------------------------------------------------------------------------- /cpp/include/fibre/connection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_SERVER_STREAM_HPP 2 | #define __FIBRE_SERVER_STREAM_HPP 3 | 4 | namespace fibre { 5 | class Connection; 6 | struct ConnectionInputSlot; 7 | } // namespace fibre 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace fibre { 16 | 17 | class Domain; 18 | struct FrameStreamSink; 19 | struct Node; 20 | 21 | struct Fifo { 22 | using TIndex = uint8_t; 23 | using TOffset = uint16_t; 24 | 25 | struct ReadIterator { 26 | ReadIterator(const Fifo* fifo, TIndex idx, TOffset offset) 27 | : fifo_(fifo), idx_(idx), offset_(offset) {} 28 | ReadIterator() : ReadIterator(nullptr, 0, 0) {} 29 | ReadIterator& operator++(); 30 | bool operator!=(const ReadIterator& other) { 31 | return idx_ != other.idx_ || offset_ != other.offset_; 32 | } 33 | Chunk chunk(); 34 | 35 | const Fifo* fifo_; 36 | TIndex idx_; 37 | TOffset offset_; 38 | }; 39 | 40 | CBufIt append(BufChain chain); 41 | 42 | ReadIterator read_begin() const; 43 | ReadIterator read_end() const; 44 | bool has_data() const; 45 | ReadIterator read(ReadIterator it, write_iterator target) const; 46 | ReadIterator advance_it(ReadIterator it, std::array n_frames, 47 | std::array n_bytes); 48 | ReadIterator advance_it(ReadIterator it, Chunk* c_begin, Chunk* c_end, 49 | CBufIt end); 50 | void drop_until(ReadIterator it); 51 | void consume(size_t n_chunks); // TODO: deprecate (?) (use iterators) 52 | bool fsck(TOffset it) const; 53 | bool fsck() const { 54 | return fsck(read_idx_); 55 | } 56 | 57 | uint8_t buf_[256]; // TODO: make customizable 58 | TIndex read_idx_ = 0; 59 | TIndex write_idx_ = 0; 60 | TOffset read_idx_offset_ = 0; 61 | }; 62 | 63 | struct ConnectionPos { 64 | std::array frame_ids; 65 | std::array offsets; 66 | }; 67 | 68 | struct ConnectionInputSlot { 69 | ConnectionInputSlot(Connection& conn) : conn_(conn) {} 70 | 71 | void process_sync(BufChain chain); 72 | 73 | Connection& conn_; 74 | 75 | uint8_t layer0_cache_[13]; 76 | size_t layer0_cache_pos_ = 0; 77 | 78 | ConnectionPos pos_; 79 | }; 80 | 81 | struct ConnectionOutputSlot final : TxPipe { 82 | ConnectionOutputSlot(Connection& conn); 83 | 84 | bool has_data() final; 85 | BufChain get_task() final; 86 | void release_task(CBufIt end) final; 87 | 88 | Connection& conn_; 89 | 90 | Chunk storage_[10]; 91 | uint8_t pos_header_[13]; 92 | uint8_t ack_buf_[13]; 93 | bool sent_header_recently_ = false; 94 | bool sending_ = false; // true while there is a send task pending 95 | Chunk* sending_storage_begin_; 96 | Chunk* sending_storage_end_; 97 | Fifo::ReadIterator tx_it_; 98 | Fifo::ReadIterator sending_tx_it_; 99 | }; 100 | 101 | class Connection { 102 | friend struct ConnectionInputSlot; 103 | friend struct ConnectionOutputSlot; 104 | 105 | public: 106 | Connection(Domain* domain, std::array tx_call_id, 107 | uint8_t tx_protocol) 108 | : domain_{domain}, tx_call_id_{tx_call_id}, tx_protocol_{tx_protocol} {} 109 | 110 | ConnectionInputSlot* open_rx_slot(); 111 | void close_rx_slot(ConnectionInputSlot* slot); 112 | 113 | bool open_tx_slot(FrameStreamSink* sink, Node* node); 114 | void close_tx_slot(FrameStreamSink* sink); 115 | 116 | protected: 117 | void handle_rx_not_empty(); 118 | void handle_tx_not_empty(); 119 | void handle_tx_not_full(); 120 | 121 | void on_ack(ConnectionPos pos); 122 | WriteResult tx(WriteArgs args); 123 | virtual WriteArgs on_tx_done(WriteResult result) = 0; 124 | virtual WriteResult on_rx(WriteArgs args) = 0; 125 | WriteArgs rx_logic(); 126 | WriteArgs rx_logic(WriteResult result); 127 | WriteArgs rx_done(WriteResult result); 128 | 129 | Domain* domain_; // TODO: mignt not be required? 130 | std::array tx_call_id_; 131 | uint8_t tx_protocol_; 132 | bool send_ack_ = 133 | false; // Indicates if an ack is to be sent. Becomes true 134 | // on any incoming payload chunk on any input slot. Becomes 135 | // false whenever an ack is sent on any output slot. 136 | 137 | ConnectionPos rx_tail_; 138 | ConnectionPos tx_head_; 139 | 140 | // TODO: customizable capacity 141 | Pool input_slots_; 142 | Map output_slots_; 143 | 144 | Fifo rx_fifo_; 145 | Fifo tx_fifo_; 146 | 147 | WriteArgs pending_tx_; 148 | bool rx_busy_ = false; 149 | 150 | Chunk upcall_chunks_[8]; 151 | Chunk* upcall_chunks_end_; 152 | }; 153 | 154 | } // namespace fibre 155 | 156 | #endif // __FIBRE_SERVER_STREAM_HPP 157 | -------------------------------------------------------------------------------- /cpp/include/fibre/domain.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_DOMAIN_HPP 2 | #define __FIBRE_DOMAIN_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include // TODO: move file 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace fibre { 16 | 17 | struct Fibre; 18 | class Function; // defined in function.hpp 19 | class Interface; // defined in interface.hpp 20 | struct ChannelDiscoveryResult; 21 | struct Object; 22 | 23 | // TODO: legacy stuff - remove 24 | struct ChannelDiscoveryContext; 25 | struct LegacyProtocolPacketBased; 26 | class LegacyObjectClient; 27 | struct LegacyObject; 28 | 29 | class Domain { 30 | friend struct Fibre; 31 | public: 32 | void show_device_dialog(std::string backend); 33 | 34 | #if FIBRE_ENABLE_CLIENT 35 | // TODO: add interface argument 36 | // TODO: support multiple discovery instances 37 | void start_discovery(Callback on_found_object, Callback on_lost_object); 38 | void stop_discovery(); 39 | #endif 40 | 41 | void add_legacy_channels(ChannelDiscoveryResult result, const char* name); // TODO: deprecate 42 | 43 | #if FIBRE_ENABLE_SERVER 44 | const Function* get_server_function(ServerFunctionId id); 45 | ServerObjectDefinition* get_server_object(ServerObjectId id); 46 | #endif 47 | void on_found_node(const NodeId& node_id, FrameStreamSink* sink, const char* intf_name, Node** p_node); 48 | void on_lost_node(Node* node, FrameStreamSink* sink); 49 | 50 | void open_call(const std::array& call_id, uint8_t protocol, FrameStreamSink* return_path, Node* return_node, ConnectionInputSlot** slot); 51 | void close_call(ConnectionInputSlot* slot); 52 | 53 | #if FIBRE_ENABLE_CLIENT 54 | void on_found_root_object(Object* obj, Interface* intf, std::string path); 55 | void on_lost_root_object(Object* obj); 56 | #endif 57 | 58 | Fibre* ctx; 59 | 60 | // It is theoretically possible to run a node without node ID but if a 61 | // client is connected over two interfaces it cannot determine that the node 62 | // is the same and would detect it as two nodes. Maybe worth consideration 63 | // for very constrained nodes. 64 | NodeId node_id; 65 | MiniRng rng; // initialized from node_id seed and used to generate call IDs 66 | 67 | private: 68 | bool connect_slots(Connection* conn, FrameStreamSink* sink); 69 | bool disconnect_slots(Connection* conn, FrameStreamSink* sink); 70 | 71 | void on_stopped_p(LegacyProtocolPacketBased* protocol, StreamStatus status); 72 | void on_stopped_s(LegacyProtocolPacketBased* protocol, StreamStatus status); 73 | 74 | #if FIBRE_ALLOW_HEAP 75 | std::unordered_map channel_discovery_handles; 76 | #endif 77 | #if FIBRE_ENABLE_CLIENT 78 | Callback on_found_object_; 79 | Callback on_lost_object_; 80 | std::unordered_map> root_objects_; 81 | #endif 82 | 83 | #if FIBRE_ENABLE_CLIENT == F_RUNTIME_CONFIG 84 | bool enable_client; 85 | #endif 86 | 87 | #if FIBRE_ENABLE_SERVER 88 | // TODO: selectable capacity 89 | Map, EndpointServerConnection, 3> server_connections; 90 | #endif 91 | 92 | #if FIBRE_ENABLE_CLIENT 93 | // TODO: selectable capacity 94 | Map, EndpointClientConnection, 3> client_connections; 95 | #endif 96 | 97 | #if FIBRE_ENABLE_CLIENT 98 | // TODO: selectable capacity 99 | Map nodes; 100 | #endif 101 | }; 102 | 103 | } 104 | 105 | #endif // __FIBRE_DOMAIN_HPP 106 | -------------------------------------------------------------------------------- /cpp/include/fibre/endpoint_connection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_ENDPOINT_CONNECTION 2 | #define __FIBRE_ENDPOINT_CONNECTION 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace fibre { 9 | 10 | using Cont0 = WriteArgs; 11 | using Cont1 = WriteResult; 12 | using Cont = std::variant; 13 | 14 | struct EndpointServerConnection : Connection { 15 | struct Call : Socket { 16 | WriteResult write(WriteArgs args) final; 17 | WriteArgs on_write_done(WriteResult result) final; 18 | EndpointServerConnection* parent_; 19 | WriteArgs pending; 20 | CBufIt footer_pos; 21 | Socket* socket_; 22 | }; 23 | 24 | EndpointServerConnection(Domain* domain, std::array tx_call_id) 25 | : Connection{domain, tx_call_id, 0x01} {} 26 | 27 | WriteArgs on_tx_done(WriteResult result) final; 28 | WriteResult on_rx(WriteArgs args) final; 29 | 30 | Cont tx_logic(WriteArgs args); 31 | Cont tx_logic(WriteResult result); 32 | Cont rx_logic(WriteArgs args); 33 | Cont rx_logic(WriteResult result); 34 | 35 | void start_endpoint_operation(uint16_t endpoint_id, bool exchange); 36 | 37 | alignas(std::max_align_t) uint8_t call_frame[512]; // TODO: make customizable 38 | 39 | Call call0; // no call pipelining supported currently 40 | 41 | bool rx_active = false; 42 | bool tx_active = false; 43 | 44 | uint8_t buf[4]; 45 | size_t buf_offset = 0; 46 | 47 | WriteArgs pending; 48 | Chunk boundary[1] = {Chunk::frame_boundary(0)}; 49 | }; 50 | 51 | struct EndpointClientConnection : Connection { 52 | struct Call final : Socket { 53 | WriteResult write(WriteArgs args) final; 54 | WriteArgs on_write_done(WriteResult result) final; 55 | EndpointClientConnection* parent_; 56 | uint8_t header[4]; 57 | Chunk chunks_[1]; 58 | WriteArgs pending; 59 | CBufIt header_pos; 60 | CBufIt footer_pos; 61 | Socket* caller_; 62 | }; 63 | 64 | EndpointClientConnection(Domain* domain, std::array tx_call_id) 65 | : Connection{domain, tx_call_id, 0x00} {} 66 | 67 | Socket* start_call(uint16_t ep_num, uint16_t json_crc, 68 | std::vector in_arg_ep_nums, 69 | std::vector out_arg_ep_nums, Socket* caller); 70 | 71 | WriteArgs on_tx_done(WriteResult result) final; 72 | WriteResult on_rx(WriteArgs args) final; 73 | 74 | Cont tx_logic(WriteArgs args); 75 | Cont tx_logic(WriteResult result); 76 | void tx_loop(); 77 | Cont rx_logic(WriteArgs args); 78 | Cont rx_logic(WriteResult result); 79 | void rx_loop(Cont cont); 80 | 81 | std::vector tx_queue_; 82 | std::vector rx_queue_; 83 | 84 | WriteArgs pending; 85 | bool call_closed_ = false; 86 | Chunk boundary[1] = {Chunk::frame_boundary(0)}; 87 | }; 88 | 89 | } // namespace fibre 90 | 91 | #endif // __FIBRE_ENDPOINT_CONNECTION -------------------------------------------------------------------------------- /cpp/include/fibre/event_loop.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_EVENT_LOOP_HPP 2 | #define __FIBRE_EVENT_LOOP_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace fibre { 9 | 10 | /** 11 | * @brief Base class for event loops. 12 | * 13 | * Thread-safety: The public functions of this class except for post() must not 14 | * be assumed to be thread-safe. 15 | * Generally the functions of an event loop are only safe to be called from the 16 | * event loop's thread itself. 17 | */ 18 | class EventLoop : public TimerProvider { 19 | public: 20 | /** 21 | * @brief Registers a callback for immediate execution on the event loop 22 | * thread. 23 | * 24 | * This function must be thread-safe. 25 | */ 26 | virtual RichStatus post(Callback callback) = 0; 27 | 28 | /** 29 | * @brief Registers the given file descriptor on this event loop. 30 | * 31 | * This function is only implemented on Unix-like systems. 32 | * 33 | * @param fd: A waitable Unix file descriptor on which to listen for events. 34 | * @param events: A bitfield that specifies the events to listen for. 35 | * For instance EPOLLIN or EPOLLOUT. 36 | * @param callback: The callback to invoke every time the event triggers. 37 | * A bitfield is passed to the callback to indicate which events were 38 | * triggered. This callback must remain valid until 39 | * deregister_event() is called for the same file descriptor. 40 | */ 41 | virtual RichStatus register_event(int fd, uint32_t events, Callback callback) = 0; 42 | 43 | /** 44 | * @brief Deregisters the given event. 45 | * 46 | * Once this function returns, the associated callback will no longer be 47 | * invoked and its resources can be freed. 48 | */ 49 | virtual RichStatus deregister_event(int fd) = 0; 50 | }; 51 | 52 | } 53 | 54 | #endif // __FIBRE_EVENT_LOOP_HPP -------------------------------------------------------------------------------- /cpp/include/fibre/fibre.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_HPP 2 | #define __FIBRE_HPP 3 | 4 | namespace fibre { 5 | 6 | struct Fibre; 7 | 8 | } 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace fibre { 19 | 20 | struct Backend; // defined in channel_discoverer.hpp 21 | class ChannelDiscoverer; // defined in channel_discoverer.hpp 22 | 23 | struct Fibre { 24 | size_t n_domains = 0; 25 | EventLoop* event_loop; 26 | Logger logger = Logger::none(); 27 | 28 | #if FIBRE_ALLOW_HEAP 29 | std::unordered_map discoverers; 30 | #endif 31 | 32 | /** 33 | * @brief Creates a domain on which objects can subsequently be published 34 | * and discovered. 35 | * 36 | * This potentially starts looking for channels on this domain. 37 | */ 38 | Domain* create_domain(std::string specs, const uint8_t* node_id, F_CONFIG_ENABLE_SERVER_T enable_server); 39 | void close_domain(Domain* domain); 40 | 41 | RichStatus register_backend(std::string name, ChannelDiscoverer* backend); 42 | RichStatus deregister_backend(std::string name); 43 | 44 | // internal use 45 | RichStatus init_backend(std::string name, Backend* backend); 46 | RichStatus deinit_backends(); 47 | }; 48 | 49 | 50 | 51 | 52 | /** 53 | * @brief Opens and initializes a Fibre context. 54 | * 55 | * If FIBRE_ALLOW_HEAP=0 only one Fibre context can be open at a time. 56 | * 57 | * @param logger: A logger that receives debug/warning/error events from Fibre. 58 | * If compiled with FIBRE_ENABLE_TEXT_LOGGING=0 the text parameter is 59 | * always NULL. 60 | * If you don't have special logging needs consider passing 61 | * `fibre::log_to_stderr`. 62 | * @returns: A non-null pointer on success, null otherwise. 63 | */ 64 | RichStatus open(EventLoop* event_loop, Logger logger, Fibre** p_ctx); 65 | 66 | void close(Fibre*); 67 | 68 | /** 69 | * @brief Logs an event to stderr. 70 | * 71 | * If Fibre is compiled with FIBRE_ENABLE_TEXT_LOGGING=1 this function logs the 72 | * event to stderr. Otherwise it does nothing. 73 | */ 74 | void log_to_stderr(void* ctx, const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, const char* text); 75 | 76 | /** 77 | * @brief Launches an event loop on the current thread. 78 | * 79 | * This function returns when the event loop becomes empty. 80 | * 81 | * If FIBRE_ALLOW_HEAP=0 only one event loop can be running at a time. 82 | * 83 | * This function returns false if Fibre was compiled with 84 | * FIBRE_ENABLE_EVENT_LOOP=0. 85 | * 86 | * @param on_started: This function is the first event that is placed on the 87 | * event loop. This function usually creates further events, for instance 88 | * by calling open(). 89 | * @returns: true if the event loop ran to completion. False if this function is 90 | * not implemented on this operating system or if another error 91 | * occurred. 92 | */ 93 | RichStatus launch_event_loop(Logger logger, Callback on_started); 94 | 95 | } 96 | 97 | #endif // __FIBRE_HPP -------------------------------------------------------------------------------- /cpp/include/fibre/function.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_FUNCTION_HPP 2 | #define __FIBRE_FUNCTION_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace fibre { 12 | 13 | class Domain; 14 | class Socket; 15 | 16 | struct CallBuffers { 17 | Status status; 18 | cbufptr_t tx_buf; 19 | bufptr_t rx_buf; 20 | }; 21 | 22 | struct CallBufferRelease { 23 | Status status; 24 | const uint8_t* tx_end; 25 | uint8_t* rx_end; 26 | }; 27 | 28 | struct FunctionInfo { 29 | std::string name; 30 | std::vector> inputs; 31 | std::vector> outputs; 32 | }; 33 | 34 | class Function { 35 | public: 36 | Function() {} 37 | Function(const Function&) = 38 | delete; // functions must not move around in memory 39 | 40 | /** 41 | * @brief Starts a call on this function. 42 | * 43 | * The call is ended when it is closed in both directions. 44 | * 45 | * @param domain: The domain on which the call is made. 46 | * @param call_frame: A buffer where the implementation can store the call 47 | * state in case heap allocation is forbidden (FIBRE_ALLOW_HEAP == 0). 48 | * The buffer must be be aligned on `std::max_align_t` and not move 49 | * around or change size during an ongoing call. 50 | * The buffer must remain valid until the call is ended. 51 | * @returns A duplex channel for the call. 52 | */ 53 | virtual Socket* start_call( 54 | Domain* domain, bufptr_t call_frame, Socket* caller) const = 0; 55 | 56 | virtual FunctionInfo* get_info() const = 0; 57 | virtual void free_info(FunctionInfo* info) const = 0; 58 | }; 59 | 60 | } 61 | 62 | #endif // __FIBRE_FUNCTION_HPP 63 | -------------------------------------------------------------------------------- /cpp/include/fibre/interface.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_INTERFACE_HPP 2 | #define __FIBRE_INTERFACE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace fibre { 10 | 11 | class Interface; 12 | struct InterfaceInfo; 13 | struct Object; 14 | class Function; // defined in function.hpp 15 | 16 | struct AttributeInfo { 17 | std::string name; 18 | Interface* intf; 19 | }; 20 | 21 | struct InterfaceInfo { 22 | std::string name; 23 | std::vector functions; 24 | std::vector attributes; 25 | }; 26 | 27 | class Interface { 28 | public: 29 | Interface() {} 30 | Interface(const Interface&) = delete; // interfaces must not move around in memory 31 | 32 | virtual InterfaceInfo* get_info() = 0; 33 | virtual void free_info(InterfaceInfo* info) = 0; 34 | virtual RichStatusOr get_attribute(Object* parent_obj, size_t attr_id) = 0; 35 | }; 36 | 37 | } 38 | 39 | #endif // __FIBRE_INTERFACE_HPP 40 | -------------------------------------------------------------------------------- /cpp/include/fibre/logging.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_LOGGING_HPP 2 | #define __FIBRE_LOGGING_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #if FIBRE_ENABLE_TEXT_LOGGING 10 | #include 11 | #include 12 | #include 13 | 14 | #if defined(_WIN32) || defined(_WIN64) 15 | #include "windows.h" // required for GetLastError() 16 | #endif 17 | #endif 18 | 19 | namespace fibre { 20 | 21 | enum class LogLevel : int { 22 | kError = 1, 23 | kDebug = 4, 24 | kTrace = 5, 25 | }; 26 | 27 | /** 28 | * @brief Log function callback type 29 | * 30 | * @param ctx: An opaque user defined context pointer. 31 | * @param file: The file name of the call site. Valid until the program terminates. 32 | * @param line: The line number of the call site. 33 | * @param info0: A general purpose information parameter. The meaning of this depends on the call site. 34 | * @param info1: A general purpose information parameter. The meaning of this depends on the call site. 35 | * @param text: Text to log. Valid only for the duration of the log call. Always 36 | * Null if Fibre is compiled with FIBRE_ENABLE_TEXT_LOGGING=0. 37 | */ 38 | typedef Callback log_fn_t; 39 | 40 | class Logger { 41 | public: 42 | Logger(log_fn_t impl, LogLevel verbosity) 43 | : impl_{impl}, verbosity_{verbosity} {} 44 | 45 | template 46 | void log(const char* file, unsigned line, int level, uintptr_t info0, uintptr_t info1, TFunc text_gen) const { 47 | if (level <= (int)verbosity_) { 48 | const char* c_str = nullptr; 49 | #if FIBRE_ENABLE_TEXT_LOGGING 50 | std::ostringstream stream; 51 | text_gen(stream); 52 | std::string str = stream.str(); 53 | c_str = str.c_str(); 54 | #endif 55 | impl_.invoke(file, line, level, info0, info1, c_str); 56 | } 57 | } 58 | 59 | static Logger none() { 60 | return { 61 | {[](void*, const char*, unsigned, int, uintptr_t, uintptr_t, const char*){}, nullptr}, 62 | (LogLevel)-1 63 | }; 64 | } 65 | 66 | private: 67 | log_fn_t impl_; 68 | LogLevel verbosity_; 69 | }; 70 | 71 | /** 72 | * @brief Returns the log verbosity as configured by the environment variable 73 | * `FIBRE_LOG`. 74 | * 75 | * On platforms that don't have environment variables (like embedded systems) 76 | * this returns LogLevel::kError. 77 | */ 78 | static inline LogLevel get_log_verbosity() { 79 | const char * var_val = std::getenv("FIBRE_LOG"); 80 | if (var_val) { 81 | unsigned long num = strtoul(var_val, nullptr, 10); 82 | return (LogLevel)num; 83 | } else { 84 | return LogLevel::kError; 85 | } 86 | } 87 | 88 | } 89 | 90 | /** 91 | * @brief Tag type to print the last system error 92 | * 93 | * The statement `std::out << sys_err();` will print the last system error 94 | * in the following format: "error description (errno)". 95 | * This is based on `GetLastError()` (Windows) or `errno` (all other systems). 96 | */ 97 | struct sys_err {}; 98 | 99 | #if FIBRE_ENABLE_TEXT_LOGGING 100 | 101 | namespace std { 102 | static inline std::ostream& operator<<(std::ostream& stream, const sys_err&) { 103 | #if defined(_WIN32) || defined(_WIN64) 104 | auto error_code = GetLastError(); 105 | #else 106 | auto error_code = errno; 107 | #endif 108 | return stream << strerror(error_code) << " (" << error_code << ")"; 109 | } 110 | } 111 | 112 | #endif 113 | 114 | namespace fibre { 115 | 116 | template 117 | const T& with(const T& val, TFunc func) { 118 | func(val); 119 | return val; 120 | } 121 | 122 | } 123 | 124 | #if FIBRE_ENABLE_TEXT_LOGGING 125 | #define STR_BUILDER(msg) ([&](std::ostream& str) { str << msg; }) 126 | #else 127 | #define STR_BUILDER(msg) (0) 128 | #endif 129 | 130 | #define F_LOG_IF(logger, expr, msg) \ 131 | fibre::with((bool)(expr), [&](bool __expr) { \ 132 | if (__expr) (logger).log(__FILE__, __LINE__, (int)fibre::LogLevel::kError, 0, 0, STR_BUILDER(msg)); \ 133 | }) 134 | 135 | #define F_LOG_IF_ERR(logger, status, msg) \ 136 | fibre::with((status), [&](const fibre::RichStatus& __status) { \ 137 | if (__status.is_error()) (logger).log(__FILE__, __LINE__, (int)fibre::LogLevel::kError, (uintptr_t)__status.inner_file(), __status.inner_line(), STR_BUILDER(msg << ": " << __status)); \ 138 | }).is_error() 139 | 140 | #define F_LOG_T(logger, msg) \ 141 | (logger).log(__FILE__, __LINE__, (int)fibre::LogLevel::kTrace, 0, 0, STR_BUILDER(msg)) 142 | 143 | #define F_LOG_D(logger, msg) \ 144 | (logger).log(__FILE__, __LINE__, (int)fibre::LogLevel::kDebug, 0, 0, STR_BUILDER(msg)) 145 | 146 | #define F_LOG_E(logger, msg) \ 147 | (logger).log(__FILE__, __LINE__, (int)fibre::LogLevel::kError, 0, 0, STR_BUILDER(msg)) 148 | 149 | // TODO: fix 150 | #define F_LOG_W F_LOG_E 151 | 152 | #endif // __FIBRE_LOGGING_HPP 153 | -------------------------------------------------------------------------------- /cpp/include/fibre/multiplexer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_MULTIPLEXER_HPP 2 | #define __FIBRE_MULTIPLEXER_HPP 3 | 4 | #include 5 | 6 | namespace fibre { 7 | 8 | struct CBufIt; 9 | struct FrameStreamSink; 10 | struct TxPipe; 11 | 12 | struct Multiplexer { 13 | public: 14 | Multiplexer(FrameStreamSink* sink) : sink_{sink} {} 15 | void add_source(TxPipe* pipe); 16 | void remove_source(TxPipe* pipe); 17 | void maybe_send_next(); 18 | void send_next(TxPipe* pipe); 19 | void on_sent(TxPipe* pipe, CBufIt end); 20 | void on_cancelled(TxPipe* pipe, CBufIt end); 21 | 22 | FrameStreamSink* sink_; 23 | std::vector queue_; // TODO: no dynamic allication 24 | TxPipe* sending_pipe_ = nullptr; 25 | }; 26 | 27 | } 28 | 29 | #endif // __FIBRE_MULTIPLEXER_HPP -------------------------------------------------------------------------------- /cpp/include/fibre/node.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_NODE_HPP 2 | #define __FIBRE_NODE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace fibre { 9 | 10 | struct FrameStreamSink; 11 | 12 | struct Node { 13 | NodeId id; 14 | 15 | // TODO: configurable capacity 16 | Pool sinks; 17 | }; 18 | 19 | } 20 | 21 | #endif // __FIBRE_NODE_HPP 22 | -------------------------------------------------------------------------------- /cpp/include/fibre/object_server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_OBJECT_SERVER_HPP 2 | #define __FIBRE_OBJECT_SERVER_HPP 3 | 4 | #include 5 | #include "../../static_exports.hpp" 6 | #include "../../codecs.hpp" 7 | #include 8 | 9 | namespace fibre { 10 | 11 | template 12 | constexpr ServerObjectDefinition make_obj(T* obj) { 13 | return {obj, get_interface_id()}; 14 | } 15 | 16 | template 17 | constexpr ServerObjectDefinition make_obj(const T* obj) { 18 | return {const_cast(obj), get_interface_id()}; 19 | } 20 | 21 | 22 | } 23 | 24 | #endif // __FIBRE_OBJECT_SERVER_HPP 25 | -------------------------------------------------------------------------------- /cpp/include/fibre/pool.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_POOL_HPP 2 | #define __FIBRE_POOL_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace fibre { 11 | 12 | template struct Pool; 13 | 14 | template struct PoolIterator { 15 | bool operator==(PoolIterator other) const { 16 | return (container_ == other.container_) && (bitpos_ == other.bitpos_); 17 | } 18 | bool operator!=(PoolIterator other) const { 19 | return !(*this == other); 20 | } 21 | PoolIterator& operator++() { 22 | bitpos_ = find_next(container_->allocation_table, bitpos_); 23 | return *this; 24 | } 25 | T& operator*() const { 26 | return container_->content[bitpos_].as_T(); 27 | } 28 | T* operator->() const { 29 | return &container_->content[bitpos_].as_T(); 30 | } 31 | Pool* container_; 32 | size_t bitpos_; 33 | }; 34 | 35 | template struct Pool { 36 | using iterator = PoolIterator; 37 | 38 | Pool() = default; 39 | Pool(const Pool&) = delete; // since we hand out pointers to the internal 40 | // storage we're not allowed to move a pool 41 | 42 | struct alignas(T) StorageElement { 43 | uint8_t storage[sizeof(T)]; 44 | T& as_T() { 45 | return *reinterpret_cast(this); 46 | } 47 | }; 48 | 49 | template T* alloc(TArgs&&... args) { 50 | for (size_t i = 0; i < Size; ++i) { 51 | if (!allocation_table.test(i)) { 52 | allocation_table[i] = true; 53 | // TODO: calling the desctructor here is a bit of a hack because 54 | // the array initializer already calls the constructor 55 | // content[i].~T(); 56 | new (&content[i].as_T()) T{args...}; 57 | return &content[i].as_T(); 58 | } 59 | } 60 | return nullptr; 61 | } 62 | 63 | void free(T* ptr) { 64 | content[index_of(ptr)].as_T().~T(); 65 | allocation_table[index_of(ptr)] = false; 66 | } 67 | 68 | size_t index_of(T* val) { 69 | return (StorageElement*)val - content.begin(); 70 | } 71 | 72 | iterator begin() { 73 | return {this, find_first(allocation_table)}; 74 | } 75 | 76 | iterator end() { 77 | return {this, Size}; 78 | } 79 | 80 | std::array content; 81 | std::bitset allocation_table; 82 | }; 83 | 84 | template struct Map { 85 | using TItem = std::pair; 86 | using iterator = typename Pool::iterator; 87 | 88 | iterator begin() { 89 | return pool_.begin(); 90 | } 91 | iterator end() { 92 | return pool_.end(); 93 | } 94 | 95 | // TODO: remove (find does the job) 96 | TVal* get(const TKey& key) { 97 | for (auto& item : pool_) { 98 | if (item.first == key) { 99 | return &item.second; 100 | } 101 | } 102 | return nullptr; 103 | } 104 | 105 | iterator find(const TKey& key) { 106 | return std::find_if(begin(), end(), 107 | [&](TItem& item) { return item.first == key; }); 108 | } 109 | 110 | template TVal* alloc(const TKey& key, TArgs&&... args) { 111 | TItem* item = 112 | pool_.alloc(std::piecewise_construct_t{}, std::tuple{key}, 113 | std::forward_as_tuple(args...)); 114 | if (item) { 115 | return &item->second; 116 | } else { 117 | return nullptr; 118 | } 119 | } 120 | 121 | void erase(iterator it) { 122 | pool_.free(&*it); 123 | } 124 | 125 | Pool, Size> pool_; 126 | }; 127 | 128 | } // namespace fibre 129 | 130 | namespace std { 131 | 132 | template 133 | struct iterator_traits> { 134 | typedef std::forward_iterator_tag iterator_category; 135 | }; 136 | 137 | } // namespace std 138 | 139 | #endif // __FIBRE_POOL_HPP 140 | -------------------------------------------------------------------------------- /cpp/include/fibre/rich_status.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_RICH_STATUS_HPP 2 | #define __FIBRE_RICH_STATUS_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if FIBRE_ENABLE_TEXT_LOGGING 11 | #include 12 | #include 13 | #include 14 | #endif 15 | 16 | 17 | namespace fibre { 18 | 19 | #if FIBRE_ENABLE_TEXT_LOGGING 20 | using logstr = std::string; 21 | #else 22 | struct logstr {}; 23 | #endif 24 | 25 | #if __cplusplus < 201703L 26 | struct RichStatus { 27 | #else 28 | struct [[nodiscard]] RichStatus { 29 | #endif 30 | RichStatus() : n_msgs(0) {} 31 | 32 | template 33 | RichStatus(TFunc msg_gen, const char* file, size_t line, const RichStatus& inner) 34 | : msgs_(inner.msgs_), n_msgs(inner.n_msgs) { 35 | if (n_msgs < msgs_.size()) { 36 | logstr msg; 37 | #if FIBRE_ENABLE_TEXT_LOGGING 38 | std::ostringstream stream; 39 | msg_gen(stream); 40 | msg = stream.str(); 41 | #endif 42 | msgs_[n_msgs++] = {msg, file, line}; 43 | } 44 | } 45 | 46 | 47 | struct StackFrame { 48 | logstr msg; 49 | const char* file; 50 | size_t line; 51 | }; 52 | 53 | std::array msgs_; 54 | size_t n_msgs; 55 | 56 | bool is_error() const { 57 | return n_msgs > 0; 58 | } 59 | 60 | bool is_success() const { 61 | return !is_error(); 62 | } 63 | 64 | template 65 | bool on_error(TFunc func) { 66 | if (is_error()) { 67 | func(); 68 | } 69 | return is_error(); 70 | } 71 | 72 | const char* inner_file() const { return n_msgs ? msgs_[0].file : nullptr; } 73 | size_t inner_line() const { return n_msgs ? msgs_[0].line : 0; } 74 | 75 | static RichStatus success() { 76 | return {}; 77 | } 78 | }; 79 | 80 | 81 | #if FIBRE_ENABLE_TEXT_LOGGING 82 | 83 | static inline std::ostream& operator<<(std::ostream& stream, RichStatus const& status) { 84 | for (size_t i = 0; i < status.n_msgs; ++i) { 85 | stream << "\n\t\tin " << status.msgs_[i].file << ":" << status.msgs_[i].line << ": " << status.msgs_[i].msg; 86 | } 87 | return stream; 88 | } 89 | 90 | #endif 91 | 92 | template 93 | class RichStatusOr { 94 | public: 95 | RichStatusOr(T val) : status_{RichStatus::success()}, val_{val} {} 96 | RichStatusOr(RichStatus status) : status_{status}, val_{std::nullopt} {} 97 | 98 | RichStatus status() { return status_; } 99 | T& value() { return *val_; } 100 | bool has_value() { return val_.has_value(); } 101 | 102 | private: 103 | RichStatus status_; 104 | std::optional val_; 105 | }; 106 | 107 | } 108 | 109 | 110 | #if FIBRE_ENABLE_TEXT_LOGGING 111 | 112 | #define F_MAKE_ERR(msg) fibre::RichStatus{[&](std::ostream& str) { str << msg; }, __FILE__, __LINE__, fibre::RichStatus::success()} 113 | #define F_AMEND_ERR(inner, msg) fibre::RichStatus{[&](std::ostream& str) { str << msg; }, __FILE__, __LINE__, (inner)} 114 | 115 | #else 116 | 117 | #define F_MAKE_ERR(msg) fibre::RichStatus{0, __FILE__, __LINE__, fibre::RichStatus::success()} 118 | #define F_AMEND_ERR(inner, msg) fibre::RichStatus{0, __FILE__, __LINE__, (inner)} 119 | 120 | #endif 121 | 122 | /** 123 | * @brief Returns an error object from the current function if `expr` evaluates 124 | * to false. 125 | * 126 | * The containing function must have a return type that is assignable from 127 | * RichStatus. 128 | * 129 | * If `FIBRE_ENABLE_TEXT_LOGGING` is non-zero, `msg` is evaluated and attached 130 | * to the error object. 131 | */ 132 | #define F_RET_IF(expr, msg) \ 133 | do { \ 134 | bool __err = (expr); \ 135 | if (__err) \ 136 | return F_MAKE_ERR(msg); \ 137 | } while (0) 138 | 139 | /** 140 | * @brief Returns an error object from the current function if `status` is an 141 | * error. 142 | * 143 | * The containing function must have a return type that is assignable from 144 | * RichStatus. 145 | * 146 | * If `FIBRE_ENABLE_TEXT_LOGGING` is non-zero, `msg` is evaluated and attached 147 | * to the error object. 148 | */ 149 | #define F_RET_IF_ERR(status, msg) \ 150 | do { \ 151 | fibre::RichStatus __status = (status); \ 152 | if (__status.is_error()) \ 153 | return F_AMEND_ERR(__status, msg); \ 154 | } while (0) 155 | 156 | #endif // __FIBRE_RICH_STATUS_HPP -------------------------------------------------------------------------------- /cpp/include/fibre/simple_serdes.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_SIMPLE_SERDES 2 | #define __FIBRE_SIMPLE_SERDES 3 | 4 | #include "cpp_utils.hpp" 5 | #include "limits.h" 6 | #include // TODO: make C++11 backport of this 7 | #include 8 | #include 9 | 10 | template 11 | struct SimpleSerializer; 12 | template 13 | using LittleEndianSerializer = SimpleSerializer; 14 | template 15 | using BigEndianSerializer = SimpleSerializer; 16 | 17 | 18 | /* @brief Serializer/deserializer for arbitrary integral number types */ 19 | // TODO: allow reading an arbitrary number of bits 20 | template 21 | struct SimpleSerializer::value>> { 22 | static constexpr size_t BIT_WIDTH = std::numeric_limits::digits; 23 | static constexpr size_t BYTE_WIDTH = (BIT_WIDTH + 7) / 8; 24 | 25 | template 26 | static std::optional read(TIterator* begin, TIterator end = nullptr) { 27 | T result = 0; 28 | if (BigEndian) { 29 | for (size_t i = BYTE_WIDTH; i > 0; (i++, (*begin)++)) { 30 | if (end && !(*begin < end)) 31 | return std::nullopt; 32 | uint8_t byte = **begin; 33 | result |= static_cast(byte) << ((i - 1) << 3); 34 | } 35 | } else { 36 | for (size_t i = 0; i < BYTE_WIDTH; (i++, (*begin)++)) { 37 | if (end && !(*begin < end)) 38 | return std::nullopt; 39 | uint8_t byte = **begin; 40 | result |= static_cast(byte) << (i << 3); 41 | } 42 | } 43 | return result; 44 | } 45 | 46 | template 47 | static bool write(T value, TIterator* begin, TIterator end = nullptr) { 48 | if (BigEndian) { 49 | for (size_t i = BYTE_WIDTH; i > 0; (i--, (*begin)++)) { 50 | if (end && !(*begin < end)) 51 | return false; 52 | uint8_t byte = static_cast((value >> ((i - 1) << 3)) & 0xff); 53 | **begin = byte; 54 | } 55 | } else { 56 | for (size_t i = 0; i < BYTE_WIDTH; (i++, (*begin)++)) { 57 | if (end && !(*begin < end)) 58 | return false; 59 | uint8_t byte = static_cast((value >> (i << 3)) & 0xff); 60 | **begin = byte; 61 | } 62 | } 63 | return true; 64 | } 65 | }; 66 | 67 | template 68 | inline std::optional read_le(fibre::cbufptr_t* buffer) { 69 | static_assert(is_complete>(), "no LittleEndianSerializer is defined for type T"); 70 | return LittleEndianSerializer::read(&buffer->begin(), buffer->end()); 71 | } 72 | 73 | template 74 | inline bool write_le(T value, fibre::bufptr_t* buffer) { 75 | static_assert(is_complete>(), "no LittleEndianSerializer is defined for type T"); 76 | return LittleEndianSerializer::write(value, &buffer->begin(), buffer->end()); 77 | } 78 | 79 | template::value>> 80 | inline size_t write_le(T value, uint8_t* buffer){ 81 | //TODO: add static_assert that this is still a little endian machine 82 | std::memcpy(&buffer[0], &value, sizeof(value)); 83 | return sizeof(value); 84 | } 85 | 86 | template 87 | typename std::enable_if_t::value, size_t> 88 | write_le(T value, uint8_t* buffer) { 89 | return write_le>(value, buffer); 90 | } 91 | 92 | template<> 93 | inline size_t write_le(float value, uint8_t* buffer) { 94 | static_assert(CHAR_BIT * sizeof(float) == 32, "32 bit floating point expected"); 95 | static_assert(std::numeric_limits::is_iec559, "IEEE 754 floating point expected"); 96 | uint32_t value_as_uint32; 97 | std::memcpy(&value_as_uint32, &value, sizeof(uint32_t)); 98 | return write_le(value_as_uint32, buffer); 99 | } 100 | 101 | template 102 | inline size_t read_le(T* value, const uint8_t* buffer){ 103 | // TODO: add static_assert that this is still a little endian machine 104 | std::memcpy(value, buffer, sizeof(*value)); 105 | return sizeof(*value); 106 | } 107 | 108 | template<> 109 | inline size_t read_le(float* value, const uint8_t* buffer) { 110 | static_assert(CHAR_BIT * sizeof(float) == 32, "32 bit floating point expected"); 111 | static_assert(std::numeric_limits::is_iec559, "IEEE 754 floating point expected"); 112 | return read_le(reinterpret_cast(value), buffer); 113 | } 114 | 115 | // @brief Reads a value of type T from the buffer. 116 | // @param buffer Pointer to the buffer to be read. The pointer is updated by the number of bytes that were read. 117 | // @param length The number of available bytes in buffer. This value is updated to subtract the bytes that were read. 118 | template 119 | static inline T read_le(const uint8_t** buffer, size_t* length) { 120 | T result; 121 | size_t cnt = read_le(&result, *buffer); 122 | *buffer += cnt; 123 | *length -= cnt; 124 | return result; 125 | } 126 | 127 | template 128 | inline T read_le(const uint8_t* buf) { 129 | static_assert(is_complete>(), "no LittleEndianSerializer is defined for type T"); 130 | return LittleEndianSerializer::read(&buf, (const uint8_t*)nullptr).value(); 131 | } 132 | 133 | #endif -------------------------------------------------------------------------------- /cpp/include/fibre/socket.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_SOCKET_HPP 2 | #define __FIBRE_SOCKET_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace fibre { 8 | 9 | struct WriteArgs { 10 | bool is_busy() { 11 | return status == kFibreBusy; 12 | } 13 | static WriteArgs busy() { 14 | return {{}, kFibreBusy}; 15 | } 16 | 17 | BufChain buf; 18 | Status status; 19 | }; 20 | 21 | struct WriteResult { 22 | bool is_busy() { 23 | return status == kFibreBusy; 24 | } 25 | static WriteResult busy() { 26 | return {kFibreBusy}; 27 | } 28 | Status status; 29 | CBufIt end; 30 | }; 31 | 32 | /** 33 | * @brief Bidirectional socket for layered frame streams. 34 | * 35 | * The socket follows push-semantics in both directions, that means the data 36 | * source writes to the data sink whenever data becomes available. 37 | */ 38 | class Socket { 39 | public: 40 | /** 41 | * @brief Writes data to the socket (in its role as a sink). 42 | * 43 | * If the socket can handle the request synchronously without blocking the 44 | * return value indicates until which position the input data could be 45 | * consumed as well as the error code of the operation. 46 | * 47 | * If the socket cannot handle the request immediately it returns an error 48 | * code of kFibreBusy and the source must not call write() again until the 49 | * operation completes. Once the request completes (for instance as a result 50 | * of I/O activity), the actual result will be returned to the originating 51 | * socket via its on_write_done() function. 52 | * 53 | * The mechanism through which two sockets are connected is implementation- 54 | * specific. 55 | * 56 | * 57 | * If the input consists of more than zero chunks then the sink must either 58 | * process at least one chunk or return a status different from kFibreOk 59 | * (or both). 60 | * 61 | * If the input consists of zero chunks and the input status is different 62 | * from kFibreOk then the sink must return a status different from kFibreOk 63 | * too (usually identical to the input status). 64 | * 65 | * If the input consists of zero chunks and the input status is kFibreOk the 66 | * sink is allowed not to make progress (return kFibreOk), therefore the 67 | * source should avoid this. 68 | * 69 | * Once the sink returns a status other than kFibreOk and kFibreBusy it is 70 | * considerd closed and must not be written to anymore. 71 | */ 72 | virtual WriteResult write(WriteArgs args) = 0; 73 | 74 | /** 75 | * @brief Informs the socket (in its role as a source) that a write 76 | * operation to a sink socket has completed. 77 | * 78 | * If the source can start a new write operation synchronously without 79 | * blocking it can do so by returning the corresponding status and buffers. 80 | * 81 | * If the source cannot start a new write operation synchronously it shall 82 | * return a status of kFibreBusy. 83 | * 84 | * If result holds a status other than kFibreOk (meaning that the sink 85 | * closed) the source must return a status different from kFibreOk and 86 | * kFibreBusy. 87 | */ 88 | virtual WriteArgs on_write_done(WriteResult result) = 0; 89 | }; 90 | 91 | template struct UpfacingSocket : Socket { 92 | WriteResult write(WriteArgs args) final { 93 | return static_cast(this)->downstream_write(args); 94 | } 95 | WriteArgs on_write_done(WriteResult result) final { 96 | return static_cast(this)->on_upstream_write_done(result); 97 | } 98 | }; 99 | 100 | template struct DownfacingSocket : Socket { 101 | WriteResult write(WriteArgs args) final { 102 | return static_cast(this)->upstream_write(args); 103 | } 104 | WriteArgs on_write_done(WriteResult result) final { 105 | return static_cast(this)->on_downstream_write_done(result); 106 | } 107 | }; 108 | 109 | struct TwoSidedSocket : UpfacingSocket, 110 | DownfacingSocket { 111 | Socket* upfacing_socket() { 112 | return static_cast*>(this); 113 | } 114 | Socket* downfacing_socket() { 115 | return static_cast*>(this); 116 | } 117 | virtual WriteResult downstream_write(WriteArgs args) = 0; 118 | virtual WriteArgs on_upstream_write_done(WriteResult result) = 0; 119 | virtual WriteResult upstream_write(WriteArgs args) = 0; 120 | virtual WriteArgs on_downstream_write_done(WriteResult result) = 0; 121 | }; 122 | 123 | } // namespace fibre 124 | 125 | #endif // __FIBRE_SOCKET_HPP -------------------------------------------------------------------------------- /cpp/include/fibre/status.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_STATUS_HPP 2 | #define __FIBRE_STATUS_HPP 3 | 4 | namespace fibre { 5 | 6 | enum Status { 7 | kFibreOk, 8 | kFibreBusy, // 5 | 6 | namespace fibre { 7 | 8 | struct RichStatus; 9 | 10 | enum class TimerMode { 11 | kNever, 12 | kOnce, 13 | kPeriodic, 14 | }; 15 | 16 | class Timer { 17 | public: 18 | /** 19 | * @brief Sets the timer state. 20 | * 21 | * This can be called at any time while the timer is open, regardless 22 | * whether it is running or stopped. 23 | * 24 | * @param interval: The delay from now when the timer should fire the next 25 | * time. For periodic timers this also sets the interval between 26 | * subsequent triggers. For TimerMode::kNever this parameter is 27 | * ignored. 28 | * Periodic timers will attempt to keep the exact interval, even if 29 | * the callback takes a non-negligible time (due to CPU bound work). 30 | * If the callback takes very long (on the order of an interval or 31 | * longer the timer shall skip triggers as appropriate. 32 | * @param mode: If false, the timer will fire only once unless the 33 | * set() function is called again. If true, the timer will fire 34 | * repeatedly in intervals specified by `interval`. 35 | */ 36 | virtual RichStatus set(float interval, TimerMode mode) = 0; 37 | }; 38 | 39 | class TimerProvider { 40 | public: 41 | /** 42 | * @brief Opens a new timer. 43 | * 44 | * The timer starts in stopped state. 45 | * 46 | * @param on_trigger: The callback that will be called whenever the timer 47 | * fires. 48 | */ 49 | virtual RichStatus open_timer(Timer** p_timer, Callback on_trigger) = 0; 50 | 51 | /** 52 | * @brief Closes the specified timer. 53 | * 54 | * This can be called regardless of whether the timer is running or not. 55 | * The associated callback will not be called again after (nor during) this 56 | * function. 57 | */ 58 | virtual RichStatus close_timer(Timer* timer) = 0; 59 | }; 60 | 61 | } 62 | 63 | #endif // __FIBRE_TIMER_HPP -------------------------------------------------------------------------------- /cpp/include/fibre/tx_pipe.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_TX_PIPE_HPP 2 | #define __FIBRE_TX_PIPE_HPP 3 | 4 | namespace fibre { 5 | 6 | struct Multiplexer; 7 | struct BufChain; 8 | struct CBufIt; 9 | 10 | struct TxPipe { 11 | Multiplexer* multiplexer_ = nullptr; 12 | bool waiting_for_multiplexer_ = false; 13 | uintptr_t backend_slot_id; 14 | virtual bool has_data() = 0; 15 | virtual BufChain get_task() = 0; 16 | virtual void release_task(CBufIt end) = 0; 17 | }; 18 | 19 | } 20 | 21 | #endif // __FIBRE_TX_PIPE_HPP -------------------------------------------------------------------------------- /cpp/json.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_JSON_HPP 2 | #define __FIBRE_JSON_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct json_error { 12 | const char* ptr; 13 | std::string str; 14 | }; 15 | 16 | struct json_value; 17 | using json_list = std::vector>; 18 | using json_dict = std::vector, std::shared_ptr>>; 19 | using json_value_variant = std::variant; 20 | 21 | struct json_value : json_value_variant { 22 | //json_value(const json_value_variant& v) : json_value_variant{v} {} 23 | template json_value(T&& arg) : json_value_variant{std::forward(arg)} {} 24 | //json_value_variant v; 25 | }; 26 | 27 | // helper functions 28 | inline bool json_is_str(json_value val) { return val.index() == 0; } 29 | inline bool json_is_int(json_value val) { return val.index() == 1; } 30 | inline bool json_is_list(json_value val) { return val.index() == 2; } 31 | inline bool json_is_dict(json_value val) { return val.index() == 3; } 32 | inline bool json_is_err(json_value val) { return val.index() == 4; } 33 | inline std::string json_as_str(json_value val) { return std::get<0>(val); } 34 | inline int json_as_int(json_value val) { return std::get<1>(val); } 35 | inline json_list json_as_list(json_value val) { return std::get<2>(val); } 36 | inline json_dict json_as_dict(json_value val) { return std::get<3>(val); } 37 | inline json_error json_as_err(json_value val) { return std::get<4>(val); } 38 | 39 | inline json_value json_make_error(const char* ptr, std::string str) { 40 | return {json_error{ptr, str}}; 41 | } 42 | 43 | 44 | inline void json_skip_whitespace(const char** begin, const char* end) { 45 | while (*begin < end && std::isspace(**begin)) { 46 | (*begin)++; 47 | } 48 | } 49 | 50 | inline bool json_comp(const char* begin, const char* end, char c) { 51 | return begin < end && *begin == c; 52 | } 53 | 54 | inline json_value json_parse(const char** begin, const char* end, fibre::Logger logger) { 55 | // skip whitespace 56 | 57 | if (*begin >= end) { 58 | return json_make_error(*begin, "expected value but got EOF"); 59 | } 60 | 61 | if (json_comp(*begin, end, '{')) { 62 | // parse dict 63 | (*begin)++; // consume leading '{' 64 | json_dict dict; 65 | bool expect_comma = false; 66 | 67 | json_skip_whitespace(begin, end); 68 | while (!json_comp(*begin, end, '}')) { 69 | if (expect_comma) { 70 | if (!json_comp(*begin, end, ',')) { 71 | return json_make_error(*begin, "expected ',' or '}'"); 72 | } 73 | (*begin)++; // consume comma 74 | json_skip_whitespace(begin, end); 75 | } 76 | expect_comma = true; 77 | 78 | // Parse key-value pair 79 | json_value key = json_parse(begin, end, logger); 80 | if (json_is_err(key)) return key; 81 | json_skip_whitespace(begin, end); 82 | if (!json_comp(*begin, end, ':')) { 83 | return json_make_error(*begin, "expected :"); 84 | } 85 | (*begin)++; 86 | json_value val = json_parse(begin, end, logger); 87 | if (json_is_err(val)) return val; 88 | dict.push_back({std::make_shared(key), std::make_shared(val)}); 89 | 90 | json_skip_whitespace(begin, end); 91 | } 92 | 93 | (*begin)++; 94 | return {dict}; 95 | 96 | } else if (json_comp(*begin, end, '[')) { 97 | // parse list 98 | (*begin)++; // consume leading '[' 99 | json_list list; 100 | bool expect_comma = false; 101 | 102 | json_skip_whitespace(begin, end); 103 | while (!json_comp(*begin, end, ']')) { 104 | if (expect_comma) { 105 | if (!json_comp(*begin, end, ',')) { 106 | return json_make_error(*begin, "expected ',' or ']'"); 107 | } 108 | (*begin)++; // consume comma 109 | json_skip_whitespace(begin, end); 110 | } 111 | expect_comma = true; 112 | 113 | // Parse item 114 | json_value val = json_parse(begin, end, logger); 115 | if (json_is_err(val)) return val; 116 | list.push_back(std::make_shared(val)); 117 | 118 | json_skip_whitespace(begin, end); 119 | } 120 | 121 | (*begin)++; // consume trailing ']' 122 | return {list}; 123 | 124 | } else if (json_comp(*begin, end, '"')) { 125 | // parse string 126 | (*begin)++; // consume leading '"' 127 | std::string str; 128 | 129 | while (!json_comp(*begin, end, '"')) { 130 | if (*begin >= end) { 131 | return json_make_error(*begin, "expected '\"' but got EOF"); 132 | } 133 | if (json_comp(*begin, end, '\\')) { 134 | return json_make_error(*begin, "escaped strings not supported"); 135 | } 136 | str.push_back(**begin); 137 | (*begin)++; 138 | } 139 | 140 | (*begin)++; // consume trailing '"' 141 | return {str}; 142 | 143 | } else if (std::isdigit(**begin)) { 144 | // parse int 145 | 146 | std::string str; 147 | while (*begin < end && std::isdigit(**begin)) { 148 | str.push_back(**begin); 149 | (*begin)++; 150 | } 151 | 152 | return {std::stoi(str)}; // note: this can throw an exception if the int is too long 153 | 154 | } else { 155 | return json_make_error(*begin, "unexpected character '" + std::string(*begin, *begin + 1) + "'"); 156 | } 157 | } 158 | 159 | inline json_value json_dict_find(json_dict dict, std::string key) { 160 | auto it = std::find_if(dict.begin(), dict.end(), 161 | [&](std::pair, std::shared_ptr>& kv){ 162 | return json_is_str(*kv.first) && json_as_str(*kv.first) == key; 163 | }); 164 | return (it == dict.end()) ? json_make_error(nullptr, "key not found") : *it->second; 165 | } 166 | 167 | #endif // __FIBRE_JSON_HPP 168 | -------------------------------------------------------------------------------- /cpp/legacy_endpoints_template.j2: -------------------------------------------------------------------------------- 1 | /*[# This is the original template, thus the warning below does not apply to this file #] 2 | * ============================ WARNING ============================ 3 | * ==== This is an autogenerated file. ==== 4 | * ==== Any changes to this file will be lost when recompiling. ==== 5 | * ================================================================= 6 | * 7 | * This file contains the toplevel handler for Fibre v0.1 endpoint operations. 8 | * 9 | * This endpoint-oriented approach will be deprecated in Fibre v0.2 in favor of 10 | * a function-oriented approach and a more powerful object model. 11 | * 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace fibre { 19 | 20 | const unsigned char embedded_json[] = [[endpoint_descr | to_c_string]]; 21 | const size_t embedded_json_length = sizeof(embedded_json) - 1; 22 | 23 | EndpointDefinition endpoint_table[] = { 24 | [%- for ep in endpoint_table %] 25 | [[ep]], 26 | [%- endfor %] 27 | }; 28 | 29 | const uint16_t json_crc_ = calc_crc16(PROTOCOL_VERSION, embedded_json, embedded_json_length); 30 | const uint32_t json_version_id_ = (json_crc_ << 16) | calc_crc16(json_crc_, embedded_json, embedded_json_length); 31 | const size_t n_endpoints = sizeof(endpoint_table) / sizeof(endpoint_table[0]); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /cpp/legacy_object_client.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_LEGACY_OBJECT_MODEL_HPP 2 | #define __FIBRE_LEGACY_OBJECT_MODEL_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | struct json_value; 15 | 16 | namespace fibre { 17 | 18 | struct LegacyProtocolPacketBased; 19 | struct Transcoder; 20 | struct LegacyObject; 21 | class LegacyObjectClient; 22 | struct LegacyInterface; 23 | 24 | // Lower 16 bits are the seqno. Upper 16 bits are all 1 for valid handles 25 | // (such that seqno 0 doesn't cause the handle to be 0) 26 | using EndpointOperationHandle = uint32_t; 27 | 28 | struct EndpointOperationResult { 29 | EndpointOperationHandle op; 30 | StreamStatus status; 31 | const uint8_t* tx_end; 32 | uint8_t* rx_end; 33 | }; 34 | 35 | struct LegacyFibreArg { 36 | std::string name; 37 | std::string app_codec; 38 | Transcoder* transcoder; 39 | size_t ep_num; 40 | }; 41 | 42 | 43 | struct LegacyFunction final : Function { 44 | LegacyFunction(LegacyObjectClient* client, std::string name, 45 | size_t ep_num, LegacyObject* obj, 46 | std::vector inputs, 47 | std::vector outputs) 48 | : Function{}, 49 | client(client), 50 | name(name), 51 | ep_num(ep_num), 52 | obj_(obj), 53 | inputs_(inputs), 54 | outputs_(outputs) {} 55 | 56 | virtual Socket* start_call( 57 | Domain* domain, bufptr_t call_frame, Socket* caller) const final; 58 | 59 | FunctionInfo* get_info() const final; 60 | void free_info(FunctionInfo* info) const final; 61 | 62 | LegacyObjectClient* client; 63 | std::string name; 64 | size_t ep_num; // 0 for property read/write/exchange functions 65 | LegacyObject* 66 | obj_; // null for property read/write/exchange functions (all other 67 | // functions are associated with one object only) 68 | std::vector inputs_; 69 | std::vector outputs_; 70 | }; 71 | 72 | struct LegacyFibreAttribute { 73 | std::string name; 74 | std::shared_ptr object; 75 | }; 76 | 77 | struct LegacyInterface final : Interface { 78 | std::string name; 79 | std::vector> functions; 80 | std::vector attributes; 81 | 82 | InterfaceInfo* get_info() final; 83 | void free_info(InterfaceInfo* info) final; 84 | RichStatusOr get_attribute(Object* parent_obj, 85 | size_t attr_id) final; 86 | }; 87 | 88 | struct LegacyObject { 89 | Node* node; 90 | size_t ep_num; 91 | uint16_t json_crc; 92 | std::shared_ptr intf; 93 | }; 94 | 95 | using EndpointClientCallback = Callback, std::vector, Socket*>; 96 | 97 | class LegacyObjectClient : public Socket { 98 | public: 99 | void start(Node* node, Domain* domain_, EndpointClientCallback default_endpoint_client, std::string path); 100 | 101 | std::shared_ptr get_property_interfaces(std::string codec, 102 | bool write); 103 | std::shared_ptr load_object(json_value list_val); 104 | void load_json(cbufptr_t json); 105 | 106 | WriteResult write(WriteArgs args) final; 107 | WriteArgs on_write_done(WriteResult result) final; 108 | 109 | // call endpoint 0 110 | uint8_t data0[4] = {0x00, 0x00, 0x00, 0x00}; 111 | 112 | Node* node_; 113 | Domain* domain_; 114 | EndpointClientCallback default_endpoint_client_; 115 | std::string path_; // TODO: get dynamically from node 116 | CBufIt tx_pos_; 117 | std::vector json_; 118 | uint16_t json_crc_; 119 | std::vector> objects_; 120 | std::shared_ptr root_obj_ = nullptr; 121 | Chunk chunks_[2]; 122 | std::unordered_map> 123 | rw_property_interfaces; 124 | std::unordered_map> 125 | ro_property_interfaces; 126 | }; 127 | 128 | template 129 | T* alloc_ctx(bufptr_t buf, TArgs... args) { 130 | #if FIBRE_ALLOW_HEAP 131 | return new T{args...}; 132 | #else 133 | if (buf.size() >= sizeof(T)) { 134 | return new ((T*)buf.begin()) T{args...}; 135 | } 136 | return nullptr; 137 | #endif 138 | } 139 | 140 | template void delete_ctx(T* ptr) { 141 | #if FIBRE_ALLOW_HEAP 142 | delete ptr; 143 | #else 144 | if (ptr) { 145 | ptr->~T(); 146 | return; 147 | } 148 | #endif 149 | } 150 | 151 | } // namespace fibre 152 | 153 | #endif // __FIBRE_LEGACY_OBJECT_MODEL_HPP -------------------------------------------------------------------------------- /cpp/legacy_object_server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __LEGACY_OBJECT_SERVER_HPP 2 | #define __LEGACY_OBJECT_SERVER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace fibre { 14 | 15 | class Domain; 16 | 17 | struct LegacyObjectServer { 18 | uint8_t rx_buf_[128]; 19 | size_t rx_pos_; 20 | uint8_t tx_buf_[128]; 21 | size_t tx_pos_; 22 | size_t expected_ep_ = 0; // 0 while no call in progress 23 | size_t trigger_ep_; 24 | size_t n_inputs_; 25 | size_t n_outputs_; 26 | size_t output_size_; 27 | 28 | uint8_t call_state_[256]; 29 | 30 | void reset() { 31 | rx_pos_ = 0; 32 | tx_pos_ = 0; 33 | expected_ep_ = 0; 34 | trigger_ep_ = 0; 35 | n_inputs_ = 0; 36 | n_outputs_ = 0; 37 | output_size_ = 0; 38 | } 39 | 40 | RichStatus endpoint_handler(Domain* domain, int idx, 41 | cbufptr_t* input_buffer, 42 | bufptr_t* output_buffer); 43 | }; 44 | 45 | enum class EndpointType { 46 | kFunctionTrigger, 47 | kFunctionInput, 48 | kFunctionOutput, 49 | kRoProperty, 50 | kRwProperty, 51 | }; 52 | 53 | struct EndpointDefinition { 54 | EndpointType type; 55 | union { 56 | struct { 57 | ServerFunctionId function_id; 58 | ServerObjectId object_id; 59 | } function_trigger; 60 | struct { 61 | unsigned size; 62 | } function_input; 63 | struct { 64 | unsigned size; 65 | } function_output; 66 | struct { 67 | ServerFunctionId read_function_id; 68 | ServerObjectId object_id; 69 | } ro_property; 70 | struct { 71 | ServerFunctionId read_function_id; 72 | ServerFunctionId exchange_function_id; 73 | ServerObjectId object_id; 74 | } rw_property; 75 | }; 76 | }; 77 | 78 | // Defined in autogenerated endpoints.cpp 79 | extern const unsigned char embedded_json[]; 80 | extern const size_t embedded_json_length; 81 | extern EndpointDefinition endpoint_table[]; 82 | extern const uint16_t json_crc_; 83 | extern const uint32_t json_version_id_; 84 | extern const size_t n_endpoints; 85 | 86 | } // namespace fibre 87 | 88 | #endif // __LEGACY_OBJECT_SERVER_HPP 89 | -------------------------------------------------------------------------------- /cpp/libfibre.version: -------------------------------------------------------------------------------- 1 | LIBFIBREABI_0.1.0 { 2 | global: libfibre_*; 3 | local: *; 4 | }; -------------------------------------------------------------------------------- /cpp/mini_rng.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __MINI_RNG_HPP 2 | #define __MINI_RNG_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace fibre { 8 | 9 | // Source: 10 | // https://www.electro-tech-online.com/threads/ultra-fast-pseudorandom-number-generator-for-8-bit.124249/ 11 | class MiniRng { 12 | public: 13 | void seed(uint8_t s0, uint8_t s1, uint8_t s2, uint8_t s3) { 14 | x ^= s0; 15 | a ^= s1; 16 | b ^= s2; 17 | c ^= s3; 18 | next(); 19 | } 20 | 21 | uint8_t next() { 22 | x++; 23 | a = (a ^ c ^ x); 24 | b = (b + a); 25 | c = ((c + (b >> 1)) ^ a); 26 | return c; 27 | } 28 | 29 | void get_random(bufptr_t buf) { 30 | for (; buf.size(); buf = buf.skip(1)) { 31 | *buf.begin() = next(); 32 | } 33 | } 34 | 35 | private: 36 | uint8_t a = 0, b = 0, c = 0, x = 0; 37 | }; 38 | 39 | } // namespace fibre 40 | 41 | #endif // __MINI_RNG_HPP 42 | -------------------------------------------------------------------------------- /cpp/multiplexer.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace fibre; 8 | 9 | void Multiplexer::add_source(TxPipe* pipe) { 10 | if (sending_pipe_) { 11 | queue_.push_back(pipe); 12 | } else { 13 | send_next(pipe); 14 | } 15 | } 16 | 17 | void Multiplexer::remove_source(TxPipe* pipe) { 18 | if (pipe == sending_pipe_) { 19 | sink_->cancel_write(); 20 | maybe_send_next(); 21 | } else { 22 | auto it = std::find(queue_.begin(), queue_.end(), pipe); 23 | if (it == queue_.end()) { 24 | return; // TODO: log error 25 | } 26 | queue_.erase(it); 27 | } 28 | } 29 | 30 | void Multiplexer::maybe_send_next() { 31 | if (queue_.size()) { 32 | // TODO: support sending from multiple sources at once 33 | TxPipe* pipe = queue_.front(); 34 | queue_.erase(queue_.begin()); 35 | send_next(pipe); 36 | } else { 37 | sending_pipe_ = nullptr; 38 | } 39 | } 40 | 41 | void Multiplexer::send_next(TxPipe* pipe) { 42 | TxTask jobs[1]; 43 | auto job = pipe->get_task(); 44 | jobs[0].pipe = pipe; 45 | jobs[0].slot_id = pipe->backend_slot_id; 46 | jobs[0].begin_ = job.c_begin(); 47 | jobs[0].end_ = job.c_end(); 48 | sending_pipe_ = pipe; 49 | sink_->start_write({jobs, jobs + 1}); 50 | } 51 | 52 | void Multiplexer::on_sent(TxPipe* pipe, CBufIt end) { 53 | pipe->release_task(end); 54 | 55 | if (pipe->has_data()) { 56 | queue_.push_back(pipe); 57 | } else { 58 | pipe->multiplexer_ = this; 59 | } 60 | 61 | maybe_send_next(); 62 | } 63 | 64 | void Multiplexer::on_cancelled(TxPipe* pipe, CBufIt end) { 65 | pipe->release_task(end); 66 | } 67 | -------------------------------------------------------------------------------- /cpp/platform_support/epoll_event_loop.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_LINUX_EVENT_LOOP_HPP 2 | #define __FIBRE_LINUX_EVENT_LOOP_HPP 3 | 4 | //#include 5 | #include 6 | #include 7 | #include 8 | #include 9 | //#include 10 | 11 | #include 12 | #include 13 | 14 | namespace fibre { 15 | 16 | /** 17 | * @brief Event loop based on the Linux-specific `epoll()` infrastructure. 18 | * 19 | * Thread safety: None of the public functions are thread-safe with respect to 20 | * each other. However they are thread safe with respect to the internal event 21 | * loop, that means register_event() and deregister_event() can be called from 22 | * within an event callback (which executes on the event loop thread), provided 23 | * those calls are properly synchronized with calls from other threads. 24 | */ 25 | class EpollEventLoop final : public EventLoop { 26 | public: 27 | 28 | /** 29 | * @brief Starts the event loop on the current thread and places the 30 | * specified start callback on the event queue. 31 | * 32 | * The function returns when the event loop becomes empty or if a platform 33 | * error occurs. 34 | */ 35 | RichStatus start(Logger logger, Callback on_started); 36 | 37 | RichStatus post(Callback callback) final; 38 | RichStatus register_event(int fd, uint32_t events, Callback callback) final; 39 | RichStatus deregister_event(int fd) final; 40 | RichStatus open_timer(Timer** p_timer, Callback on_trigger) final; 41 | RichStatus close_timer(Timer* timer) final; 42 | 43 | private: 44 | struct EventContext { 45 | //int fd; 46 | Callback callback; 47 | }; 48 | 49 | struct TimerContext final : Timer { 50 | RichStatus set(float interval, TimerMode mode) final; 51 | void on_timer(uint32_t); 52 | EpollEventLoop* parent; 53 | int fd; 54 | Callback callback; 55 | }; 56 | 57 | std::unordered_map::iterator drop_events(int event_fd); 58 | void run_callbacks(uint32_t); 59 | void on_timer(TimerContext* ctx); 60 | 61 | int epoll_fd_ = -1; 62 | Logger logger_ = Logger::none(); 63 | int post_fd_ = -1; 64 | unsigned int iterations_ = 0; 65 | 66 | std::unordered_map context_map_; // required to deregister callbacks 67 | 68 | static const size_t max_triggered_events_ = 16; // max number of events that can be handled per iteration 69 | int n_triggered_events_ = 0; 70 | struct epoll_event triggered_events_[max_triggered_events_]; 71 | 72 | // List of callbacks that were submitted through post(). 73 | std::vector> pending_callbacks_; 74 | 75 | // Mutex to protect pending_callbacks_ 76 | std::mutex pending_callbacks_mutex_; 77 | }; 78 | 79 | } 80 | 81 | #endif // __FIBRE_LINUX_EVENT_LOOP_HPP -------------------------------------------------------------------------------- /cpp/platform_support/libusb_backend.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_USB_DISCOVERER_HPP 2 | #define __FIBRE_USB_DISCOVERER_HPP 3 | 4 | #include 5 | 6 | #if FIBRE_ENABLE_LIBUSB_BACKEND 7 | 8 | #include 9 | #include 10 | 11 | namespace fibre { 12 | 13 | struct UsbHostAdapter; 14 | struct LibUsb; 15 | 16 | class LibUsbBackend : public Backend { 17 | public: 18 | RichStatus init(EventLoop* event_loop, Logger logger) final; 19 | RichStatus deinit() final; 20 | 21 | void start_channel_discovery(Domain* domain, const char* specs, size_t specs_len, ChannelDiscoveryContext** handle) final; 22 | RichStatus stop_channel_discovery(ChannelDiscoveryContext* handle) final; 23 | 24 | private: 25 | Logger logger_ = Logger::none(); 26 | LibUsb* libusb_ = nullptr; 27 | UsbHostAdapter* adapter_ = nullptr; 28 | }; 29 | 30 | } 31 | 32 | #endif 33 | 34 | #endif // __FIBRE_USB_DISCOVERER_HPP -------------------------------------------------------------------------------- /cpp/platform_support/posix_tcp_backend.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "posix_tcp_backend.hpp" 3 | 4 | #if FIBRE_ENABLE_TCP_CLIENT_BACKEND || FIBRE_ENABLE_TCP_SERVER_BACKEND 5 | 6 | #include "posix_socket.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace fibre; 14 | 15 | RichStatus PosixTcpBackend::init(EventLoop* event_loop, Logger logger) { 16 | F_RET_IF(event_loop_, "already initialized"); 17 | F_RET_IF(!event_loop, "invalid argument"); 18 | event_loop_ = event_loop; 19 | logger_ = logger; 20 | return RichStatus::success(); 21 | } 22 | 23 | RichStatus PosixTcpBackend::deinit() { 24 | F_RET_IF(!event_loop_, "not initialized"); 25 | F_LOG_IF(logger_, n_discoveries_, "some discoveries still ongoing"); 26 | event_loop_ = nullptr; 27 | logger_ = Logger::none(); 28 | return RichStatus::success(); 29 | } 30 | 31 | void PosixTcpBackend::start_channel_discovery(Domain* domain, const char* specs, size_t specs_len, ChannelDiscoveryContext** handle) { 32 | const char* address_begin; 33 | const char* address_end; 34 | int port; 35 | 36 | if (!event_loop_) { 37 | F_LOG_E(logger_, "not initialized"); 38 | //on_found_channels.invoke({kFibreInvalidArgument, nullptr, nullptr, 0}); 39 | return; // TODO: error reporting 40 | } 41 | 42 | if (!try_parse_key(specs, specs + specs_len, "address", &address_begin, &address_end)) { 43 | F_LOG_E(logger_, "no address specified"); 44 | //on_found_channels.invoke({kFibreInvalidArgument, nullptr, nullptr, 0}); 45 | return; // TODO: error reporting 46 | } 47 | 48 | if (!try_parse_key(specs, specs + specs_len, "port", &port)) { 49 | F_LOG_E(logger_, "no port specified"); 50 | //on_found_channels.invoke({kFibreInvalidArgument, nullptr, nullptr, 0}); 51 | return; // TODO: error reporting 52 | } 53 | 54 | n_discoveries_++; 55 | 56 | TcpChannelDiscoveryContext* ctx = new TcpChannelDiscoveryContext(); // TODO: free 57 | 58 | if (F_LOG_IF_ERR(logger_, event_loop_->open_timer(&ctx->timer, MEMBER_CB(ctx, resolve_address)), "failed to open timer")) { 59 | delete ctx; 60 | return; 61 | } 62 | 63 | ctx->parent = this; 64 | ctx->address = {{address_begin, address_end}, port}; 65 | ctx->display_name = "TCP (" + ctx->address.first + ":" + std::to_string(port) + ")"; 66 | ctx->domain = domain; 67 | ctx->addr_resolution_ctx = nullptr; 68 | ctx->resolve_address(); 69 | } 70 | 71 | RichStatus PosixTcpBackend::stop_channel_discovery(ChannelDiscoveryContext* handle) { 72 | // TODO 73 | n_discoveries_--; 74 | return RichStatus::success(); 75 | } 76 | 77 | void PosixTcpBackend::TcpChannelDiscoveryContext::resolve_address() { 78 | if (F_LOG_IF(parent->logger_, addr_resolution_ctx, "already resolving")) { 79 | return; 80 | } 81 | F_LOG_IF_ERR(parent->logger_, 82 | start_resolving_address(parent->event_loop_, parent->logger_, 83 | address, false, &addr_resolution_ctx, MEMBER_CB(this, on_found_address)), 84 | "cannot start address resolution"); 85 | } 86 | 87 | void PosixTcpBackend::TcpChannelDiscoveryContext::on_found_address(std::optional addr) { 88 | F_LOG_D(parent->logger_, "found address"); 89 | 90 | if (addr.has_value()) { 91 | // Resolved an address. If it wasn't already known, try to connect to it. 92 | std::vector vec{addr->begin(), addr->end()}; 93 | bool is_known = std::find_if(known_addresses.begin(), known_addresses.end(), 94 | [&](AddrContext& val){ return val.addr == vec; }) != known_addresses.end(); 95 | 96 | if (!is_known) { 97 | AddrContext ctx = {.addr = vec}; 98 | if (!F_LOG_IF_ERR(parent->logger_, 99 | parent->start_opening_connections(parent->event_loop_, 100 | parent->logger_, *addr, SOCK_STREAM, IPPROTO_TCP, 101 | &ctx.connection_ctx, MEMBER_CB(this, on_connected)), 102 | "failed to connect")) { 103 | known_addresses.push_back(ctx); 104 | } else { 105 | // TODO 106 | } 107 | } 108 | } else { 109 | // No more addresses. 110 | addr_resolution_ctx = nullptr; 111 | if (known_addresses.size() == 0) { 112 | // No addresses could be found. Try again using exponential backoff. 113 | // TODO: cancel timer on shutdown 114 | F_LOG_IF_ERR(parent->logger_, timer->set(lookup_period, TimerMode::kOnce), 115 | "failed to set timer"); 116 | lookup_period = std::min(lookup_period * 3.0f, 3600.0f); // exponential backoff with at most 1h period 117 | } else { 118 | // Some addresses are known from this lookup or from a previous 119 | // lookup. Resolve addresses again in 1h. 120 | // TODO: cancel timer on shutdown 121 | F_LOG_IF_ERR(parent->logger_, timer->set(3600.0, TimerMode::kOnce), 122 | "failed to set timer"); 123 | } 124 | } 125 | } 126 | 127 | void PosixTcpBackend::TcpChannelDiscoveryContext::on_connected(RichStatus status, socket_id_t socket_id) { 128 | if (!status.is_error()) { 129 | auto socket = new PosixSocket{}; // TODO: free 130 | status = socket->init(parent->event_loop_, parent->logger_, socket_id); 131 | if (!status.is_error()) { 132 | domain->add_legacy_channels({kFibreOk, socket, socket, SIZE_MAX, false}, display_name.data()); 133 | return; 134 | } 135 | delete socket; 136 | } 137 | 138 | F_LOG_IF_ERR(parent->logger_, status, "failed to connect - will retry"); 139 | 140 | // Try to reconnect soon 141 | lookup_period = 1.0f; 142 | resolve_address(); 143 | } 144 | 145 | void PosixTcpBackend::TcpChannelDiscoveryContext::on_disconnected() { 146 | lookup_period = 1.0f; // reset exponential backoff 147 | resolve_address(); 148 | } 149 | 150 | #endif 151 | -------------------------------------------------------------------------------- /cpp/platform_support/posix_tcp_backend.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_POSIX_TCP_BACKEND_HPP 2 | #define __FIBRE_POSIX_TCP_BACKEND_HPP 3 | 4 | #include 5 | 6 | #if FIBRE_ENABLE_TCP_CLIENT_BACKEND || FIBRE_ENABLE_TCP_SERVER_BACKEND 7 | 8 | #include "posix_socket.hpp" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace fibre { 16 | 17 | /** 18 | * TCP client and TCP server implementations are identical up to the function 19 | * that is used to convert an address to one or more connected socket IDs. 20 | * The client uses the posix function `connect` to do so, while the server uses 21 | * the posix functions `listen` and `accept`. 22 | */ 23 | class PosixTcpBackend : public Backend { 24 | public: 25 | RichStatus init(EventLoop* event_loop, Logger logger) final; 26 | RichStatus deinit() final; 27 | 28 | void start_channel_discovery(Domain* domain, const char* specs, size_t specs_len, ChannelDiscoveryContext** handle) final; 29 | RichStatus stop_channel_discovery(ChannelDiscoveryContext* handle) final; 30 | 31 | private: 32 | struct TcpChannelDiscoveryContext { 33 | PosixTcpBackend* parent; 34 | Timer* timer; 35 | std::pair address; 36 | std::string display_name; 37 | Domain* domain; 38 | AddressResolutionContext* addr_resolution_ctx; 39 | ConnectionContext* connection_ctx; 40 | float lookup_period = 1.0f; // wait 1s for next address resolution 41 | 42 | struct AddrContext { 43 | std::vector addr; 44 | ConnectionContext* connection_ctx; 45 | }; 46 | 47 | std::vector known_addresses; 48 | void resolve_address(); 49 | void on_found_address(std::optional addr); 50 | void on_connected(RichStatus status, socket_id_t socket_id); 51 | void on_disconnected(); 52 | }; 53 | 54 | virtual RichStatus start_opening_connections(EventLoop* event_loop, Logger logger, cbufptr_t addr, int type, int protocol, ConnectionContext** ctx, Callback on_connected) = 0; 55 | virtual void cancel_opening_connections(ConnectionContext* ctx) = 0; 56 | 57 | EventLoop* event_loop_ = nullptr; 58 | Logger logger_ = Logger::none(); 59 | size_t n_discoveries_ = 0; 60 | }; 61 | 62 | class PosixTcpClientBackend : public PosixTcpBackend { 63 | public: 64 | RichStatus start_opening_connections(EventLoop* event_loop, Logger logger, cbufptr_t addr, int type, int protocol, ConnectionContext** ctx, Callback on_connected) final { 65 | return start_connecting(event_loop, logger, addr, type, protocol, ctx, on_connected); 66 | } 67 | void cancel_opening_connections(ConnectionContext* ctx) final { 68 | stop_connecting(ctx); 69 | } 70 | }; 71 | 72 | class PosixTcpServerBackend : public PosixTcpBackend { 73 | public: 74 | RichStatus start_opening_connections(EventLoop* event_loop, Logger logger, cbufptr_t addr, int type, int protocol, ConnectionContext** ctx, Callback on_connected) final { 75 | return start_listening(event_loop, logger, addr, type, protocol, ctx, on_connected); 76 | } 77 | void cancel_opening_connections(ConnectionContext* ctx) final { 78 | stop_listening(ctx); 79 | } 80 | }; 81 | 82 | } 83 | 84 | #endif 85 | 86 | #endif // __FIBRE_POSIX_TCP_BACKEND_HPP -------------------------------------------------------------------------------- /cpp/platform_support/socket_can.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_SOCKET_CAN_HPP 2 | #define __FIBRE_SOCKET_CAN_HPP 3 | 4 | #include 5 | 6 | #if FIBRE_ENABLE_SOCKET_CAN_BACKEND 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "../interfaces/canbus.hpp" 13 | #include 14 | #include 15 | 16 | namespace fibre { 17 | 18 | struct CanAdapter; 19 | 20 | class SocketCan final : public CanInterface { 21 | public: 22 | RichStatus init(EventLoop* event_loop, Logger logger, std::string intf_name, Callback on_error); 23 | 24 | bool is_valid_baud_rate(uint32_t nominal_baud_rate, uint32_t data_baud_rate) final; 25 | bool start(uint32_t nominal_baud_rate, uint32_t data_baud_rate, on_event_cb_t rx_event_loop, on_error_cb_t on_error) final; 26 | bool stop() final; 27 | bool send_message(uint32_t tx_slot, const can_Message_t& message, on_sent_cb_t on_sent) final; 28 | bool cancel_message(uint32_t tx_slot) final; 29 | bool subscribe(uint32_t rx_slot, const MsgIdFilterSpecs& filter, on_received_cb_t on_received, CanSubscription** handle) final; 30 | bool unsubscribe(CanSubscription* handle) final; 31 | 32 | private: 33 | struct Subscription { 34 | MsgIdFilterSpecs filter; 35 | on_received_cb_t on_received; 36 | }; 37 | 38 | struct TxSlot { 39 | bool busy = false; 40 | uint8_t frame[72]; 41 | Timer* timer; 42 | SocketCan* parent; 43 | on_sent_cb_t on_sent; 44 | std::optional pending; 45 | 46 | void on_timeout() { parent->on_timeout(this); } 47 | }; 48 | 49 | void send_message_now(uint32_t tx_slot, const can_Message_t& message); 50 | void on_sent(TxSlot* slot, bool success); 51 | void update_filters(); 52 | bool read_sync(); 53 | void on_event(uint32_t mask); 54 | void on_timeout(TxSlot* tx_slot); 55 | 56 | EventLoop* event_loop_ = nullptr; 57 | Logger logger_ = Logger::none(); 58 | int socket_id_ = -1; 59 | Callback on_error_; 60 | std::vector subscriptions_; 61 | 62 | // The number of TX slots is chosen somewhat arbitrarily. We want to have 63 | // enough slots to keep the FIFOs from running dry (e.g. on the path down to 64 | // a USB-CAN dongle) but not too many to keep the buffers from throwing an 65 | // overflow error. 66 | std::array tx_slots_; 67 | }; 68 | 69 | class SocketCanBackend : public Backend { 70 | public: 71 | RichStatus init(EventLoop* event_loop, Logger logger) final; 72 | RichStatus deinit() final; 73 | 74 | void start_channel_discovery(Domain* domain, const char* specs, size_t specs_len, ChannelDiscoveryContext** handle) final; 75 | RichStatus stop_channel_discovery(ChannelDiscoveryContext* handle) final; 76 | 77 | private: 78 | RichStatus wait_for_intf(std::string intf_name_pattern); 79 | void consider_intf(const char* name); 80 | void on_intf_error(SocketCan* intf); 81 | bool on_netlink_msg(); 82 | void on_event(uint32_t mask); 83 | 84 | EventLoop* event_loop_ = nullptr; 85 | Logger logger_ = Logger::none(); 86 | Domain* domain_; 87 | std::string intf_name_pattern_; 88 | int netlink_id_ = -1; 89 | std::unordered_map> known_interfaces_; 90 | }; 91 | 92 | } 93 | 94 | #endif 95 | 96 | #endif // __FIBRE_SOCKET_CAN_HPP 97 | -------------------------------------------------------------------------------- /cpp/platform_support/usb_host_adapter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_USB_ADAPTER_HPP 2 | #define __FIBRE_USB_ADAPTER_HPP 3 | 4 | #include "../interfaces/usb.hpp" 5 | #include 6 | #include 7 | 8 | namespace fibre { 9 | 10 | class Domain; 11 | struct OpenDevice; 12 | 13 | struct UsbHostAdapter { 14 | UsbHostAdapter(Logger logger, UsbHostController* usb) : logger_(logger), usb_(usb) {} 15 | 16 | void start(Domain* domain, const char* specs, size_t specs_len); 17 | void stop(); 18 | RichStatus show_device_dialog(); 19 | 20 | private: 21 | struct InterfaceSpecs { 22 | int bus = -1; // -1 to ignore 23 | int address = -1; // -1 to ignore 24 | int vendor_id = -1; // -1 to ignore 25 | int product_id = -1; // -1 to ignore 26 | int interface_class = -1; // -1 to ignore 27 | int interface_subclass = -1; // -1 to ignore 28 | int interface_protocol = -1; // -1 to ignore 29 | }; 30 | 31 | RichStatus consider(UsbDevice* device, InterfaceSpecs* specs); 32 | void on_found_device(UsbDevice* device); 33 | void on_lost_device(UsbDevice* device); 34 | void on_opened_device(RichStatus status, UsbDevice* device); 35 | void on_claimed_interface(RichStatus status, UsbDevice* device); 36 | 37 | Logger logger_; 38 | Domain* domain_; 39 | UsbHostController* usb_; 40 | InterfaceSpecs specs_; 41 | std::unordered_map open_devices_; 42 | }; 43 | 44 | } 45 | 46 | #endif // __FIBRE_USB_ADAPTER_HPP 47 | -------------------------------------------------------------------------------- /cpp/platform_support/webusb_backend.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_USB_DISCOVERER_HPP 2 | #define __FIBRE_USB_DISCOVERER_HPP 3 | 4 | #include 5 | 6 | #if FIBRE_ENABLE_WEBUSB_BACKEND 7 | 8 | #include "dom_connector.hpp" 9 | //#include "usb_host_adapter.hpp" 10 | #include 11 | #include 12 | 13 | namespace fibre { 14 | 15 | struct UsbHostAdapter; 16 | class WebUsb; 17 | 18 | class WebusbBackend : public Backend { 19 | public: 20 | RichStatus init(EventLoop* event_loop, Logger logger) final; 21 | RichStatus deinit() final; 22 | 23 | void start_channel_discovery(Domain* domain, const char* specs, size_t specs_len, ChannelDiscoveryContext** handle) final; 24 | RichStatus stop_channel_discovery(ChannelDiscoveryContext* handle) final; 25 | RichStatus show_device_dialog() final; 26 | 27 | private: 28 | Logger logger_ = Logger::none(); 29 | WebUsb* webusb_ = nullptr; 30 | UsbHostAdapter* adapter_ = nullptr; 31 | }; 32 | 33 | } 34 | 35 | #endif 36 | 37 | #endif // __FIBRE_USB_DISCOVERER_HPP -------------------------------------------------------------------------------- /cpp/print_utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_PRINT_UTILS_HPP 2 | #define __FIBRE_PRINT_UTILS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace fibre { 11 | 12 | template 13 | constexpr size_t hex_digits() { 14 | return (std::numeric_limits::digits + 3) / 4; 15 | } 16 | 17 | /* @brief Converts a hexadecimal digit to a uint8_t. 18 | * @param output If not null, the digit's value is stored in this output 19 | * Returns true if the char is a valid hex digit, false otherwise 20 | */ 21 | static inline bool hex_digit_to_byte(char ch, uint8_t* output) { 22 | uint8_t nil_output = 0; 23 | if (!output) 24 | output = &nil_output; 25 | if (ch >= '0' && ch <= '9') 26 | return (*output) = ch - '0', true; 27 | if (ch >= 'a' && ch <= 'f') 28 | return (*output) = ch - 'a' + 10, true; 29 | if (ch >= 'A' && ch <= 'F') 30 | return (*output) = ch - 'A' + 10, true; 31 | return false; 32 | } 33 | 34 | /* @brief Converts a hex string to an integer 35 | * @param output If not null, the result is stored in this output 36 | * Returns true if the string represents a valid hex value, false otherwise. 37 | */ 38 | template 39 | bool hex_string_to_int(const char * str, size_t length, TInt* output) { 40 | constexpr size_t N_DIGITS = hex_digits(); 41 | TInt result = 0; 42 | if (length > N_DIGITS) 43 | length = N_DIGITS; 44 | for (size_t i = 0; i < length && str[i]; i++) { 45 | uint8_t digit = 0; 46 | if (!hex_digit_to_byte(str[i], &digit)) 47 | return false; 48 | result <<= 4; 49 | result += digit; 50 | } 51 | if (output) 52 | *output = result; 53 | return true; 54 | } 55 | 56 | template 57 | bool hex_string_to_int(const char * str, TInt* output) { 58 | return hex_string_to_int(str, hex_digits(), output); 59 | } 60 | 61 | template 62 | bool hex_string_to_int_arr(const char * str, size_t length, TInt (&output)[ICount]) { 63 | for (size_t i = 0; i < ICount; i++) { 64 | if (!hex_string_to_int(&str[i * hex_digits()], &output[i])) 65 | return false; 66 | } 67 | return true; 68 | } 69 | 70 | template 71 | bool hex_string_to_int_arr(const char * str, TInt (&output)[ICount]) { 72 | return hex_string_to_int_arr(str, hex_digits() * ICount, output); 73 | } 74 | 75 | // TODO: move to print_utils.hpp 76 | template 77 | class HexPrinter { 78 | public: 79 | HexPrinter(T val, bool prefix) : val_(val) /*, prefix_(prefix)*/ { 80 | const char digits[] = "0123456789abcdef"; 81 | size_t prefix_length = prefix ? 2 : 0; 82 | if (prefix) { 83 | str[0] = '0'; 84 | str[1] = 'x'; 85 | } 86 | str[prefix_length + hex_digits()] = '\0'; 87 | 88 | for (size_t i = 0; i < hex_digits(); ++i) { 89 | str[prefix_length + hex_digits() - i - 1] = digits[val & 0xf]; 90 | val >>= 4; 91 | } 92 | } 93 | std::string to_string() const { return str; } 94 | void to_string(char* buf) const { 95 | for (size_t i = 0; (i < sizeof(str)) && str[i]; ++i) 96 | buf[i] = str[i]; 97 | } 98 | 99 | T val_; 100 | //bool prefix_; 101 | char str[hex_digits() + 3]; // 3 additional characters 0x and \0 102 | }; 103 | 104 | template 105 | std::ostream& operator<<(std::ostream& stream, const HexPrinter& printer) { 106 | // TODO: specialize for char 107 | return stream << printer.to_string(); 108 | } 109 | 110 | template 111 | HexPrinter as_hex(T val, bool prefix = true) { return HexPrinter(val, prefix); } 112 | 113 | template 114 | class HexArrayPrinter { 115 | public: 116 | HexArrayPrinter(const T* ptr, size_t length) : ptr_(ptr), length_(length) {} 117 | const T* ptr_; 118 | size_t length_; 119 | }; 120 | 121 | template 122 | TStream& operator<<(TStream& stream, const HexArrayPrinter& printer) { 123 | for (size_t pos = 0; pos < printer.length_; ++pos) { 124 | stream << " " << as_hex(printer.ptr_[pos]); 125 | if (((pos + 1) % 16) == 0) 126 | stream << "\n"; 127 | } 128 | return stream; 129 | } 130 | 131 | template 132 | HexArrayPrinter as_hex(T (&val)[ILength]) { return HexArrayPrinter(val, ILength); } 133 | 134 | template 135 | HexArrayPrinter as_hex(generic_bufptr_t buffer) { return HexArrayPrinter(buffer.begin(), buffer.size()); } 136 | 137 | template 138 | HexArrayPrinter as_hex(const std::array& arr) { return HexArrayPrinter(arr.data(), Size); } 139 | 140 | 141 | static inline std::ostream& operator <<(std::ostream& stream, const Chunk& chunk) { 142 | stream << "L" << (int)chunk.layer() << ": "; 143 | if (chunk.is_frame_boundary()) { 144 | stream << "frame boundary"; 145 | } else { 146 | stream << chunk.buf().size() << " bytes"; 147 | } 148 | return stream; 149 | } 150 | 151 | static inline std::ostream& operator <<(std::ostream& stream, const BufChain& c) { 152 | BufChain chain = c; 153 | stream << chain.n_chunks() << " chunks" << (chain.n_chunks() ? ":" : ""); 154 | while (chain.n_chunks()) { 155 | Chunk chunk = chain.front(); 156 | stream << "\n\t\t" << chunk; 157 | chain = chain.skip_chunks(1); 158 | } 159 | return stream; 160 | } 161 | 162 | } 163 | 164 | #endif // __FIBRE_PRINT_UTILS_HPP -------------------------------------------------------------------------------- /cpp/property.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_PROPERTY_HPP 2 | #define __FIBRE_PROPERTY_HPP 3 | 4 | namespace fibre { 5 | 6 | template 7 | struct Property { 8 | Property(void* ctx, T(*getter)(void*), void(*setter)(void*, T)) 9 | : ctx_(ctx), getter_(getter), setter_(setter) {} 10 | Property(T* ctx) 11 | : ctx_(ctx), getter_([](void* ctx){ return *(T*)ctx; }), setter_([](void* ctx, T val){ *(T*)ctx = val; }) {} 12 | Property& operator*() { return *this; } 13 | Property* operator->() { return this; } 14 | 15 | T read() const { 16 | return (*getter_)(ctx_); 17 | } 18 | 19 | T exchange(T value) const { 20 | T old_value = (*getter_)(ctx_); 21 | (*setter_)(ctx_, value); 22 | return old_value; 23 | } 24 | 25 | void* ctx_; 26 | T(*getter_)(void*); 27 | void(*setter_)(void*, T); 28 | }; 29 | 30 | template 31 | struct Property { 32 | Property(void* ctx, T(*getter)(void*)) 33 | : ctx_(ctx), getter_(getter) {} 34 | Property(const T* ctx) 35 | : ctx_(const_cast(ctx)), getter_([](void* ctx){ return *(const T*)ctx; }) {} 36 | Property& operator*() { return *this; } 37 | Property* operator->() { return this; } 38 | 39 | T read() const { 40 | return (*getter_)(ctx_); 41 | } 42 | 43 | void* ctx_; 44 | T(*getter_)(void*); 45 | }; 46 | 47 | } 48 | 49 | #endif // __FIBRE_PROPERTY_HPP 50 | -------------------------------------------------------------------------------- /cpp/protocol.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | see protocol.md for the protocol specification 3 | */ 4 | 5 | #ifndef __PROTOCOL_HPP 6 | #define __PROTOCOL_HPP 7 | 8 | #include 9 | #include 10 | #include 11 | //#include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | 21 | typedef struct { 22 | uint16_t json_crc; 23 | uint16_t endpoint_id; 24 | } endpoint_ref_t; 25 | 26 | 27 | 28 | /* ToString / FromString functions -------------------------------------------*/ 29 | /* 30 | * These functions are currently not used by Fibre and only here to 31 | * support the ODrive ASCII protocol. 32 | * TODO: find a general way for client code to augment endpoints with custom 33 | * functions 34 | */ 35 | 36 | template 37 | struct format_traits_t; 38 | 39 | // template<> struct format_traits_t { using type = void; 40 | // static constexpr const char * fmt = "%f"; 41 | // static constexpr const char * fmtp = "%f"; 42 | // }; 43 | template<> struct format_traits_t { using type = void; 44 | static constexpr const char * fmt = "%lld"; 45 | static constexpr const char * fmtp = "%lld"; 46 | }; 47 | template<> struct format_traits_t { using type = void; 48 | static constexpr const char * fmt = "%llu"; 49 | static constexpr const char * fmtp = "%llu"; 50 | }; 51 | template<> struct format_traits_t { using type = void; 52 | static constexpr const char * fmt = "%ld"; 53 | static constexpr const char * fmtp = "%ld"; 54 | }; 55 | template<> struct format_traits_t { using type = void; 56 | static constexpr const char * fmt = "%lu"; 57 | static constexpr const char * fmtp = "%lu"; 58 | }; 59 | template<> struct format_traits_t { using type = void; 60 | static constexpr const char * fmt = "%d"; 61 | static constexpr const char * fmtp = "%d"; 62 | }; 63 | template<> struct format_traits_t { using type = void; 64 | static constexpr const char * fmt = "%ud"; 65 | static constexpr const char * fmtp = "%ud"; 66 | }; 67 | template<> struct format_traits_t { using type = void; 68 | static constexpr const char * fmt = "%hd"; 69 | static constexpr const char * fmtp = "%hd"; 70 | }; 71 | template<> struct format_traits_t { using type = void; 72 | static constexpr const char * fmt = "%hu"; 73 | static constexpr const char * fmtp = "%hu"; 74 | }; 75 | template<> struct format_traits_t { using type = void; 76 | static constexpr const char * fmt = "%hhd"; 77 | static constexpr const char * fmtp = "%d"; 78 | }; 79 | template<> struct format_traits_t { using type = void; 80 | static constexpr const char * fmt = "%hhu"; 81 | static constexpr const char * fmtp = "%u"; 82 | }; 83 | 84 | template::type> 85 | static bool to_string(const T& value, char * buffer, size_t length, int) { 86 | snprintf(buffer, length, format_traits_t::fmtp, value); 87 | return true; 88 | } 89 | // Special case for float because printf promotes float to double, and we get warnings 90 | template 91 | static bool to_string(const float& value, char * buffer, size_t length, int) { 92 | snprintf(buffer, length, "%f", (double)value); 93 | return true; 94 | } 95 | template 96 | static bool to_string(const bool& value, char * buffer, size_t length, int) { 97 | buffer[0] = value ? '1' : '0'; 98 | buffer[1] = 0; 99 | return true; 100 | } 101 | template 102 | static bool to_string(const T& value, char * buffer, size_t length, ...) { 103 | return false; 104 | } 105 | 106 | template::type> 107 | static bool from_string(const char * buffer, size_t length, T* property, int) { 108 | // Note for T == uint8_t: Even though we supposedly use the correct format 109 | // string sscanf treats our pointer as pointer-to-int instead of 110 | // pointer-to-uint8_t. To avoid an unexpected memory access we first read 111 | // into a union. 112 | union { T t; int i; } val; 113 | if (sscanf(buffer, format_traits_t::fmt, &val.t) == 1) { 114 | *property = val.t; 115 | return true; 116 | } else { 117 | return false; 118 | } 119 | } 120 | // Special case for float because printf promotes float to double, and we get warnings 121 | template 122 | static bool from_string(const char * buffer, size_t length, float* property, int) { 123 | return sscanf(buffer, "%f", property) == 1; 124 | } 125 | template 126 | static bool from_string(const char * buffer, size_t length, bool* property, int) { 127 | int val; 128 | if (sscanf(buffer, "%d", &val) != 1) 129 | return false; 130 | *property = val; 131 | return true; 132 | } 133 | template 134 | static bool from_string(const char * buffer, size_t length, T* property, ...) { 135 | return false; 136 | } 137 | 138 | 139 | //template 140 | //bool set_from_float_ex(float value, T* property) { 141 | // return false; 142 | //} 143 | 144 | namespace conversion { 145 | //template 146 | template 147 | bool set_from_float_ex(float value, float* property, int) { 148 | return *property = value, true; 149 | } 150 | template 151 | bool set_from_float_ex(float value, bool* property, int) { 152 | return *property = (value >= 0.0f), true; 153 | } 154 | template::value && !std::is_const::value>> 155 | bool set_from_float_ex(float value, T* property, int) { 156 | return *property = static_cast(std::round(value)), true; 157 | } 158 | template 159 | bool set_from_float_ex(float value, T* property, ...) { 160 | return false; 161 | } 162 | template 163 | bool set_from_float(float value, T* property) { 164 | return set_from_float_ex(value, property, 0); 165 | } 166 | } 167 | 168 | 169 | 170 | #endif 171 | -------------------------------------------------------------------------------- /cpp/static_exports.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __STATIC_EXPORTS_HPP 2 | #define __STATIC_EXPORTS_HPP 3 | 4 | #include 5 | #include 6 | 7 | #if FIBRE_ENABLE_SERVER 8 | namespace fibre { 9 | 10 | // Forward declarations for autogenerated code in static_exports.cpp 11 | template ServerInterfaceId get_interface_id(); 12 | 13 | extern const Function* static_server_function_table[]; 14 | extern size_t n_static_server_functions; 15 | //extern ServerInterfaceDefinition static_server_interface_table[]; 16 | 17 | extern ServerObjectDefinition static_server_object_table[]; 18 | extern size_t n_static_server_objects; 19 | 20 | } 21 | #endif 22 | 23 | #endif // __STATIC_EXPORTS_HPP 24 | -------------------------------------------------------------------------------- /cpp/static_exports_template.j2: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include "interfaces.hpp" 5 | 6 | using namespace fibre; 7 | 8 | [%- macro intf_name(intf) %] 9 | [%- if intf.builtin %]fibre::Property<[['const ' if intf.mode == 'readonly']][[type_name(intf.value_type)]]> 10 | [%- else %] 11 | [%- for elem in intf.name %][[elem | to_pascal_case]][['_' if not loop.last]][% endfor %]Intf 12 | [%- endif %] 13 | [%- endmacro %] 14 | 15 | [%- macro type_name(type) %] 16 | [%- if type.c_name %][[type.c_name]][% else %]unknown name for [[type]][% endif %] 17 | [%- endmacro %] 18 | 19 | const Function* fibre::static_server_function_table[] = { 20 | [%- for func in exported_functions %] 21 | &SyncMemberWrapper::instance, 22 | [%- endfor %] 23 | }; 24 | [% for intf in exported_interfaces %] 25 | template<> ServerInterfaceId fibre::get_interface_id<[[intf_name(intf)]]>() { return [[intf.id]]; }; 26 | [%- endfor %] 27 | 28 | // Must be defined by the application 29 | [%- for obj in published_objects %] 30 | extern [[intf_name(obj.type)]]* [[obj.name[0]]]_ptr; 31 | [%- endfor %] 32 | 33 | ServerObjectDefinition fibre::static_server_object_table[] = { 34 | [%- for name, obj in exported_objects.items() %] 35 | make_obj([[obj.name[0]]]_ptr[% for elem in obj.name[1:] %]->get_[[elem]]()[% endfor %]), 36 | [%- endfor %] 37 | }; 38 | 39 | size_t fibre::n_static_server_functions = [[exported_functions | length]]; 40 | size_t fibre::n_static_server_objects = [[exported_objects | length]]; 41 | -------------------------------------------------------------------------------- /cpp/type_info_template.j2: -------------------------------------------------------------------------------- 1 | /*[# This is the original template, thus the warning below does not apply to this file #] 2 | * ============================ WARNING ============================ 3 | * ==== This is an autogenerated file. ==== 4 | * ==== Any changes to this file will be lost when recompiling. ==== 5 | * ================================================================= 6 | * 7 | * This file contains support functions for the ODrive ASCII protocol. 8 | * 9 | * TODO: might generalize this as an approach to runtime introspection. 10 | */ 11 | 12 | #include 13 | 14 | #pragma GCC push_options 15 | #pragma GCC optimize ("s") 16 | 17 | [% for intf in interfaces.values() %][% if not intf.builtin %] 18 | template 19 | struct [[intf.fullname | to_pascal_case]]TypeInfo : TypeInfo { 20 | using TypeInfo::TypeInfo; 21 | static const PropertyInfo property_table[]; 22 | static const [[intf.fullname | to_pascal_case]]TypeInfo singleton; 23 | static Introspectable make_introspectable(T& obj) { return TypeInfo::make_introspectable(&obj, &singleton); } 24 | 25 | introspectable_storage_t get_child(introspectable_storage_t obj, size_t idx) const override { 26 | [%- if intf.get_all_attributes().values() | length %] 27 | T* ptr = *(T**)&obj; 28 | [%- endif %] 29 | introspectable_storage_t res; 30 | switch (idx) { 31 | [%- for property in intf.get_all_attributes().values() %] 32 | case [[loop.index0]]: *(decltype([[intf.c_name]]::get_[[property.name]](std::declval()))*)(&res) = [[intf.c_name]]::get_[[property.name]](ptr); break; 33 | [%- endfor %] 34 | } 35 | return res; 36 | } 37 | }; 38 | [% endif %][% endfor %] 39 | 40 | [% for intf in interfaces.values() %][% if not intf.builtin %] 41 | template 42 | const PropertyInfo [[intf.fullname | to_pascal_case]]TypeInfo::property_table[] = { 43 | [%- for property in intf.get_all_attributes().values() %] 44 | {"[[property.name]]", &[[(property.type.purename or property.type.fullname) | to_pascal_case]]TypeInfo()))>>::singleton}, 45 | [%- endfor %] 46 | }; 47 | template 48 | const [[intf.fullname | to_pascal_case]]TypeInfo [[intf.fullname | to_pascal_case]]TypeInfo::singleton{[[intf.fullname | to_pascal_case]]TypeInfo::property_table, sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table) / sizeof([[intf.fullname | to_pascal_case]]TypeInfo::property_table[0])}; 49 | 50 | [% endif %][% endfor %] 51 | 52 | #pragma GCC pop_options 53 | -------------------------------------------------------------------------------- /js/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | libfibre-wasm.js 3 | libfibre-wasm.wasm 4 | -------------------------------------------------------------------------------- /js/README.md: -------------------------------------------------------------------------------- 1 | # fibre-js 2 | 3 | This directory provides JavaScript bindings for [Fibre](https://github.com/samuelsadok/fibre). Its home is located [here](https://github.com/samuelsadok/fibre/tree/master/js). There's also a standalone repository for this directory [here](https://github.com/samuelsadok/fibre-js). 4 | 5 | ## Current Status 6 | 7 | So far fibre-js was only tested to run in Chrome but it should work in any browser that [supports WebAssembly](https://caniuse.com/wasm). 8 | 9 | fibre-js currently supports the following transport providers: 10 | 11 | - WebUSB ([Chrome and Edge only](https://caniuse.com/webusb)) 12 | 13 | NodeJS environments (incl Electron) are currently not supported due to issues with the WebUSB polyfill. 14 | 15 | ## How to use 16 | 17 | So you want your website to connect to some resource(s) using Fibre. Let's start with a simple HTML scaffolding. 18 | 19 | ```HTML 20 | 21 | 22 | 23 | 24 | fibre-js Test 25 | 26 | 27 |

not connected

28 | 29 | 30 | 31 | ``` 32 | 33 | Now, right before the closing ``, insert a script tag to load and open fibre-js: 34 | 35 | ```HTML 36 | 42 | ``` 43 | 44 | This will load the files `fibre.js`, `libfibre-wasm.js` and `libfibre-wasm.wasm` from the same directory the HTML file comes from so make sure those files exist. 45 | 46 | Now that fibre-js is ready to use we can start discovering remote objects. For this we need to specify a filter string which tells Fibre where to look for objects. In this example we want to talk to a USB device that runs Fibre (more specfically an [ODrive](https://odriverobotics.com/)). The parameters we use here are something you can find out from reading the docs of your USB device or inspecting the device by using `lsusb` on a Linux or macOS computer. 47 | 48 | ```JS 49 | const filter = 'usb:idVendor=0x1209,idProduct=0x0D32,bInterfaceClass=0,bInterfaceSubClass=1,bInterfaceProtocol=0'; 50 | const domain = libfibre.openDomain(filter); 51 | const onFoundObject = async (obj) => { 52 | console.log("found an object!", obj); 53 | // TODO: do something with obj 54 | } 55 | domain.startDiscovery(onFoundObject); 56 | ``` 57 | 58 | Now that we have the object we can start calling functions on it. Let's assume our object has the property `vbus_voltage`. In the `onFoundObject` callback, add: 59 | 60 | ```JS 61 | while (true) { 62 | document.getElementById("statusText").innerHTML = "vbus_voltage: " + (await obj.vbus_voltage.read()); 63 | await new Promise((resolve) => setTimeout(resolve, 100)); 64 | } 65 | ``` 66 | 67 | This will read the `vbus_voltage` property every 100ms and display the value on the web page. 68 | 69 | Optionally you could add a callback to the `obj._onLost` promise in order to gracefully stop polling when the device disconnects. 70 | 71 | Since we're using WebUSB in this example, there's one more thing left to do: The user needs to authorize the website (only once) to access the USB device. This must happen on a user gesture, which is why earlier we put a Connect-button into our HTML. 72 | 73 | So directly after the call to `libfibre.startDiscovery()`, add: 74 | 75 | ```JS 76 | document.getElementById("connectBtn").onclick = libfibre.usbDiscoverer.showDialog; 77 | document.getElementById("connectBtn").removeAttribute('disabled'); 78 | ``` 79 | 80 | That's it! You can now open the HTML page in the browser and should see something like this (using Chrome 86 on Linux): 81 | 82 | ![Example](example.gif) 83 | 84 | Note how the user doesn't need to manually reselect the device after the website is reloaded. The website is now already authorized to access the device so Fibre immediately connects to it. The same is true when the device is unpluggend and replugged to the computer. 85 | 86 | You can find the complete example code [here](example.html). A more elaborate example which can connect to multiple objects can be found here [here](multidevice_example.html). 87 | 88 | ## Sidenote on WebUSB examples 89 | 90 | WebUSB cannot be used on sites served via HTTP. To run the examples you must serve them from a HTTPS server. To do this, navigate to this directory and run: 91 | 92 | ``` 93 | openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 10000 -nodes 94 | python3 server.py 95 | ``` 96 | 97 | where `server.py`: 98 | ```python 99 | from http.server import HTTPServer, SimpleHTTPRequestHandler 100 | import ssl 101 | httpd = HTTPServer(('localhost', 8000), SimpleHTTPRequestHandler) 102 | httpd.socket = ssl.wrap_socket (httpd.socket, 103 | keyfile="key.pem", 104 | certfile='cert.pem', server_side=True) 105 | httpd.serve_forever() 106 | ``` 107 | -------------------------------------------------------------------------------- /js/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuelsadok/fibre/c803b878458567dc693117b7540df9f030f90525/js/example.gif -------------------------------------------------------------------------------- /js/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fibre-js Test 6 | 7 | 8 |

not connected

9 | 10 | 11 | 42 | 43 | -------------------------------------------------------------------------------- /js/multidevice_example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | fibre-js Test 6 | 11 | 12 | 13 |
loading...
14 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fibre-js", 3 | "version": "0.1.0", 4 | "eslintConfig": { 5 | "rules": { 6 | "no-empty": "off", 7 | "no-unused-vars": "off", 8 | "no-undef": "off", 9 | "no-constant-condition": "off", 10 | "no-extra-semi": "off", 11 | "no-async-promise-executor": "off", 12 | "no-redeclare": "off", 13 | "no-prototype-builtins": "off", 14 | "no-global-assign": "off", 15 | "no-useless-escape": "off", 16 | "no-useless-catch": "off" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Python Distribution / packaging 3 | .Python 4 | /dist/ 5 | /*.egg-info/ 6 | /MANIFEST 7 | 8 | # PyInstaller 9 | # Usually these files are written by a python script from a template 10 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 11 | *.manifest 12 | *.spec 13 | 14 | # Installer logs 15 | pip-log.txt 16 | pip-delete-this-directory.txt 17 | 18 | libfibre-*.so 19 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # PyFibre 2 | 3 | This directory provides Python bindings for [Fibre](https://github.com/samuelsadok/fibre). Its home is located [here](https://github.com/samuelsadok/fibre/tree/master/python). There's also a standalone repository for this directory [here](https://github.com/samuelsadok/pyfibre). 4 | 5 | ## Current Status 6 | 7 | Currently only client-side features are implemented, that means you can discover objects but you cannot publish objects. 8 | 9 | ## How to use 10 | 11 | ```python 12 | import fibre 13 | 14 | with fibre.Domain("tcp-client:address=localhost,port=14220") as domain: 15 | obj = domain.discover_one() 16 | obj.test_function() 17 | ``` 18 | -------------------------------------------------------------------------------- /python/fibre/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .utils import Event, Logger, TimeoutError 3 | from .shell import launch_shell 4 | from .libfibre import Domain, ObjectLostError 5 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script is used to deploy the Fibre python library to PyPi 3 | so that users can install them easily with 4 | "pip install fibre" 5 | 6 | To install the package and its dependencies locally, run: 7 | sudo pip install -r requirements.txt 8 | 9 | To build and package the python tools into a tar archive: 10 | python setup.py sdist 11 | 12 | Warning: Before you proceed, be aware that you can upload a 13 | specific version only once ever. After that you need to increment 14 | the hotfix number. Deleting the release manually on the PyPi 15 | website does not help. 16 | 17 | Use TestPyPi while developing. 18 | 19 | To build, package and upload the python tools to TestPyPi, run: 20 | python setup.py sdist upload -r pypitest 21 | To make a real release ensure you're at the release commit 22 | and then run the above command without the "test" (so just "pypi"). 23 | 24 | To install a prerelease version from test index: 25 | sudo pip install --pre --index-url https://test.pypi.org/simple/ --no-cache-dir fibre 26 | 27 | 28 | PyPi access requires that you have set up ~/.pypirc with your 29 | PyPi credentials and that your account has the rights 30 | to publish packages with the name fibre. 31 | """ 32 | 33 | # TODO: add additional y/n prompt to prevent from erroneous upload 34 | 35 | from setuptools import setup 36 | import os 37 | import sys 38 | 39 | # Change this if you already uploaded the current 40 | # version but need to release a hotfix 41 | hotfix = 0 42 | 43 | #creating_package = "sdist" in sys.argv 44 | # 45 | ## Load version from Git tag 46 | #import odrive.version 47 | #version = odrive.version.get_version_str(git_only=creating_package) 48 | # 49 | #if creating_package and (hotfix > 0 or not version[-1].isdigit()): 50 | # # Add this for hotfixes 51 | # version += "-" + str(hotfix) 52 | # 53 | # 54 | ## If we're currently creating the package we need to autogenerate 55 | ## a file that contains the version string 56 | #if creating_package: 57 | # version_file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'odrive', 'version.txt') 58 | # with open(version_file_path, mode='w') as version_file: 59 | # version_file.write(version) 60 | # 61 | ## TODO: find a better place for this 62 | #if not creating_package: 63 | # import platform 64 | # if platform.system() == 'Linux': 65 | # import odrive.utils 66 | # odrive.utils.setup_udev_rules(odrive.utils.Logger()) 67 | 68 | setup( 69 | name = 'fibre', 70 | packages = ['fibre'], 71 | #scripts = ['..fibre', 'odrivetool.bat', 'odrive_demo.py'], 72 | version = '0.0.1dev0', 73 | description = 'Abstraction layer for painlessly building object oriented distributed systems that just work', 74 | author = 'Samuel Sadok', 75 | author_email = 'samuel.sadok@bluewin.ch', 76 | license='MIT', 77 | url = 'https://github.com/samuelsadok/fibre', 78 | keywords = ['communication', 'transport-layer', 'rpc'], 79 | install_requires = [], 80 | #package_data={'': ['version.txt']}, 81 | classifiers = [], 82 | ) 83 | 84 | # TODO: include README 85 | 86 | ## clean up 87 | #if creating_package: 88 | # os.remove(version_file_path) 89 | -------------------------------------------------------------------------------- /sim/Tupfile.lua: -------------------------------------------------------------------------------- 1 | 2 | fibre_cpp_dir = '../cpp' 3 | tup.include(fibre_cpp_dir..'/package.lua') 4 | 5 | CXX='clang++' 6 | LINKER='clang++' 7 | CFLAGS={'-g', '-I.'} 8 | LDFLAGS={} 9 | object_files = {} 10 | 11 | 12 | fibre_pkg = get_fibre_package({ 13 | enable_server=true, 14 | enable_client=true, 15 | enable_event_loop=false, 16 | allow_heap=true, 17 | enable_libusb_backend=false, 18 | enable_tcp_client_backend=false, 19 | enable_tcp_server_backend=false, 20 | }) 21 | 22 | CFLAGS += fibre_pkg.cflags 23 | LDFLAGS += fibre_pkg.ldflags 24 | 25 | for _, inc in pairs(fibre_pkg.include_dirs) do 26 | CFLAGS += '-I'..fibre_cpp_dir..'/'..inc 27 | end 28 | 29 | for _, src in pairs(fibre_pkg.code_files) do 30 | object_files += compile(fibre_cpp_dir..'/'..src) 31 | end 32 | 33 | 34 | autogen_pkg = fibre_autogen('../test/test-interface.yaml') 35 | 36 | object_files += compile('sim_main.cpp') 37 | object_files += compile('simulator.cpp') 38 | object_files += compile('mock_can.cpp') 39 | object_files += compile('../test/test_node.cpp', autogen_pkg.autogen_headers) 40 | 41 | -- TODO: move up 42 | for _, src in pairs(autogen_pkg.code_files) do 43 | object_files += compile(src, autogen_pkg.autogen_headers) 44 | end 45 | 46 | 47 | compile_outname='build/fibre_sim' 48 | 49 | tup.frule{ 50 | inputs=object_files, 51 | command='^c^ '..LINKER..' %f '..tostring(CFLAGS)..' '..tostring(LDFLAGS)..' -o %o', 52 | outputs={compile_outname} 53 | } 54 | -------------------------------------------------------------------------------- /sim/fibre_config.hpp: -------------------------------------------------------------------------------- 1 | 2 | #define FIBRE_ENABLE_SERVER F_RUNTIME_CONFIG 3 | #define FIBRE_ENABLE_CLIENT F_RUNTIME_CONFIG 4 | #define FIBRE_ENABLE_EVENT_LOOP 0 5 | #define FIBRE_ENABLE_TCP_SERVER_BACKEND 0 6 | #define FIBRE_ENABLE_TCP_CLIENT_BACKEND 0 7 | #define FIBRE_ENABLE_CAN_ADAPTER 1 8 | -------------------------------------------------------------------------------- /sim/mock_can.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_SIM_MOCK_CAN_HPP 2 | #define __FIBRE_SIM_MOCK_CAN_HPP 3 | 4 | #include "simulator.hpp" 5 | #include 6 | #include 7 | #include 8 | 9 | namespace fibre { 10 | namespace simulator { 11 | 12 | struct CanBus; 13 | struct CanMedium; 14 | 15 | class SimCanInterface : public CanInterface { 16 | public: 17 | bool is_valid_baud_rate(uint32_t nominal_baud_rate, 18 | uint32_t data_baud_rate) final; 19 | bool start(uint32_t nominal_baud_rate, uint32_t data_baud_rate, 20 | on_event_cb_t rx_event_loop, on_error_cb_t on_error) final; 21 | bool stop() final; 22 | bool send_message(uint32_t tx_slot, const can_Message_t& message, 23 | on_sent_cb_t on_sent) final; 24 | bool cancel_message(uint32_t tx_slot) final; 25 | bool subscribe(uint32_t rx_slot, const MsgIdFilterSpecs& filter, 26 | on_received_cb_t on_received, 27 | CanSubscription** handle) final; 28 | bool unsubscribe(CanSubscription* handle) final; 29 | 30 | // private: 31 | struct TxSlot { 32 | can_Message_t msg; 33 | on_sent_cb_t on_sent; 34 | }; 35 | 36 | using TxIt = std::unordered_map::iterator; 37 | 38 | TxIt get_tx_msg(); 39 | void on_start_tx(TxIt tx); 40 | void on_finished_tx(bool ackd); 41 | bool will_ack(const can_Message_t& msg); 42 | void on_finished_rx(const can_Message_t& msg); 43 | 44 | CanBus* bus_; 45 | simulator::Port* port_; 46 | 47 | uint32_t current_tx_slot_; 48 | std::unordered_map tx_slots_; 49 | 50 | struct Rx { 51 | MsgIdFilterSpecs filter; 52 | on_received_cb_t on_received; 53 | }; 54 | std::vector subscriptions_; 55 | 56 | /* 57 | Callback rx_completer_; 58 | Callback tx_completer_; 59 | cbufptr_t tx_buf_; 60 | 61 | can_Message_t rx_msg_; 62 | bufptr_t rx_buf_;*/ 63 | 64 | uint32_t nominal_baud_rate_ = 1000000; 65 | uint32_t data_baud_rate_ = 1000000; 66 | }; 67 | 68 | struct CanMedium { 69 | CanMedium(Simulator* simulator) : simulator_{simulator} {} 70 | 71 | SimCanInterface* new_intf(Node* node, std::string port_name); 72 | void join(std::vector busses, std::string joined_bus); 73 | // void split(std::string bus, std::string part1, std::string part2); // 74 | // TODO 75 | 76 | void dispatch(); 77 | void on_tx_pending(); 78 | 79 | std::unordered_map busses_; 80 | Simulator* simulator_; 81 | Simulator::Event* dispatch_event_ = nullptr; 82 | }; 83 | 84 | struct CanBus : Node { 85 | CanBus(CanMedium* medium, std::string name) 86 | : Node{medium->simulator_, name, {}}, medium_{medium} {} 87 | 88 | void on_tx_pending(); 89 | void on_sent(); 90 | void send_next(); 91 | 92 | CanMedium* medium_; 93 | std::vector members_; 94 | bool busy = false; 95 | 96 | Simulator::Event* current_event_; 97 | can_Message_t current_msg_; 98 | SimCanInterface* current_transmitter_; 99 | std::vector current_receivers_; 100 | }; 101 | 102 | } // namespace simulator 103 | } // namespace fibre 104 | 105 | #endif // __FIBRE_SIM_MOCK_CAN_HPP 106 | -------------------------------------------------------------------------------- /sim/sim_main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "../cpp/mini_rng.hpp" 3 | #include "../cpp/platform_support/can_adapter.hpp" 4 | #include "../test/test_node.hpp" 5 | #include "mock_can.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace fibre { 13 | 14 | struct FibreNode { 15 | FibreNode(simulator::Simulator* simulator, std::string name) 16 | : simulator_{simulator}, sim_node_{simulator, name} {} 17 | 18 | void start(bool enable_server, bool enable_client); 19 | 20 | void add_can_intf(simulator::SimCanInterface* intf) { 21 | // intf->start(1000000, 1000000, {}, {}); 22 | CanAdapter* can_backend = new CanAdapter{ 23 | simulator_, impl_.domain_, intf, intf->port_->name.data()}; 24 | can_backend->start(0, 128); 25 | } 26 | 27 | simulator::Simulator* simulator_; 28 | simulator::Node sim_node_; 29 | TestNode impl_; 30 | }; 31 | 32 | } // namespace fibre 33 | 34 | using namespace fibre; 35 | using namespace fibre::simulator; 36 | 37 | struct Pipe {}; 38 | 39 | Pipe* start_call(std::string destination) { 40 | return new Pipe{}; 41 | } 42 | 43 | void FibreNode::start(bool enable_server, bool enable_client) { 44 | uint8_t node_id[16]; 45 | simulator_->rng.get_random(node_id); 46 | 47 | impl_.start(simulator_, node_id, "", enable_server, enable_client, 48 | sim_node_.logger()); 49 | 50 | /* 51 | // optimistic weak pipe 52 | auto p = fibre::start_call("the_destination_uuid"); 53 | p->write("endp0 operation"); 54 | 55 | // stong pipe 56 | auto p = fibre:: 57 | impl_.domain_-> 58 | */ 59 | } 60 | 61 | int main() { 62 | printf("Starting Fibre server...\n"); 63 | 64 | Simulator simulator; 65 | CanMedium can_medium{&simulator}; 66 | 67 | FibreNode server{&simulator, "server"}; 68 | FibreNode client{&simulator, "client"}; 69 | 70 | // TODO: try both init orders 71 | client.start(false, true); 72 | server.start(true, false); 73 | 74 | server.add_can_intf(can_medium.new_intf(&server.sim_node_, "can0")); 75 | client.add_can_intf(can_medium.new_intf(&client.sim_node_, "can0")); 76 | can_medium.join({"server.can0", "client.can0"}, "the_can_bus"); 77 | 78 | // TODO: remove hack 79 | // server.impl_.domain_->on_found_node(client.impl_.domain_->node_id); 80 | // client.impl_.domain_->on_found_node(server.impl_.domain_->node_id); 81 | 82 | simulator.run(200, 0.35f); 83 | 84 | printf("Simulation terminated.\n"); 85 | } 86 | -------------------------------------------------------------------------------- /sim/simulator.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "simulator.hpp" 3 | #include 4 | #include 5 | 6 | using namespace fibre; 7 | using namespace fibre::simulator; 8 | 9 | class SimulatorTimer final : public Timer { 10 | RichStatus set(float interval, TimerMode mode) final; 11 | void on_trigger(); 12 | public: 13 | Simulator* sim_; 14 | Callback callback; 15 | Simulator::Event* evt_; 16 | bool periodic_; 17 | float interval_; 18 | }; 19 | 20 | RichStatus Simulator::post(Callback callback) { 21 | return F_MAKE_ERR("not implemented"); 22 | } 23 | RichStatus Simulator::register_event(int fd, uint32_t events, 24 | Callback callback) { 25 | return F_MAKE_ERR("not implemented"); 26 | } 27 | RichStatus Simulator::deregister_event(int fd) { 28 | return F_MAKE_ERR("not implemented"); 29 | } 30 | 31 | RichStatus Simulator::open_timer(Timer** p_timer, Callback on_trigger) { 32 | SimulatorTimer* t = new SimulatorTimer{}; // deleted in close_timer() 33 | t->sim_ = this; 34 | t->callback = on_trigger; 35 | if (p_timer) { 36 | *p_timer = t; 37 | } 38 | return RichStatus::success(); 39 | } 40 | 41 | RichStatus SimulatorTimer::set(float interval, TimerMode mode) { 42 | if (evt_) { 43 | sim_->cancel(evt_); 44 | } 45 | 46 | periodic_ = mode == TimerMode::kPeriodic; 47 | interval_ = interval; 48 | 49 | if (mode != TimerMode::kNever) { 50 | uint64_t delay_ns = interval_ * (float)1e9; 51 | evt_ = sim_->add_event({sim_->t_ns + delay_ns, MEMBER_CB(this, on_trigger), nullptr, {}}); 52 | } 53 | 54 | return RichStatus::success(); 55 | } 56 | 57 | void SimulatorTimer::on_trigger() { 58 | evt_ = nullptr; 59 | 60 | if (periodic_) { 61 | uint64_t delay_ns = interval_ * (float)1e9; 62 | evt_ = sim_->add_event({sim_->t_ns + delay_ns, MEMBER_CB(this, on_trigger), nullptr, {}}); 63 | } 64 | 65 | callback.invoke(); 66 | } 67 | 68 | RichStatus Simulator::close_timer(Timer* timer) { 69 | SimulatorTimer* t = static_cast(timer); 70 | cancel(t->evt_); 71 | delete t; 72 | return RichStatus::success(); 73 | } 74 | 75 | Simulator::Event* Simulator::send(Port* from, std::vector to, 76 | float duration, Callback on_delivery) { 77 | uint64_t duration_ns = duration * (float)1e9; 78 | return add_event(Event{t_ns + duration_ns, on_delivery, from, to}); 79 | } 80 | 81 | Simulator::Event* Simulator::add_event(Event new_evt) { 82 | auto it = std::find_if(backlog.begin(), backlog.end(), [&](Event* evt) { 83 | return (evt->t_ns - t_ns) > (new_evt.t_ns - t_ns); 84 | }); 85 | Event* evt = new Event{new_evt}; 86 | backlog.insert(it, evt); 87 | return evt; 88 | } 89 | 90 | void Simulator::cancel(Event* evt) { 91 | backlog.erase(std::find(backlog.begin(), backlog.end(), evt)); 92 | } 93 | 94 | void Simulator::run(size_t n_events, float dt) { 95 | uint64_t t_0 = t_ns; 96 | uint64_t dt_ns = (uint64_t)(dt * 1e9); 97 | 98 | for (;;) { 99 | if (!backlog.size()) { 100 | printf("No more events in queue.\n"); 101 | return; 102 | } else if (!(n_events--)) { 103 | printf("Event limit reached.\n"); 104 | return; 105 | } else if ((backlog.front()->t_ns - t_0) > dt_ns) { 106 | printf("Time limit reached.\n"); 107 | return; 108 | } 109 | 110 | Event* evt = backlog.front(); 111 | backlog.erase(backlog.begin()); 112 | t_ns = evt->t_ns; 113 | evt->trigger.invoke(); 114 | delete evt; 115 | } 116 | } 117 | 118 | void Node::log(const char* file, unsigned line, int level, uintptr_t info0, 119 | uintptr_t info1, const char* text) { 120 | switch ((LogLevel)level) { 121 | case LogLevel::kDebug: 122 | // std::cerr << "\x1b[93;1m"; // yellow 123 | break; 124 | case LogLevel::kError: 125 | std::cerr << "\x1b[91;1m"; // red 126 | break; 127 | default: 128 | break; 129 | } 130 | 131 | float sim_time = (float)simulator_->t_ns / 1e6; 132 | std::cerr << "t=" << sim_time << "ms " << name << " [" << file << ":" 133 | << line << "] " << text << "\x1b[0m" << std::endl; 134 | } 135 | -------------------------------------------------------------------------------- /sim/simulator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __FIBRE_SIMULATOR_HPP 2 | #define __FIBRE_SIMULATOR_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace fibre { 13 | namespace simulator { 14 | 15 | struct Port; 16 | struct Simulator; 17 | 18 | struct Node { 19 | Simulator* simulator_; 20 | std::string name; 21 | std::unordered_map ports; 22 | 23 | void log(const char* file, unsigned line, int level, uintptr_t info0, 24 | uintptr_t info1, const char* text); 25 | 26 | Logger logger() { 27 | return Logger{MEMBER_CB(this, log), get_log_verbosity()}; 28 | } 29 | }; 30 | 31 | struct Port { 32 | Node* node; 33 | std::string name; 34 | }; 35 | 36 | static inline std::ostream& operator<<(std::ostream& str, Port* port) { 37 | return str << port->node->name << "." << port->name; 38 | } 39 | 40 | class Simulator final : public EventLoop { 41 | public: 42 | Simulator() { 43 | rng.seed(0, 1, 2, 3); 44 | } 45 | 46 | struct Event { 47 | uint64_t t_ns; 48 | Callback trigger; 49 | Port* from; // optional 50 | std::vector to; // optional 51 | }; 52 | 53 | Event* send(Port* from, std::vector to, float duration, 54 | Callback on_delivery); 55 | Event* add_event(Event evt); 56 | void cancel(Event* evt); 57 | 58 | void run(size_t n_events, float dt); 59 | 60 | RichStatus post(Callback callback) final; 61 | RichStatus register_event(int fd, uint32_t events, 62 | Callback callback) final; 63 | RichStatus deregister_event(int fd) final; 64 | RichStatus open_timer(Timer** p_timer, Callback on_trigger) final; 65 | RichStatus close_timer(Timer* timer) final; 66 | 67 | uint64_t t_ns = 0; 68 | MiniRng rng; 69 | 70 | private: 71 | 72 | std::vector backlog; 73 | }; 74 | 75 | } // namespace simulator 76 | } // namespace fibre 77 | 78 | #endif // __FIBRE_SIMULATOR_HPP 79 | -------------------------------------------------------------------------------- /test/Tupfile.lua: -------------------------------------------------------------------------------- 1 | 2 | fibre_cpp_dir = '../cpp' 3 | tup.include(fibre_cpp_dir..'/package.lua') 4 | 5 | CXX='clang++' 6 | LINKER='clang++' 7 | CFLAGS={'-g', '-I.', '-DSTANDALONE_NODE'} 8 | LDFLAGS={} 9 | object_files = {} 10 | 11 | 12 | fibre_pkg = get_fibre_package({ 13 | enable_server=true, 14 | enable_client=true, 15 | enable_event_loop=true, 16 | allow_heap=true, 17 | enable_libusb_backend=false, 18 | enable_tcp_client_backend=true, 19 | enable_tcp_server_backend=true, 20 | enable_socket_can_backend=true, 21 | }) 22 | 23 | CFLAGS += fibre_pkg.cflags 24 | LDFLAGS += fibre_pkg.ldflags 25 | 26 | for _, inc in pairs(fibre_pkg.include_dirs) do 27 | CFLAGS += '-I'..fibre_cpp_dir..'/'..inc 28 | end 29 | 30 | for _, src in pairs(fibre_pkg.code_files) do 31 | object_files += compile(fibre_cpp_dir..'/'..src) 32 | end 33 | 34 | 35 | autogen_pkg = fibre_autogen('test-interface.yaml') 36 | 37 | object_files += compile('test_node.cpp', autogen_pkg.autogen_headers) 38 | 39 | -- TODO: move up 40 | for _, src in pairs(autogen_pkg.code_files) do 41 | object_files += compile(src, autogen_pkg.autogen_headers) 42 | end 43 | 44 | 45 | compile_outname='build/test_node.elf' 46 | 47 | tup.frule{ 48 | inputs=object_files, 49 | command='^c^ '..LINKER..' %f '..tostring(CFLAGS)..' '..tostring(LDFLAGS)..' -o %o', 50 | outputs={compile_outname} 51 | } 52 | -------------------------------------------------------------------------------- /test/fibre_config.hpp: -------------------------------------------------------------------------------- 1 | 2 | #define FIBRE_ENABLE_SERVER F_RUNTIME_CONFIG 3 | #define FIBRE_ENABLE_CLIENT F_RUNTIME_CONFIG 4 | #define FIBRE_ENABLE_EVENT_LOOP 1 5 | 6 | #if defined(__linux__) 7 | #define FIBRE_ENABLE_TCP_CLIENT_BACKEND 1 8 | #define FIBRE_ENABLE_TCP_SERVER_BACKEND 1 9 | #define FIBRE_ENABLE_SOCKET_CAN_BACKEND 1 10 | #endif 11 | 12 | #if FIBRE_ENABLE_SOCKET_CAN_BACKEND 13 | #define FIBRE_ENABLE_CAN_ADAPTER 1 14 | #endif 15 | -------------------------------------------------------------------------------- /test/test-interface.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 0.0.1 3 | ns: fibre 4 | summary: Fibre Test Interface 5 | 6 | interfaces: 7 | TestIntf1: 8 | c_is_class: True 9 | brief: Toplevel interface of the test service. 10 | attributes: 11 | #prop_int8: uint8 12 | #prop_uint8: uint8 13 | #prop_int16: uint8 14 | #prop_uint16: uint8 15 | #prop_int32: uint8 16 | prop_uint32: readonly uint32 17 | prop_uint32_rw: uint32 18 | #prop_uint32: readonly uint32 19 | subobj: 20 | c_is_class: True 21 | functions: 22 | subfunc: {out: {out1: uint32}} 23 | #attributes: 24 | # parent: TestIntf1 25 | functions: 26 | func00: 27 | func01: {out: {out1: uint32}} 28 | func02: {out: {out1: uint32, out2: uint32}} 29 | func10: {in: {in1: uint32}} 30 | func11: {in: {in1: uint32}, out: {out1: uint32}} 31 | func12: {in: {in1: uint32}, out: {out1: uint32, out2: uint32}} 32 | func20: {in: {in1: uint32, in2: uint32}} 33 | func21: {in: {in1: uint32, in2: uint32}, out: {out1: uint32}} 34 | func22: {in: {in1: uint32, in2: uint32}, out: {out1: uint32, out2: uint32}} 35 | 36 | # TODO: these should be given as command line option to the interface generator 37 | exports: 38 | test_object: TestIntf1 39 | -------------------------------------------------------------------------------- /test/test_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys, os 4 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/python") 5 | import fibre 6 | 7 | with fibre.Domain("tcp-client:address=localhost,port=14220") as domain: 8 | obj = domain.discover_one() 9 | 10 | try: 11 | obj.inexistent_prop = 123 12 | failed = False 13 | except AttributeError as ex: 14 | failed = True 15 | assert(failed) # above line should throw an exception 16 | 17 | assert(obj.prop_uint32 == 135) 18 | assert(obj.prop_uint32 == 135) 19 | assert(obj.prop_uint32_rw == 246) 20 | assert(obj.prop_uint32_rw == 246) 21 | obj.prop_uint32_rw = 789 22 | assert(obj.prop_uint32_rw == 789) 23 | obj.func00() 24 | assert(obj.func01() == 123) 25 | assert(obj.subobj.subfunc() == 321) 26 | assert(obj.func02() == (456, 789)) 27 | obj.func10(1) 28 | assert(obj.func11(1) == 123) 29 | assert(obj.func12(1) == (456, 789)) 30 | obj.func20(1, 2) 31 | assert(obj.func21(1, 2) == 123) 32 | assert(obj.func22(1, 2) == (456, 789)) 33 | 34 | as_str = str(obj) 35 | print(as_str) 36 | assert(len(as_str) > 100) 37 | 38 | print("done") 39 | -------------------------------------------------------------------------------- /test/test_node.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __TEST_NODE_HPP 2 | #define __TEST_NODE_HPP 3 | 4 | #include 5 | 6 | struct TestNode { 7 | fibre::RichStatus start(fibre::EventLoop* event_loop, 8 | const uint8_t* node_id, std::string domain_path, 9 | bool enable_server, bool enable_client, 10 | fibre::Logger logger); 11 | void on_found_object(fibre::Object* obj, fibre::Interface* intf, 12 | std::string path); 13 | void on_lost_object(fibre::Object* obj); 14 | void on_finished_call(fibre::Socket* call, fibre::Status success, 15 | const fibre::cbufptr_t* out, size_t n_out); 16 | fibre::Logger logger_ = fibre::Logger::none(); 17 | fibre::Domain* domain_ = nullptr; 18 | }; 19 | 20 | #endif // __TEST_NODE_HPP 21 | -------------------------------------------------------------------------------- /tools/fibre-shell: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Connect to a Fibre-enabled device to play with in the IPython interactive shell. 4 | """ 5 | import argparse 6 | import sys 7 | import os 8 | 9 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + "/python") 10 | 11 | from fibre import Logger, Event 12 | 13 | # Parse arguments 14 | parser = argparse.ArgumentParser(description='Connect to a fibre-enabled device to play with it in the IPython interactive shell.') 15 | parser.add_argument("-p", "--path", metavar="PATH", action="store", 16 | help="The path(s) where ODrive(s) should be discovered.\n" 17 | "By default the script will connect to any ODrive on USB.\n\n" 18 | "To select a specific USB device:\n" 19 | " --path usb:BUS:DEVICE\n" 20 | "usbwhere BUS and DEVICE are the bus and device numbers as shown in `lsusb`.\n\n" 21 | "To select a specific serial port:\n" 22 | " --path serial:PATH\n" 23 | "where PATH is the path of the serial port. For example \"/dev/ttyUSB0\".\n" 24 | "You can use `ls /dev/tty*` to find the correct port.\n\n" 25 | "You can combine USB and serial specs by separating them with a comma (no space!)\n" 26 | "Example:\n" 27 | " --path usb,serial:/dev/ttyUSB0\n" 28 | "means \"discover any USB device or a serial device on /dev/ttyUSB0\"") 29 | parser.add_argument("-s", "--serial-number", action="store", 30 | help="The 12-digit serial number of the device. " 31 | "This is a string consisting of 12 upper case hexadecimal " 32 | "digits as displayed in lsusb. \n" 33 | " example: 385F324D3037\n" 34 | "You can list all devices connected to USB by running\n" 35 | "(lsusb -d 1209:0d32 -v; lsusb -d 0483:df11 -v) | grep iSerial\n" 36 | "If omitted, any device is accepted.") 37 | parser.add_argument("--no-ipython", action="store_true", 38 | help="Use the regular Python shell " 39 | "instead of the IPython shell, " 40 | "even if IPython is installed.") 41 | parser.add_argument("-v", "--verbose", action="store_true", 42 | help="print debug information") 43 | 44 | parser.set_defaults(path="usb,tcp:localhost:9910") 45 | args = parser.parse_args() 46 | 47 | logger = Logger(verbose=args.verbose) 48 | 49 | def print_banner(): 50 | pass 51 | 52 | def print_help(args, have_devices): 53 | pass 54 | 55 | import fibre 56 | fibre.launch_shell(args, {}, print_banner, print_help, logger) 57 | -------------------------------------------------------------------------------- /tools/interface-definition-file.md: -------------------------------------------------------------------------------- 1 | 2 | ## Interface Definition File Structure 3 | 4 | This section describes the structure of Fibre interface definition YAML files. Use this as reference if you're writing YAML files for Fibre. 5 | 6 | **TODO** 7 | 8 | 9 | ## Template API 10 | 11 | This section describes what data is available to a template that is run through the `interface_generator.py`. Use this as reference if you're writing template files. 12 | 13 | ### Globals 14 | 15 | - `interfaces` (`list`): List of all interfaces defined in the input file(s) including on-demand generated interfaces (e.g. `fibre.Property`) 16 | - `enums` (`list`): List of all enums defined in the input file(s) 17 | - `exported_functions` (`list`): List of all statically exported functions 18 | - `exported_interfaces` (`list`): List of all statically exported interfaces 19 | - `exported_objects` (`list`): Expanded list of all statically exported objects that should be directly addressable by object ID. 20 | - `published_objects` (`list`): List of toplevel published objects. This is a subset of `exported_objects`. 21 | 22 | ### `interface` object 23 | - `name` (`name_info`): Name of the interface. 24 | - `id` (`int`): If this interface is statically exported, this is the ID of the interface. 25 | - `functions` (`dict`): Functions implemented by the interface 26 | - `attributes` (`dict`): Attributes implemented by the interface 27 | 28 | ### `enum` object 29 | - `name` (`name_info`): Name of the enum 30 | - `is_flags` (`bool`): Indicates if this enum is a bit field or not 31 | - `nullflag` (`string`): Name of the enumerator that represents the absence of any flags (only present if `is_flags == true`) 32 | - `values` (`list`): Values of the enum 33 | 34 | ### `enumerator` object 35 | - `name` (`string`): Name of the enumerator value 36 | - `value` (`int`): Value of the enumerator value 37 | 38 | ### `name_info` object 39 | - `get_fibre_name() -> string`: Returns a string of the full name in canonical Fibre notation 40 | 41 | ### `function` object 42 | - `name` (`name_info`): Full name of the function including the interface. 43 | - `id` (`int`): If this function is statically exported, this is the ID of the function. 44 | - `intf` (`interface`): The interface that contains this function. 45 | - `in` (`dict`): Input arguments 46 | - `out` (`dict`): Output arguments 47 | 48 | ### `argument` object 49 | - `type` (`value_type`): Type of the argument 50 | 51 | ### `value_type` object 52 | - `name` (`name_info`): Name of the value type 53 | - `c_type` (`string`): C name of the data type (only present for basic value types, not for enums) 54 | 55 | ### `attribute` object 56 | - `name` (`name_info`): Name of the attribute 57 | - `type` (`interface`): Interface implemented by the attribute 58 | 59 | ### `object` object 60 | - `name` (`name_info`): Full name of the object including its parent objects 61 | --------------------------------------------------------------------------------