diff --git a/.codespell-ignore b/.codespell-ignore new file mode 100644 index 000000000..773f808d4 --- /dev/null +++ b/.codespell-ignore @@ -0,0 +1 @@ +re-use diff --git a/.github/workflows/abi-checker.yml b/.github/workflows/abi-checker.yml index 71a5b8c9f..1ace9ea01 100644 --- a/.github/workflows/abi-checker.yml +++ b/.github/workflows/abi-checker.yml @@ -1,5 +1,7 @@ name: ABI Compliance Check on: [pull_request] +permissions: + contents: read jobs: abi-check: strategy: @@ -20,7 +22,7 @@ jobs: sudo apt-get install abi-compliance-checker abi-dumper - name: Checkout merged-base - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.base.ref }} @@ -30,7 +32,7 @@ jobs: abi-dumper build/libcsp.so -lver "merged-base" -o ../tmp/libcsp-merged-base.dump - name: Checkout Current PR - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{github.event.pull_request.head.ref}} @@ -51,7 +53,7 @@ jobs: - name: Upload ABI Report if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: abi-compliance-report-${{ github.run_id }} path: compat_reports diff --git a/.github/workflows/build-test-freertos.yml b/.github/workflows/build-test-freertos.yml index 56b3b38fb..b9060a9ef 100644 --- a/.github/workflows/build-test-freertos.yml +++ b/.github/workflows/build-test-freertos.yml @@ -1,5 +1,10 @@ name: FreeRTOS Build and Test on: [push, pull_request] +permissions: + contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true jobs: build-freertos: runs-on: ubuntu-latest @@ -12,18 +17,18 @@ jobs: sudo apt-get install ninja-build meson tree - name: Checkout Test App - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: libcsp/libcsp-freertos - name: Checkout FreeRTOS Kernel - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: FreeRTOS/FreeRTOS-Kernel path: freertos - name: Checkout libcsp under subprojects - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: path: subprojects/libcsp diff --git a/.github/workflows/build-test-python.yml b/.github/workflows/build-test-python.yml index 62ed362c2..8bbd830af 100644 --- a/.github/workflows/build-test-python.yml +++ b/.github/workflows/build-test-python.yml @@ -1,26 +1,31 @@ name: Python Bindings on: [push, pull_request] +permissions: + contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true jobs: build-test-python: strategy: fail-fast: false matrix: python-version: - - '3.9' - '3.10' - '3.11' - '3.12' + - '3.13' + - '3.14' os: - ubuntu-24.04 - ubuntu-22.04 - - ubuntu-20.04 buildsystem: - meson - cmake runs-on: ${{ matrix.os }} steps: - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -34,7 +39,7 @@ jobs: sudo apt-get install ninja-build ${{ matrix.buildsystem }} - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Build libcsp examples run: python3 examples/buildall.py --build-system=${{ matrix.buildsystem }} @@ -46,7 +51,7 @@ jobs: - name: Install the latest Meson using Pip if Python is 3.12 or later # Meson 1.3+ supports Python 3.12 (distutils removed) - if: ${{ matrix.buildsystem == 'meson' && matrix.python-version == '3.12' }} + if: ${{ matrix.buildsystem == 'meson' && matrix.python-version >= '3.12' }} run: | pip install meson meson --version diff --git a/.github/workflows/build-test-zephyr.yml b/.github/workflows/build-test-zephyr.yml index b5518dc27..db56242b2 100644 --- a/.github/workflows/build-test-zephyr.yml +++ b/.github/workflows/build-test-zephyr.yml @@ -5,6 +5,11 @@ on: schedule: - cron: '0 10 * * 0' # Run it every Sunday 10am UTC +permissions: + contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true jobs: build-zephyr: runs-on: ubuntu-24.04 @@ -12,17 +17,18 @@ jobs: fail-fast: false matrix: board: - - scobc_module1 + - scobc_a1 - qemu_cortex_m3 - - mps2_an385 + - mps2/an385 python-version: - - '3.9' - '3.10' - '3.11' + - '3.12' + - '3.13' steps: - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -31,7 +37,7 @@ jobs: python3 --version - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: path: libcsp-zephyr repository: yashi/libcsp-zephyr diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 1ab5cad58..5784ac126 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -1,5 +1,10 @@ name: Build and Test on: [push, pull_request] +permissions: + contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true jobs: run-tests: strategy: @@ -8,16 +13,42 @@ jobs: os: - ubuntu-24.04 - ubuntu-22.04 - - ubuntu-20.04 buildsystem: - meson - cmake - waf compiler: - - CC: gcc - CXX: g++ + - CC: gcc-10 + CXX: g++-10 + - CC: gcc-11 + CXX: g++-11 + - CC: gcc-12 + CXX: g++-12 + - CC: gcc-13 + CXX: g++-13 + - CC: gcc-14 + CXX: g++-14 - CC: clang CXX: clang++ + csp_version: + - 1 + - 2 + exclude: + # Ubuntu 22.04 only has GCC 10, 11, 12 + - os: ubuntu-22.04 + compiler: + CC: gcc-13 + - os: ubuntu-22.04 + compiler: + CC: gcc-14 + # Ubuntu 24.04 only has GCC 12, 13, 14 + - os: ubuntu-24.04 + compiler: + CC: gcc-10 + - os: ubuntu-24.04 + compiler: + CC: gcc-11 + runs-on: ${{ matrix.os }} steps: - name: Setup packages on Linux @@ -32,15 +63,8 @@ jobs: run: | sudo apt-get install ninja-build ${{ matrix.buildsystem }} - - name: Setup packages on MacOS - if: ${{ runner.os == 'macOS' && matrix.buildsystem != 'waf' }} - run: | - brew update - brew install ninja ${{ matrix.buildsystem }} - brew install zeromq - - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Build env: @@ -57,30 +81,30 @@ jobs: run: | socat -d -d -d pty,raw,echo=0,link=/tmp/pty1 pty,raw,echo=0,link=/tmp/pty2 & sleep 1 - ./build/examples/csp_server -k /tmp/pty1 -a 1 -T 10 & - ./build/examples/csp_client -k /tmp/pty2 -a 2 -C 1 -t + ./build/examples/csp_server -k /tmp/pty1 -a 1 -T 10 -v ${{ matrix.csp_version }} & + ./build/examples/csp_client -k /tmp/pty2 -a 2 -C 1 -t -v ${{ matrix.csp_version }} pkill socat - name: Run KISS Server Test run: | socat -d -d -d pty,raw,echo=0,link=/tmp/pty1 pty,raw,echo=0,link=/tmp/pty2 & sleep 1 - ./build/examples/csp_client -k /tmp/pty2 -a 2 -C 1 -T 10 & - ./build/examples/csp_server -k /tmp/pty1 -a 1 -t + ./build/examples/csp_client -k /tmp/pty2 -a 2 -C 1 -T 10 -v ${{ matrix.csp_version }} & + ./build/examples/csp_server -k /tmp/pty1 -a 1 -t -v ${{ matrix.csp_version }} pkill socat - name: Run ZMQ Client Test run: | ./build/examples/zmqproxy & - ./build/examples/csp_server -z localhost -a 1 -T 10 & - ./build/examples/csp_client -z localhost -a 2 -C 1 -t + ./build/examples/csp_server -z localhost -a 1 -T 10 -v ${{ matrix.csp_version }} & + ./build/examples/csp_client -z localhost -a 2 -C 1 -t -v ${{ matrix.csp_version }} pkill zmqproxy - name: Run ZMQ Server Test run: | ./build/examples/zmqproxy & - ./build/examples/csp_client -z localhost -a 2 -C 1 -T 10 & - ./build/examples/csp_server -z localhost -a 1 -t + ./build/examples/csp_client -z localhost -a 2 -C 1 -T 10 -v ${{ matrix.csp_version }} & + ./build/examples/csp_server -z localhost -a 1 -t -v ${{ matrix.csp_version }} pkill zmqproxy - name: Setup vcan0 @@ -96,10 +120,32 @@ jobs: - name: Run CAN Server Test run: | - ./build/examples/csp_server -c vcan0 -a 1 -T 10 & - ./build/examples/csp_client -c vcan0 -a 2 -C 1 -t + ./build/examples/csp_server -c vcan0 -a 1 -T 10 -v ${{ matrix.csp_version }} & + ./build/examples/csp_client -c vcan0 -a 2 -C 1 -t -v ${{ matrix.csp_version }} - name: Run CAN Client Test run: | - ./build/examples/csp_client -c vcan0 -a 2 -C 1 -T 10 & - ./build/examples/csp_server -c vcan0 -a 1 -t + ./build/examples/csp_client -c vcan0 -a 2 -C 1 -T 10 -v ${{ matrix.csp_version }} & + ./build/examples/csp_server -c vcan0 -a 1 -t -v ${{ matrix.csp_version }} + + - name: Run UDP Server Test + run: | + ./build/examples/csp_server -u "127.0.0.1" -a 1 -T 10 -v ${{ matrix.csp_version }} & + ./build/examples/csp_client -u "127.0.0.1" -a 2 -C 1 -T 10 -v ${{ matrix.csp_version }} + wait "$!" + + - name: Run UDP Client Test + run: | + ./build/examples/csp_client -u "127.0.0.1" -a 2 -C 1 -T 10 -v ${{ matrix.csp_version }} & + ./build/examples/csp_server -u "127.0.0.1" -a 1 -T 10 -v ${{ matrix.csp_version }} + + - name: Run SFP Test + run: | + ./build/examples/csp_sfp_server_client --mtu 1 --size 1 + ./build/examples/csp_sfp_server_client --mtu 1 --size 128 + ./build/examples/csp_sfp_server_client --mtu 128 --size 1 + ./build/examples/csp_sfp_server_client --mtu 128 --size 10 + ./build/examples/csp_sfp_server_client --mtu 128 --size 100 + ./build/examples/csp_sfp_server_client --mtu 128 --size 1000 + ./build/examples/csp_sfp_server_client --mtu 128 --size 10000 + ./build/examples/csp_sfp_server_client --mtu 128 --size 100000 diff --git a/.github/workflows/check-merge-commits.yml b/.github/workflows/check-merge-commits.yml new file mode 100644 index 000000000..310c9fbd8 --- /dev/null +++ b/.github/workflows/check-merge-commits.yml @@ -0,0 +1,32 @@ +name: Merge Commits Check + +on: + pull_request: + +permissions: + contents: read + +jobs: + check_merge_commits: + runs-on: ubuntu-latest + steps: + + - name: Checkout PR head + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + + - name: Print commits for debugging + run: git log --graph --oneline -n 50 + + - name: Check for merge commits + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + merges=$(git rev-list --merges --count "${BASE_SHA}..${HEAD_SHA}") + if [ "$merges" != "0" ]; then + echo "::error ::Merge commits not allowed; please rebase." + exit 1 + fi diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml new file mode 100644 index 000000000..219f55141 --- /dev/null +++ b/.github/workflows/codespell.yml @@ -0,0 +1,22 @@ +name: Codespell Check + +on: + pull_request: + push: +permissions: + contents: read + +jobs: + spellcheck: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Install codespell + run: pip install codespell + + - name: Run codespell + run: | + codespell --skip="waf" --ignore-words=.codespell-ignore --quiet-level=2 diff --git a/.github/workflows/develop-build-sphinx-docs.yml b/.github/workflows/develop-build-sphinx-docs.yml index 5cf48da51..8e5af2159 100644 --- a/.github/workflows/develop-build-sphinx-docs.yml +++ b/.github/workflows/develop-build-sphinx-docs.yml @@ -5,13 +5,19 @@ on: push: branches: - develop +permissions: + contents: read jobs: build-docs: if: github.repository_owner == 'libcsp' runs-on: ubuntu-latest + + permissions: + contents: write + steps: - name: Checkout the repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup packages on Linux if: ${{ runner.os == 'Linux' }} diff --git a/.github/workflows/gitlint.yml b/.github/workflows/gitlint.yml index c4c47cf7a..318b3cb9f 100644 --- a/.github/workflows/gitlint.yml +++ b/.github/workflows/gitlint.yml @@ -2,6 +2,8 @@ name: GitLint on: pull_request +permissions: + contents: read jobs: gitlint: @@ -14,7 +16,7 @@ jobs: pip3 install gitlint - name: Checkout the code - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 diff --git a/.github/workflows/linelint.yaml b/.github/workflows/linelint.yaml index c337d48ec..ee6190415 100644 --- a/.github/workflows/linelint.yaml +++ b/.github/workflows/linelint.yaml @@ -2,6 +2,8 @@ name: EOF newline on: pull_request +permissions: + contents: read jobs: linelint: @@ -9,7 +11,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Linelint uses: fernandrone/linelint@master diff --git a/.gitlint b/.gitlint new file mode 100644 index 000000000..87587e6f4 --- /dev/null +++ b/.gitlint @@ -0,0 +1,5 @@ +[general] +regex-style-search=true + +[ignore-body-lines] +regex=^(?:(?:Signed-off|Acked|Co-Authored|Reported|Tested)-by: |\[\d+\]: https:\/\/) diff --git a/AUTHORS b/AUTHORS index d24ee85f2..de660085b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -7,7 +7,7 @@ Author: Johan was the inventor of CSP in 2008 and is still the primary developer in 2020 -Contributers: +Contributors: Jeppe Ledet-Pedersen company: GomSpace diff --git a/CHANGELOG b/CHANGELOG index 28763aeed..ab95f359b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,44 @@ -libcsp 2.1, xx-yy-zzzz +libcsp 2.2, xx-xx-202x ---------------------- +- new: csp_sfp_send(), csp_sfp_conn_max_mtu(), csp_sfp_opts_max_mtu() +- new: Make KISS interface CRC optional via CSP_ENABLE_KISS_CRC (#892) +- new: Meson v0.61.2 is the minimal version now (#927) +- new: A new build time constat CSP_BUFFER_RESERVED_COUNT is added (#925) +- improvement: Small Fragmentation Protocol has been reworked to remove malloc() (#742) +- improvement: PyCSP can now be built without SocketCAN (#638) +- improvement: PyCSP can now take mask and is_default optional parameter (#924) +- removed: csp_sfp_send_own_memcpy() +- break; csp_sfp_recv_fp() +- fix: csp_accept() now explicitly rejects connection-less sockets (#766) +- fix: Meson can now install libcsp.so when told to do so (#931) + +libcsp 2.1, 11-10-2025 +---------------------- +- new: Add reproducible builds +- new: Move to reStructuredText and automated documentation deployment +- new: Add convenient macros like `__noinit`, `__weak`, `__packed`, etc. +- new: Add simple sample code under `samples/` +- new: Add separate `csp_server` and `csp_client` in addition to `csp_server_client` +- new: Add raw Ethernet driver for Linux +- new: Add CAN drivers for Zephyr +- new: Begin adding unit tests +- new: Enable `-Wpointer-arith` +- new: Enable `-Wpedantic` +- break: Rename `csp_autoconfig.h` to `build/include/csp/autoconfig.h` +- removed: Remove additional Windows and macOS support +- improvement: Update documentation +- improvement: Improve C++ support by adding `extern "C"` +- improvement: Improve Python support +- improvement: Update examples with various features +- improvement: Update support for FreeRTOS, POSIX, and Zephyr +- improvement: Update build systems: Waf, Meson, and CMake +- improvement: Update various libcsp core modules: + - bridge, conn, crc32, dedup, id, iflist, io, port, packet + - promisc, queue, rdp, route, service, sfp, yaml, and more +- improvement: Update various libcsp device drivers: + - can, socketcan, eth, usart +- improvement: Update various libcsp interface drivers: + - can, eth, kiss, tun, udp, zmq, loopback libcsp 2.0, 19-04-2024 ---------------------- @@ -36,11 +75,11 @@ libcsp 2.0, 19-04-2024 - api: csp_init(): Now a void function, because allocation cannot fail - api: python bindings: Refreshed to new 2.0 api, builds with meson too - api: Interface names are now case sensitive (this is faster and avoids pulling in _ctypes_ and saves 340 bytes of flash) -- api: csp_socket(): is replaced by simply `csp_socket_t my_sock;` static allocation. This is very leightweight +- api: csp_socket(): is replaced by simply `csp_socket_t my_sock;` static allocation. This is very lightweight - api: csp_bind_callback(): instead of using a socket, and binding that socket to a port. - api: new debug API. CSP does not print error messages any longer. Instead a new series of error counters has been added and a new error code system. There are flags to enable the old level 4 and 5 debug to stdout with printf. -- api: csp reboot and shutdown are now implemented using hooks instaed of callback functions (see hooks.md) +- api: csp reboot and shutdown are now implemented using hooks instead of callback functions (see hooks.md) - fix: router counts incoming packets before deduplication - fix: rdp: Fix ack_delay_count off by one error - fix: rtable cidr: continue parsing, even if an unknown interface is found @@ -61,7 +100,7 @@ libcsp 1.6, 16-04-2020 - Added support for timestamps in logs by setting CSP_DEBUG_TIMESTAMP (uses csp_clock_get_time()). - Renamed mac to via (structs, functions, examples and documentation) - RDP: Ensure connection is kept in CLOSE_WAIT for period of time. In some cases, the connection would switch to CLOSED immediately. -- RDP: Fixed connection leak, if a RST segment was received on a closed connecton. +- RDP: Fixed connection leak, if a RST segment was received on a closed connection. - RDP: Ensure connection is closed from both userspace and protocol, before closing completely (preventing undetermined behaviour). - RDP: Added support for fast close of a connection (skipping the CLOSE_WAIT period), but only if both ends agree on close. - RDP: Fixed issue "Possible bug in RDP TX timeout" (#109), see issue on github for further details. @@ -204,7 +243,7 @@ libcsp 1.0, 24-10-2011 ---------------------- - First official release - New: CSP 32-bit header 1.0 -- Features: Network Router with promiscous mode, broadcast and QoS +- Features: Network Router with promiscuous mode, broadcast and QoS - Features: Connection-oriented transport protocol w. flow-control - Features: Connection-less "UDP" like transport - Features: Encryption, Authentication and message check diff --git a/CMakeLists.txt b/CMakeLists.txt index ff0447087..2752465aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,14 @@ cmake_minimum_required(VERSION 3.20) -project(CSP VERSION 2.1) +project(CSP VERSION 2.2) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - set(BUILD_SHARED_LIBS ON) + set(DEFAULT_BUILD_SHARED_LIBS ON) endif() +option(BUILD_SHARED_LIBS "Build using shared libraries" ${DEFAULT_BUILD_SHARED_LIBS}) add_library(csp) set_target_properties(csp PROPERTIES C_STANDARD 11) set_target_properties(csp PROPERTIES C_EXTENSIONS ON) -target_compile_options(csp PRIVATE -Wall -Wextra) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release MinSizeRel) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) @@ -23,7 +23,7 @@ option(CSP_ENABLE_CSP_PRINT "Enable csp_print() function" ON) option(CSP_PRINT_STDIO "Use vprintf() for csp_print() function" ON) set(CSP_QFIFO_LEN 15 CACHE STRING "Length of incoming queue for router task") -set(CSP_PORT_MAX_BIND 16 CACHE STRING "Length of incoming queue for router task") +set(CSP_PORT_MAX_BIND 16 CACHE STRING "Maximum number of bindable ports") set(CSP_CONN_RXQUEUE_LEN 16 CACHE STRING "Number of packets in connection queue") set(CSP_CONN_MAX 8 CACHE STRING "Number of new connections on socket queue") set(CSP_BUFFER_SIZE 256 CACHE STRING "Bytes in each packet buffer") @@ -35,17 +35,32 @@ option(CSP_USE_RDP "Reliable Datagram Protocol" ON) option(CSP_USE_HMAC "Hash-based message authentication code" ON) option(CSP_USE_PROMISC "Promiscious mode" ON) option(CSP_USE_RTABLE "Use routing table" OFF) +option(CSP_BUFFER_ZERO_CLEAR "Zero out the packet buffer upon allocation" ON) option(CSP_ENABLE_PYTHON3_BINDINGS "Build Python3 binding" OFF) +option(CSP_BUILD_SAMPLES "Build samples and examples by default" OFF) + +option(CSP_FIXUP_V1_ZMQ_LITTLE_ENDIAN "Use little-endian CSP ID for ZMQ with CSPv1" ON) + +option(CSP_ENABLE_KISS_CRC "Enables the extra CRC in the KISS interface (legacy)" ON) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") set(CSP_POSIX 1) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Zephyr") - set(CSP_ZEPHYR 1) +elseif(CMAKE_SYSTEM_NAME STREQUAL "CYGWIN") + set(CSP_POSIX 1) + message(WARNING "CYGWIN detected: experimental port and ethernet not implemented") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Generic") + string(TOLOWER "${CSP_SYSTEM_NAME}" csp_system_name) + if(csp_system_name STREQUAL "freertos") + set(CSP_FREERTOS 1) + elseif(csp_system_name STREQUAL "zephyr") + set(CSP_ZEPHYR 1) + else() + message(FATAL_ERROR "Not supported ${CSP_SYSTEM_NAME}") + endif() endif() -if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND - CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") +if(CSP_POSIX AND CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") include(CheckIncludeFiles) check_include_files(sys/socket.h HAVE_SYS_SOCKET_H) @@ -78,6 +93,14 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND else() message(NOTICE "No libsocketcan found") endif() + + pkg_search_module(CHECK check) + if(${CHECK_FOUND}) + message(STATUS "Found ${CHECK_MODULE_NAME} ${CHECK_VERSION}") + set(CSP_HAVE_CHECK 1) + else() + message(NOTICE "No libcheck found") + endif() else() message(NOTICE "No pkg-config found") endif() @@ -91,20 +114,38 @@ target_include_directories(csp PRIVATE ${csp_inc_src} ) +add_library(csp_common INTERFACE) if(CSP_POSIX) - set(CSP_C_ARGS -Wshadow -Wcast-align -Wpointer-arith -Wwrite-strings -Wno-unused-parameter) -elseif(CSP_ZEPHYR) - set(CSP_C_ARGS -Wwrite-strings -Wno-unused-parameter) + target_compile_options(csp_common INTERFACE + -Wall + -Wcast-align + -Werror + -Wextra + -Wmissing-prototypes + -Wpedantic + -Wpointer-arith + -Wshadow + -Wwrite-strings + $<$:-Wno-gnu-zero-variadic-macro-arguments>) +endif() +target_link_libraries(csp PRIVATE csp_common) + +if(NOT CSP_BUILD_SAMPLES) + set(CSP_SAMPLES_EXCLUDE "EXCLUDE_FROM_ALL") endif() -target_compile_options(csp PRIVATE ${CSP_C_ARGS}) add_subdirectory(src) -add_subdirectory(examples) +add_subdirectory(unittests) +if(NOT CMAKE_SYSTEM_NAME STREQUAL "CYGWIN") + add_subdirectory(examples) + add_subdirectory(samples) +endif() if(${CSP_ENABLE_PYTHON3_BINDINGS}) find_package(Python3 COMPONENTS Development.Module) if(Python3_Development.Module_FOUND) Python3_add_library(libcsp_py3 MODULE WITH_SOABI src/bindings/python/pycsp.c) + add_compile_options(-Wno-missing-prototypes -Wno-unused-parameter) target_include_directories(libcsp_py3 PUBLIC ${csp_inc}) target_link_libraries(libcsp_py3 PUBLIC csp) else() @@ -114,7 +155,7 @@ endif() configure_file(csp_autoconfig.h.in include/csp/autoconfig.h) -if(NOT CMAKE_SYSTEM_NAME STREQUAL "Zephyr") +if(NOT CSP_ZEPHYR) install(TARGETS csp LIBRARY COMPONENT runtime) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/;${CMAKE_CURRENT_SOURCE_DIR}/include/; TYPE INCLUDE diff --git a/LICENSE b/LICENSE index 832670406..0e818d622 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2009-2021 CSP Contributers (see AUTHORS file) +Copyright (c) 2009-2021 CSP Contributors (see AUTHORS file) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index bb0c3feb7..242f2ce47 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ header. The small footprint and simple implementation allows a small 8-bit system to be fully connected on the network. This allows all subsystems to provide their services on the same network level, without any master node required. Using a service oriented architecture has -several advantages compared to the traditional mater/slave topology used +several advantages compared to the traditional master/slave topology used on many cubesats. - Standardised network protocol: All subsystems can communicate with - eachother (multi-master) + each other (multi-master) - Service loose coupling: Services maintain a relationship that minimizes dependencies between subsystems - Service abstraction: Beyond descriptions in the service contract, @@ -42,7 +42,7 @@ on many cubesats. single master node to several well defined services on the network The implementation of `libcsp` is written -with simplicity in mind, but it's compile time configuration allows it +with simplicity in mind, but its compile time configuration allows it to have some rather advanced features as well. ## Features diff --git a/contrib/drivers/can/csp_driver_can.c b/contrib/drivers/can/csp_driver_can.c index caaeb7fce..9e9f4e106 100644 --- a/contrib/drivers/can/csp_driver_can.c +++ b/contrib/drivers/can/csp_driver_can.c @@ -47,7 +47,7 @@ static StackType_t can_task_stack[500]; static TaskHandle_t can_task_handle; #endif -/** Driver configration */ +/** Driver configuration */ static struct mcan_s { can_mode_e mode; uint32_t id; @@ -162,10 +162,10 @@ int csp_can_tx_frame(void *driver_data, uint32_t id, const uint8_t * data, uint8 * We could go for async transmission with software queues, this will allow for larger amounts * of data to be queued. However in this case, simplicity is chosen over performance. * When the TX FIFO is full, we ask the task to sleep a tick. - * Usually data heavy fuctions are running in their separate tasks anyways. + * Usually data heavy functions are running in their separate tasks anyways. * A CAN frame is 96 bits and is transmitted within 96 us. * A tick period can vary from 1 to 10 ms (typically) - * 3 retries have been chosen because a futher dealy than this would almost certainly be + * 3 retries have been chosen because a further dealy than this would almost certainly be * due to an error. */ int attempts = 3; @@ -226,7 +226,7 @@ csp_iface_t * csp_driver_can_init(int addr, int netmask, int id, can_mode_e mode mcan[id].interface.addr = addr; mcan[id].interface.netmask = netmask; - /* Regsiter interface */ + /* Register interface */ csp_can_add_interface(&mcan[id].interface); #if CAN_DEFER_TASK diff --git a/contrib/macosx/pthread_queue.c b/contrib/macosx/pthread_queue.c index eb242bf85..374b82676 100644 --- a/contrib/macosx/pthread_queue.c +++ b/contrib/macosx/pthread_queue.c @@ -93,7 +93,7 @@ int pthread_queue_enqueue(pthread_queue_t * queue, const void * value, uint32_t queue->in = (queue->in + 1) % queue->size; pthread_mutex_unlock(&(queue->mutex)); - /* Nofify blocked threads */ + /* Notify blocked threads */ pthread_cond_broadcast(&(queue->cond_empty)); return PTHREAD_QUEUE_OK; @@ -139,7 +139,7 @@ int pthread_queue_dequeue(pthread_queue_t * queue, void * buf, uint32_t timeout) queue->out = (queue->out + 1) % queue->size; pthread_mutex_unlock(&(queue->mutex)); - /* Nofify blocked threads */ + /* Notify blocked threads */ pthread_cond_broadcast(&(queue->cond_full)); return PTHREAD_QUEUE_OK; diff --git a/contrib/windows/usart_windows.c b/contrib/windows/usart_windows.c index ce0f9cd4e..bfc52ab2e 100644 --- a/contrib/windows/usart_windows.c +++ b/contrib/windows/usart_windows.c @@ -69,7 +69,7 @@ static int setPortTimeouts(csp_usart_fd_t fd) { COMMTIMEOUTS timeouts = {0}; if (!GetCommTimeouts(fd, &timeouts)) { - csp_print("Error gettings current timeout settings, error: %lu\n", GetLastError()); + csp_print("Error getting current timeout settings, error: %lu\n", GetLastError()); return CSP_ERR_INVAL; } diff --git a/contrib/zephyr/samples/server-client/main.c b/contrib/zephyr/samples/server-client/main.c index 49747bd6c..727c86ba2 100644 --- a/contrib/zephyr/samples/server-client/main.c +++ b/contrib/zephyr/samples/server-client/main.c @@ -49,7 +49,7 @@ void server(void) { continue; } - /* Read packets on connection, timout is 100 mS */ + /* Read packets on connection, timeout is 100 mS */ csp_packet_t *packet; while ((packet = csp_read(conn, 50)) != NULL) { switch (csp_conn_dport(conn)) { @@ -89,9 +89,8 @@ void client(void) { k_sleep(test_mode ? K_USEC(200000) : K_USEC(1000000)); /* Send ping to server, timeout 1000 mS, ping size 100 bytes */ - int result = csp_ping(server_address, 1000, 100, CSP_O_NONE); + int __maybe_unused result = csp_ping(server_address, 1000, 100, CSP_O_NONE); LOG_INF("Ping address: %u, result %d [mS]", server_address, result); - (void) result; /* Send reboot request to server, the server has no actual implementation of csp_sys_reboot() and fails to reboot */ csp_reboot(server_address); @@ -139,7 +138,8 @@ void client(void) { int main(void) { int ret; - uint8_t address = 0; + uint8_t uart_address = 1; + uint8_t can_address = 10; const char * kiss_device = NULL; const char * rtable = NULL; csp_iface_t * can_iface = NULL; @@ -162,7 +162,7 @@ int main(void) { .stopbits = 1, .paritysetting = 0, }; - int error = csp_usart_open_and_add_kiss_interface(&conf, CSP_IF_KISS_DEFAULT_NAME, addr, &default_iface); + int error = csp_usart_open_and_add_kiss_interface(&conf, CSP_IF_KISS_DEFAULT_NAME, uart_address, &default_iface); if (error != CSP_ERR_NONE) { LOG_ERR("failed to add KISS interface [%s], error: %d", kiss_device, error); exit(1); @@ -178,7 +178,6 @@ int main(void) { * server address to any address not 255. */ const char * ifname = "CAN0"; - address = 10; server_address = 255; const struct device * device = DEVICE_DT_GET(DT_NODELABEL(can0)); uint32_t bitrate = 1000000; @@ -189,11 +188,11 @@ int main(void) { * by me. If you want to receive all packets, please change the filter address * and mask. (For example, filter_addr: 0x3FFF, filter_mask: 0x0000) */ - uint16_t filter_addr = address; + uint16_t filter_addr = can_address; uint16_t filter_mask = 0x3FFF; - int error = csp_can_open_and_add_interface(device, ifname, address, bitrate, - filter_addr, filter_mask, &can_iface); + int error = csp_can_open_and_add_interface(device, ifname, can_address, bitrate, + filter_addr, filter_mask, &can_iface); if (error != CSP_ERR_NONE) { LOG_ERR("failed to add CAN interface [%s], error: %d\n", ifname, error); exit(1); @@ -216,7 +215,7 @@ int main(void) { if (!default_iface) { /* no interfaces configured - run server and client in process, using loopback interface */ - server_address = address; + server_address = 0; /* run as test mode only use loopback interface */ test_mode = true; } diff --git a/csp_autoconfig.h.in b/csp_autoconfig.h.in index d5449b51e..1ee30f001 100644 --- a/csp_autoconfig.h.in +++ b/csp_autoconfig.h.in @@ -1,5 +1,6 @@ #cmakedefine01 CSP_POSIX #cmakedefine01 CSP_ZEPHYR +#cmakedefine01 CSP_FREERTOS #cmakedefine01 CSP_HAVE_STDIO #cmakedefine01 CSP_ENABLE_CSP_PRINT @@ -20,6 +21,11 @@ #cmakedefine01 CSP_USE_HMAC #cmakedefine01 CSP_USE_PROMISC #cmakedefine01 CSP_USE_RTABLE +#cmakedefine01 CSP_BUFFER_ZERO_CLEAR #cmakedefine01 CSP_HAVE_LIBSOCKETCAN #cmakedefine01 CSP_HAVE_LIBZMQ + +#cmakedefine01 CSP_FIXUP_V1_ZMQ_LITTLE_ENDIAN + +#cmakedefine01 CSP_ENABLE_KISS_CRC diff --git a/doc/.nojekyll b/doc/.nojekyll deleted file mode 100644 index e69de29bb..000000000 diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 5ddb21dfc..eef3239ee 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -8,13 +8,13 @@ set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html) file(GLOB DOCS_FILES "${SPHINX_SOURCE}/*.md" + "${SPHINX_SOURCE}/samples/posix/*.md" "${SPHINX_SOURCE}/api/*.rst" "${SPHINX_SOURCE}/api/arch/*.rst" "${SPHINX_SOURCE}/api/crypto/*.rst" "${SPHINX_SOURCE}/api/drivers/*.rst" "${SPHINX_SOURCE}/api/interfaces/*.rst") -file(COPY ${SPHINX_SOURCE}/.nojekyll DESTINATION ${SPHINX_BUILD}) add_custom_target(docs ALL DEPENDS ${SPHINX_INDEX_FILE}) add_custom_command(OUTPUT ${SPHINX_INDEX_FILE} COMMAND diff --git a/doc/Dockerfile b/doc/Dockerfile index f474f4d38..70d0cf963 100644 --- a/doc/Dockerfile +++ b/doc/Dockerfile @@ -1,22 +1,21 @@ -FROM ubuntu:22.04 +FROM ubuntu:latest ARG DEBIAN_FRONTEND=noninteractive MAINTAINER "Iliya Iliev" # Install dependencies RUN apt-get update && \ apt-get install --no-install-recommends --no-install-suggests -y sudo git python3-pip cmake \ - libclang-14-dev build-essential locales locales-all + libclang-dev build-essential locales locales-all ENV LC_ALL en_US.UTF-8 ENV LANG en_US.utf8 ENV LANGUAGE en_US.UTF-8 -# Install libxdp & libbpf RUN mkdir /home/libcsp_sphinx_docs && \ cd /home/libcsp_sphinx_docs && \ git clone https://github.com/libcsp/libcsp.git --branch develop && \ cd libcsp && \ - pip3 install -r doc/requirements.txt && \ + pip3 install -r doc/requirements.txt --break-system-packages && \ cmake -B build-docs -S doc && \ cmake --build build-docs diff --git a/doc/INSTALL.md b/doc/INSTALL.md index 89cbe3950..b1395b875 100644 --- a/doc/INSTALL.md +++ b/doc/INSTALL.md @@ -88,6 +88,34 @@ use the following command: cmake --install builddir --component runtime ``` +### Building All Samples with CMake + +if you want to build tools and samples, define `CSP_BUILD_SAMPLES=ON` +when you run `cmake`. + +```shell +cmake -B builddir -DCSP_BUILD_SAMPLES=ON +``` + +### Python Bindings with CMake + +If you want to build Python bindings, define +`CSP_ENABLE_PYTHON3_BINDINGS=ON` when you run `cmake`. You also need +to enable the routing table (`CSP_USE_RTABLE`) when building the +Python bindings. + +```shell +cmake -B builddir -DCSP_ENABLE_PYTHON3_BINDINGS=ON -DCSP_USE_RTABLE=ON +``` + +To use the bindings, you need to install them to a location where +Python searches by default or specify the path to Python: + +``` +PYTHONPATH=builddir python3 -c 'import libcsp_py3 as csp' +``` + + ## Reproducible Builds libcsp supports Reproducible Builds. To enable it, set diff --git a/doc/api/csp_buffer_h.rst b/doc/api/csp_buffer_h.rst index 74cb87c8a..c7289b399 100644 --- a/doc/api/csp_buffer_h.rst +++ b/doc/api/csp_buffer_h.rst @@ -11,6 +11,7 @@ Interface Functions .. autocfunction:: csp_buffer.h::csp_buffer_free .. autocfunction:: csp_buffer.h::csp_buffer_free_isr .. autocfunction:: csp_buffer.h::csp_buffer_clone +.. autocfunction:: csp_buffer.h::csp_buffer_copy .. autocfunction:: csp_buffer.h::csp_buffer_remaining .. autocfunction:: csp_buffer.h::csp_buffer_init .. autocfunction:: csp_buffer.h::csp_buffer_refc_inc diff --git a/doc/api/csp_crc32_h.rst b/doc/api/csp_crc32_h.rst index b67087aa4..928bcfce1 100644 --- a/doc/api/csp_crc32_h.rst +++ b/doc/api/csp_crc32_h.rst @@ -9,3 +9,6 @@ Interface Functions .. autocfunction:: csp_crc32.h::csp_crc32_append .. autocfunction:: csp_crc32.h::csp_crc32_verify .. autocfunction:: csp_crc32.h::csp_crc32_memory +.. autocfunction:: csp_crc32.h::csp_crc32_init +.. autocfunction:: csp_crc32.h::csp_crc32_update +.. autocfunction:: csp_crc32.h::csp_crc32_final diff --git a/doc/api/csp_debug_h.rst b/doc/api/csp_debug_h.rst index 39b335972..45b2943af 100644 --- a/doc/api/csp_debug_h.rst +++ b/doc/api/csp_debug_h.rst @@ -2,4 +2,77 @@ CSP Debug ========= .. autocmodule:: csp_debug.h - :members: + +Variables +--------- + +.. autocdata:: csp_debug.h::csp_dbg_buffer_out + +.. autocdata:: csp_debug.h::csp_dbg_conn_out + +.. autocdata:: csp_debug.h::csp_dbg_conn_ovf + +.. autocdata:: csp_debug.h::csp_dbg_conn_noroute + +.. autocdata:: csp_debug.h::csp_dbg_inval_reply + +.. autocdata:: csp_debug.h::csp_dbg_errno + +.. autocdata:: csp_debug.h::csp_dbg_can_errno + +.. autocdata:: csp_debug.h::csp_dbg_eth_errno + +.. autocdata:: csp_debug.h::csp_dbg_rdp_print + +.. autocdata:: csp_debug.h::csp_dbg_packet_print + +Defines +------- + +General error codes +~~~~~~~~~~~~~~~~~~~ + +.. autocmacro:: csp_debug.h::CSP_DBG_ERR_CORRUPT_BUFFER +.. autocmacro:: csp_debug.h::CSP_DBG_ERR_MTU_EXCEEDED +.. autocmacro:: csp_debug.h::CSP_DBG_ERR_ALREADY_FREE +.. autocmacro:: csp_debug.h::CSP_DBG_ERR_REFCOUNT +.. autocmacro:: csp_debug.h::CSP_DBG_ERR_INVALID_RTABLE_ENTRY +.. autocmacro:: csp_debug.h::CSP_DBG_ERR_UNSUPPORTED +.. autocmacro:: csp_debug.h::CSP_DBG_ERR_INVALID_BIND_PORT +.. autocmacro:: csp_debug.h::CSP_DBG_ERR_PORT_ALREADY_IN_USE +.. autocmacro:: csp_debug.h::CSP_DBG_ERR_ALREADY_CLOSED +.. autocmacro:: csp_debug.h::CSP_DBG_ERR_INVALID_POINTER +.. autocmacro:: csp_debug.h::CSP_DBG_ERR_CLOCK_SET_FAIL + +CAN-specific error codes +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autocmacro:: csp_debug.h::CSP_DBG_CAN_ERR_FRAME_LOST +.. autocmacro:: csp_debug.h::CSP_DBG_CAN_ERR_RX_OVF +.. autocmacro:: csp_debug.h::CSP_DBG_CAN_ERR_RX_OUT +.. autocmacro:: csp_debug.h::CSP_DBG_CAN_ERR_SHORT_BEGIN +.. autocmacro:: csp_debug.h::CSP_DBG_CAN_ERR_INCOMPLETE +.. autocmacro:: csp_debug.h::CSP_DBG_CAN_ERR_UNKNOWN + +ETH-specific error codes +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autocmacro:: csp_debug.h::CSP_DBG_ETH_ERR_FRAME_LOST +.. autocmacro:: csp_debug.h::CSP_DBG_ETH_ERR_RX_OVF +.. autocmacro:: csp_debug.h::CSP_DBG_ETH_ERR_RX_OUT +.. autocmacro:: csp_debug.h::CSP_DBG_ETH_ERR_SHORT_BEGIN +.. autocmacro:: csp_debug.h::CSP_DBG_ETH_ERR_INCOMPLETE +.. autocmacro:: csp_debug.h::CSP_DBG_ETH_ERR_UNKNOWN + +Print macros +~~~~~~~~~~~~ + +.. autocmacro:: csp_debug.h::csp_print +.. autocmacro:: csp_debug.h::csp_rdp_error +.. autocmacro:: csp_debug.h::csp_rdp_protocol +.. autocmacro:: csp_debug.h::csp_print_packet + +Functions +--------- + +.. autocfunction:: csp_debug.h::csp_print_func diff --git a/doc/api/csp_error_h.rst b/doc/api/csp_error_h.rst index deb5322ad..7d6803d04 100644 --- a/doc/api/csp_error_h.rst +++ b/doc/api/csp_error_h.rst @@ -20,3 +20,4 @@ Error Codes .. autocmacro:: csp_error.h::CSP_ERR_HMAC .. autocmacro:: csp_error.h::CSP_ERR_CRC32 .. autocmacro:: csp_error.h::CSP_ERR_SFP +.. autocmacro:: csp_error.h::CSP_ERR_MTU diff --git a/doc/api/csp_h.rst b/doc/api/csp_h.rst index 36a5dae2d..76708da36 100644 --- a/doc/api/csp_h.rst +++ b/doc/api/csp_h.rst @@ -25,7 +25,6 @@ Interface Functions ------------------- .. autocfunction:: csp.h::csp_init -.. autocfunction:: csp.h::csp_free_resources .. autocfunction:: csp.h::csp_get_conf .. autocfunction:: csp.h::csp_id_copy .. autocfunction:: csp.h::csp_accept diff --git a/doc/api/csp_hooks_h.rst b/doc/api/csp_hooks_h.rst index f60164d7f..b3439c404 100644 --- a/doc/api/csp_hooks_h.rst +++ b/doc/api/csp_hooks_h.rst @@ -2,4 +2,18 @@ CSP Hooks ========= .. autocmodule:: csp_hooks.h - :members: + +Hook Functions +-------------- + +.. autocfunction:: csp_hooks.h::csp_output_hook +.. autocfunction:: csp_hooks.h::csp_input_hook +.. autocfunction:: csp_hooks.h::csp_reboot_hook +.. autocfunction:: csp_hooks.h::csp_shutdown_hook +.. autocfunction:: csp_hooks.h::csp_memfree_hook +.. autocfunction:: csp_hooks.h::csp_ps_hook +.. autocfunction:: csp_hooks.h::csp_panic +.. autocfunction:: csp_hooks.h::csp_crypto_decrypt +.. autocfunction:: csp_hooks.h::csp_crypto_encrypt +.. autocfunction:: csp_hooks.h::csp_clock_get_time +.. autocfunction:: csp_hooks.h::csp_clock_set_time diff --git a/doc/api/csp_id_h.rst b/doc/api/csp_id_h.rst index 0bf0933d9..259e9f14d 100644 --- a/doc/api/csp_id_h.rst +++ b/doc/api/csp_id_h.rst @@ -2,4 +2,20 @@ CSP ID ====== .. autocmodule:: csp_id.h - :members: + +Functions +--------- + +.. autocfunction:: csp_id.h::csp_id_prepend +.. autocfunction:: csp_id.h::csp_id_strip +.. autocfunction:: csp_id.h::csp_id_setup_rx +.. autocfunction:: csp_id.h::csp_id_get_host_bits +.. autocfunction:: csp_id.h::csp_id_get_max_nodeid +.. autocfunction:: csp_id.h::csp_id_get_max_port +.. autocfunction:: csp_id.h::csp_id_is_broadcast + +Fixup Functions for ZMQ CSP v1 little-endian +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autocfunction:: csp_id.h::csp_id_prepend_fixup_cspv1 +.. autocfunction:: csp_id.h::csp_id_strip_fixup_cspv1 diff --git a/doc/api/csp_sfp_h.rst b/doc/api/csp_sfp_h.rst index d9a917561..88dbb0304 100644 --- a/doc/api/csp_sfp_h.rst +++ b/doc/api/csp_sfp_h.rst @@ -6,7 +6,8 @@ Small Fragmentation Protocol (SFP) Interface Functions ------------------- -.. autocfunction:: csp_sfp.h::csp_sfp_send_own_memcpy +.. autocfunction:: csp_sfp.h::csp_sfp_opts_max_mtu +.. autocfunction:: csp_sfp.h::csp_sfp_conn_max_mtu .. autocfunction:: csp_sfp.h::csp_sfp_send .. autocfunction:: csp_sfp.h::csp_sfp_recv_fp .. autocfunction:: csp_sfp.h::csp_sfp_recv diff --git a/doc/api/drivers/can_zephyr_h.rst b/doc/api/drivers/can_zephyr_h.rst new file mode 100644 index 000000000..4b3bcdc2c --- /dev/null +++ b/doc/api/drivers/can_zephyr_h.rst @@ -0,0 +1,11 @@ +Socket CAN driver (Zephyr) +========================== + +.. autocmodule:: drivers/can_zephyr.h + +Interface Functions +------------------- + +.. autocfunction:: drivers/can_zephyr.h::csp_can_open_and_add_interface +.. autocfunction:: drivers/can_zephyr.h::csp_can_set_rx_filter +.. autocfunction:: drivers/can_zephyr.h::csp_can_stop diff --git a/doc/api/drivers/drivers.rst b/doc/api/drivers/drivers.rst index fa442241d..3beeff18d 100644 --- a/doc/api/drivers/drivers.rst +++ b/doc/api/drivers/drivers.rst @@ -5,5 +5,6 @@ Drivers :maxdepth: 4 can_socketcan_h + can_zephyr_h eth_linux_h usart_h diff --git a/doc/basic.md b/doc/basic.md index 60ad6d00b..541212510 100644 --- a/doc/basic.md +++ b/doc/basic.md @@ -48,6 +48,10 @@ version is for task-context and interrupt-context. Using fixed size buffer elements that are preallocated is again a question of speed and safety. +if you use `csp_buffer_get_always()` instead csp will panic if there is +not enough available buffers. This ensures that incoming hardware +always gets a buffer, or the system will reboot. + Definition of a buffer element `csp_packet_t`: ```c @@ -62,7 +66,6 @@ Definition of a buffer element `csp_packet_t`: the CSP id to different endian (e.g. I2C), etc. */ typedef struct { - uint32_t rdp_quarantine; // EACK quarantine period uint32_t timestamp_tx; // Time the message was sent uint32_t timestamp_rx; // Time the message was received @@ -73,7 +76,7 @@ typedef struct { uint16_t frame_length; /* Additional header bytes, to prepend packed data before transmission - * This must be minimum 6 bytes to accomodate CSP 2.0. But some implementations + * This must be minimum 6 bytes to accommodate CSP 2.0. But some implementations * require much more scratch working area for encryption for example. * * Ultimately after csp_id_pack() this area will be filled with the CSP header @@ -222,7 +225,7 @@ time). > `static` routing table has the > fastest lookup, but requires more setup. > - cidr (Classless Inter-Domain Routing): supports a one-to-many -> mapping, meaning routes can be configued for a range of +> mapping, meaning routes can be configured for a range of > destination addresses. The `cidr` is > a bit slower for lookup, but simple to setup. diff --git a/doc/build-doc.md b/doc/build-doc.md index 0cdebae96..5838d0ea8 100644 --- a/doc/build-doc.md +++ b/doc/build-doc.md @@ -12,5 +12,5 @@ python3 -m venv venv pip install -r doc/requirements.txt mkdir builddir cmake -S doc -B builddir -cmake --build +cmake --build builddir ``` diff --git a/doc/conf.py b/doc/conf.py index 3b05aa2e6..bbd3ba2bf 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -10,8 +10,33 @@ import pygit2 # -- Path setup -------------------------------------------------------------- -cl.Config.set_library_file('/usr/lib/llvm-14/lib/libclang-14.so') -cl.Config.set_library_path('/usr/lib/llvm-14/lib/libclang-14.so') +def find_libclang() -> str: + """ + Searches for the `libclang-xx.so` file in common system directories. Where + 'xx' is the current libclang version installed on the system. + + This function looks for the shared library file `libclang-xx.so` in typical + installation paths such as `/usr/lib`, `/usr/lib/llvm`, and `/usr/lib/x86_64-linux-gnu`. + + Returns: + str: The full path to the `libclang-xx.so` file if found. + + Raises: + FileNotFoundError: If the `libclang-xx.so` file cannot be located in the + predefined search paths. + """ + search_paths = [Path('/usr/lib'), Path('/usr/lib/llvm'), Path('/usr/lib/x86_64-linux-gnu')] + + for path in search_paths: + if path.exists() and path.is_dir(): + for file in path.rglob('libclang*.so'): # Recursively search for matching files + return str(file) # Return the first match as a string + + raise FileNotFoundError("libclang not found in the predefined search paths.") + +libclang_path = find_libclang() +cl.Config.set_library_file(libclang_path) +cl.Config.set_library_path(libclang_path) # -- Project information ----------------------------------------------------- project = 'Lib CSP' @@ -31,7 +56,8 @@ 'sphinx_c_autodoc.viewcode', "sphinx_design", "sphinx_git", - "sphinx_copybutton" + "sphinx_copybutton", + "sphinx.ext.githubpages" ] # Add any paths that contain templates here, relative to this directory. @@ -66,7 +92,6 @@ 'navigation_depth': 2, 'includehidden': True, 'titles_only': True, - 'sticky_navigation': True } def include_readme_file(app, docname, source): diff --git a/doc/git-commit.md b/doc/git-commit.md index 8671bf32e..d0c25b2c9 100644 --- a/doc/git-commit.md +++ b/doc/git-commit.md @@ -70,7 +70,7 @@ These are examples taken from our own commit hisotry. cmake: Fix python binding option, change to py3 The previous option name for the python bindings, - 'enable-python3-bindings' is not a valid varaible name in CMAKE. + 'enable-python3-bindings' is not a valid variable name in CMAKE. Also only Python3 is supported forwards so the CMake package Python3 is now used. @@ -90,7 +90,7 @@ These are examples taken from our own commit hisotry. All RX functions need to check for overflow of the packet data field. All TX functions that is askd to transmit a csp packet larger than their - underlaying layer cannot handle, should drop the packet. + underlying layer cannot handle, should drop the packet. In-the field MTU analysis is done by sending larger and larger ping packets over the network to determine what the end-to-end MTU is. diff --git a/doc/index.md b/doc/index.md index 0c59079af..847c67088 100644 --- a/doc/index.md +++ b/doc/index.md @@ -15,6 +15,17 @@ INSTALL build-doc ``` +```{toctree} +:caption: how to run samples +:hidden: + +samples/posix/simple-send-canbus +samples/posix/simple-send-udp +samples/posix/simple-send-usart +samples/posix/simple-send-zmq +samples/posix/simple-sfp-send-recv +``` + ```{toctree} :caption: CSP API :hidden: diff --git a/doc/memory.md b/doc/memory.md index 6f7381a7a..f18c92092 100644 --- a/doc/memory.md +++ b/doc/memory.md @@ -9,7 +9,7 @@ allocation during initialization, where all structures are allocated: port tables, connection pools, buffer pools, message queues, semaphores, tasks, etc. -Once the initiallization is complete, there are only a few functions +Once the initialization is complete, there are only a few functions that uses dynamic allocation, such as: - `csp_sfp_recv()` - sending larger memory chunks than can fit into a diff --git a/doc/outflow.md b/doc/outflow.md index 2c6ac8fe8..70295d32e 100644 --- a/doc/outflow.md +++ b/doc/outflow.md @@ -16,7 +16,7 @@ copies id to packet csp_send_direct() ----------------- called by: send, sendto, router, rdp - perfoms outgoing routing, selects interface + performs outgoing routing, selects interface csp_send_direct_iface() ----------------------- diff --git a/doc/protocolstack.md b/doc/protocolstack.md index 4317347b2..1a58f0b33 100644 --- a/doc/protocolstack.md +++ b/doc/protocolstack.md @@ -118,6 +118,6 @@ a few additional features: - Packet re-ordering - Retransmission - Windowing - - Extended Acknowledgment + - Extended Acknowledgment (deliberately not supported) For more information on this, please refer to RFC908 and RFC1151. diff --git a/doc/samples/posix/simple-send-canbus.md b/doc/samples/posix/simple-send-canbus.md new file mode 100644 index 000000000..bf06fe4ee --- /dev/null +++ b/doc/samples/posix/simple-send-canbus.md @@ -0,0 +1 @@ +```{include} ../../../samples/posix/simple-send-canbus/README.md diff --git a/doc/samples/posix/simple-send-udp.md b/doc/samples/posix/simple-send-udp.md new file mode 100644 index 000000000..71dab4fbc --- /dev/null +++ b/doc/samples/posix/simple-send-udp.md @@ -0,0 +1 @@ +```{include} ../../../samples/posix/simple-send-udp/README.md diff --git a/doc/samples/posix/simple-send-usart.md b/doc/samples/posix/simple-send-usart.md new file mode 100644 index 000000000..7e50b460f --- /dev/null +++ b/doc/samples/posix/simple-send-usart.md @@ -0,0 +1 @@ +```{include} ../../../samples/posix/simple-send-usart/README.md diff --git a/doc/samples/posix/simple-send-zmq.md b/doc/samples/posix/simple-send-zmq.md new file mode 100644 index 000000000..5ac91578a --- /dev/null +++ b/doc/samples/posix/simple-send-zmq.md @@ -0,0 +1 @@ +```{include} ../../../samples/posix/simple-send-zmq/README.md diff --git a/doc/samples/posix/simple-sfp-send-recv.md b/doc/samples/posix/simple-sfp-send-recv.md new file mode 100644 index 000000000..dcef6a37c --- /dev/null +++ b/doc/samples/posix/simple-sfp-send-recv.md @@ -0,0 +1 @@ +```{include} ../../../samples/posix/simple-sfp-send-recv/README.md diff --git a/doc/structure.md b/doc/structure.md index b3b0eacc1..9ed68f040 100644 --- a/doc/structure.md +++ b/doc/structure.md @@ -14,15 +14,21 @@ following table: | `libcsp/src/arch` | Architecture (platform) specific code | | `libcsp/src/arch/freertos` | FreeRTOS | | `libcsp/src/arch/posix` | Posix (Linux) | -| `libcsp/src/arch/windows` | Windows | +| `libcsp/src/arch/zephyr` | Zephyr | | `libcsp/src/bindings/python` | Python3 wrapper for libcsp | | `libcsp/src/crypto` | HMAC, SHA | | `libcsp/src/drivers` | Drivers, mostly platform specific (Linux) | | `libcsp/src/drivers/can` | CAN | | `libcsp/src/drivers/usart` | USART | +| `libcsp/src/drivers/eth` | ETH | | `libcsp/src/interfaces` | Interfaces, CAN, I2C, KISS, LOOPBACK, ZMQHUB and others | -| `libcsp/src/rtable` | Routing tables | -| `libcsp/src/transport` | Transport layer: UDP, RDP | | `libcsp/utils` | Utilities, Python scripts for decoding CSP headers. | | `libcsp/examples` | CSP examples, C/Python, zmqproxy | | `libcsp/doc` | RST based documentation (this documentation) | +| `libcsp/samples` | Simple sample | +| `libcsp/samples/posix` | Posix simple sample | +| `libcsp/unittests` | Unittests | +| `libcsp/contrib` | Community contributions | +| `libcsp/contrib/macosx` | MacOSx | +| `libcsp/contrib/windows` | Windows | +| `libcsp/contrib/zephyr` | Zephyr samples | diff --git a/doc/tunnel.md b/doc/tunnel.md index fab035bfa..ba34fcf4e 100644 --- a/doc/tunnel.md +++ b/doc/tunnel.md @@ -8,7 +8,7 @@ The usage is best explained using a little example. So lets start with a satelli 0/8 satellite bus (node id 0-63) 64/8 mission control bus (node id 64-127) -Let's say we wish to create an encrypted tunned between these two. We define a new "open" network to sit +Let's say we wish to create an encrypted tunnel between these two. We define a new "open" network to sit between the satellite and the ground station 128/8 open / insecure transport network (128-195) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 6fce8b760..ba76fbcda 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,21 +1,28 @@ -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - add_executable(csp_server_client EXCLUDE_FROM_ALL csp_server_client.c csp_server_client_posix.c) - add_executable(csp_server EXCLUDE_FROM_ALL csp_server.c csp_server_posix.c) - add_executable(csp_client EXCLUDE_FROM_ALL csp_client.c csp_client_posix.c) +if(CSP_POSIX) + + add_library(csp_posix_helper OBJECT ${CSP_SAMPLES_EXCLUDE} csp_posix_helper.c) + add_executable(csp_arch ${CSP_SAMPLES_EXCLUDE} csp_arch.c) + add_executable(csp_server_client ${CSP_SAMPLES_EXCLUDE} csp_server_client.c) + add_executable(csp_server ${CSP_SAMPLES_EXCLUDE} csp_server.c) + add_executable(csp_client ${CSP_SAMPLES_EXCLUDE} csp_client.c) + add_executable(csp_bridge_can2udp ${CSP_SAMPLES_EXCLUDE} csp_bridge_can2udp.c) + add_executable(zmqproxy ${CSP_SAMPLES_EXCLUDE} zmqproxy.c) + add_executable(csp_sfp_server_client ${CSP_SAMPLES_EXCLUDE} csp_sfp_server_client.c) + + target_include_directories(csp_posix_helper PRIVATE ${csp_inc}) + target_include_directories(csp_arch PRIVATE ${csp_inc}) target_include_directories(csp_server_client PRIVATE ${csp_inc}) target_include_directories(csp_server PRIVATE ${csp_inc}) target_include_directories(csp_client PRIVATE ${csp_inc}) - target_link_libraries(csp_server_client PRIVATE csp Threads::Threads) - target_link_libraries(csp_server PRIVATE csp Threads::Threads) - target_link_libraries(csp_client PRIVATE csp Threads::Threads) -endif() - -add_executable(csp_arch EXCLUDE_FROM_ALL csp_arch.c) -target_include_directories(csp_arch PRIVATE ${csp_inc}) -target_link_libraries(csp_arch PRIVATE csp) - -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - add_executable(zmqproxy EXCLUDE_FROM_ALL zmqproxy.c) target_include_directories(zmqproxy PRIVATE ${csp_inc} ${LIBZMQ_INCLUDE_DIRS}) - target_link_libraries(zmqproxy PRIVATE csp Threads::Threads ${LIBZMQ_LIBRARIES}) + target_include_directories(csp_sfp_server_client PRIVATE ${csp_inc}) + + target_link_libraries(csp_posix_helper PRIVATE csp_common) + target_link_libraries(csp_arch PRIVATE csp csp_common) + target_link_libraries(csp_server_client PRIVATE csp csp_common csp_posix_helper Threads::Threads) + target_link_libraries(csp_server PRIVATE csp csp_common csp_posix_helper Threads::Threads) + target_link_libraries(csp_client PRIVATE csp csp_common csp_posix_helper Threads::Threads) + target_link_libraries(csp_bridge_can2udp PRIVATE csp csp_common) + target_link_libraries(zmqproxy PRIVATE csp csp_common Threads::Threads ${LIBZMQ_LIBRARIES}) + target_link_libraries(csp_sfp_server_client PRIVATE csp Threads::Threads) endif() diff --git a/examples/buildall.py b/examples/buildall.py index 17cce4db5..e25f0916a 100755 --- a/examples/buildall.py +++ b/examples/buildall.py @@ -12,8 +12,10 @@ def build_with_meson(): targets = ['examples/csp_server_client', 'examples/csp_server', 'examples/csp_client', + 'examples/csp_bridge_can2udp', 'examples/csp_arch', - 'examples/zmqproxy'] + 'examples/zmqproxy', + 'examples/csp_sfp_server_client'] builddir = 'build' meson_setup = ['meson', 'setup', builddir] @@ -23,17 +25,13 @@ def build_with_meson(): def build_with_cmake(): - targets = ['examples/csp_server_client', - 'examples/csp_server', - 'examples/csp_client', - 'examples/csp_arch', - 'examples/zmqproxy'] + build_samples = '-DCSP_BUILD_SAMPLES=ON' builddir = 'build' - cmake_setup = ['cmake', '-GNinja', '-B' + builddir] + cmake_setup = ['cmake', '-GNinja', '-B' + builddir, build_samples] cmake_compile = ['ninja', '-C', builddir] subprocess.check_call(cmake_setup) - subprocess.check_call(cmake_compile + targets) + subprocess.check_call(cmake_compile) def build_with_waf(): diff --git a/examples/csp_arch.c b/examples/csp_arch.c index 8cd3f4ba0..4842f94cb 100644 --- a/examples/csp_arch.c +++ b/examples/csp_arch.c @@ -1,4 +1,3 @@ - #include #include #include @@ -8,14 +7,23 @@ #include #include +#ifdef NDEBUG +#define ASSERT(expr) ((void)(expr)) +#else +#define ASSERT(expr) assert(expr) +#endif -int main(int argc, char * argv[]) { +void csp_panic(const char * msg) { + csp_print("csp_panic: %s\n", msg); + exit(1); +} +int main(void) { // clock - csp_timestamp_t csp_clock = {}; + csp_timestamp_t csp_clock = {0}; csp_clock_get_time(&csp_clock); - assert(csp_clock.tv_sec != 0); + ASSERT(csp_clock.tv_sec != 0); csp_print("csp_clock_get_time(..) -> sec:nsec = %"PRIu32":%"PRIu32"\n", csp_clock.tv_sec, csp_clock.tv_nsec); // relative time @@ -24,10 +32,10 @@ int main(int argc, char * argv[]) { const uint32_t sec1 = csp_get_s(); const uint32_t sec2 = csp_get_s_isr(); sleep(2); - assert(csp_get_ms() >= (msec1 + 500)); - assert(csp_get_ms_isr() >= (msec2 + 500)); - assert(csp_get_s() >= (sec1 + 1)); - assert(csp_get_s_isr() >= (sec2 + 1)); + ASSERT(csp_get_ms() >= (msec1 + 500)); + ASSERT(csp_get_ms_isr() >= (msec2 + 500)); + ASSERT(csp_get_s() >= (sec1 + 1)); + ASSERT(csp_get_s_isr() >= (sec2 + 1)); // queue handling uint32_t value; @@ -35,32 +43,32 @@ int main(int argc, char * argv[]) { csp_queue_handle_t q; char buf[3 * sizeof(value)]; q = csp_queue_create_static(3, sizeof(value), buf, &sq); - assert(csp_queue_size(q) == 0); - assert(csp_queue_size_isr(q) == 0); - assert(csp_queue_dequeue(q, &value, 0) == CSP_QUEUE_ERROR); - assert(csp_queue_dequeue(q, &value, 200) == CSP_QUEUE_ERROR); - assert(csp_queue_dequeue_isr(q, &value, NULL) == CSP_QUEUE_ERROR); + ASSERT(csp_queue_size(q) == 0); + ASSERT(csp_queue_size_isr(q) == 0); + ASSERT(csp_queue_dequeue(q, &value, 0) == CSP_QUEUE_ERROR); + ASSERT(csp_queue_dequeue(q, &value, 200) == CSP_QUEUE_ERROR); + ASSERT(csp_queue_dequeue_isr(q, &value, NULL) == CSP_QUEUE_ERROR); value = 1; - assert(csp_queue_enqueue(q, &value, 0) == CSP_QUEUE_OK); + ASSERT(csp_queue_enqueue(q, &value, 0) == CSP_QUEUE_OK); value = 2; - assert(csp_queue_enqueue(q, &value, 200) == CSP_QUEUE_OK); + ASSERT(csp_queue_enqueue(q, &value, 200) == CSP_QUEUE_OK); value = 3; - assert(csp_queue_enqueue_isr(q, &value, NULL) == CSP_QUEUE_OK); - assert(csp_queue_size(q) == 3); - assert(csp_queue_size_isr(q) == 3); + ASSERT(csp_queue_enqueue_isr(q, &value, NULL) == CSP_QUEUE_OK); + ASSERT(csp_queue_size(q) == 3); + ASSERT(csp_queue_size_isr(q) == 3); value = 10; - assert(csp_queue_enqueue(q, &value, 0) == CSP_QUEUE_ERROR); + ASSERT(csp_queue_enqueue(q, &value, 0) == CSP_QUEUE_ERROR); value = 20; - assert(csp_queue_enqueue(q, &value, 200) == CSP_QUEUE_ERROR); + ASSERT(csp_queue_enqueue(q, &value, 200) == CSP_QUEUE_ERROR); value = 30; - assert(csp_queue_enqueue_isr(q, &value, NULL) == CSP_QUEUE_ERROR); + ASSERT(csp_queue_enqueue_isr(q, &value, NULL) == CSP_QUEUE_ERROR); value = 100; - assert(csp_queue_dequeue(q, &value, 0) == CSP_QUEUE_OK); - assert(value == 1); - assert(csp_queue_dequeue(q, &value, 200) == CSP_QUEUE_OK); - assert(value == 2); - assert(csp_queue_dequeue_isr(q, &value, NULL) == CSP_QUEUE_OK); - assert(value == 3); + ASSERT(csp_queue_dequeue(q, &value, 0) == CSP_QUEUE_OK); + ASSERT(value == 1); + ASSERT(csp_queue_dequeue(q, &value, 200) == CSP_QUEUE_OK); + ASSERT(value == 2); + ASSERT(csp_queue_dequeue_isr(q, &value, NULL) == CSP_QUEUE_OK); + ASSERT(value == 3); return 0; } diff --git a/examples/csp_bridge_can2udp.c b/examples/csp_bridge_can2udp.c new file mode 100644 index 000000000..e5982d495 --- /dev/null +++ b/examples/csp_bridge_can2udp.c @@ -0,0 +1,138 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define DEFAULT_CAN_NAME "can0" +#define DEFAULT_UDP_ADDRESS "127.0.0.1" +#define DEFAULT_UDP_REMOTE_PORT (0) +#define DEFAULT_UDP_LOCAL_PORT (0) + +static struct option long_options[] = { + {"can", required_argument, 0, 'c'}, + {"remote-address", required_argument, 0, 'a'}, + {"remote-port", required_argument, 0, 'r'}, + {"local-port", required_argument, 0, 'l'}, + {"protocol-version", required_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} +}; + +/* Overwrite input hook to print packet information */ +void csp_input_hook(csp_iface_t * iface, csp_packet_t * packet) { + char dst_name[] = "UDP"; + + if (strncmp(iface->name, "UDP", strlen(iface->name)) == 0) { + strncpy(dst_name, "CAN", sizeof(dst_name)); + } + + csp_print("%s: %u(%u) --> %s: %u(%u), priority: %u, flags: 0x%02X, size: %" PRIu16 "\n", + iface->name, packet->id.src, packet->id.sport, + dst_name, packet->id.dst, packet->id.dport, + packet->id.pri, packet->id.flags, packet->length); +} + +static void print_help(void) { + csp_print("Usage: csp_bridge_can2udp [options]\n"); + csp_print(" --can set CAN interface\n"); + csp_print(" --remote-address
set UDP remote address\n" + " --remote-port set UDP remote port\n" + " --local-port set UDP local port\n" + " -v,--protocol-version set protocol version\n" + " -h,--help print help\n"); +} + +static csp_iface_t * add_can_iface(const char * can_name) +{ + csp_iface_t * iface = NULL; + + int error = csp_can_socketcan_open_and_add_interface(can_name, CSP_IF_CAN_DEFAULT_NAME, + 0, 1000000, true, &iface); + if (error != CSP_ERR_NONE) { + csp_print("Failed to add CAN interface [%s], error: %d\n", can_name, error); + exit(1); + } + + return iface; +} + +static csp_iface_t * add_udp_iface(char * address, int lport, int rport) +{ + csp_iface_t * iface = malloc(sizeof(csp_iface_t)); + csp_if_udp_conf_t * conf = malloc(sizeof(csp_if_udp_conf_t)); + + conf->host = address; + conf->lport = lport; + conf->rport = rport; + csp_if_udp_init(iface, conf); + + return iface; +} + +int main(int argc, char * argv[]) { + char default_can_name[] = DEFAULT_CAN_NAME; + char * can_name = default_can_name; + + char default_address[] = DEFAULT_UDP_ADDRESS; + char * address = default_address; + int rport = DEFAULT_UDP_REMOTE_PORT; + int lport = DEFAULT_UDP_LOCAL_PORT; + + csp_iface_t * can_iface; + csp_iface_t * udp_iface; + + int opt; + + while ((opt = getopt_long(argc, argv, "v:h", long_options, NULL)) != -1) { + switch (opt) { + case 'c': + can_name = optarg; + break; + case 'a': + address = optarg; + break; + case 'r': + rport = atoi(optarg); + break; + case 'l': + lport = atoi(optarg); + break; + case 'v': + csp_conf.version = atoi(optarg); + break; + case 'h': + print_help(); + exit(EXIT_SUCCESS); + case '?': + /* Invalid option or missing argument */ + print_help(); + exit(EXIT_FAILURE); + } + } + + /* Init CSP */ + csp_init(); + + /* Add interfaces */ + can_iface = add_can_iface(can_name); + udp_iface = add_udp_iface(address, lport, rport); + + /* Set interfaces to bridge */ + csp_bridge_set_interfaces(can_iface, udp_iface); + + /* Print interfaces list */ + csp_iflist_print(); + + /* Start bridge */ + while(1) { + csp_bridge_work(); + } + + return 0; +} diff --git a/examples/csp_client.c b/examples/csp_client.c index 37625a31a..6715f0e42 100644 --- a/examples/csp_client.c +++ b/examples/csp_client.c @@ -9,13 +9,14 @@ #include #include #include +#include - -/* This function must be provided in arch specific way */ -int router_start(void); +#include "csp_posix_helper.h" /* Server port, the port the server listens on for incoming connections from the client. */ -#define SERVER_PORT 10 +#define SERVER_PORT 10 +#define DEFAULT_UDP_REMOTE_PORT (1501) +#define DEFAULT_UDP_LOCAL_PORT (1500) /* Commandline options */ static uint8_t server_address = 0; @@ -31,6 +32,7 @@ enum DeviceType { DEVICE_CAN, DEVICE_KISS, DEVICE_ZMQ, + DEVICE_UDP, }; #define __maybe_unused __attribute__((__unused__)) @@ -55,15 +57,17 @@ static struct option long_options[] = { #else #define OPTION_R #endif + {"udp-address", required_argument, 0, 'u'}, {"interface-address", required_argument, 0, 'a'}, {"connect-to", required_argument, 0, 'C'}, + {"protocol-version", required_argument, 0, 'v'}, {"test-mode", no_argument, 0, 't'}, {"test-mode-with-sec", required_argument, 0, 'T'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; -void print_help() { +static void print_help(void) { csp_print("Usage: csp_client [options]\n"); if (CSP_HAVE_LIBSOCKETCAN) { csp_print(" -c set CAN device\n"); @@ -77,16 +81,18 @@ void print_help() { if (CSP_USE_RTABLE) { csp_print(" -R set routing table\n"); } + csp_print(" -u set UDP address\n"); if (1) { csp_print(" -a
set interface address\n" " -C
connect to server at address\n" + " -v set protocol version\n" " -t enable test mode\n" " -T enable test mode with running time in seconds\n" " -h print help\n"); } } -csp_iface_t * add_interface(enum DeviceType device_type, const char * device_name) +static csp_iface_t * add_interface(enum DeviceType device_type, const char * device_name) { csp_iface_t * default_iface = NULL; @@ -124,6 +130,19 @@ csp_iface_t * add_interface(enum DeviceType device_type, const char * device_nam default_iface->is_default = 1; } + if (device_type == DEVICE_UDP) { + default_iface = malloc(sizeof(csp_iface_t)); + static csp_if_udp_conf_t udp_conf; + + udp_conf.host = strdup(device_name); + udp_conf.lport = DEFAULT_UDP_LOCAL_PORT; + udp_conf.rport = DEFAULT_UDP_REMOTE_PORT; + + csp_if_udp_init(default_iface, &udp_conf); + default_iface->addr = client_address; + default_iface->is_default = 1; + } + return default_iface; } @@ -139,7 +158,7 @@ int main(int argc, char * argv[]) { int ret = EXIT_SUCCESS; int opt; - while ((opt = getopt_long(argc, argv, OPTION_c OPTION_z OPTION_R "k:a:C:tT:h", long_options, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, OPTION_c OPTION_z OPTION_R "k:u:a:C:v:tT:h", long_options, NULL)) != -1) { switch (opt) { case 'c': device_name = optarg; @@ -152,7 +171,11 @@ int main(int argc, char * argv[]) { case 'z': device_name = optarg; device_type = DEVICE_ZMQ; - break; + break; + case 'u': + device_name = optarg; + device_type = DEVICE_UDP; + break; #if (CSP_USE_RTABLE) case 'R': rtable = optarg; @@ -164,6 +187,9 @@ int main(int argc, char * argv[]) { case 'C': server_address = atoi(optarg); break; + case 'v': + csp_conf.version = atoi(optarg); + break; case 't': test_mode = true; break; diff --git a/examples/csp_client_posix.c b/examples/csp_posix_helper.c similarity index 87% rename from examples/csp_client_posix.c rename to examples/csp_posix_helper.c index be15f0de8..ca70a4947 100644 --- a/examples/csp_client_posix.c +++ b/examples/csp_posix_helper.c @@ -1,8 +1,10 @@ +#include "csp_posix_helper.h" + #include #include #include -static int csp_pthread_create(void * (*routine)(void *)) { +int csp_pthread_create(void * (*routine)(void *)) { pthread_attr_t attributes; pthread_t handle; @@ -26,6 +28,8 @@ static int csp_pthread_create(void * (*routine)(void *)) { static void * task_router(void * param) { + (void)param; + /* Here there be routing */ while (1) { csp_route_work(); diff --git a/examples/csp_posix_helper.h b/examples/csp_posix_helper.h new file mode 100644 index 000000000..9f4c22f59 --- /dev/null +++ b/examples/csp_posix_helper.h @@ -0,0 +1,4 @@ +#pragma once + +int router_start(void); +int csp_pthread_create(void * (*routine)(void *)); diff --git a/examples/csp_server.c b/examples/csp_server.c index e124c28dd..f3f163622 100644 --- a/examples/csp_server.c +++ b/examples/csp_server.c @@ -8,14 +8,14 @@ #include #include #include +#include - -/* These three functions must be provided in arch specific way */ -int router_start(void); -int server_start(void); +#include "csp_posix_helper.h" /* Server port, the port the server listens on for incoming connections from the client. */ -#define SERVER_PORT 10 +#define SERVER_PORT 10 +#define DEFAULT_UDP_REMOTE_PORT (1500) +#define DEFAULT_UDP_LOCAL_PORT (1501) /* Commandline options */ static uint8_t server_address = 0; @@ -30,12 +30,15 @@ enum DeviceType { DEVICE_CAN, DEVICE_KISS, DEVICE_ZMQ, + DEVICE_UDP, }; #define __maybe_unused __attribute__((__unused__)) /* Server task - handles requests from clients */ -void server(void) { +static void * server(void * param) { + + (void)param; csp_print("Server task started\n"); @@ -58,7 +61,7 @@ void server(void) { continue; } - /* Read packets on connection, timout is 100 mS */ + /* Read packets on connection, timeout is 50 mS */ csp_packet_t *packet; while ((packet = csp_read(conn, 50)) != NULL) { switch (csp_conn_dport(conn)) { @@ -80,7 +83,7 @@ void server(void) { csp_close(conn); } - return; + return NULL; } /* End of server task */ @@ -104,15 +107,17 @@ static struct option long_options[] = { #else #define OPTION_R #endif + {"udp-address", required_argument, 0, 'u'}, {"interface-address", required_argument, 0, 'a'}, {"connect-to", required_argument, 0, 'C'}, + {"protocol-version", required_argument, 0, 'v'}, {"test-mode", no_argument, 0, 't'}, {"test-mode-with-sec", required_argument, 0, 'T'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; -void print_help() { +static void print_help(void) { csp_print("Usage: csp_server [options]\n"); if (CSP_HAVE_LIBSOCKETCAN) { csp_print(" -c set CAN device\n"); @@ -126,15 +131,17 @@ void print_help() { if (CSP_USE_RTABLE) { csp_print(" -R set routing table\n"); } + csp_print(" -u set UDP address\n"); if (1) { csp_print(" -a
set interface address\n" + " -v set protocol version\n" " -t enable test mode\n" " -T enable test mode with running time in seconds\n" " -h print help\n"); } } -csp_iface_t * add_interface(enum DeviceType device_type, const char * device_name) +static csp_iface_t * add_interface(enum DeviceType device_type, const char * device_name) { csp_iface_t * default_iface = NULL; @@ -172,6 +179,19 @@ csp_iface_t * add_interface(enum DeviceType device_type, const char * device_nam default_iface->is_default = 1; } + if (device_type == DEVICE_UDP) { + default_iface = malloc(sizeof(csp_iface_t)); + static csp_if_udp_conf_t udp_conf; + + udp_conf.host = strdup(device_name); + udp_conf.lport = DEFAULT_UDP_LOCAL_PORT; + udp_conf.rport = DEFAULT_UDP_REMOTE_PORT; + + csp_if_udp_init(default_iface, &udp_conf); + default_iface->addr = server_address; + default_iface->is_default = 1; + } + return default_iface; } @@ -184,7 +204,7 @@ int main(int argc, char * argv[]) { csp_iface_t * default_iface; int opt; - while ((opt = getopt_long(argc, argv, OPTION_c OPTION_z OPTION_R "k:a:tT:h", long_options, NULL)) != -1) { + while ((opt = getopt_long(argc, argv, OPTION_c OPTION_z OPTION_R "k:u:a:v:tT:h", long_options, NULL)) != -1) { switch (opt) { case 'c': device_name = optarg; @@ -197,7 +217,11 @@ int main(int argc, char * argv[]) { case 'z': device_name = optarg; device_type = DEVICE_ZMQ; - break; + break; + case 'u': + device_name = optarg; + device_type = DEVICE_UDP; + break; #if (CSP_USE_RTABLE) case 'R': rtable = optarg; @@ -206,6 +230,9 @@ int main(int argc, char * argv[]) { case 'a': server_address = atoi(optarg); break; + case 'v': + csp_conf.version = atoi(optarg); + break; case 't': test_mode = true; break; @@ -266,7 +293,7 @@ int main(int argc, char * argv[]) { } /* Start server thread */ - server_start(); + csp_pthread_create(server); /* Wait for execution to end (ctrl+c) */ while(1) { diff --git a/examples/csp_server_client.c b/examples/csp_server_client.c index 2ded19ec4..da1bd466c 100644 --- a/examples/csp_server_client.c +++ b/examples/csp_server_client.c @@ -8,11 +8,7 @@ #include #include - -/* These three functions must be provided in arch specific way */ -int router_start(void); -int server_start(void); -int client_start(void); +#include "csp_posix_helper.h" /* Server port, the port the server listens on for incoming connections from the client. */ #define MY_SERVER_PORT 10 @@ -26,7 +22,9 @@ static unsigned int server_received = 0; static unsigned int run_duration_in_sec = 3; /* Server task - handles requests from clients */ -void server(void) { +static void * server(void * param) { + + (void)param; csp_print("Server task started\n"); @@ -49,7 +47,7 @@ void server(void) { continue; } - /* Read packets on connection, timout is 100 mS */ + /* Read packets on connection, timeout is 100 mS */ csp_packet_t *packet; while ((packet = csp_read(conn, 50)) != NULL) { switch (csp_conn_dport(conn)) { @@ -72,13 +70,15 @@ void server(void) { } - return; + return NULL; } /* End of server task */ /* Client task sending requests to server task */ -void client(void) { +static void * client(void * param) { + + (void)param; csp_print("Client task started\n"); @@ -104,15 +104,14 @@ void client(void) { if (conn == NULL) { /* Connect failed */ csp_print("Connection failed\n"); - return; + return NULL; } /* 2. Get packet buffer for message/data */ csp_packet_t * packet = csp_buffer_get(0); if (packet == NULL) { - /* Could not get buffer element */ - csp_print("Failed to get CSP buffer\n"); - return; + csp_print("Failed to get buffer\n"); + csp_close(conn); } /* 3. Copy data to packet */ @@ -131,13 +130,14 @@ void client(void) { csp_close(conn); } - return; + return NULL; } /* End of client task */ static void print_usage(void) { csp_print("Usage:\n" + " -v set protocol version\n" " -t enable test mode\n" " -T enable test mode with running time in seconds\n" " -h print help\n"); @@ -148,7 +148,7 @@ int main(int argc, char * argv[]) { uint8_t address = 0; int opt; - while ((opt = getopt(argc, argv, "tT:h")) != -1) { + while ((opt = getopt(argc, argv, "v:tT:h")) != -1) { switch (opt) { case 'a': address = atoi(optarg); @@ -156,6 +156,9 @@ int main(int argc, char * argv[]) { case 'r': server_address = atoi(optarg); break; + case 'v': + csp_conf.version = atoi(optarg); + break; case 't': test_mode = true; break; @@ -196,10 +199,10 @@ int main(int argc, char * argv[]) { csp_iflist_print(); /* Start server thread */ - server_start(); + csp_pthread_create(server); /* Start client thread */ - client_start(); + csp_pthread_create(client); /* Wait for execution to end (ctrl+c) */ while(1) { diff --git a/examples/csp_server_client.py b/examples/csp_server_client.py index 6e42768ac..ad56a85b3 100644 --- a/examples/csp_server_client.py +++ b/examples/csp_server_client.py @@ -4,7 +4,7 @@ import time import threading import libcsp_py3 as csp -from typing import Any, Callable +from typing import Callable def printer(node: str, color: str) -> Callable: @@ -32,7 +32,7 @@ def server_task(addr: int, port: int) -> None: while (packet := csp.read(conn, 50)) is not None: if csp.conn_dport(conn) == port: - _print('Recieved on {port}: {data}'.format( + _print('Received on {port}: {data}'.format( port=port, data=csp.packet_get_data(packet).decode('utf-8')) ) diff --git a/examples/csp_server_client_posix.c b/examples/csp_server_client_posix.c deleted file mode 100644 index a96d935e4..000000000 --- a/examples/csp_server_client_posix.c +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include -#include - -void server(void); -void client(void); - -static int csp_pthread_create(void * (*routine)(void *)) { - - pthread_attr_t attributes; - pthread_t handle; - int ret; - - if (pthread_attr_init(&attributes) != 0) { - return CSP_ERR_NOMEM; - } - /* no need to join with thread to free its resources */ - pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED); - - ret = pthread_create(&handle, &attributes, routine, NULL); - pthread_attr_destroy(&attributes); - - if (ret != 0) { - return ret; - } - - return CSP_ERR_NONE; -} - -static void * task_router(void * param) { - - /* Here there be routing */ - while (1) { - csp_route_work(); - } - - return NULL; -} - -static void * task_server(void * param) { - server(); - return NULL; -} - -static void * task_client(void * param) { - client(); - return NULL; -} - -int router_start(void) { - return csp_pthread_create(task_router); -} - -int server_start(void) { - return csp_pthread_create(task_server); -} - -int client_start(void) { - return csp_pthread_create(task_client); -} diff --git a/examples/csp_server_client_windows.c b/examples/csp_server_client_windows.c deleted file mode 100644 index 94ce03d05..000000000 --- a/examples/csp_server_client_windows.c +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include - -void server(void); -void client(void); - -static int csp_win_thread_create(unsigned int (* routine)(void *)) { - - uintptr_t ret = _beginthreadex(NULL, 0, routine, NULL, 0, NULL); - if (ret == 0) { - return CSP_ERR_NOMEM; - } - - return CSP_ERR_NONE; -} - -static unsigned int task_router(void * param) { - - /* Here there be routing */ - while (1) { - csp_route_work(); - } - - return 0; -} - -static unsigned int task_server(void * param) { - server(); - return 0; -} - -static unsigned int task_client(void * param) { - client(); - return 0; -} - -int router_start(void) { - return csp_win_thread_create(task_router); -} - -int server_start(void) { - return csp_win_thread_create(task_server); -} - -int client_start(void) { - return csp_win_thread_create(task_client); -} diff --git a/examples/csp_server_posix.c b/examples/csp_server_posix.c deleted file mode 100644 index 5ec5fbc32..000000000 --- a/examples/csp_server_posix.c +++ /dev/null @@ -1,50 +0,0 @@ -#include -#include -#include - -void server(void); - -static int csp_pthread_create(void * (*routine)(void *)) { - - pthread_attr_t attributes; - pthread_t handle; - int ret; - - if (pthread_attr_init(&attributes) != 0) { - return CSP_ERR_NOMEM; - } - /* no need to join with thread to free its resources */ - pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED); - - ret = pthread_create(&handle, &attributes, routine, NULL); - pthread_attr_destroy(&attributes); - - if (ret != 0) { - return ret; - } - - return CSP_ERR_NONE; -} - -static void * task_router(void * param) { - - /* Here there be routing */ - while (1) { - csp_route_work(); - } - - return NULL; -} - -static void * task_server(void * param) { - server(); - return NULL; -} - -int router_start(void) { - return csp_pthread_create(task_router); -} - -int server_start(void) { - return csp_pthread_create(task_server); -} diff --git a/examples/csp_sfp_server_client.c b/examples/csp_sfp_server_client.c new file mode 100644 index 000000000..7c43de635 --- /dev/null +++ b/examples/csp_sfp_server_client.c @@ -0,0 +1,334 @@ +/* needed for pthread_timedjoin_np */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RECEIVER_ADDRESS 0 +#define RECEIVER_PORT 10 +#define TIMEOUT 10000 /* in ms */ +#define perr(fmt, ...) do { fprintf(stderr, "line %d: " fmt "\n", __LINE__, ##__VA_ARGS__); fflush(stderr); } while (0) +#define print(fmt, ...) do { fprintf(stderr, fmt "\n", ##__VA_ARGS__); fflush(stderr); } while (0) + +typedef struct { + uint32_t size; + uint32_t crc; +} sfp_data_t; + +typedef struct { + int size; + int timeout; + int rdp; + int mtu; +} test_options_t; + +static test_options_t test_options = { .size = 1000000, .rdp = 0, .timeout = 30, .mtu = 128 }; +static uint32_t sender_crc = 0; +static uint32_t receiver_crc = 0xffffffff; +static uint32_t received_sz = 0; + +static const char * csp_error_to_str(int err) { + switch (err) { + case CSP_ERR_NONE: return "No error"; + case CSP_ERR_NOMEM: return "Not enough memory"; + case CSP_ERR_INVAL: return "Invalid argument"; + case CSP_ERR_TIMEDOUT: return "Operation timed out"; + case CSP_ERR_USED: return "Resource already in use"; + case CSP_ERR_NOTSUP: return "Operation not supported"; + case CSP_ERR_BUSY: return "Device or resource busy"; + case CSP_ERR_ALREADY: return "Connection already in progress"; + case CSP_ERR_RESET: return "Connection reset"; + case CSP_ERR_NOBUFS: return "No more buffer space available"; + case CSP_ERR_TX: return "Transmission failed"; + case CSP_ERR_DRIVER: return "Error in driver layer"; + case CSP_ERR_AGAIN: return "Resource temporarily unavailable"; + case CSP_ERR_NOSYS: return "Function not implemented"; + case CSP_ERR_HMAC: return "HMAC failed"; + case CSP_ERR_CRC32: return "CRC32 failed"; + case CSP_ERR_SFP: return "SFP protocol error or inconsistency"; + case CSP_ERR_MTU: return "Invalid MTU"; + default: return "Unknown error"; + } +} + +static void print_help(const char * program_name) { + uint32_t max_mtu_nordp = csp_sfp_opts_max_mtu(CSP_O_CRC32); + uint32_t max_mtu_rdp = csp_sfp_opts_max_mtu(CSP_O_CRC32 | CSP_O_RDP); + + print("Usage: %s [OPTIONS]", program_name); + print("Options:"); + print(" -r,--rdp=VALUE Enable/Disable (1 or 0) RDP mode (optional)."); + print(" Default is %s", test_options.rdp ? "Enabled" : "Disabled"); + print(" -t,--timeout=SECONDS Set the timeout in seconds (optional)."); + print(" Values above INT_MAX might crash the program."); + print(" Default is %d seconds", test_options.timeout); + print(" -s,--size=BYTES Set the total transfer size in bytes (optional)."); + print(" Values above INT_MAX might crash the program"); + print(" Default is %d bytes", test_options.size); + print(" -m,--mtu=BYTES Set the maximum transfer unit in bytes (optional)."); + print(" Values above INT_MAX might crash the program"); + print(" Default is %d bytes", test_options.mtu); + print(" Max mtu with RDP disabled is %d bytes", max_mtu_nordp); + print(" Max mtu with RDP enabled is %d bytes", max_mtu_rdp); + print(" -h,--help Show this help message."); +} + +static void process_args(int argc, char * argv[], test_options_t * test_opts) { + static struct option long_options[] = { + { "rdp", required_argument, NULL, 'r' }, + { "timeout", required_argument, NULL, 't' }, + { "size", required_argument, NULL, 's' }, + { "mtu", required_argument, NULL, 'm' }, + { "help", no_argument, NULL, 'h' }, + { NULL, no_argument, NULL, 0 } /* Terminator */ + }; + + int opt; + + while ((opt = getopt_long(argc, argv, "r:t:s:m:h", long_options, NULL)) != -1) { + switch (opt) { + case 'r': + test_opts->rdp = atoi(optarg); + if (test_opts->rdp < 0) { + perr("'rdp' must be non-negative."); + exit(EXIT_FAILURE); + } + break; + + case 't': + test_opts->timeout = atoi(optarg); + if (test_opts->timeout < 0) { + perr("'timeout' must be non-negative."); + exit(EXIT_FAILURE); + } + break; + + case 's': + test_opts->size = atoi(optarg); + if (test_opts->size < 0) { + perr("'size' must be non-negative."); + exit(EXIT_FAILURE); + } + break; + + case 'm': + test_opts->mtu = atoi(optarg); + if (test_opts->mtu < 0) { + perr("'mtu' must be non-negative."); + exit(EXIT_FAILURE); + } + break; + + case 'h': + print_help(argv[0]); + exit(EXIT_SUCCESS); + + default: /* Invalid option */ + print_help(argv[0]); + exit(EXIT_FAILURE); + } + } +} + +static int read_from_buffer(uint8_t * buffer, uint32_t size, uint32_t offset, void * data) { + (void)offset; + + sfp_data_t * d = (sfp_data_t *)data; + + /* Seed the random number generator */ + srand(time(NULL)); + + /* Fill the array with random values */ + for (uint32_t i = 0; i < size; i++) { + buffer[i] = (uint8_t)(rand() % 256); /* Assign a random number to each element */ + } + + /* update crc */ + csp_crc32_update(&d->crc, buffer, size); + + return CSP_ERR_NONE; +} + +static int write_to_buffer(const uint8_t * buffer, uint32_t size, uint32_t offset, uint32_t totalsz, void * data) { + (void)offset; + (void)totalsz; + + sfp_data_t * d = (sfp_data_t *)data; + + /* update crc */ + csp_crc32_update(&d->crc, buffer, size); + + /* update counter */ + received_sz += size; + + return CSP_ERR_NONE; +} + +static void * router(void * params) { + (void)params; + + while (1) { + (void)csp_route_work(); + } + + return NULL; +} + +static void * sender(void * params) { + const test_options_t * test_opts = (test_options_t *)params; + sfp_data_t data; + csp_crc32_init(&data.crc); + csp_conn_t * conn = NULL; + uint32_t opts = CSP_O_CRC32; + opts |= test_opts->rdp ? CSP_O_RDP : 0; + + /* Connect to receiver */ + conn = csp_connect(CSP_PRIO_NORM, RECEIVER_ADDRESS, RECEIVER_PORT, TIMEOUT, opts); + if (NULL == conn) { + perr("Failed csp_connect"); + goto exit; + } + + csp_sfp_read_t user; + user.data = &data; + user.read = read_from_buffer; + + /* Send data */ + int ret = csp_sfp_send(conn, &user, test_opts->size, test_opts->mtu, 0); + if (CSP_ERR_NONE != ret) { + perr("Failed csp_sfp_send due to: %s", csp_error_to_str(ret)); + goto exit; + } + + sender_crc = csp_crc32_final(&data.crc); +exit: + csp_close(conn); + + return NULL; +} + +static void * receiver(void * params) { + const test_options_t * test_opts = (test_options_t *)params; + sfp_data_t data; + csp_crc32_init(&data.crc); + csp_conn_t * conn = NULL; + + csp_socket_t sock = {0}; + sock.opts |= CSP_SO_CRC32REQ; + sock.opts |= test_opts->rdp ? CSP_SO_RDPREQ : 0; + csp_listen(&sock, 0); + csp_bind(&sock, CSP_ANY); + + conn = csp_accept(&sock, TIMEOUT); + if (!conn) { + perr("Failed csp_accept"); + goto exit; + } + + csp_sfp_recv_t user; + user.data = &data; + user.write = write_to_buffer; + + /* Send data */ + int ret = csp_sfp_recv(conn, &user, 1000); + if (CSP_ERR_NONE != ret) { + perr("Failed csp_sfp_recv due to: %s", csp_error_to_str(ret)); + goto exit; + } + + receiver_crc = csp_crc32_final(&data.crc); +exit: + csp_close(conn); + + return NULL; +} + +static int loopback_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { + (void)iface; + (void)via; + (void)from_me; + + /* add some sleep to avoid starving the system when RDP is not used */ + if (!test_options.rdp) + usleep(1000); + csp_qfifo_write(packet, &csp_if_lo, NULL); + return CSP_ERR_NONE; +} + +int main(int argc, char * argv[]) { + process_args(argc, argv, &test_options); + csp_init(); + + csp_if_lo.is_default = 1; + csp_if_lo.nexthop = loopback_tx; /* replace default tx function */ + + pthread_t router_thread; + if (0 != pthread_create(&router_thread, NULL, router, NULL)) { + perr("Failed to create thread"); + exit(EXIT_FAILURE); + } + + pthread_t receiver_thread; + if (0 != pthread_create(&receiver_thread, NULL, receiver, &test_options)) { + perr("Failed to create thread"); + exit(EXIT_FAILURE); + } + + pthread_t sender_thread; + if (0 != pthread_create(&sender_thread, NULL, sender, &test_options)) { + perr("Failed to create thread"); + exit(EXIT_FAILURE); + } + + /* wait for sender to finish */ + struct timespec timeout; + if (0 != clock_gettime(CLOCK_REALTIME, &timeout)) { + perr("Failed to get time"); + exit(EXIT_FAILURE); + } + timeout.tv_sec += test_options.timeout; + timeout.tv_nsec = 0; + if (0 != pthread_timedjoin_np(sender_thread, NULL, &timeout)) { + perr("Sender timeout"); + exit(EXIT_FAILURE); + } + + /* wait for receiver to finish */ + if (0 != clock_gettime(CLOCK_REALTIME, &timeout)) { + perr("Failed to get time"); + exit(EXIT_FAILURE); + } + timeout.tv_sec += 2; + timeout.tv_nsec = 0; + if (0 != pthread_timedjoin_np(receiver_thread, NULL, &timeout)) { + perr("Receiver timeout"); + exit(EXIT_FAILURE); + } + + /* compare calculated CRCs */ + if (sender_crc != receiver_crc) { + perr("CRC mismatch"); + exit(EXIT_FAILURE); + } + + if ((uint32_t)test_options.size != received_sz) { + perr("SIZE mismatch"); + exit(EXIT_FAILURE); + } + + printf("Test completed!\n"); + + exit(EXIT_SUCCESS); +} diff --git a/examples/iflist.yaml b/examples/iflist.yaml index 09a26f058..7b850c1e8 100644 --- a/examples/iflist.yaml +++ b/examples/iflist.yaml @@ -7,7 +7,7 @@ # List of supported drivers: # can, zmq, uart, tun, TODO: udp # -# The following additional attirbutes are optional: +# The following additional attributes are optional: # device: used for can, and uart typically set to /dev/ttyUSB0 or can0 # server: used for zmq, typically set to an IP address of a zmqproxy # default: true, set to true on one interface only. Sets the default route to this if. diff --git a/examples/meson.build b/examples/meson.build index fda70e25b..833b92690 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -1,19 +1,35 @@ +csp_posix_helper = static_library('csp_posix_helper', + 'csp_posix_helper.c', + include_directories : csp_inc, + build_by_default : false + ).extract_all_objects(recursive : true) + executable('csp_server_client', - ['csp_server_client.c', 'csp_server_client_posix.c'], + 'csp_server_client.c', include_directories : csp_inc, c_args : csp_c_args, dependencies : csp_dep, + objects : csp_posix_helper, build_by_default : false) executable('csp_server', - ['csp_server.c', 'csp_server_posix.c'], + 'csp_server.c', include_directories : csp_inc, c_args : csp_c_args, dependencies : csp_dep, + objects : csp_posix_helper, build_by_default : false) executable('csp_client', - ['csp_client.c', 'csp_client_posix.c'], + 'csp_client.c', + include_directories : csp_inc, + c_args : csp_c_args, + dependencies : csp_dep, + objects : csp_posix_helper, + build_by_default : false) + +executable('csp_bridge_can2udp', + ['csp_bridge_can2udp.c'], include_directories : csp_inc, c_args : csp_c_args, dependencies : csp_dep, @@ -32,3 +48,10 @@ executable('zmqproxy', c_args : csp_c_args, dependencies : csp_dep, build_by_default : false) + +executable('csp_sfp_server_client', + 'csp_sfp_server_client.c', + include_directories : csp_inc, + c_args : csp_c_args, + dependencies : csp_dep, + build_by_default : false) diff --git a/examples/python_bindings_example_client.py b/examples/python_bindings_example_client.py index ef39861be..ee920ba47 100644 --- a/examples/python_bindings_example_client.py +++ b/examples/python_bindings_example_client.py @@ -10,7 +10,6 @@ # $ LD_LIBRARY_PATH=build PYTHONPATH=build python3 examples/python_bindings_example_client.py -z localhost # -import os import time import sys import argparse @@ -63,7 +62,6 @@ def get_options(): # same format/use as line above libcsp.rtable_load(options.routing_table) - # Parameters: {priority} - 0 (critical), 1 (high), 2 (norm), 3 (low) ---- default=2 # Start the router task - creates routing thread libcsp.route_start_task() time.sleep(0.2) # allow router task startup @@ -106,6 +104,6 @@ def get_options(): # 10 - dest port # 1000 - timeout ms # outbuf - outgoing data (request) - # inbuf - buffer provided for recieving data (reply) + # inbuf - buffer provided for receiving data (reply) libcsp.transaction(0, options.server_address, 10, 1000, outbuf, inbuf) print (" got reply from server [%s]" % (''.join('{:02x}'.format(x) for x in inbuf))) diff --git a/examples/python_bindings_example_server.py b/examples/python_bindings_example_server.py index 49e89c69f..af35cc4cb 100644 --- a/examples/python_bindings_example_server.py +++ b/examples/python_bindings_example_server.py @@ -10,9 +10,6 @@ # $ LD_LIBRARY_PATH=build PYTHONPATH=build python3 examples/python_bindings_example_server.py # -import os -import time -import sys import threading import argparse @@ -124,7 +121,6 @@ def csp_server(): libcsp.kiss_init(options.kiss, options.address) libcsp.rtable_load("0/0 KISS") - # Parameters: {priority} - 0 (critical), 1 (high), 2 (norm), 3 (low) ---- default=2 # Start the router task - creates routing thread libcsp.route_start_task() diff --git a/examples/zmqproxy.c b/examples/zmqproxy.c index 709106c49..492a45aff 100644 --- a/examples/zmqproxy.c +++ b/examples/zmqproxy.c @@ -1,14 +1,14 @@ #include #include +#include #include #include #include +#include #include - -int csp_id_strip(csp_packet_t * packet); -int csp_id_setup_rx(csp_packet_t * packet); -extern csp_conf_t csp_conf; +#include +#include int debug = 0; const char * sub_str = "tcp://0.0.0.0:6000"; @@ -25,9 +25,16 @@ static void * task_capture(void * ctx) { /* Subscriber (RX) */ void * subscriber = zmq_socket(ctx, ZMQ_SUB); ret = zmq_connect(subscriber, pub_str); - assert(ret == 0); + if (ret < 0) { + perror("Unable to connect"); + exit(1); + } ret = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, "", 0); - assert(ret == 0); + if (ret < 0) { + perror("Failed to call setsockopt"); + exit(1); + } + /* Allocated 'raw' CSP packet */ csp_packet_t * packet = malloc(1024); @@ -52,7 +59,7 @@ static void * task_capture(void * ctx) { continue; } - int datalen = zmq_msg_size(&msg); + size_t datalen = zmq_msg_size(&msg); if (datalen < 5) { csp_print("ZMQ: Too short datalen: %u\n", datalen); while (zmq_msg_recv(&msg, subscriber, ZMQ_NOBLOCK) > 0) @@ -60,19 +67,21 @@ static void * task_capture(void * ctx) { continue; } + uint8_t * rx_data = csp_zmqhub_fixup_cspv1_del_dest_addr(zmq_msg_data(&msg), &datalen); + /* Copy to packet */ csp_id_setup_rx(packet); - memcpy(packet->frame_begin, zmq_msg_data(&msg), datalen); + memcpy(packet->frame_begin, rx_data, datalen); packet->frame_length = datalen; /* Parse header */ - csp_id_strip(packet); + csp_id_strip_fixup_cspv1(packet); /* Print header data */ csp_print("Packet: Src %u, Dst %u, Dport %u, Sport %u, Pri %u, Flags 0x%02X, Size %" PRIu16 "\n", packet->id.src, packet->id.dst, packet->id.dport, packet->id.sport, packet->id.pri, packet->id.flags, packet->length); - + if (logfile) { const char * delimiter = "--------\n"; @@ -127,13 +136,19 @@ int main(int argc, char ** argv) { void * frontend = zmq_socket(ctx, ZMQ_XSUB); assert(frontend); ret = zmq_bind(frontend, sub_str); - assert(ret == 0); + if (ret < 0) { + perror("Failed to bind to ZMQ_XSUB"); + return 1; + } csp_print("Subscriber task listening on %s\n", sub_str); void * backend = zmq_socket(ctx, ZMQ_XPUB); - assert(backend); + ret = zmq_bind(backend, pub_str); - assert(ret == 0); + if (ret < 0) { + perror("Failed to bind to ZMQ_XPUB"); + return 1; + } csp_print("Publisher task listening on %s\n", pub_str); pthread_t capworker; diff --git a/include/csp/arch/csp_queue.h b/include/csp/arch/csp_queue.h index fdd0d3960..c6f5ff43a 100644 --- a/include/csp/arch/csp_queue.h +++ b/include/csp/arch/csp_queue.h @@ -5,7 +5,7 @@ ****************************************************************************/ #pragma once -#include +#include #include #include "csp/autoconfig.h" @@ -21,14 +21,16 @@ extern "C" { #define CSP_QUEUE_OK 0 #define CSP_QUEUE_ERROR -1 -typedef void * csp_queue_handle_t; - #if (CSP_FREERTOS) +typedef QueueHandle_t csp_queue_handle_t; typedef StaticQueue_t csp_static_queue_t; #elif (CSP_ZEPHYR) #include +typedef struct k_msgq * csp_queue_handle_t; typedef struct k_msgq csp_static_queue_t; #else +typedef struct pthread_queue_s pthread_queue_t; // Opaque pointer +typedef pthread_queue_t * csp_queue_handle_t; typedef void * csp_static_queue_t; #endif diff --git a/include/csp/csp.h b/include/csp/csp.h index 103f41824..41f968dfe 100644 --- a/include/csp/csp.h +++ b/include/csp/csp.h @@ -55,12 +55,6 @@ extern csp_conf_t csp_conf; */ void csp_init(void); -/** - * Free allocated resorces in CSP. - * This is intended for testing of CSP, in order to be able re-initialize CSP by calling csp_init() again. - */ -void csp_free_resources(void); - /** * Get a \a read-only reference to the active CSP configuration. * @@ -71,7 +65,12 @@ const csp_conf_t * csp_get_conf(void); /** * Copy csp id fields from source to target object */ -void csp_id_copy(csp_id_t * target, csp_id_t * source); +void csp_id_copy(csp_id_t * target, const csp_id_t * source); + +/** + * Clear csp id fields after creating new buffer + */ +void csp_id_clear(csp_id_t * target); /** * Wait/accept a new connection. @@ -84,7 +83,7 @@ csp_conn_t *csp_accept(csp_socket_t *socket, uint32_t timeout); /** * Read packet from a connection. - * This fuction will wait on the connection's RX queue for the specified timeout. + * This function will wait on the connection's RX queue for the specified timeout. * * @param[in] conn connection * @param[in] timeout timeout in mS to wait for a packet, use CSP_MAX_TIMEOUT for infinite timeout. @@ -130,7 +129,7 @@ void csp_send_prio(uint8_t prio, csp_conn_t *conn, csp_packet_t *packet); * Returns: * int: 1 or reply size on success, 0 on failure (error, incoming length does not match, timeout) */ -int csp_transaction_w_opts(uint8_t prio, uint16_t dst, uint8_t dst_port, uint32_t timeout, void *outbuf, int outlen, void *inbuf, int inlen, uint32_t opts); +int csp_transaction_w_opts(uint8_t prio, uint16_t dst, uint8_t dst_port, uint32_t timeout, const void *outbuf, int outlen, void *inbuf, int inlen, uint32_t opts); /** * Perform an entire request & reply transaction. @@ -146,7 +145,7 @@ int csp_transaction_w_opts(uint8_t prio, uint16_t dst, uint8_t dst_port, uint32_ * @param[in] inlen length of expected reply, -1 for unknown size (inbuf MUST be large enough), 0 for no reply. * @return 1 or reply size on success, 0 on failure (error, incoming length does not match, timeout) */ -static inline int csp_transaction(uint8_t prio, uint16_t dest, uint8_t port, uint32_t timeout, void * outbuf, int outlen, void * inbuf, int inlen) { +static inline int csp_transaction(uint8_t prio, uint16_t dest, uint8_t port, uint32_t timeout, const void * outbuf, int outlen, void * inbuf, int inlen) { return csp_transaction_w_opts(prio, dest, port, timeout, outbuf, outlen, inbuf, inlen, 0); } @@ -162,7 +161,7 @@ static inline int csp_transaction(uint8_t prio, uint16_t dest, uint8_t port, uin * @param[in] inlen length of expected reply, -1 for unknown size (inbuf MUST be large enough), 0 for no reply. * @return 1 or reply size on success, 0 on failure (error, incoming length does not match, timeout) */ -int csp_transaction_persistent(csp_conn_t *conn, uint32_t timeout, void *outbuf, int outlen, void *inbuf, int inlen); +int csp_transaction_persistent(csp_conn_t *conn, uint32_t timeout, const void *outbuf, int outlen, void *inbuf, int inlen); /** * Read data from a connection-less server socket. @@ -198,7 +197,7 @@ void csp_sendto_reply(const csp_packet_t * request, csp_packet_t * reply, uint32 /** * Establish outgoing connection. * The call will return immediately, unless it is a RDP connection (#CSP_O_RDP) in which case it will wait until the other - * end acknowleges the connection (timeout is determined by the current connection timeout set by csp_rdp_set_opt()). + * end acknowledges the connection (timeout is determined by the current connection timeout set by csp_rdp_set_opt()). * * @param[in] prio priority, see #csp_prio_t * @param[in] dst Destination address @@ -233,7 +232,7 @@ int csp_socket_close(csp_socket_t* sock); * @param[in] conn connection * @return destination port of an incoming connection */ -int csp_conn_dport(csp_conn_t *conn); +int csp_conn_dport(const csp_conn_t *conn); /** * Return source port of connection. @@ -241,7 +240,7 @@ int csp_conn_dport(csp_conn_t *conn); * @param[in] conn connection * @return source port of an incoming connection */ -int csp_conn_sport(csp_conn_t *conn); +int csp_conn_sport(const csp_conn_t *conn); /** * Return destination address of connection. @@ -249,7 +248,7 @@ int csp_conn_sport(csp_conn_t *conn); * @param[in] conn connection * @return destination address of an incoming connection */ -int csp_conn_dst(csp_conn_t *conn); +int csp_conn_dst(const csp_conn_t *conn); /** * Return source address of connection. @@ -257,7 +256,7 @@ int csp_conn_dst(csp_conn_t *conn); * @param[in] conn connection * @return source address of an incoming connection */ -int csp_conn_src(csp_conn_t *conn); +int csp_conn_src(const csp_conn_t *conn); /** * Return flags of connection. @@ -265,7 +264,21 @@ int csp_conn_src(csp_conn_t *conn); * @param[in] conn connection * @return flags of an incoming connection, see @ref CSP_HEADER_FLAGS */ -int csp_conn_flags(csp_conn_t *conn); +int csp_conn_flags(const csp_conn_t *conn); + +/** + * Return if the CSP connection is active + * + * Active in this context means if the protocol layers is connected and no time outs has happened. + * Especially if a connection is marked as a RDP connection, the active state means that the + * RDP layers are connected and no time outs have happened. If the RDP layer has a connection timeout + * or of the connection is closing, the connection is inactive, and ready to be closed. + * + * @param conn connection + * @return true if the connection is active + * @return false if the connection is in-active + */ +bool csp_conn_is_active(csp_conn_t *conn); /** * Set socket to listen for incoming connections. @@ -297,7 +310,7 @@ int csp_bind_callback(csp_callback_t callback, uint8_t port); /** * Route packet from the incoming router queue and check RDP timeouts. - * In order for incoming packets to routed and RDP timeouts to be checked, this function must be called reguarly. + * In order for incoming packets to routed and RDP timeouts to be checked, this function must be called regularly. * @return #CSP_ERR_NONE on success, otherwise an error code. */ int csp_route_work(void); @@ -353,7 +366,7 @@ void csp_ping_noreply(uint16_t node); * .. note:: This is currently only supported on FreeRTOS systems. * * @param[in] node address of subsystem. - * @param[in] timeout timeout in mS to wait for replies. The function will not return until the timeout occurrs. + * @param[in] timeout timeout in mS to wait for replies. The function will not return until the timeout occurs. */ void csp_ps(uint16_t node, uint32_t timeout); @@ -433,7 +446,7 @@ int csp_get_uptime(uint16_t node, uint32_t timeout, uint32_t * uptime); /** * Set RDP options. * The RDP options are used from the connecting/client side. When a RDP connection - * is established, the client tranmits the options to the server. + * is established, the client transmits the options to the server. * * @param[in] window_size window size * @param[in] conn_timeout_ms connection timeout in mS @@ -462,9 +475,11 @@ void csp_rdp_get_opt(unsigned int *window_size, unsigned int *conn_timeout_ms, unsigned int *ack_timeout, unsigned int *ack_delay_count); /** - * Set platform specific memory copy function. + * Set platform specific memory copy functions. */ void csp_cmp_set_memcpy(csp_memcpy_fnc_t fnc); +void csp_cmp_set_memread64(csp_memread64_fnc_t fnc); +void csp_cmp_set_memwrite64(csp_memwrite64_fnc_t fnc); #if (CSP_ENABLE_CSP_PRINT) @@ -481,7 +496,7 @@ void csp_conn_print_table(void); * @param[in] len number of bytes to dump, starting from \a addr. * */ -void csp_hex_dump(const char *desc, void *addr, int len); +void csp_hex_dump(const char *desc, const void *addr, int len); #else diff --git a/include/csp/csp_buffer.h b/include/csp/csp_buffer.h index d9cce32ef..05c15aba0 100644 --- a/include/csp/csp_buffer.h +++ b/include/csp/csp_buffer.h @@ -11,6 +11,15 @@ extern "C" { #endif +/** + * Number of buffers reserved by CSP for fault-tolerant operations. + * + * These reserved buffers are used for operations that can tolerate allocation failure, + * such as client requests with proper error handling, or services that may timeout + * safely when memory is low. + */ +#define CSP_BUFFER_RESERVED_COUNT 2 + /** * Get free buffer from task context. * @@ -48,7 +57,15 @@ void csp_buffer_free_isr(void * buffer); * @param[in] buffer buffer to clone. * @return cloned buffer on success, or NULL on failure. */ -void * csp_buffer_clone(void * buffer); +csp_packet_t * csp_buffer_clone(const csp_packet_t * packet); + +/** + * Copy the contents of a buffer. + * + * @param[in] src Source buffer. + * @param[out] dst Destination buffer. + */ +void csp_buffer_copy(const csp_packet_t * src, csp_packet_t * dst); /** * Return number of remaining/free buffers. diff --git a/include/csp/csp_cmp.h b/include/csp/csp_cmp.h index 96f5132ad..91d4b0188 100644 --- a/include/csp/csp_cmp.h +++ b/include/csp/csp_cmp.h @@ -62,6 +62,14 @@ extern "C" { * Set/configure routing. */ #define CSP_CMP_ROUTE_SET_V2 7 +/** + * Peek/read data from memory - 64-bit version. + */ +#define CSP_CMP_PEEK_V2 8 +/** + * Poke/write data from memory - 64-bit version. + */ +#define CSP_CMP_POKE_V2 9 /**@}*/ /** @@ -83,15 +91,25 @@ extern "C" { #define CSP_CMP_ROUTE_IFACE_LEN 11 /** - * CMP peek/read memeory - max read length. + * CMP peek/read memory - max read length. */ #define CSP_CMP_PEEK_MAX_LEN 200 /** - * CMP poke/write memeory - max write length. + * CMP poke/write memory - max write length. */ #define CSP_CMP_POKE_MAX_LEN 200 +/** + * CMP peek/read memory - max read length - 64-bit. + */ +#define CSP_CMP_PEEK_V2_MAX_LEN 196 + +/** + * CMP poke/write memory - max write length - 64-bit. + */ +#define CSP_CMP_POKE_V2_MAX_LEN 196 + /** * CSP management protocol description. */ @@ -142,6 +160,16 @@ struct csp_cmp_message { uint8_t len; char data[CSP_CMP_POKE_MAX_LEN]; } poke; + struct { + uint64_t vaddr; /* Virtual 64-bit address on the target system */ + uint8_t len; + char data[CSP_CMP_PEEK_V2_MAX_LEN]; + } peek_v2; + struct { + uint64_t vaddr; /* Virtual 64-bit address on the target system */ + uint8_t len; + char data[CSP_CMP_POKE_V2_MAX_LEN]; + } poke_v2; csp_timestamp_t clock; }; } __attribute__((__packed__)); @@ -201,6 +229,30 @@ static inline int csp_cmp_poke(uint16_t node, uint32_t timeout, struct csp_cmp_m return csp_cmp(node, timeout, CSP_CMP_POKE, CMP_SIZE(poke) - sizeof(msg->poke.data) + msg->poke.len, msg); } +/** + * Peek (read) memory on remote node - 64-bit version. + * + * @param[in] node address of subsystem. + * @param[in] timeout timeout in mS to wait for reply.. + * @param[in/out] msg memory address and number of bytes to peek. (msg peeked/read memory) + * @return #CSP_ERR_NONE on success, otherwise an error code. + */ +static inline int csp_cmp_peek_v2(uint16_t node, uint32_t timeout, struct csp_cmp_message *msg) { + return csp_cmp(node, timeout, CSP_CMP_PEEK_V2, CMP_SIZE(peek_v2) - sizeof(msg->peek_v2.data) + msg->peek_v2.len, msg); +} + +/** + * Poke (write) memory on remote node - 64-bit version. + * + * @param[in] node address of subsystem. + * @param[in] timeout timeout in mS to wait for reply.. + * @param[in] msg memory address, number of bytes and the actual bytes to poke/write. + * @return #CSP_ERR_NONE on success, otherwise an error code. + */ +static inline int csp_cmp_poke_v2(uint16_t node, uint32_t timeout, struct csp_cmp_message *msg) { + return csp_cmp(node, timeout, CSP_CMP_POKE_V2, CMP_SIZE(poke_v2) - sizeof(msg->poke_v2.data) + msg->poke_v2.len, msg); +} + #ifdef __cplusplus } #endif diff --git a/include/csp/csp_crc32.h b/include/csp/csp_crc32.h index 83f30aed2..78fe02fb3 100644 --- a/include/csp/csp_crc32.h +++ b/include/csp/csp_crc32.h @@ -42,7 +42,7 @@ int csp_crc32_verify(csp_packet_t * packet); * @param[in] length length of memory to do checksum on * @return checksum */ -uint32_t csp_crc32_memory(const uint8_t * addr, uint32_t length); +uint32_t csp_crc32_memory(const void * addr, uint32_t length); /** Initialize the CRC32 object prior to calculating checksum. @@ -56,7 +56,7 @@ void csp_crc32_init(csp_crc32_t *crc); @param[in] data pointer to data for which to update checksum on @param[in] length number of bytes in the array pointed to by data */ -void csp_crc32_update(csp_crc32_t * crc, const uint8_t * data, uint32_t length); +void csp_crc32_update(csp_crc32_t * crc, const void * data, uint32_t length); /** Finalize the CRC32 checksum calculation. diff --git a/include/csp/csp_debug.h b/include/csp/csp_debug.h index 8f4924100..acaeaaf0e 100644 --- a/include/csp/csp_debug.h +++ b/include/csp/csp_debug.h @@ -16,7 +16,7 @@ #pragma once #include "csp/autoconfig.h" -#include +#include #ifdef __cplusplus extern "C" { @@ -62,7 +62,7 @@ extern uint8_t csp_dbg_eth_errno; #define CSP_DBG_ETH_ERR_INCOMPLETE 5 #define CSP_DBG_ETH_ERR_UNKNOWN 6 -/* Toogle flags for rdp and packet print */ +/* Toggle flags for rdp and packet print */ extern uint8_t csp_dbg_rdp_print; extern uint8_t csp_dbg_packet_print; diff --git a/include/csp/csp_error.h b/include/csp/csp_error.h index 26a8da621..f72a813e1 100644 --- a/include/csp/csp_error.h +++ b/include/csp/csp_error.h @@ -26,4 +26,5 @@ #define CSP_ERR_HMAC -100 /**< HMAC failed */ #define CSP_ERR_CRC32 -102 /**< CRC32 failed */ #define CSP_ERR_SFP -103 /**< SFP protocol error or inconsistency */ +#define CSP_ERR_MTU -104 /**< Invalid MTU */ /**@}*/ diff --git a/include/csp/csp_hooks.h b/include/csp/csp_hooks.h index 2f2d57aef..12af6afab 100644 --- a/include/csp/csp_hooks.h +++ b/include/csp/csp_hooks.h @@ -1,7 +1,7 @@ /**************************************************************************** * **File:** csp/csp_hooks.h * - * **Description:** See Hooks in CSP + * **Description:** Hooks that can be implemented in CSP, see Hooks in csp for more information ****************************************************************************/ #pragma once @@ -12,20 +12,91 @@ extern "C" { #endif -void csp_output_hook(csp_id_t * idout, csp_packet_t * packet, csp_iface_t * iface, uint16_t via, int from_me); +/** + * Hook called when a packet is sent + * + * @param idout ID of the recipient + * @param packet CSP packet to be sent + * @param iface Outgoing interface + * @param via Next hop address (if applicable) + * @param from_me Whether the packet originates from this node + */ +void csp_output_hook(const csp_id_t * idout, csp_packet_t * packet, csp_iface_t * iface, uint16_t via, int from_me); + +/** + * Hook called when a packet is received + * + * @param iface Interface that received the packet + * @param packet Received packet + */ void csp_input_hook(csp_iface_t * iface, csp_packet_t * packet); +/** + * Hook called for system reboot + */ void csp_reboot_hook(void); + +/** + * Hook called for system shutdown + */ void csp_shutdown_hook(void); +/** + * Returns the available free memory + * @return Free memory in bytes + */ uint32_t csp_memfree_hook(void); + +/** + * Collects process information into a packet + * + * @param packet Packet to be filled with process info + * @return Number of entries written + */ unsigned int csp_ps_hook(csp_packet_t * packet); -/** Implement these, if you use csp_if_tun */ -int csp_crypto_decrypt(uint8_t * ciphertext_in, uint8_t ciphertext_len, uint8_t * msg_out); // Returns -1 for failure, length if ok -int csp_crypto_encrypt(uint8_t * msg_begin, uint8_t msg_len, uint8_t * ciphertext_out); // Returns length of encrypted data +/** + * Called in case of a fatal error + * This function must not return, and should reboot the system + * or the program running CSP to recover responsiveness of the system. + * + * @param msg Error message + */ +void csp_panic(const char * msg); +/** + * Decrypt a message (Implement these if you used csp_if_tun) + * + * @param ciphertext_in Encrypted input data + * @param ciphertext_len Length of encrypted data + * @param msg_out Output buffer for the decrypted message + * @return Length of the decrypted data or an error code on failure + */ +int csp_crypto_decrypt(uint8_t * ciphertext_in, uint8_t ciphertext_len, uint8_t * msg_out); + +/** + * Encrypt a message (Implement these if you used csp_if_tun) + * + * @param msg_begin Plaintext message to encrypt + * @param msg_len Length of the message + * @param ciphertext_out Output buffer for encrypted data + * @return Length of the encrypted data or an error code on failure + */ +int csp_crypto_encrypt(uint8_t * msg_begin, uint8_t msg_len, uint8_t * ciphertext_out); + +/** + * Get the current system time + * + * @param time Structure to be filled with the current time + */ void csp_clock_get_time(csp_timestamp_t * time); + +/** + * Set the system time + * + * @param time Structure containing the new time to set + * @return 0 on success, -1 on failure + */ int csp_clock_set_time(const csp_timestamp_t * time); #ifdef __cplusplus diff --git a/include/csp/csp_id.h b/include/csp/csp_id.h index 19c88b664..5bdbab9cf 100644 --- a/include/csp/csp_id.h +++ b/include/csp/csp_id.h @@ -6,15 +6,114 @@ extern "C" { #include +/** + * Prepend CSP header fields into the packet's data buffer. + * + * This function encodes the CSP ID header into the packet and adjusts the data offset. + * + * @param packet Pointer to the packet to modify. + */ void csp_id_prepend(csp_packet_t * packet); + +/** + * Strip CSP header fields from the packet's data buffer. + * + * This function decodes the CSP ID header from the packet and adjusts the data offset. + * + * @param packet Pointer to the packet to modify. + * @return 0 on success, -1 on failure. + */ int csp_id_strip(csp_packet_t * packet); + +/** + * Setup reception information from CSP ID header. + * + * Typically called after stripping the header to configure addressing fields. + * + * @param packet Pointer to the received packet. + * @return 0 on success, -1 on failure. + */ int csp_id_setup_rx(csp_packet_t * packet); + +/** + * Get number of bits allocated for the host part of the address. + * + * @return Number of bits used for the host ID. + */ unsigned int csp_id_get_host_bits(void); + +/** + * Get maximum allowed node ID. + * + * @return Maximum node ID based on current configuration. + */ unsigned int csp_id_get_max_nodeid(void); + +/** + * Get maximum allowed port number. + * + * @return Maximum port number. + */ unsigned int csp_id_get_max_port(void); +/** + * Check whether a given address is a broadcast address. + * + * @param addr The address to test. + * @param iface Interface to use for context (netmask, etc.). + * @return 1 if broadcast, 0 otherwise. + */ int csp_id_is_broadcast(uint16_t addr, csp_iface_t * iface); +/** + * Get the size of the CSP header based on the configured version. + * + * This function returns the size in bytes of the CSP packet header, + * depending on whether CSP version 1 or 2 is configured. + * + * @return The header size in bytes. + */ +int csp_id_get_header_size(void); + +#if (CSP_FIXUP_V1_ZMQ_LITTLE_ENDIAN) + +/** + * Prepend CSPv1-compatible ID header (ZMQ fixup). + * + * Used when sending CSPv1 packets on little-endian ZMQ transport. + * + * @param packet Pointer to the packet to modify. + */ +void csp_id_prepend_fixup_cspv1(csp_packet_t * packet); + +/** + * Strip CSPv1-compatible ID header (ZMQ fixup). + * + * Used when receiving CSPv1 packets on little-endian ZMQ transport. + * + * @param packet Pointer to the packet to modify. + * @return 0 on success, -1 on failure. + */ +int csp_id_strip_fixup_cspv1(csp_packet_t * packet); + +#else + +/** + * Wrapper for csp_id_prepend when no fixup is required. + */ +static inline void csp_id_prepend_fixup_cspv1(csp_packet_t * packet) { + csp_id_prepend(packet); +} + +/** + * Wrapper for csp_id_strip when no fixup is required. + */ +static inline int csp_id_strip_fixup_cspv1(csp_packet_t * packet) { + return csp_id_strip(packet); +} + +#endif + #ifdef __cplusplus } #endif diff --git a/include/csp/csp_iflist.h b/include/csp/csp_iflist.h index d6ee975f0..478805545 100644 --- a/include/csp/csp_iflist.h +++ b/include/csp/csp_iflist.h @@ -15,9 +15,8 @@ extern "C" { * Add interface to the list. * * @param[in] iface The interface must remain valid as long as the application is running. - * @return #CSP_ERR_NONE on success, otherwise an error code. */ -int csp_iflist_add(csp_iface_t * iface); +void csp_iflist_add(csp_iface_t * iface); /** * Remove interface from the list. @@ -26,8 +25,17 @@ int csp_iflist_add(csp_iface_t * iface); */ void csp_iflist_remove(csp_iface_t * ifc); +/** + * Iterate over the list of interfaces. + * + * @param[in] ifc Previous interface to continue iteration, or NULL to start at the head of the list. + * @return Pointer to the next interface, or NULL if the end of the list is reached. + */ +csp_iface_t * csp_iflist_iterate(csp_iface_t * ifc); + csp_iface_t * csp_iflist_get_by_name(const char * name); csp_iface_t * csp_iflist_get_by_addr(uint16_t addr); +csp_iface_t * csp_iflist_get_by_broadcast(uint16_t addr); csp_iface_t * csp_iflist_get_by_subnet(uint16_t addr, csp_iface_t * from); csp_iface_t * csp_iflist_get_by_isdfl(csp_iface_t * ifc); csp_iface_t * csp_iflist_get_by_index(int idx); @@ -35,6 +43,9 @@ int csp_iflist_is_within_subnet(uint16_t addr, csp_iface_t * ifc); csp_iface_t * csp_iflist_get(void); +int csp_addr_is_alias(uint16_t addr); +int csp_alias_add(csp_alias_t * addr); + /** * Convert bytes to readable string */ diff --git a/include/csp/csp_interface.h b/include/csp/csp_interface.h index 33a9f4748..b8daac3b6 100644 --- a/include/csp/csp_interface.h +++ b/include/csp/csp_interface.h @@ -19,6 +19,7 @@ extern "C" { * @return #CSP_ERR_NONE on success, otherwise an error code. */ typedef int (*nexthop_t)(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me); +typedef int (*csp_alias_add_t)(void * driver_data, uint16_t addr); /** * This struct is referenced in documentation. @@ -33,6 +34,7 @@ struct csp_iface_s { void * interface_data; /**< Interface data, only known/used by the interface layer, e.g. state information. */ void * driver_data; /**< Driver data, only known/used by the driver layer, e.g. device/channel references. */ nexthop_t nexthop; /**< Next hop (Tx) function */ + csp_alias_add_t add_alias; /**< Add receive address to interface (could be multicast receptions) */ uint8_t is_default; /**< Set default IF flag (CSP supports multiple defaults) */ /* Stats */ @@ -52,6 +54,18 @@ struct csp_iface_s { }; +/** + * Used to represent an alias reception address, bound to a particular interface + */ +typedef struct csp_alias_s { + uint16_t addr; + csp_iface_t * iface; + + /* For linked lists*/ + struct csp_alias_s * next; + +} csp_alias_t; + /** * Inputs a new packet into the system. * diff --git a/include/csp/csp_rtable.h b/include/csp/csp_rtable.h index 9270b99c6..57c383fd2 100644 --- a/include/csp/csp_rtable.h +++ b/include/csp/csp_rtable.h @@ -73,9 +73,25 @@ int csp_rtable_load(const char * rtable); int csp_rtable_check(const char * rtable); #else -inline int csp_rtable_save(char * buffer, size_t buffer_size) { return CSP_ERR_NOSYS; } -inline int csp_rtable_load(const char * rtable) { return CSP_ERR_NOSYS; } -inline int csp_rtable_check(const char * rtable) { return CSP_ERR_NOSYS; } +inline int csp_rtable_save(char * buffer, size_t buffer_size) { + /* Avoid compiler warnings about unused parameter */ + (void)buffer; + (void)buffer_size; + + return CSP_ERR_NOSYS; +} + +inline int csp_rtable_load(const char * rtable) { + (void)rtable; /* Avoid compiler warnings about unused parameter */ + + return CSP_ERR_NOSYS; +} + +inline int csp_rtable_check(const char * rtable) { + (void)rtable; /* Avoid compiler warnings about unused parameter */ + + return CSP_ERR_NOSYS; +} #endif /** diff --git a/include/csp/csp_sfp.h b/include/csp/csp_sfp.h index 68f35c8fb..cb0a0c437 100644 --- a/include/csp/csp_sfp.h +++ b/include/csp/csp_sfp.h @@ -4,15 +4,13 @@ * **Description:** Simple Fragmentation Protocol (SFP). * * The SFP API can transfer a blob of data across an established CSP connection, - * by chopping the data into smaller chuncks of data, that can fit into a single CSP message. + * by chopping the data into smaller chunks of data, that can fit into a single CSP message. * * SFP will add a small header to each packet, containing information about the transfer. * SFP is usually sent over a RDP connection (which also adds a header), ****************************************************************************/ #pragma once -#include // memcpy() - #include #ifdef __cplusplus @@ -21,40 +19,98 @@ extern "C" { /** - * Send data over a CSP connection. - * - * Data will be send in chunks of \a mtu bytes. The MTU must be small enough to fit - * into a CSP packat + SFP header + other transport headers. - * - * csp_sfp_recv() or csp_sfp_recv_fp() can be used at the other end to receive data. + * Structure to be passed as an input parameter to csp_sfp_send(...). + * This structure encapsulates user-defined data and a function for reading data from storage. + */ +typedef struct { + /** + * User-defined data. SFP does not interpret or manipulate this data. + * It can be any user-defined data such as a file handle, a raw buffer pointer, or any other + * relevant information required for the implementation. NULL is accepted. + */ + void * data; + + /** + * User-defined function for reading data from a storage medium. + * + * This callback allows users to define a custom mechanism for retrieving data, such as reading + * from a file, memory buffer, or another type of storage. The SFP layer invokes this function + * during data transmission. + * + * @param[out] buffer Pointer to the buffer where the read data should be stored. This pointer is always valid. + * @param[in] size The number of bytes to read. Guaranteed to be greater than 0. + * @param[in] offset The offset in the source storage from where the data should be read. This allows + * the user to retrieve data from specific positions, such as file offsets or + * buffer indices. + * @param[in] data Pointer to user-specific data provided by the caller (e.g., a file handle, buffer state, + * or context). This pointer is optional and may be NULL, depending on the user implementation. + * @return #CSP_ERR_NONE on success, otherwise an error. + */ + int (* read)(uint8_t * buffer, uint32_t size, uint32_t offset, void * data); +} csp_sfp_read_t; + +/** + * Structure to be passed as an input parameter to csp_sfp_recv(...). + * This structure encapsulates user-defined data and a function for writing data to storage. + */ +typedef struct { + /** + * User-defined data. SFP does not interpret or manipulate this data. + * It can be any user-defined data such as a file handle, a raw buffer pointer, or any other + * relevant information required for the implementation. NULL is accepted. + */ + void * data; + + /** + * User-defined function for writing data to a storage medium. + * + * This callback allows users to define a custom mechanism for handling incoming data, + * such as writing it to a file, buffer, or another type of storage. The SFP layer + * invokes this function during data reception. + * + * @param[in] buffer Pointer to the buffer containing the data to be written. This pointer is always valid. + * @param[in] size The number of bytes to write. Guaranteed to be greater than 0. + * @param[in] offset The offset in the destination storage where the data should be written. This + * allows the user to place data at specific locations, such as file positions + * or buffer indices. + * @param[in] totalsz The total expected size of the incoming data. This value is consistent + * across all calls to this function during a single transfer and can be used + * for validation or progress tracking. + * @param[in] data Pointer to user-specific data provided by the caller (e.g., a file handle, + * buffer state, or context). This pointer is optional and may be NULL, depending + * on the user implementation. + * @return #CSP_ERR_NONE on success, otherwise an error. + */ + int (* write)(const uint8_t * buffer, uint32_t size, uint32_t offset, uint32_t totalsz, void * data); +} csp_sfp_recv_t; + +/** + * Get the maximum MTU (Maximum Transmission Unit) for options. * - * This is usefull if you wish to send data stored in flash memory or another location, where standard memcpy() doesn't work. + * @param opts Options indicating required protocol features (RDP, CRC, etc.). + * @return The maximum MTU in bytes. + */ +uint32_t csp_sfp_opts_max_mtu(uint32_t opts); + +/** + * Get the maximum MTU (Maximum Transmission Unit) for a connection. * - * @param[in] conn established connection for sending SFP packets. - * @param[in] data data to send - * @param[in] datasize tsize of \a data - * @param[in] mtu maximum transfer unit (bytes), max data chunk to send. - * @param[in] timeout unused as of CSP version 1.6 - * @param[in] memcpyfcn memory copy function. - * @return #CSP_ERR_NONE on success, otherwise an error. + * @param conn Connection indicating required protocol features (RDP, CRC, etc.). + * @return The maximum MTU in bytes. */ -int csp_sfp_send_own_memcpy(csp_conn_t * conn, const void * data, unsigned int datasize, unsigned int mtu, uint32_t timeout, csp_memcpy_fnc_t memcpyfcn); +uint32_t csp_sfp_conn_max_mtu(const csp_conn_t * conn); /** * Send data over a CSP connection. * - * Uses csp_sfp_send_own_memcpy() with standard memcpy(). - * * @param[in] conn established connection for sending SFP packets. - * @param[in] data data to send - * @param[in] datasize size of \a data + * @param[in] user User-defined read function and data pointer. + * @param[in] datasize Total size to send. * @param[in] mtu maximum transfer unit (bytes), max data chunk to send. * @param[in] timeout unused as of CSP version 1.6 * @return #CSP_ERR_NONE on success, otherwise an error. */ -static inline int csp_sfp_send(csp_conn_t * conn, const void * data, unsigned int datasize, unsigned int mtu, uint32_t timeout) { - return csp_sfp_send_own_memcpy(conn, data, datasize, mtu, timeout, (csp_memcpy_fnc_t) &memcpy); -} +int csp_sfp_send(csp_conn_t * conn, const csp_sfp_read_t * user, uint32_t datasize, uint32_t mtu, uint32_t timeout); /** * Receive data over a CSP connection. @@ -62,30 +118,26 @@ static inline int csp_sfp_send(csp_conn_t * conn, const void * data, unsigned in * This is the counterpart to the csp_sfp_send() and csp_sfp_send_own_memcpy(). * * @param[in] conn established connection for receiving SFP packets. - * @param[out] dataout received data on success. Allocated with malloc(), so - * should be freed with free(). The pointer will be NULL on failure. - * @param[out] datasize size of received data. + * @param[in] user User-defined data with write function and data pointer. * @param[in] timeout timeout in ms to wait for csp_read() * @param[in] first_packet First packet of a SFP transfer. * Use NULL to receive first packet on the connection. * @return #CSP_ERR_NONE on success, otherwise an error. */ -int csp_sfp_recv_fp(csp_conn_t * conn, void ** dataout, int * datasize, uint32_t timeout, csp_packet_t * first_packet); +int csp_sfp_recv_fp(csp_conn_t * conn, const csp_sfp_recv_t * user, uint32_t timeout, csp_packet_t * first_packet); /** * Receive data over a CSP connection. * - * This is the counterpart to the csp_sfp_send() and csp_sfp_send_own_memcpy(). + * This is the counterpart to the csp_sfp_send() * * @param[in] conn established connection for receiving SFP packets. - * @param[out] dataout received data on success. Allocated with malloc(), so - * should be freed with free(). The pointer will be NULL on failure. - * @param[out] datasize size of received data. + * @param[in] user User-defined data with write function and data pointer. * @param[in] timeout timeout in ms to wait for csp_read() * @return #CSP_ERR_NONE on success, otherwise an error. */ -static inline int csp_sfp_recv(csp_conn_t * conn, void ** dataout, int * datasize, uint32_t timeout) { - return csp_sfp_recv_fp(conn, dataout, datasize, timeout, NULL); +static inline int csp_sfp_recv(csp_conn_t * conn, const csp_sfp_recv_t * user, uint32_t timeout) { + return csp_sfp_recv_fp(conn, user, timeout, NULL); } #ifdef __cplusplus diff --git a/include/csp/csp_types.h b/include/csp/csp_types.h index dee5fd463..578a4f01c 100644 --- a/include/csp/csp_types.h +++ b/include/csp/csp_types.h @@ -51,14 +51,14 @@ typedef enum { /** CSP identifier/header. */ -typedef struct __packed { +typedef struct { uint8_t pri; uint8_t flags; uint16_t src; uint16_t dst; uint8_t dport; uint8_t sport; -} csp_id_t ; +} __attribute__ ((__packed__)) csp_id_t ; /** @defgroup CSP_HEADER_FLAGS CSP header flags. @@ -85,7 +85,7 @@ typedef struct __packed { #define CSP_SO_CRC32REQ 0x0040 /*< Require CRC32 */ #define CSP_SO_CRC32PROHIB 0x0080 /*< Prohibit CRC32 */ #define CSP_SO_CONN_LESS 0x0100 /*< Enable Connection Less mode */ -#define CSP_SO_SAME 0x8000 /*< Copy opts from incoming packet only apllies to csp_sendto_reply() */ +#define CSP_SO_SAME 0x8000 /*< Copy opts from incoming packet only applies to csp_sendto_reply() */ /**@}*/ @@ -117,27 +117,16 @@ typedef struct __packed { */ typedef struct csp_packet_s { - union { - - /* Only used on layer 3 (RDP) */ - struct { - uint32_t rdp_quarantine; /*< EACK quarantine period */ - uint32_t timestamp_tx; /*< Time the message was sent */ - uint32_t timestamp_rx; /*< Time the message was received */ - struct csp_conn_s * conn; /*< Associated connection (this is used in RDP queue) */ - }; - - /* Only used on interface RX/TX (layer 2) */ - struct { - uint16_t rx_count; /*< Received bytes */ - uint16_t remain; /*< Remaining packets */ - uint32_t cfpid; /*< Connection CFP identification number */ - uint32_t last_used; /*< Timestamp in ms for last use of buffer */ - uint8_t * frame_begin; - uint16_t frame_length; - }; + uint32_t timestamp_tx; /*< Time the message was sent */ + uint32_t timestamp_rx; /*< Time the message was received */ + struct csp_conn_s * conn; /*< Associated connection (this is used in RDP queue) */ - }; + uint16_t rx_count; /*< Received bytes */ + uint16_t remain; /*< Remaining packets */ + uint32_t cfpid; /*< Connection CFP identification number */ + uint32_t last_used; /*< Timestamp in ms for last use of buffer */ + uint8_t * frame_begin; + uint16_t frame_length; uint16_t length; /*< Data length */ csp_id_t id; /*< CSP id (unpacked version CPU readable) */ @@ -147,7 +136,7 @@ typedef struct csp_packet_s { /** * Additional header bytes, to prepend packed data before transmission - * This must be minimum 6 bytes to accomodate CSP 2.0. But some implementations + * This must be minimum 6 bytes to accommodate CSP 2.0. But some implementations * require much more scratch working area for encryption for example. * * Ultimately after csp_id_pack() this area will be filled with the CSP header @@ -205,12 +194,16 @@ typedef const uint32_t csp_const_memptr_t; typedef void * csp_memptr_t; /** Const memory pointer */ typedef const void * csp_const_memptr_t; +/** Memory pointer 64-bit */ +typedef uint64_t csp_memptr64_t; #endif /** * Platform specific memory copy function. */ typedef csp_memptr_t (*csp_memcpy_fnc_t)(csp_memptr_t, csp_const_memptr_t, size_t); +typedef csp_memptr64_t (*csp_memread64_fnc_t)(csp_const_memptr_t, csp_memptr64_t, size_t); +typedef csp_memptr64_t (*csp_memwrite64_fnc_t)(csp_memptr64_t, csp_memptr_t, size_t); /** * Compile check/asserts. diff --git a/include/csp/csp_yaml.h b/include/csp/csp_yaml.h index bc0bcb84c..25d1a169c 100644 --- a/include/csp/csp_yaml.h +++ b/include/csp/csp_yaml.h @@ -13,7 +13,7 @@ extern "C" { * Setup the CSP stack based on a yaml configuration file * * @param[in] filename YAML configuration file. - * @param[in] dfl_addr The default interface address can be overriden + * @param[in] dfl_addr The default interface address can be overridden * by passing a pointer to an integer higher than zero. * The default interface address can be read back using the * same integer, if its set to zero. Passing NULL to diff --git a/include/csp/drivers/can_zephyr.h b/include/csp/drivers/can_zephyr.h index 8169fd30e..d00f71f05 100644 --- a/include/csp/drivers/can_zephyr.h +++ b/include/csp/drivers/can_zephyr.h @@ -13,6 +13,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * Open CAN and add CSP interface. * @@ -47,3 +51,7 @@ int csp_can_set_rx_filter(csp_iface_t * iface, uint16_t filter_addr, uint16_t fi * @return #CSP_ERR_NONE on success, otherwise an error code. */ int csp_can_stop(csp_iface_t * iface); + +#ifdef __cplusplus +} +#endif diff --git a/include/csp/drivers/eth_linux.h b/include/csp/drivers/eth_linux.h index 7e08f2ef1..e0233926a 100644 --- a/include/csp/drivers/eth_linux.h +++ b/include/csp/drivers/eth_linux.h @@ -11,6 +11,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + /** * Open RAW socket and add CSP interface. * @@ -28,11 +32,11 @@ int csp_eth_init(const char * device, const char * ifname, int mtu, unsigned int /** * Transmit an CSP ethernet frame * - * @param[in] iface Ethernet interface to use. + * @param[in] driver_data Ethernet interface to use. * @param[in] eth_frame The CSP ethernet frame to transmit. * @return #CSP_ERR_NONE on success, otherwise an error code. */ -int csp_eth_tx_frame(csp_iface_t * iface, csp_eth_header_t * eth_frame); +int csp_eth_tx_frame(void * driver_data, csp_eth_header_t * eth_frame); /** * Posix ethernet RX thread @@ -41,3 +45,7 @@ int csp_eth_tx_frame(csp_iface_t * iface, csp_eth_header_t * eth_frame); * @return NULL */ void * csp_eth_rx_loop(void * param); + +#ifdef __cplusplus +} +#endif diff --git a/include/csp/drivers/usart.h b/include/csp/drivers/usart.h index 2a68df9f2..fabb51caf 100644 --- a/include/csp/drivers/usart.h +++ b/include/csp/drivers/usart.h @@ -89,12 +89,12 @@ void csp_usart_unlock(void * driver_data); /** * Opens UART device and add KISS interface. * - * This is a convience function for opening an UART device and adding it as an interface with a given name. + * This is a convenience function for opening an UART device and adding it as an interface with a given name. * * .. note:: On read failures, exit() will be called - terminating the process. * * @param[in] conf UART configuration. - * @param[in] ifname internface name (will be copied), or use NULL for default name. + * @param[in] ifname interface name (will be copied), or use NULL for default name. * @param[in] addr CSP address of the interface. * @param[out] return_iface the added interface. * @return #CSP_ERR_NONE on success, otherwise an error code. diff --git a/include/csp/interfaces/csp_if_can.h b/include/csp/interfaces/csp_if_can.h index 7cb364529..5d04b17e7 100644 --- a/include/csp/interfaces/csp_if_can.h +++ b/include/csp/interfaces/csp_if_can.h @@ -14,7 +14,7 @@ * - Remain: 8 bits * - Identifier: 10 bits * - * The Source and Destination fields must match the source and destiantion addressses + * The Source and Destination fields must match the source and destination addresses * in the CSP packet. The Type field is used to distinguish the first and subsequent * frames in a fragmented CSP packet. Type is BEGIN (0) for the first fragment and * MORE (1) for all other fragments. The Remain field indicates number of remaining @@ -22,6 +22,39 @@ * field serves the same purpose as in the Internet Protocol, and should be an auto * incrementing integer to uniquely separate sessions. * + * For networks configured as CSP version 2, the CAN identifier is divided into: + * + * - Priority: 2 bits + * - Destination: 14 bits + * - Sender id: 6 bits + * - Packet count: 2 bits + * - Frame count: 3 bits + * - Begin flag: 1 bit + * - End flag: 1 bit + * + * The \b Priority represents the CSP prio field. Placing this as the first bits in + * the transmission ensure that packets with high priority is prioritized on the bus + * due to the nature of CAN arbitration. + * The \b Destination field represents the CSP node of the receiving node + * The \b Sender holds the least significant bits of the transmitting interface, + * i.e. the local address when a packet is forwarded by a routing instance. + * The \b Packet \b count is an incrementing value for every CSP packet + * The \b Frame \b count represents the frame fragment index for the particular packet + * The \b Begin \b flag is set for the first CAN frame in a CSP packet + * The \b End \b flag is set for the last CAN frame in a CSP packet + * + * In addition to the 29 bit extended CAN header, CSP utilize four bytes in the + * first CAN fragment in every CSP packet as: + * + * - Source: 14 bits + * - Dest port: 6 bits + * - Source port: 6 bits + * - CSP flags: 6 bits + + * The \b Source holds the CSP node address of the origin of the CSP packet. + * The \b Dest and \Source \b port represents the port numbers for the transmission. + * The \b CSP \b flags holds the CSP_HEADER_FLAGS. + * * Other CAN communication using a standard 11 bit identifier, can co-exist on the wire. ****************************************************************************/ #pragma once @@ -89,18 +122,6 @@ extern "C" { CFP_MAKE_ID((uint32_t)(1 << CFP_ID_SIZE) - 1)) -/** - * CFP 2.0 - * - * PRIO: 2 - * DST: 14 - * Sender id: 6 - * Sender counter: 2 - * Fragment counter: 3 - * Begin: 1 - * End: 1 - */ - #define CFP2_PRIO_MASK 0x3 #define CFP2_PRIO_OFFSET 27 diff --git a/include/csp/interfaces/csp_if_eth.h b/include/csp/interfaces/csp_if_eth.h index ad9577213..33c452730 100644 --- a/include/csp/interfaces/csp_if_eth.h +++ b/include/csp/interfaces/csp_if_eth.h @@ -43,6 +43,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + /* 0x88B5 : IEEE Std 802 - Local Experimental Ethertype (RFC 5342) */ #define CSP_ETH_TYPE_CSP 0x88B5 @@ -59,7 +63,7 @@ #define CSP_ETH_ALEN 6 /* Octets in one ethernet addr */ /** - * Definition of ethernet header, including reqired MAC addresses + * Definition of ethernet header, including required MAC addresses * Frame data is required to proceed in memory space after this struct */ typedef struct csp_eth_header_s @@ -146,3 +150,7 @@ int csp_eth_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int fro * @return #CSP_ERR_NONE on success, otherwise an error code. */ int csp_eth_rx(csp_iface_t * iface, csp_eth_header_t * eth_frame, uint32_t received_len, int * task_woken); + +#ifdef __cplusplus +} +#endif diff --git a/include/csp/interfaces/csp_if_eth_pbuf.h b/include/csp/interfaces/csp_if_eth_pbuf.h index de9408d2d..01e1e0e94 100644 --- a/include/csp/interfaces/csp_if_eth_pbuf.h +++ b/include/csp/interfaces/csp_if_eth_pbuf.h @@ -9,7 +9,7 @@ * counter that wraps around at 32768. With multiple nodes connected on ethernet * the packet identifiers on different nodes may collide, which is why the CSP * source address is included in the pbuf identifier. Multiple source addresses - * causes no problems, as the packet_id dows not depend on the packet content. + * causes no problems, as the packet_id does not depend on the packet content. * * A segment identifier is considered not required, as the stream of ethernet * frames follows a single path, and there are no retransmission, so it can be @@ -18,7 +18,7 @@ * CSP_IF_ETH_PBUF_TIMEOUT_MS after latest data was received. * * There is a minimum ethernet frame size, such that the received size is at - * leas 60 bytes. The segment size field of the header provides the length of + * least 60 bytes. The segment size field of the header provides the length of * the actual payload. * * The size of a CSP packet can generally not be determined from a partial CSP @@ -35,20 +35,22 @@ #include -#define CSP_IF_ETH_PBUF_TIMEOUT_MS 2000 - -/** Packet list operations */ - -csp_packet_t * csp_if_eth_pbuf_find(csp_packet_t ** plist, uint32_t pbuf_id); - -void csp_if_eth_pbuf_insert(csp_packet_t ** plist, csp_packet_t * packet); - -csp_packet_t * csp_if_eth_pbuf_get(csp_packet_t ** plist, uint32_t pbuf_id, int * task_woken); - -void csp_if_eth_pbuf_remove(csp_packet_t ** plist, csp_packet_t * packet); - -void csp_if_eth_pbuf_list_cleanup(csp_packet_t ** plist); - -void csp_if_eth_pbuf_print(const char * descr, csp_packet_t * packet); - -void csp_if_eth_pbuf_list_print(csp_packet_t ** plist); +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint16_t rx_count; /* Received bytes */ + uint16_t remain; /* Remaining packets */ + uint32_t cfpid; /* Connection CFP identification number */ + uint32_t last_used; /* Timestamp in ms for last use of buffer */ +} csp_eth_pbuf_element_t; + +void csp_eth_pbuf_free(csp_eth_interface_data_t * ifdata, csp_packet_t * buffer, int buf_free, int * task_woken); +csp_packet_t * csp_eth_pbuf_new(csp_eth_interface_data_t * ifdata, uint32_t id, uint32_t now, int * task_woken); +csp_packet_t * csp_eth_pbuf_find(csp_eth_interface_data_t * ifdata, uint32_t id, int * task_woken); +void csp_eth_pbuf_cleanup(csp_eth_interface_data_t * ifdata, uint32_t now, int * task_woken); + +#ifdef __cplusplus +} +#endif diff --git a/include/csp/interfaces/csp_if_kiss.h b/include/csp/interfaces/csp_if_kiss.h index 97a3ee1a4..a40f6cd7b 100644 --- a/include/csp/interfaces/csp_if_kiss.h +++ b/include/csp/interfaces/csp_if_kiss.h @@ -71,7 +71,7 @@ int csp_kiss_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int fr * frame has been received, the CSP packet will be routed on. * * @param[in] iface incoming interface. - * @param[in] buf reveived data. + * @param[in] buf received data. * @param[in] len length of \a buf. * @param[out] pxTaskWoken Valid reference if called from ISR, otherwise NULL! */ diff --git a/include/csp/interfaces/csp_if_udp.h b/include/csp/interfaces/csp_if_udp.h index a37cf260c..69ddf5575 100644 --- a/include/csp/interfaces/csp_if_udp.h +++ b/include/csp/interfaces/csp_if_udp.h @@ -34,7 +34,7 @@ typedef struct { * RX task: * A server task will attempt at binding to ip 0.0.0.0 port 9600 * If this fails, it is because another udp server is already running. - * The server task will continue attemting the bind and will not exit before the application is closed. + * The server task will continue attempting the bind and will not exit before the application is closed. * * TX peer: * Outgoing CSP packets will be transferred to the peer specified by the host argument diff --git a/include/csp/interfaces/csp_if_zmqhub.h b/include/csp/interfaces/csp_if_zmqhub.h index 17d972442..ae6cec903 100644 --- a/include/csp/interfaces/csp_if_zmqhub.h +++ b/include/csp/interfaces/csp_if_zmqhub.h @@ -3,7 +3,7 @@ * * **Description:** ZMQ (ZeroMQ) interface. * - * The ZMQ interface is designed to connect to a ZMQ hub, also refered to as + * The ZMQ interface is designed to connect to a ZMQ hub, also referred to as * zmqproxy. The zmqproxy can be found under examples, and is based on * zmq_proxy() - provided by the ZMQ API. * @@ -44,7 +44,7 @@ extern "C" { * @param[in] port IP port. * @param[out] buf user allocated buffer for receiving formatted string. * @param[in] buf_size size of buf. - * @return #CSP_ERR_NONE on succcess. #CSP_ERR_NOMEM if supplied buffer too small. + * @return #CSP_ERR_NONE on success. #CSP_ERR_NOMEM if supplied buffer too small. */ int csp_zmqhub_make_endpoint(const char * host, uint16_t port, char * buf, size_t buf_size); @@ -57,7 +57,7 @@ int csp_zmqhub_make_endpoint(const char * host, uint16_t port, char * buf, size_ * created using the host and the default subscribe/publish ports. * @param[in] flags flags for controlling features on the connection. * @param[out] return_interface created CSP interface. - * @return #CSP_ERR_NONE on succcess - else assert. + * @return #CSP_ERR_NONE on success - else assert. */ int csp_zmqhub_init(uint16_t addr, const char * host, @@ -75,7 +75,7 @@ int csp_zmqhub_init(uint16_t addr, * to zmqproxy's publish port #CSP_ZMQPROXY_PUBLISH_PORT. * @param[in] flags flags for controlling features on the connection. * @param[out] return_interface created CSP interface. - * @return #CSP_ERR_NONE on succcess - else assert. + * @return #CSP_ERR_NONE on success - else assert. */ int csp_zmqhub_init_w_endpoints(uint16_t addr, const char * publish_endpoint, @@ -97,7 +97,7 @@ int csp_zmqhub_init_w_endpoints(uint16_t addr, * publish port #CSP_ZMQPROXY_PUBLISH_PORT. * @param[in] flags flags for controlling features on the connection. * @param[out] return_interface created CSP interface. - * @return #CSP_ERR_NONE on succcess - else assert. + * @return #CSP_ERR_NONE on success - else assert. */ int csp_zmqhub_init_w_name_endpoints_rxfilter(const char * ifname, uint16_t addr, const uint16_t rx_filter[], unsigned int rx_filter_count, @@ -123,6 +123,23 @@ int csp_zmqhub_init_w_name_endpoints_rxfilter(const char * ifname, uint16_t addr int csp_zmqhub_init_filter2(const char * ifname, const char * host, uint16_t addr, uint16_t netmask, int promisc, csp_iface_t ** return_interface, char * sec_key, uint16_t subport, uint16_t pubport); +void csp_zmqhub_fixup_cspv1_add_dest_addr(csp_packet_t * packet); +void * csp_zmqhub_fixup_cspv1_del_dest_addr(uint8_t * rx_data, size_t * datalen); +/** + * Make `zmq_iface` promiscuous (parse all packets) + * + * Safe to call after `csp_zmqhub_init_filter2()` to change promiscuity. + */ +int csp_zmqhub_remove_filters(csp_iface_t * zmq_iface); + +/** + * Make `zmq_iface` unpromiscuous, only parse matching unicast and broadcast addresses. + * + * Safe to call after `csp_zmqhub_init_filter2()` to change promiscuity. + */ +int csp_zmqhub_add_filters(csp_iface_t * zmq_iface); + + #ifdef __cplusplus } #endif diff --git a/meson.build b/meson.build index 70d83a33a..145abb472 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,7 @@ -project('csp', 'c', version: '2.1', license: 'LGPL', meson_version : '>=0.53.2', default_options : [ +project('csp', 'c', version: '2.2', license: 'LGPL', meson_version : '>=0.61.2', default_options : [ 'c_std=gnu11', 'optimization=s', - 'warning_level=2', + 'warning_level=3', 'werror=true']) cc = meson.get_compiler('c') @@ -14,6 +14,7 @@ conf.set('CSP_CONN_RXQUEUE_LEN', get_option('conn_rxqueue_len')) conf.set('CSP_CONN_MAX', get_option('conn_max')) conf.set('CSP_BUFFER_SIZE', get_option('buffer_size')) conf.set('CSP_BUFFER_COUNT', get_option('buffer_count')) +conf.set('CSP_PACKET_PADDING_BYTES', get_option('packet_padding_bytes')) conf.set('CSP_RDP_MAX_WINDOW', get_option('rdp_max_window')) conf.set('CSP_RTABLE_SIZE', get_option('rtable_size')) @@ -26,6 +27,11 @@ conf.set10('CSP_HAVE_STDIO', get_option('have_stdio')) conf.set10('CSP_ENABLE_CSP_PRINT', get_option('enable_csp_print')) conf.set10('CSP_PRINT_STDIO', get_option('print_stdio')) conf.set10('CSP_USE_RTABLE', get_option('use_rtable')) +conf.set10('CSP_BUFFER_ZERO_CLEAR', get_option('buffer_zero_clear')) + +conf.set10('CSP_FIXUP_V1_ZMQ_LITTLE_ENDIAN', get_option('fixup_v1_zmq_little_endian')) + +conf.set10('CSP_ENABLE_KISS_CRC', get_option('enable_kiss_crc')) csp_deps = [] csp_sources = [] @@ -59,18 +65,23 @@ csp_deps += clib csp_inc = include_directories('include', 'src') # Default compile options -csp_c_args = ['-Wshadow', - '-Wcast-align', - '-Wwrite-strings', - '-Wpointer-arith', - '-Wno-unused-parameter'] +csp_c_args = ['-Wall', + '-Wcast-align', + '-Wextra', + '-Wmissing-prototypes', + '-Wpedantic', + '-Wpointer-arith', + '-Wshadow', + '-Wwrite-strings'] +if cc.get_id() == 'clang' + csp_c_args += '-Wno-gnu-zero-variadic-macro-arguments' +endif subdir('src') subdir('include/csp') if not meson.is_subproject() - install_subdir('include', install_dir : '.') - install_headers(csp_config_h, install_dir : 'include/csp') + install_subdir('include', install_dir : '', exclude_files : ['csp/meson.build']) endif @@ -79,7 +90,7 @@ csp_lib = library('csp', include_directories : csp_inc, dependencies : csp_deps, c_args : csp_c_args, - install : false, + install : true, pic:true, ) @@ -101,7 +112,9 @@ if get_option('enable_python3_bindings') # dependency() instead pydep = dependency('python3', version : '>=3.5', required : true) py.extension_module('libcsp_py3', 'src/bindings/python/pycsp.c', - c_args : csp_c_args, + c_args : [csp_c_args, + '-Wno-missing-prototypes', + '-Wno-unused-parameter'], dependencies : [csp_dep, pydep], install : true) endif diff --git a/meson_options.txt b/meson_options.txt index dc87cdc30..72a1a3c07 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -13,15 +13,20 @@ option('enable_csp_print', type: 'boolean', value: true, description: 'Enable cs option('have_stdio', type: 'boolean', value: true, description: 'Use print and scan functions (some features may be missing without)') option('use_rtable', type: 'boolean', value: false, description: 'Allows to setup a list of static routes. End nodes do not need this. But radios and routers might') option('print_stdio', type: 'boolean', value: true, description: 'Use vprintf for csp_print_func') +option('buffer_zero_clear', type: 'boolean', value: false, description: 'Zero out the packet buffer upon allocation') # Memory tuning parameters: # Try to balance these so there is enough memory to handle expected system usage plus some, # while avoiding over-allocating too much memory, that would be better used elsewhere option('qfifo_len', type: 'integer', value: 15, description: 'Length of incoming queue for router task') -option('port_max_bind', type: 'integer', value: 16, description: 'Length of incoming queue for router task') +option('port_max_bind', type: 'integer', value: 16, description: 'Maximum number of bindable ports') option('conn_rxqueue_len', type: 'integer', value: 15, description: 'Number of packets in connection queue') option('conn_max', type: 'integer', value: 8, description: 'Number of new connections on socket queue') option('buffer_size', type: 'integer', value: 256, description: 'Bytes in each packet buffer') option('buffer_count', type: 'integer', value: 15, description: 'Number of total packet buffers') option('rdp_max_window', type: 'integer', value: 5, description: 'Max window size for RDP') option('rtable_size', type: 'integer', value: 10, description: 'Number of elements in routing table') + +option('fixup_v1_zmq_little_endian', type: 'boolean', value: false, description: 'Use little-endian CSP ID for ZMQ with CSPv1') + +option('enable_kiss_crc', type: 'boolean', value: true, description: 'Enables the extra CRC in the KISS interface (legacy)') diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt new file mode 100644 index 000000000..1880767af --- /dev/null +++ b/samples/CMakeLists.txt @@ -0,0 +1,3 @@ +if(CSP_POSIX) + add_subdirectory(posix) +endif() diff --git a/samples/posix/CMakeLists.txt b/samples/posix/CMakeLists.txt new file mode 100644 index 000000000..d8d90678c --- /dev/null +++ b/samples/posix/CMakeLists.txt @@ -0,0 +1,6 @@ +add_subdirectory(simple-send-canbus) +add_subdirectory(simple-send-usart) +add_subdirectory(hmac) +add_subdirectory(simple-send-udp) +add_subdirectory(simple-send-zmq) +add_subdirectory(simple-sfp-send-recv) diff --git a/samples/posix/hmac/CMakeLists.txt b/samples/posix/hmac/CMakeLists.txt new file mode 100644 index 000000000..1fd085248 --- /dev/null +++ b/samples/posix/hmac/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(sample-hmac ${CSP_SAMPLES_EXCLUDE} src/main.c) +target_include_directories(sample-hmac PRIVATE ${csp_inc}) +target_link_libraries(sample-hmac PRIVATE csp) diff --git a/samples/posix/hmac/src/main.c b/samples/posix/hmac/src/main.c new file mode 100644 index 000000000..3bb7b841c --- /dev/null +++ b/samples/posix/hmac/src/main.c @@ -0,0 +1,29 @@ +#include + +#include +#include +#include +#include + +int main(int argc, char * argv[]) +{ + csp_packet_t * packet; + + csp_init(); + + packet = csp_buffer_get(0); + if (packet == NULL) { + return 1; + } + + csp_id_prepend(packet); + + memcpy(packet->data, "abc", 3); + packet->length += 3; + packet->frame_length += 3; + + csp_hmac_append(packet, true); + csp_hmac_verify(packet, true); + + return 0; +} diff --git a/samples/posix/simple-send-canbus/CMakeLists.txt b/samples/posix/simple-send-canbus/CMakeLists.txt new file mode 100644 index 000000000..5881d9838 --- /dev/null +++ b/samples/posix/simple-send-canbus/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(simple-send-canbus ${CSP_SAMPLES_EXCLUDE} src/main.c) +target_include_directories(simple-send-canbus PRIVATE ${csp_inc}) +target_link_libraries(simple-send-canbus PRIVATE csp) diff --git a/samples/posix/simple-send-canbus/README.md b/samples/posix/simple-send-canbus/README.md new file mode 100644 index 000000000..b679c62da --- /dev/null +++ b/samples/posix/simple-send-canbus/README.md @@ -0,0 +1,86 @@ +# Simple Send via CANbus + +This is a simple sample code to send `"abc"` to a server via the +CANbus interface. + +## How to Build + +``` +$ cmake -B builddir +$ ninja -C builddir simple-send-canbus csp_server +``` + +## How to Test + +To run without a real CANbus hardware, you can setup a vcan interface +on Linux: + +``` +$ sudo ip link add dev vcan0 type vcan +$ sudo ip link set up vcan0 +``` + +This command will create a virtual CANbus interface that a server and +a client can share. + +You’ll need the `ip` command. If you don't have it, install it with: + +``` +$ sudo apt-get install iproute2 +``` + +First, you need to run a CSP server: + +``` +$ ./builddir/examples/csp_server -c vcan0 -a 1 +Initialising CSP +INIT CAN: device: [vcan0], bitrate: 1000000, promisc: 1 +RTNETLINK answers: Operation not permitted +RTNETLINK answers: Operation not permitted +RTNETLINK answers: Operation not permitted +RTNETLINK answers: Operation not permitted +Connection table +[00 0x7fbfc89208e0] S:0, 0 -> 0, 0 -> 0 (17) fl 0 +[01 0x7fbfc89209f8] S:0, 0 -> 0, 0 -> 0 (18) fl 0 +[02 0x7fbfc8920b10] S:0, 0 -> 0, 0 -> 0 (19) fl 0 +[03 0x7fbfc8920c28] S:0, 0 -> 0, 0 -> 0 (20) fl 0 +[04 0x7fbfc8920d40] S:0, 0 -> 0, 0 -> 0 (21) fl 0 +[05 0x7fbfc8920e58] S:0, 0 -> 0, 0 -> 0 (22) fl 0 +[06 0x7fbfc8920f70] S:0, 0 -> 0, 0 -> 0 (23) fl 0 +[07 0x7fbfc8921088] S:0, 0 -> 0, 0 -> 0 (24) fl 0 +Interfaces +LOOP addr: 0 netmask: 14 dfl: 0 + tx: 00000 rx: 00000 txe: 00000 rxe: 00000 + drop: 00000 autherr: 00000 frame: 00000 + txb: 0 (0B) rxb: 0 (0B) + +CAN addr: 1 netmask: 0 dfl: 1 + tx: 00000 rx: 00000 txe: 00000 rxe: 00000 + drop: 00000 autherr: 00000 frame: 00000 + txb: 0 (0B) rxb: 0 (0B) + +Server task started +``` + +Then, in another terminal, run `simple-send-canbus` + +``` +$ ./builddir/samples/posix/simple-send-canbus/simple-send-canbus +INIT CAN: device: [vcan0], bitrate: 1000000, promisc: 1 +RTNETLINK answers: Operation not permitted +RTNETLINK answers: Operation not permitted +RTNETLINK answers: Operation not permitted +RTNETLINK answers: Operation not permitted +``` + +You see warnings like `RTNETLINK answers: Operation not permitted` +because your are not root. It should work without using `sudo`. Also +even you use `sudo`, you still see a few warnings because `vcan0` does +not support a few operations. + +If you successfully run `simple-send-canbus`, you see the following +message on the server terminal. + +``` +Packet received on SERVER_PORT: abc +``` diff --git a/samples/posix/simple-send-canbus/src/main.c b/samples/posix/simple-send-canbus/src/main.c new file mode 100644 index 000000000..8e353c09b --- /dev/null +++ b/samples/posix/simple-send-canbus/src/main.c @@ -0,0 +1,55 @@ +#include + +#include +#include +#include + +#define SERVER_ADDR 1 +#define SERVER_PORT 10 + +#define CLIENT_ADDR 2 +#define DEVICE_NAME "vcan0" + +int main(int argc, char * argv[]) +{ + csp_iface_t * iface; + csp_conn_t * conn; + csp_packet_t * packet; + int ret; + + /* init */ + csp_init(); + + /* open */ + ret = csp_can_socketcan_open_and_add_interface(DEVICE_NAME, CSP_IF_CAN_DEFAULT_NAME, CLIENT_ADDR, 1000000, true, &iface); + if (ret != CSP_ERR_NONE) { + csp_print("failed to open: %d\n", ret); + return 1; + } + iface->is_default = 1; + + /* connect */ + conn = csp_connect(CSP_PRIO_NORM, SERVER_ADDR, SERVER_PORT, 1000, CSP_O_NONE); + if (conn == NULL) { + csp_print("Connection failed\n"); + return 1; + } + + /* prepare data */ + packet = csp_buffer_get(0); + if (packet == NULL) { + csp_print("Failed to get buffer\n"); + csp_close(conn); + return 1; + } + memcpy(packet->data, "abc", 3); + packet->length = 3; + + /* send */ + csp_send(conn, packet); + + /* close */ + csp_close(conn); + + return 0; +} diff --git a/samples/posix/simple-send-udp/CMakeLists.txt b/samples/posix/simple-send-udp/CMakeLists.txt new file mode 100644 index 000000000..0bc80ea12 --- /dev/null +++ b/samples/posix/simple-send-udp/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(simple-send-udp ${CSP_SAMPLES_EXCLUDE} src/main.c) +target_include_directories(simple-send-udp PRIVATE ${csp_inc}) +target_link_libraries(simple-send-udp PRIVATE csp) diff --git a/samples/posix/simple-send-udp/README.md b/samples/posix/simple-send-udp/README.md new file mode 100644 index 000000000..0ff664297 --- /dev/null +++ b/samples/posix/simple-send-udp/README.md @@ -0,0 +1,56 @@ +# Simple Send via UDP + +This is a simple sample code to send `"abc"` to a server via the +UDP interface. + +## How to Build + +``` +$ cmake -B builddir +$ ninja -C builddir simple-send-udp csp_server +``` + +## How to Test + +First, you need to run a CSP server: + +``` +$ ./builddir/examples/csp_server csp_server -u "127.0.0.1" -a 1 +Initialising CSP +UDP peer address: 127.0.0.1:1500 (listening on port 1501) +Connection table +[00 0x745a7ae4e920] S:0, 0 -> 0, 0 -> 0 (17) fl 0 +[01 0x745a7ae4ea38] S:0, 0 -> 0, 0 -> 0 (18) fl 0 +[02 0x745a7ae4eb50] S:0, 0 -> 0, 0 -> 0 (19) fl 0 +[03 0x745a7ae4ec68] S:0, 0 -> 0, 0 -> 0 (20) fl 0 +[04 0x745a7ae4ed80] S:0, 0 -> 0, 0 -> 0 (21) fl 0 +[05 0x745a7ae4ee98] S:0, 0 -> 0, 0 -> 0 (22) fl 0 +[06 0x745a7ae4efb0] S:0, 0 -> 0, 0 -> 0 (23) fl 0 +[07 0x745a7ae4f0c8] S:0, 0 -> 0, 0 -> 0 (24) fl 0 +Interfaces +LOOP addr: 0 netmask: 14 dfl: 0 + tx: 00000 rx: 00000 txe: 00000 rxe: 00000 + drop: 00000 autherr: 00000 frame: 00000 + txb: 0 (0B) rxb: 0 (0B) + +UDP addr: 5 netmask: 0 dfl: 1 + tx: 00000 rx: 00000 txe: 00000 rxe: 00000 + drop: 00000 autherr: 00000 frame: 00000 + txb: 0 (0B) rxb: 0 (0B) + +Server task started +``` + +Then, in another terminal, run `simple-send-udp` + +``` +$ ./builddir/samples/posix/simple-send-udp/simple-send-udp +UDP peer address: 127.0.0.1:1501 (listening on port 1500) +``` + +If you successfully run `simple-send-udp`, you see the following +message on the server terminal. + +``` +Packet received on SERVER_PORT: abc +``` diff --git a/samples/posix/simple-send-udp/src/main.c b/samples/posix/simple-send-udp/src/main.c new file mode 100644 index 000000000..821cbdf0e --- /dev/null +++ b/samples/posix/simple-send-udp/src/main.c @@ -0,0 +1,60 @@ +#include +#include +#include + +#include +#include + +#define CLIENT_ADDR 2 + +#define SERVER_ADDR 1 +#define SERVER_PORT 10 + +#define DEFAULT_UDP_ADDRESS "127.0.0.1" +#define DEFAULT_UDP_REMOTE_PORT 1501 +#define DEFAULT_UDP_LOCAL_PORT 1500 + +int main(int argc, char * argv[]) { + csp_conn_t * conn; + csp_packet_t * packet; + int ret; + + /* init */ + csp_init(); + + /* Interface config */ + csp_iface_t iface; + csp_if_udp_conf_t conf = { + .host = DEFAULT_UDP_ADDRESS, + .lport = DEFAULT_UDP_LOCAL_PORT, + .rport = DEFAULT_UDP_REMOTE_PORT}; + + csp_if_udp_init(&iface, &conf); + iface.is_default = 1; + iface.addr = CLIENT_ADDR; + + /* connect */ + conn = csp_connect(CSP_PRIO_NORM, SERVER_ADDR, SERVER_PORT, 1000, CSP_O_NONE); + if (conn == NULL) { + csp_print("Connection failed\n"); + return 1; + } + + /* prepare data */ + packet = csp_buffer_get(0); + if (packet == NULL) { + csp_print("Failed to get buffer\n"); + csp_close(conn); + return 1; + } + memcpy(packet->data, "abc", 3); + packet->length = 3; + + /* send */ + csp_send(conn, packet); + + /* close */ + csp_close(conn); + + return 0; +} diff --git a/samples/posix/simple-send-usart/CMakeLists.txt b/samples/posix/simple-send-usart/CMakeLists.txt new file mode 100644 index 000000000..59e5dc281 --- /dev/null +++ b/samples/posix/simple-send-usart/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(simple-send-usart ${CSP_SAMPLES_EXCLUDE} src/main.c) +target_include_directories(simple-send-usart PRIVATE ${csp_inc}) +target_link_libraries(simple-send-usart PRIVATE csp) diff --git a/samples/posix/simple-send-usart/README.md b/samples/posix/simple-send-usart/README.md new file mode 100644 index 000000000..56e907eec --- /dev/null +++ b/samples/posix/simple-send-usart/README.md @@ -0,0 +1,69 @@ +# Simple Send via USART + +This is a simple sample code to send `"abc"` to a server via the USART +interface. + +## How to Build + +``` +$ cmake -B builddir +$ ninja -C builddir simple-send-usart csp_server +``` + +## How to Test + +To run without UART/USART, you can setup a pair of PTY on Linux: + +``` +$ socat -dd pty,raw,echo=0,link=/tmp/pty1 pty,raw,echo=0,link=/tmp/pty2 +``` + +This command will create two device files `/tmp/pty1` and `/tmp/pty2` +for a server and client respectively. + +You’ll need the socat command. If you don't have it, install it with: + +``` +$ sudo apt-get install socat +``` + +First, you need to run a CSP server: + +``` +$ ./builddir/examples/csp_server -k /tmp/pty1 -a 1 +Initialising CSP +Connection table +[00 0x7f1332d208e0] S:0, 0 -> 0, 0 -> 0 (17) fl 0 +[01 0x7f1332d209f8] S:0, 0 -> 0, 0 -> 0 (18) fl 0 +[02 0x7f1332d20b10] S:0, 0 -> 0, 0 -> 0 (19) fl 0 +[03 0x7f1332d20c28] S:0, 0 -> 0, 0 -> 0 (20) fl 0 +[04 0x7f1332d20d40] S:0, 0 -> 0, 0 -> 0 (21) fl 0 +[05 0x7f1332d20e58] S:0, 0 -> 0, 0 -> 0 (22) fl 0 +[06 0x7f1332d20f70] S:0, 0 -> 0, 0 -> 0 (23) fl 0 +[07 0x7f1332d21088] S:0, 0 -> 0, 0 -> 0 (24) fl 0 +Interfaces +LOOP addr: 0 netmask: 14 dfl: 0 + tx: 00000 rx: 00000 txe: 00000 rxe: 00000 + drop: 00000 autherr: 00000 frame: 00000 + txb: 0 (0B) rxb: 0 (0B) + +KISS addr: 1 netmask: 0 dfl: 1 + tx: 00000 rx: 00000 txe: 00000 rxe: 00000 + drop: 00000 autherr: 00000 frame: 00000 + txb: 0 (0B) rxb: 0 (0B) + +Server task started +``` + +Then, in another terminal, run `simple-send-usart` + +``` +./builddir/samples/posix/simple-send-usart/simple-send-usart +``` + +If it runs successfully, you’ll see a new message on the server +terminal: + +``` +Packet received on SERVER_PORT: abc +``` diff --git a/samples/posix/simple-send-usart/src/main.c b/samples/posix/simple-send-usart/src/main.c new file mode 100644 index 000000000..104c96d69 --- /dev/null +++ b/samples/posix/simple-send-usart/src/main.c @@ -0,0 +1,63 @@ +#include + + +#include +#include +#include + +#define SERVER_ADDR 1 +#define SERVER_PORT 10 + +#define CLIENT_ADDR 2 +#define DEVICE_NAME "/tmp/pty2" + +int main(int argc, char * argv[]) +{ + csp_usart_conf_t conf = { + .device = DEVICE_NAME, + .baudrate = 115200, + .databits = 8, + .stopbits = 1, + .paritysetting = 0, + }; + csp_iface_t * iface; + csp_conn_t * conn; + csp_packet_t * packet; + int ret; + + /* init */ + csp_init(); + + /* open */ + ret = csp_usart_open_and_add_kiss_interface(&conf, CSP_IF_KISS_DEFAULT_NAME, CLIENT_ADDR, &iface); + if (ret != CSP_ERR_NONE) { + csp_print("failed to open: %d\n", ret); + return 1; + } + iface->is_default = 1; + + /* connect */ + conn = csp_connect(CSP_PRIO_NORM, SERVER_ADDR, SERVER_PORT, 1000, CSP_O_NONE); + if (conn == NULL) { + csp_print("Connection failed\n"); + return 1; + } + + /* prepare data */ + packet = csp_buffer_get(0); + if (packet == NULL) { + csp_print("Failed to get buffer\n"); + csp_close(conn); + return 1; + } + memcpy(packet->data, "abc", 3); + packet->length = 3; + + /* send */ + csp_send(conn, packet); + + /* close */ + csp_close(conn); + + return 0; +} diff --git a/samples/posix/simple-send-zmq/CMakeLists.txt b/samples/posix/simple-send-zmq/CMakeLists.txt new file mode 100644 index 000000000..31d1a2ecb --- /dev/null +++ b/samples/posix/simple-send-zmq/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(simple-send-zmq EXCLUDE_FROM_ALL src/main.c) +target_include_directories(simple-send-zmq PRIVATE ${csp_inc}) +target_link_libraries(simple-send-zmq PRIVATE csp) diff --git a/samples/posix/simple-send-zmq/README.md b/samples/posix/simple-send-zmq/README.md new file mode 100644 index 000000000..ead7201d6 --- /dev/null +++ b/samples/posix/simple-send-zmq/README.md @@ -0,0 +1,94 @@ +# Simple Send via zmqhub + +This is a simple sample code to send `"abc"` to a server via zmqhub +interface. + +## How to Build + +``` +$ cmake -B builddir +$ ninja -C builddir simple-send-zmq csp_server zmqproxy +``` + +## How to Test + +You’ll need to install libzmq3. If you don't have it, install it with: + +``` +$ sudo apt-get install libzmq3-dev +``` + +First, you need to run a CSP server: + +``` +$ ./builddir/examples/csp_server -z localhost -a 1 +Initialising CSP +Connection table +[00 0x756e9c2f3900] S:0, 0 -> 0, 0 -> 0 (17) fl 0 +[01 0x756e9c2f3a18] S:0, 0 -> 0, 0 -> 0 (18) fl 0 +[02 0x756e9c2f3b30] S:0, 0 -> 0, 0 -> 0 (19) fl 0 +[03 0x756e9c2f3c48] S:0, 0 -> 0, 0 -> 0 (20) fl 0 +[04 0x756e9c2f3d60] S:0, 0 -> 0, 0 -> 0 (21) fl 0 +[05 0x756e9c2f3e78] S:0, 0 -> 0, 0 -> 0 (22) fl 0 +[06 0x756e9c2f3f90] S:0, 0 -> 0, 0 -> 0 (23) fl 0 +[07 0x756e9c2f40a8] S:0, 0 -> 0, 0 -> 0 (24) fl 0 +Interfaces +LOOP addr: 0 netmask: 14 dfl: 0 + tx: 00000 rx: 00000 txe: 00000 rxe: 00000 + drop: 00000 autherr: 00000 frame: 00000 + txb: 0 (0B) rxb: 0 (0B) + +ZMQHUB addr: 1 netmask: 0 dfl: 1 + tx: 00000 rx: 00000 txe: 00000 rxe: 00000 + drop: 00000 autherr: 00000 frame: 00000 + txb: 0 (0B) rxb: 0 (0B) + +Server task started +``` + +Then, in another terminal, run `zmqproxy` + +``` +$ ./builddir/examples/zmqproxy +Subscriber task listening on tcp://0.0.0.0:6000 +Publisher task listening on tcp://0.0.0.0:7000 +Capture/logging task listening on tcp://0.0.0.0:6000 +Packet: Src 2, Dst 1, Dport 10, Sport 18, Pri 2, Flags 0x00, Size 4 +``` + +Then, in another terminal, run `simple-send-zmq` + +``` +./builddir/samples/posix/simple-send-zmq/simple-send-zmq +``` + +If it runs successfully, you’ll see a new message on the server +terminal: + +``` +Packet received on SERVER_PORT: abc +``` + +### Note on `usleep(1000)` in the code + +In the `simple-send-zmq` example, a short sleep (`usleep(1000)`) is used before +sending messages. +This delay allows the ZeroMQ sockets enough time to establish their connections +properly. + +This is important because, as explained in the official ZeroMQ guide ([ZMQ +Guide, Chapter 1](https://zguide.zeromq.org/docs/chapter1/)): + +> *"There is one more important thing to know about PUB-SUB sockets: you do not +know precisely when a subscriber starts to get messages. Even if you start a +subscriber, wait a while, and then start the publisher, the subscriber will +always miss the first messages that the publisher sends. This is because as the +subscriber connects to the publisher (something that takes a small but non-zero +time), the publisher may already be sending messages out."* + +In other words, since the connection takes a non-zero amount of time to +establish, sending immediately after connecting may cause the message to be +lost. + +Therefore, the small `usleep(1000)` delay helps ensure the message is sent after +the connection is ready. diff --git a/samples/posix/simple-send-zmq/src/main.c b/samples/posix/simple-send-zmq/src/main.c new file mode 100644 index 000000000..f729a0dbd --- /dev/null +++ b/samples/posix/simple-send-zmq/src/main.c @@ -0,0 +1,58 @@ +#include + +#include +#include +#include +#include + +#define SERVER_ADDR 1 +#define SERVER_PORT 10 + +#define CLIENT_ADDR 2 +#define DEVICE_NAME "localhost" + +int main(int argc, char * argv[]) { + csp_iface_t * iface; + csp_conn_t * conn; + csp_packet_t * packet; + int ret; + + /* init */ + csp_init(); + + /* Init zmq interface */ + int error = csp_zmqhub_init(CLIENT_ADDR, DEVICE_NAME, 0, &iface); + if (error != CSP_ERR_NONE) { + csp_print("failed to Init ZMQ interface [%s], error: %d\n", DEVICE_NAME, error); + return 1; + } + iface->is_default = 1; + + /* connect */ + conn = csp_connect(CSP_PRIO_NORM, SERVER_ADDR, SERVER_PORT, 1000, CSP_O_NONE); + if (conn == NULL) { + csp_print("Connection failed\n"); + return 1; + } + + /* sleep to have the time to be connected */ + usleep(1000); + + /* prepare data */ + packet = csp_buffer_get(0); + if (packet == NULL) { + csp_print("Failed to get buffer\n"); + csp_close(conn); + return 1; + } + memcpy(packet->data, "abc", 4); + packet->length = 4; + + /* send */ + csp_send(conn, packet); + + /* close */ + csp_close(conn); + + return 0; +} diff --git a/samples/posix/simple-sfp-send-recv/CMakeLists.txt b/samples/posix/simple-sfp-send-recv/CMakeLists.txt new file mode 100644 index 000000000..f26c710fb --- /dev/null +++ b/samples/posix/simple-sfp-send-recv/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(simple-sfp-send-recv ${CSP_SAMPLES_EXCLUDE} src/main.c) +target_include_directories(simple-sfp-send-recv PRIVATE ${csp_inc}) +target_link_libraries(simple-sfp-send-recv PRIVATE csp Threads::Threads) diff --git a/samples/posix/simple-sfp-send-recv/README.md b/samples/posix/simple-sfp-send-recv/README.md new file mode 100644 index 000000000..962318110 --- /dev/null +++ b/samples/posix/simple-sfp-send-recv/README.md @@ -0,0 +1,20 @@ +# Simple Send using SFP via USART + +This is a simple sample code to send and receive a file using SFP over LOOPBACK interface. + +## How to Build + +``` +$ cmake -B builddir +$ ninja -C builddir simple-sfp-send-recv +``` + +## How to Test + +In a terminal, run `simple-sfp-send-recv` + +``` +./builddir/samples/posix/simple-sfp-send-recv/simple-sfp-send-recv existing_file_on_system.txt file_to_create_on_system.txt +``` + +If it runs successfully, the program exits with 0. diff --git a/samples/posix/simple-sfp-send-recv/src/main.c b/samples/posix/simple-sfp-send-recv/src/main.c new file mode 100644 index 000000000..676c740d1 --- /dev/null +++ b/samples/posix/simple-sfp-send-recv/src/main.c @@ -0,0 +1,209 @@ +/* needed for pthread_timedjoin_np */ +#define _GNU_SOURCE + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RECEIVER_ADDRESS 0 +#define RECEIVER_PORT 10 +#define CONN_OPTS CSP_O_CRC32 +#define TIMEOUT 10000 /* in ms */ +#define perr(fmt, ...) do { fprintf(stderr, "line %d: " fmt "\n", __LINE__, ##__VA_ARGS__); fflush(stderr); } while (0) + +typedef struct { + FILE * handle; + char * name; + off_t size; +} sfp_file_t; + +static sfp_file_t send_file; +static sfp_file_t recv_file; + +static void * router(void * params) { + (void)params; + + while (1) { + (void)csp_route_work(); + } + + return NULL; +} + +static int read_from_file(uint8_t * buffer, uint32_t size, uint32_t offset, void * data) { + (void)offset; + sfp_file_t * file = (sfp_file_t *)data; + if (size == fread(buffer, 1, size, file->handle)) + return CSP_ERR_NONE; + return CSP_ERR_SFP; +} + +static int write_to_file(const uint8_t * buffer, uint32_t size, uint32_t offset, uint32_t totalsz, void * data) { + (void)offset; + (void)totalsz; + sfp_file_t * file = (sfp_file_t *)data; + if (size == fwrite(buffer, 1, size, file->handle)) + return CSP_ERR_NONE; + return CSP_ERR_SFP; +} + +static void * sender(void * params) { + csp_conn_t * conn = NULL; + sfp_file_t * file = (sfp_file_t *)params; + + /* Connect to receiver */ + conn = csp_connect(CSP_PRIO_NORM, RECEIVER_ADDRESS, RECEIVER_PORT, TIMEOUT, CONN_OPTS); + if (conn) { + csp_sfp_read_t user; + user.data = file; + user.read = read_from_file; + + /* Send data */ + if (CSP_ERR_NONE != csp_sfp_send(conn, &user, file->size, csp_sfp_conn_max_mtu(conn), 0)) { + perr("Failed csp_sfp_send"); + } + } else { + perr("Failed csp_connect"); + } + + csp_close(conn); + + return NULL; +} + +static void * receiver(void * params) { + csp_conn_t * conn = NULL; + sfp_file_t * file = (sfp_file_t *)params; + + csp_socket_t sock = {0}; + sock.opts |= CONN_OPTS; + csp_bind(&sock, RECEIVER_PORT); + csp_listen(&sock, 0); + + conn = csp_accept(&sock, TIMEOUT); + if (conn) { + csp_sfp_recv_t user; + user.data = file; + user.write = write_to_file; + + /* Receive data */ + if (CSP_ERR_NONE != csp_sfp_recv(conn, &user, 1000)) { + perr("Failed csp_sfp_recv"); + } + } else { + perr("Failed csp_accept"); + } + + csp_close(conn); + + return NULL; +} + +static int loopback_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { + /* add some sleep to avoid starving the system when RDP is not used */ + if ((CONN_OPTS & CSP_O_RDP) == 0) + usleep(1000); + + csp_qfifo_write(packet, &csp_if_lo, NULL); + return CSP_ERR_NONE; +} + +int main(int argc, char * argv[]) { + /* open existing file */ + send_file.handle = fopen(argv[1], "rb"); + if (!send_file.handle) { + csp_print("Failed to open file %s\n", argv[1]); + exit(EXIT_FAILURE); + } + + /* get file size */ + if (fseek(send_file.handle, 0L, SEEK_END)) { + csp_print("Failed to get file size %s\n", argv[1]); + fclose(send_file.handle); + exit(EXIT_FAILURE); + } + + send_file.size = ftell(send_file.handle); + rewind(send_file.handle); + + /* create file */ + recv_file.handle = fopen(argv[2], "wb"); + if (!send_file.handle) { + csp_print("Failed to open file %s\n", argv[2]); + fclose(send_file.handle); + exit(EXIT_FAILURE); + } + + /* init csp */ + csp_init(); + + csp_if_lo.is_default = 1; + csp_if_lo.nexthop = loopback_tx; /* replace default tx function + + /* start router */ + pthread_t router_thread; + if (0 != pthread_create(&router_thread, NULL, router, NULL)) { + perr("Failed to create thread"); + goto error; + } + + /* start receiver */ + pthread_t receiver_thread; + if (0 != pthread_create(&receiver_thread, NULL, receiver, &recv_file)) { + perr("Failed to create thread"); + goto error; + } + + /* start sender */ + pthread_t sender_thread; + if (0 != pthread_create(&sender_thread, NULL, sender, &send_file)) { + perr("Failed to create thread"); + goto error; + } + + /* wait for sender to finish */ + struct timespec timeout; + if (0 != clock_gettime(CLOCK_REALTIME, &timeout)) { + perr("Failed to get time"); + goto error; + } + timeout.tv_sec += 120; + timeout.tv_nsec = 0; + if (0 != pthread_timedjoin_np(sender_thread, NULL, &timeout)) { + perr("Sender timeout"); + goto error; + } + + /* wait for receiver to finish */ + if (0 != clock_gettime(CLOCK_REALTIME, &timeout)) { + perr("Failed to get time"); + goto error; + } + timeout.tv_sec += 3; + timeout.tv_nsec = 0; + if (0 != pthread_timedjoin_np(receiver_thread, NULL, &timeout)) { + perr("Receiver timeout"); + goto error; + } + + printf("Test completed!\n"); + fclose(send_file.handle); + fclose(recv_file.handle); + exit(EXIT_SUCCESS); + +error: + fclose(send_file.handle); + fclose(recv_file.handle); + exit(EXIT_FAILURE); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6ced57b6c..3d6da4a1f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,7 +49,7 @@ if(LIBYAML_FOUND) target_include_directories(csp_yaml PRIVATE ${csp_inc} ${LIBYAML_INCLUDE_DIRS}) - target_link_libraries(csp_yaml PRIVATE ${LIBYAML_LIBRARIES}) + target_link_libraries(csp_yaml PRIVATE csp_common ${LIBYAML_LIBRARIES}) target_link_libraries(csp PRIVATE csp_yaml) if(BUILD_SHARED_LIBS) set_property(TARGET csp_yaml PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/src/arch/CMakeLists.txt b/src/arch/CMakeLists.txt index fcf6e7585..a98e0f0c5 100644 --- a/src/arch/CMakeLists.txt +++ b/src/arch/CMakeLists.txt @@ -1,12 +1,7 @@ -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - set(CSP_POSIX 1) +if(CSP_POSIX) add_subdirectory(posix) -elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeRTOS") - set(CSP_FREERTOS 1) +elseif(CSP_FREERTOS) add_subdirectory(freertos) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Zephyr") - set(CSP_ZEPHYR 1) +elseif(CSP_ZEPHYR) add_subdirectory(zephyr) -else() - message(FATAL_ERROR "invalid system ${CMAKE_SYSTEM_NAME}") endif() diff --git a/src/arch/freertos/CMakeLists.txt b/src/arch/freertos/CMakeLists.txt new file mode 100644 index 000000000..594faf000 --- /dev/null +++ b/src/arch/freertos/CMakeLists.txt @@ -0,0 +1,10 @@ +target_sources(csp PRIVATE + csp_clock.c + csp_queue.c + csp_semaphore.c + csp_system.c + csp_time.c + csp_hooks.c +) + +target_link_libraries(csp PRIVATE freertos_kernel) diff --git a/src/arch/freertos/csp_clock.c b/src/arch/freertos/csp_clock.c index 92d2c5d7d..11be803b7 100644 --- a/src/arch/freertos/csp_clock.c +++ b/src/arch/freertos/csp_clock.c @@ -9,5 +9,6 @@ __weak void csp_clock_get_time(csp_timestamp_t * time) { } __weak int csp_clock_set_time(const csp_timestamp_t * time) { + (void)time; /* Avoid compiler warnings about unused parameter */ return CSP_ERR_NOTSUP; } diff --git a/src/arch/freertos/csp_hooks.c b/src/arch/freertos/csp_hooks.c new file mode 100644 index 000000000..748467533 --- /dev/null +++ b/src/arch/freertos/csp_hooks.c @@ -0,0 +1,17 @@ +#include +#include "csp_macro.h" + +__weak uint32_t csp_memfree_hook(void) { + return 0; +} + +__weak unsigned int csp_ps_hook(csp_packet_t * packet) { + (void)packet; /* Avoid compiler warnings about unused parameter */ + return 0; +} + +__weak void csp_reboot_hook(void) { +} + +__weak void csp_shutdown_hook(void) { +} diff --git a/src/arch/freertos/csp_system.c b/src/arch/freertos/csp_system.c index 7b7229ba8..014323c95 100644 --- a/src/arch/freertos/csp_system.c +++ b/src/arch/freertos/csp_system.c @@ -13,5 +13,6 @@ __weak uint32_t csp_memfree_hook(void) { } __weak unsigned int csp_ps_hook(csp_packet_t * packet) { + (void)packet; /* Avoid compiler warnings about unused parameter */ return 0; } diff --git a/src/arch/posix/csp_queue.c b/src/arch/posix/csp_queue.c index 3c213b6e1..21628a143 100644 --- a/src/arch/posix/csp_queue.c +++ b/src/arch/posix/csp_queue.c @@ -4,6 +4,10 @@ #include "pthread_queue.h" csp_queue_handle_t csp_queue_create_static(int length, size_t item_size, char * buffer, csp_static_queue_t * queue) { + /* Avoid compiler warnings about unused parameter */ + (void)buffer; + (void)queue; + /* We ignore static allocation for posix for now */ return pthread_queue_create(length, item_size); } diff --git a/src/arch/posix/csp_system.c b/src/arch/posix/csp_system.c index 78c830aac..05d9250a7 100644 --- a/src/arch/posix/csp_system.c +++ b/src/arch/posix/csp_system.c @@ -2,8 +2,12 @@ #include #include +#ifdef __CYGWIN__ +#include +#else #include #include +#endif uint32_t csp_memfree_hook(void) { uint32_t total = 0; @@ -14,15 +18,28 @@ uint32_t csp_memfree_hook(void) { } unsigned int csp_ps_hook(csp_packet_t * packet) { + (void)packet; /* Avoid compiler warnings about unused parameter */ return 0; } void csp_reboot_hook(void) { +#ifdef __CYGWIN__ + csp_print("HALTED - Please reboot\n"); + while (true) + sleep(1); +#else sync(); reboot(LINUX_REBOOT_CMD_RESTART); +#endif } void csp_shutdown_hook(void) { +#ifdef __CYGWIN__ + csp_print("HALTED - Please power off\n"); + while (true) + sleep(1); +#else sync(); reboot(LINUX_REBOOT_CMD_HALT); +#endif } diff --git a/src/arch/posix/pthread_queue.c b/src/arch/posix/pthread_queue.c index 878e21039..66b426bf5 100644 --- a/src/arch/posix/pthread_queue.c +++ b/src/arch/posix/pthread_queue.c @@ -52,28 +52,58 @@ static inline int init_cond_clock_monotonic(pthread_cond_t * cond) { pthread_queue_t * pthread_queue_create(int length, size_t item_size) { - pthread_queue_t * q = malloc(sizeof(pthread_queue_t)); - - if (q != NULL) { - q->buffer = malloc(length * item_size); - if (q->buffer != NULL) { - q->size = length; - q->item_size = item_size; - q->items = 0; - q->in = 0; - q->out = 0; - if (pthread_mutex_init(&(q->mutex), NULL) || init_cond_clock_monotonic(&(q->cond_full)) || init_cond_clock_monotonic(&(q->cond_empty))) { - free(q->buffer); - free(q); - q = NULL; - } - } else { - free(q); - q = NULL; - } + int ret; + pthread_queue_t * q; + + q = malloc(sizeof(pthread_queue_t)); + if (q == NULL) { + goto out; + } + + q->buffer = malloc(length * item_size); + if (q->buffer == NULL) { + goto free_q; + } + + q->size = length; + q->item_size = item_size; + q->items = 0; + q->in = 0; + q->out = 0; + + ret = pthread_mutex_init(&(q->mutex), NULL); + if (ret != 0) { + goto free_q_buffer; + } + + ret = init_cond_clock_monotonic(&(q->cond_full)); + if (ret != 0) { + goto destroy_mutex; + } + + ret = init_cond_clock_monotonic(&(q->cond_empty)); + if (ret != 0) { + goto destroy_cond; } return q; + +destroy_cond: + (void)pthread_cond_destroy(&(q->cond_full)); + +destroy_mutex: + (void)pthread_mutex_destroy(&(q->mutex)); + +free_q_buffer: + free(q->buffer); + q->buffer = NULL; + +free_q: + free(q); + q = NULL; + +out: + return q; } void pthread_queue_delete(pthread_queue_t * q) { @@ -137,7 +167,7 @@ int pthread_queue_enqueue(pthread_queue_t * queue, const void * value, uint32_t pthread_mutex_unlock(&(queue->mutex)); if (ret == PTHREAD_QUEUE_OK) { - /* Nofify blocked threads */ + /* Notify blocked threads */ pthread_cond_broadcast(&(queue->cond_empty)); } @@ -170,6 +200,11 @@ int pthread_queue_dequeue(pthread_queue_t * queue, void * buf, uint32_t timeout) struct timespec ts; struct timespec * pts; + if(!queue){ + csp_print("csp not initialized\n"); + return PTHREAD_QUEUE_ERROR; + } + /* Calculate timeout */ if (timeout != CSP_MAX_TIMEOUT) { if (get_deadline(&ts, timeout) != 0) { @@ -194,7 +229,7 @@ int pthread_queue_dequeue(pthread_queue_t * queue, void * buf, uint32_t timeout) pthread_mutex_unlock(&(queue->mutex)); if (ret == PTHREAD_QUEUE_OK) { - /* Nofify blocked threads */ + /* Notify blocked threads */ pthread_cond_broadcast(&(queue->cond_full)); } diff --git a/src/arch/posix/pthread_queue.h b/src/arch/posix/pthread_queue.h index c5810b2c4..c9334fd5c 100644 --- a/src/arch/posix/pthread_queue.h +++ b/src/arch/posix/pthread_queue.h @@ -8,12 +8,9 @@ Inspired by c-pthread-queue by Matthew Dickinson: http://code.google.com/p/c-pthread-queue/ */ +#include #include -#include - - - /** Queue error codes. @{ diff --git a/src/arch/zephyr/csp_atomic.c b/src/arch/zephyr/csp_atomic.c index b6c330cef..872f37dba 100644 --- a/src/arch/zephyr/csp_atomic.c +++ b/src/arch/zephyr/csp_atomic.c @@ -4,6 +4,11 @@ K_MUTEX_DEFINE(atomic_lock); bool __atomic_compare_exchange_4(volatile void * ptr, void * expected, unsigned int desired, bool weak, int success_memorder, int failure_memorder) { + /* Avoid compiler warnings about unused parameter */ + (void)weak; + (void)success_memorder; + (void)failure_memorder; + bool ret; k_mutex_lock(&atomic_lock, K_MSEC(CONFIG_CSP_ATOMIC_MUTEX_TIMEOUT)); diff --git a/src/arch/zephyr/csp_clock.c b/src/arch/zephyr/csp_clock.c index 902de2ff6..060f8c201 100644 --- a/src/arch/zephyr/csp_clock.c +++ b/src/arch/zephyr/csp_clock.c @@ -1,6 +1,11 @@ #include #include -#include +/* https://github.com/zephyrproject-rtos/zephyr/discussions/96911*/ +#if __has_include() + #include +#else + #include +#endif #include LOG_MODULE_DECLARE(libcsp); @@ -10,7 +15,7 @@ __weak void csp_clock_get_time(csp_timestamp_t * time) { ret = clock_gettime(CLOCK_REALTIME, &ts); if (ret < 0) { - LOG_WRN("clock_gettime() failed, retruning with 0s"); + LOG_WRN("clock_gettime() failed, returning with 0s"); time->tv_sec = 0; time->tv_nsec = 0; } else { diff --git a/src/arch/zephyr/csp_hooks.c b/src/arch/zephyr/csp_hooks.c index 23c30bcc5..5ad874d71 100644 --- a/src/arch/zephyr/csp_hooks.c +++ b/src/arch/zephyr/csp_hooks.c @@ -5,6 +5,7 @@ __weak uint32_t csp_memfree_hook(void) { } __weak unsigned int csp_ps_hook(csp_packet_t * packet) { + (void)packet; /* Avoid compiler warnings about unused parameter */ return 0; } diff --git a/src/arch/zephyr/csp_zephyr_init.c b/src/arch/zephyr/csp_zephyr_init.c index 32f1c6cbc..7d9d4f2bc 100644 --- a/src/arch/zephyr/csp_zephyr_init.c +++ b/src/arch/zephyr/csp_zephyr_init.c @@ -1,8 +1,11 @@ - - #include #include -#include +/* https://github.com/zephyrproject-rtos/zephyr/discussions/96911*/ +#if __has_include() + #include +#else + #include +#endif #include #include diff --git a/src/bindings/python/pycsp.c b/src/bindings/python/pycsp.c index efff44eb0..45b4558a3 100644 --- a/src/bindings/python/pycsp.c +++ b/src/bindings/python/pycsp.c @@ -840,36 +840,52 @@ static PyObject * pycsp_cmp_clock_get(PyObject * self, PyObject * args) { #if CSP_HAVE_LIBZMQ static PyObject * pycsp_zmqhub_init(PyObject * self, PyObject * args) { uint16_t addr; - char * host; - if (!PyArg_ParseTuple(args, "Hs", &addr, &host)) { + const char * host = "localhost"; + int is_default = 0; + uint16_t mask = 8; + if (!PyArg_ParseTuple(args, "H|spH", &addr, &host, &is_default, &mask)) { return NULL; // TypeError is thrown } - int res = csp_zmqhub_init(addr, host, 0, NULL); + csp_iface_t *iface; + int res = csp_zmqhub_init(addr, host, 0, &iface); if (res != CSP_ERR_NONE) { return PyErr_Error("csp_zmqhub_init()", res); } + iface->is_default = is_default; + iface->addr = addr; + iface->netmask = mask; Py_RETURN_NONE; } #endif /* CSP_HAVE_LIBZMQ */ +#if CSP_HAVE_LIBSOCKETCAN static PyObject * pycsp_can_socketcan_init(PyObject * self, PyObject * args) { char * ifc; int bitrate = 1000000; int promisc = 0; uint16_t addr = 0; - if (!PyArg_ParseTuple(args, "s|Hii", &ifc, &addr, &bitrate, &promisc)) { + int is_default = 0; + uint16_t mask = 8; + + if (!PyArg_ParseTuple(args, "s|HiipH", &ifc, &addr, &bitrate, &promisc, &is_default, &mask)) { return NULL; } - int res = csp_can_socketcan_open_and_add_interface(ifc, CSP_IF_CAN_DEFAULT_NAME, addr, bitrate, promisc, NULL); + csp_iface_t *iface; + int res = csp_can_socketcan_open_and_add_interface(ifc, CSP_IF_CAN_DEFAULT_NAME, addr, bitrate, promisc, &iface); if (res != CSP_ERR_NONE) { return PyErr_Error("csp_can_socketcan_open_and_add_interface()", res); } + iface->is_default = is_default; + iface->addr = addr; + iface->netmask = mask; Py_RETURN_NONE; } +#endif /* CSP_HAVE_LIBSOCKETCAN */ + static PyObject * pycsp_kiss_init(PyObject * self, PyObject * args) { char * device; @@ -877,15 +893,22 @@ static PyObject * pycsp_kiss_init(PyObject * self, PyObject * args) { uint32_t mtu = 512; uint16_t addr; const char * if_name = CSP_IF_KISS_DEFAULT_NAME; - if (!PyArg_ParseTuple(args, "sH|IIs", &device, &addr, &baudrate, &mtu, &if_name)) { + int is_default = 0; + uint16_t mask = 8; + + if (!PyArg_ParseTuple(args, "sH|IIspH", &device, &addr, &baudrate, &mtu, &if_name, &is_default, &mask)) { return NULL; // TypeError is thrown } csp_usart_conf_t conf = {.device = device, .baudrate = baudrate}; - int res = csp_usart_open_and_add_kiss_interface(&conf, if_name, addr, NULL); + csp_iface_t *iface; + int res = csp_usart_open_and_add_kiss_interface(&conf, if_name, addr, &iface); if (res != CSP_ERR_NONE) { return PyErr_Error("csp_usart_open_and_add_kiss_interface()", res); } + iface->is_default = is_default; + iface->addr = addr; + iface->netmask = mask; Py_RETURN_NONE; } @@ -963,7 +986,7 @@ static PyMethodDef methods[] = { {"conn_src", pycsp_conn_src, METH_O, ""}, {"listen", pycsp_listen, METH_VARARGS, ""}, {"bind", pycsp_bind, METH_VARARGS, ""}, - {"route_start_task", pycsp_route_start_task, METH_VARARGS, ""}, + {"route_start_task", pycsp_route_start_task, METH_NOARGS, ""}, {"ping", pycsp_ping, METH_VARARGS, ""}, {"reboot", pycsp_reboot, METH_VARARGS, ""}, {"shutdown", pycsp_shutdown, METH_VARARGS, ""}, @@ -998,8 +1021,10 @@ static PyMethodDef methods[] = { #endif /* CSP_HAVE_LIBZMQ */ {"kiss_init", pycsp_kiss_init, METH_VARARGS, ""}, +#if CSP_HAVE_LIBSOCKETCAN /* csp/drivers/can_socketcan.h */ {"can_socketcan_init", pycsp_can_socketcan_init, METH_VARARGS, ""}, +#endif /* helpers */ {"packet_get_length", pycsp_packet_get_length, METH_O, ""}, @@ -1088,6 +1113,7 @@ PyMODINIT_FUNC PyInit_libcsp_py3(void) { PyModule_AddIntConstant(m, "CSP_ERR_TX", CSP_ERR_TX); PyModule_AddIntConstant(m, "CSP_ERR_DRIVER", CSP_ERR_DRIVER); PyModule_AddIntConstant(m, "CSP_ERR_AGAIN", CSP_ERR_AGAIN); + PyModule_AddIntConstant(m, "CSP_ERR_NOSYS", CSP_ERR_NOSYS); PyModule_AddIntConstant(m, "CSP_ERR_HMAC", CSP_ERR_HMAC); PyModule_AddIntConstant(m, "CSP_ERR_CRC32", CSP_ERR_CRC32); PyModule_AddIntConstant(m, "CSP_ERR_SFP", CSP_ERR_SFP); diff --git a/src/crypto/csp_hmac.c b/src/crypto/csp_hmac.c index a4daa8cfc..83d45e310 100644 --- a/src/crypto/csp_hmac.c +++ b/src/crypto/csp_hmac.c @@ -139,6 +139,7 @@ int csp_hmac_append(csp_packet_t * packet, bool include_header) { csp_hmac_memory(csp_hmac_key, sizeof(csp_hmac_key), packet->frame_begin, packet->frame_length, hmac); memcpy(&packet->frame_begin[packet->frame_length], hmac, CSP_HMAC_LENGTH); packet->frame_length += CSP_HMAC_LENGTH; + packet->length += CSP_HMAC_LENGTH; } else { diff --git a/src/csp_bridge.c b/src/csp_bridge.c index 061aaefe2..644959944 100644 --- a/src/csp_bridge.c +++ b/src/csp_bridge.c @@ -1,5 +1,6 @@ #include "csp_macro.h" +#include "csp/csp_hooks.h" #include "csp_qfifo.h" #include "csp_io.h" #include "csp_promisc.h" @@ -29,10 +30,9 @@ void csp_bridge_work(void) { return; } - /* Get next packet to route */ + /* Get next packet to bridge */ csp_qfifo_t input; if (csp_qfifo_read(&input) != CSP_ERR_NONE) { - csp_print("Failed to receive packet from router input queue\n"); return; } diff --git a/src/csp_buffer.c b/src/csp_buffer.c index 52499e278..884a4ae4d 100644 --- a/src/csp_buffer.c +++ b/src/csp_buffer.c @@ -5,6 +5,10 @@ #include #include #include "csp_macro.h" +#include +#include + +#include "csp_buffer_private.h" /** Internal buffer header */ typedef struct csp_skbf_s { @@ -16,15 +20,15 @@ typedef struct csp_skbf_s { // Queue of free CSP buffers static csp_queue_handle_t csp_buffers; -void csp_buffer_init(void) { - /** - * Chunk of memory allocated for CSP buffers: - * This is marked as .noinit, because csp buffers can never be assumed zeroed out - * Putting this section in a separate non .bss area, saves some boot time */ - static csp_skbf_t csp_buffer_pool[CSP_BUFFER_COUNT] __noinit; - static csp_static_queue_t csp_buffers_queue __noinit; - static char csp_buffer_queue_data[CSP_BUFFER_COUNT * sizeof(csp_skbf_t *)] __noinit; +/** + * Chunk of memory allocated for CSP buffers: + * This is marked as .noinit, because csp buffers can never be assumed zeroed out + * Putting this section in a separate non .bss area, saves some boot time */ +static csp_skbf_t csp_buffer_pool[CSP_BUFFER_COUNT] __noinit; +static csp_static_queue_t csp_buffers_queue __noinit; +static char csp_buffer_queue_data[CSP_BUFFER_COUNT * sizeof(csp_skbf_t *)] __noinit; +void csp_buffer_init(void) { csp_buffers = csp_queue_create_static(CSP_BUFFER_COUNT, sizeof(csp_skbf_t *), csp_buffer_queue_data, &csp_buffers_queue); for (unsigned int i = 0; i < CSP_BUFFER_COUNT; i++) { @@ -34,42 +38,59 @@ void csp_buffer_init(void) { } } -csp_packet_t * csp_buffer_get_isr(size_t unused) { +static csp_packet_t * csp_packet_init(csp_packet_t * packet) +{ - csp_skbf_t * buffer = NULL; - int task_woken = 0; - csp_queue_dequeue_isr(csp_buffers, &buffer, &task_woken); - if (buffer == NULL) { - csp_dbg_buffer_out++; - return NULL; - } +#if (CSP_BUFFER_ZERO_CLEAR) + memset(packet, 0, sizeof(csp_packet_t)); +#endif - if (buffer != buffer->skbf_addr) { - csp_dbg_errno = CSP_DBG_ERR_CORRUPT_BUFFER; - /* Best option here must be to leak the invalid buffer */ - return NULL; - } + packet->length = 0; + packet->frame_begin = packet->data; + packet->frame_length = 0; - buffer->refcount = 1; - return &buffer->skbf_data; + csp_id_clear(&packet->id); + + return packet; } -csp_packet_t * csp_buffer_get(size_t unused) { +static csp_packet_t * csp_buffer_get_actual(int reserve, int isr) { + + /* Get buffers remaining */ + int remain; + if (isr) { + remain = csp_queue_size_isr(csp_buffers); + } else { + remain = csp_queue_size(csp_buffers); + } + /* Respect the requested reserve */ + if (remain <= reserve) { + return NULL; + } - csp_skbf_t * buffer = NULL; - csp_queue_dequeue(csp_buffers, &buffer, 0); - if (buffer == NULL) { + /* Now fetch a buffer */ + csp_skbf_t * buf = NULL; + if (isr) { + int task_woken = 0; + csp_queue_dequeue_isr(csp_buffers, &buf, &task_woken); + } else { + csp_queue_dequeue(csp_buffers, &buf, 0); + } + + /* We might be out of buffers */ + if (buf == NULL) { csp_dbg_buffer_out++; return NULL; } - if (buffer != buffer->skbf_addr) { + if (buf != buf->skbf_addr) { csp_dbg_errno = CSP_DBG_ERR_CORRUPT_BUFFER; return NULL; } - buffer->refcount = 1; - return &buffer->skbf_data; + buf->refcount = 1; + + return csp_packet_init(&buf->skbf_data); } void csp_buffer_free_isr(void * packet) { @@ -127,22 +148,24 @@ void csp_buffer_free(void * packet) { csp_queue_enqueue(csp_buffers, &buf, 0); } -void * csp_buffer_clone(void * buffer) { - - csp_packet_t * packet = (csp_packet_t *)buffer; - if (!packet) { - return NULL; - } - - csp_packet_t * clone = csp_buffer_get(packet->length); - if (clone) { - size_t size = sizeof(csp_packet_t) - CSP_BUFFER_SIZE + CSP_PACKET_PADDING_BYTES + packet->length; - memcpy(clone, packet, size > sizeof(csp_packet_t) ? sizeof(csp_packet_t) : size); +csp_packet_t * csp_buffer_clone(const csp_packet_t * packet) { + csp_packet_t * clone = NULL; + if (packet) { + clone = csp_buffer_get(0); + csp_buffer_copy(packet, clone); } return clone; } +void csp_buffer_copy(const csp_packet_t * src, csp_packet_t * dst) { + if ((NULL != src) && (NULL != dst)) { + size_t size = sizeof(csp_packet_t) - CSP_BUFFER_SIZE + src->length; + (void)memcpy(dst, src, size > sizeof(csp_packet_t) ? sizeof(csp_packet_t) : size); + dst->frame_begin = (dst->header + CSP_PACKET_PADDING_BYTES) - (src->data - src->frame_begin); + } +} + void csp_buffer_refc_inc(void * buffer) { if (!buffer) { @@ -162,3 +185,41 @@ void csp_buffer_refc_inc(void * buffer) { int csp_buffer_remaining(void) { return csp_queue_size(csp_buffers); } + +/* CSP will use every remaining buffer in an attempt to allocate a packet + * buffer. Including the last two, that is normally reserved. + * This can be important for ensuring a node is always reachable, so a + * failure to allocate will cause a panic and a reboot */ + +csp_packet_t * csp_buffer_get_always(void) { + csp_packet_t * packet = csp_buffer_get_actual(0, 0); + if (packet == NULL) { + csp_panic("Out of buffers"); + while(1); + } + return packet; +} + +csp_packet_t * csp_buffer_get_always_isr(void) { + csp_packet_t * packet = csp_buffer_get_actual(0, 1); + if (packet == NULL) { + csp_panic("Out of buffers"); + while(1); + } + return packet; +} + +/* CSP will try to reserve the last two buffers for calls which can take it, + * examples are client functions that are allowed to fail and have adequate + * error checking. Or services which are allowed to timeout of memory becomes + * sparse. */ + +csp_packet_t * csp_buffer_get(size_t unused) { + (void)unused; /* Avoid compiler warnings about unused parameter */ + return csp_buffer_get_actual(CSP_BUFFER_RESERVED_COUNT, 0); +} + +csp_packet_t * csp_buffer_get_isr(size_t unused) { + (void)unused; /* Avoid compiler warnings about unused parameter */ + return csp_buffer_get_actual(CSP_BUFFER_RESERVED_COUNT, 1); +} diff --git a/src/csp_buffer_private.h b/src/csp_buffer_private.h new file mode 100644 index 000000000..8ecaab526 --- /dev/null +++ b/src/csp_buffer_private.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +/** + * Get a buffer or get killed (from task context) + * + * This function return a buffer or kill the whole program when it + * failed. DO NOT USE THIS FUNCTION if you don't know what you are + * doing. Never use this function from application layer. It is + * intended for internal use only and must not be used from + * application-level code. + * + * https://github.com/libcsp/libcsp/issues/864 + * + * @return Buffer (pointer to #csp_packet_t) + */ +csp_packet_t * csp_buffer_get_always(void); + +/** + * Get a buffer or get killed (from ISR context) + * @return Buffer (pointer to #csp_packet_t) + */ +csp_packet_t * csp_buffer_get_always_isr(void); diff --git a/src/csp_conn.c b/src/csp_conn.c index 9b013ef8d..4be164274 100644 --- a/src/csp_conn.c +++ b/src/csp_conn.c @@ -3,6 +3,7 @@ #include "csp_conn.h" #include +#include #include #include @@ -21,6 +22,9 @@ /* Connection pool */ static csp_conn_t arr_conn[CSP_CONN_MAX] __noinit; +/* Used by csp_conn_allocate */ +static uint8_t csp_conn_last_given = 0; + void csp_conn_check_timeouts(void) { #if (CSP_USE_RDP) for (int i = 0; i < CSP_CONN_MAX; i++) { @@ -106,7 +110,7 @@ csp_conn_t * csp_conn_find_existing(csp_id_t * id) { /* Outgoing connections are uniquely defined by the source port, * So only the incoming destination port must match. This means * that responses to broadcast addresses, are accepted as long - * as the incoming port matches the unique source port of the + * as the incoming port matches the unique source port of the * connection */ if (conn->type == CONN_CLIENT) { @@ -114,11 +118,11 @@ csp_conn_t * csp_conn_find_existing(csp_id_t * id) { if (conn->idin.dport != id->dport) continue; - /* Incoming connections are uniquely defined by the source amd + /* Incoming connections are uniquely defined by the source and * destination port, as well as the source node. Incoming - * connections can never come from a brodcast address */ + * connections can never come from a broadcast address */ } else { - + /* Connection must match dport */ if (conn->idin.dport != id->dport) continue; @@ -133,7 +137,7 @@ csp_conn_t * csp_conn_find_existing(csp_id_t * id) { } - + /* All conditions found! */ return conn; @@ -158,8 +162,6 @@ static int csp_conn_flush_rx_queue(csp_conn_t * conn) { csp_conn_t * csp_conn_allocate(csp_conn_type_t type) { - static uint8_t csp_conn_last_given = 0; - /* Search for free connection */ csp_conn_t * conn = NULL; int i = csp_conn_last_given; @@ -211,6 +213,7 @@ int csp_close(csp_conn_t * conn) { } int csp_conn_close(csp_conn_t * conn, uint8_t closed_by) { + (void)closed_by; /* Avoid compiler warnings about unused parameter */ if (conn == NULL) { return CSP_ERR_NONE; @@ -242,25 +245,26 @@ int csp_conn_close(csp_conn_t * conn, uint8_t closed_by) { /* Set to closed */ conn->state = CONN_CLOSED; - + return CSP_ERR_NONE; } csp_conn_t * csp_connect(uint8_t prio, uint16_t dest, uint8_t dport, uint32_t timeout, uint32_t opts) { + (void)timeout; /* Avoid compiler warnings about unused parameter */ /* Force options on all connections */ opts |= csp_conf.conn_dfl_so; - + /* Generate identifier */ - csp_id_t incoming_id, outgoing_id; + csp_id_t incoming_id = {0}, outgoing_id = {0}; /* Use 0 as incoming id (this disables the input filter on destination node) * This means that for this outgoing connection, we accept the answer coming to whatever address - * the outgoing interface has. CSP does not support "source address" on outgoing connections - * so the outgoing source address will be automatically applied after outgoing routing - * selects which interface the packet will leavve from */ - incoming_id.dst = 0; - outgoing_id.src = 0; + * the outgoing interface has. CSP does not support "source address" on outgoing connections + * so the outgoing source address will be automatically applied after outgoing routing + * selects which interface the packet will leave from */ + incoming_id.dst = 0; + outgoing_id.src = 0; incoming_id.pri = prio; outgoing_id.pri = prio; @@ -331,27 +335,27 @@ csp_conn_t * csp_connect(uint8_t prio, uint16_t dest, uint8_t dport, uint32_t ti return conn; } -int csp_conn_dport(csp_conn_t * conn) { +int csp_conn_dport(const csp_conn_t * conn) { return conn->idin.dport; } -int csp_conn_sport(csp_conn_t * conn) { +int csp_conn_sport(const csp_conn_t * conn) { return conn->idin.sport; } -int csp_conn_dst(csp_conn_t * conn) { +int csp_conn_dst(const csp_conn_t * conn) { return conn->idin.dst; } -int csp_conn_src(csp_conn_t * conn) { +int csp_conn_src(const csp_conn_t * conn) { return conn->idin.src; } -int csp_conn_flags(csp_conn_t * conn) { +int csp_conn_flags(const csp_conn_t * conn) { return conn->idin.flags; } @@ -367,7 +371,7 @@ void csp_conn_print_table(void) { conn->idin.dport, conn->idin.sport, conn->sport_outgoing, conn->idin.flags); #if (CSP_USE_RDP) if (conn->idin.flags & CSP_FRDP) { - csp_print("\tRDP: S:%d (closed by 0x%x), rcv %u, snd %u, win %" PRIu32 "\n", + csp_print("\tRDP: S:%d (closed by 0x%x), rcv %u, snd %u, win %" PRIu32 "\n", conn->rdp.state, conn->rdp.closed_by, conn->rdp.rcv_cur, conn->rdp.snd_una, conn->rdp.window_size); } #endif @@ -406,3 +410,17 @@ const csp_conn_t * csp_conn_get_array(size_t * size) { *size = CSP_CONN_MAX; return arr_conn; } + +bool csp_conn_is_active(csp_conn_t *conn) { + (void)conn; /* Avoid compiler warnings about unused parameter */ + +#if (CSP_USE_RDP) + if ((conn->idin.flags & CSP_FRDP) || (conn->idout.flags & CSP_FRDP)) { + /* This is for sure an RDP connection */ + return csp_rdp_conn_is_active(conn); + } +#endif + + /* Non RDP connections are always "active" */ + return true; +} diff --git a/src/csp_crc32.c b/src/csp_crc32.c index 2cf416afd..934778567 100644 --- a/src/csp_crc32.c +++ b/src/csp_crc32.c @@ -1,9 +1,12 @@ #include -#include #include +#include + +#include + #ifdef __AVR__ #include @@ -51,14 +54,16 @@ void csp_crc32_init(csp_crc32_t * crc) { } } -void csp_crc32_update(csp_crc32_t * crc, const uint8_t * data, uint32_t length) { +void csp_crc32_update(csp_crc32_t * crc, const void * data, uint32_t length) { + + const uint8_t * data8 = (uint8_t*)data; if (crc) { while (length--) { #ifdef __AVR__ - crc = pgm_read_dword(&crc_tab[(crc ^ *data++) & 0xFFL]) ^ (crc >> 8); + crc = pgm_read_dword(&crc_tab[(crc ^ *data8++) & 0xFFL]) ^ (crc >> 8); #else - (*crc) = crc_tab[((*crc) ^ *data++) & 0xFFL] ^ ((*crc) >> 8); + (*crc) = crc_tab[((*crc) ^ *data8++) & 0xFFL] ^ ((*crc) >> 8); #endif } } @@ -73,7 +78,7 @@ uint32_t csp_crc32_final(csp_crc32_t *crc) { return 0; } -uint32_t csp_crc32_memory(const uint8_t * data, uint32_t length) { +uint32_t csp_crc32_memory(const void * data, uint32_t length) { csp_crc32_t crc; diff --git a/src/csp_debug.c b/src/csp_debug.c index d1113fdd6..acb67c02e 100644 --- a/src/csp_debug.c +++ b/src/csp_debug.c @@ -17,6 +17,7 @@ uint8_t csp_dbg_packet_print; #if (CSP_PRINT_STDIO) #include #include +#include "csp/csp_debug.h" __weak void csp_print_func(const char * fmt, ...) { va_list args; va_start(args, fmt); diff --git a/src/csp_hex_dump.c b/src/csp_hex_dump.c index bbf70dd3e..fbcaeee1f 100644 --- a/src/csp_hex_dump.c +++ b/src/csp_hex_dump.c @@ -1,10 +1,9 @@ - - #include #include +#include #include -void csp_hex_dump_format(const char * desc, void * addr, int len, int format) { +static void csp_hex_dump_format(const char * desc, const void * addr, int len, int format) { int i; unsigned char buff[17]; unsigned char * pc = (unsigned char *)addr; @@ -54,6 +53,6 @@ void csp_hex_dump_format(const char * desc, void * addr, int len, int format) { csp_print(" %s\n", buff); } -void csp_hex_dump(const char * desc, void * addr, int len) { +void csp_hex_dump(const char * desc, const void * addr, int len) { csp_hex_dump_format(desc, addr, len, 0); } diff --git a/src/csp_id.c b/src/csp_id.c index ee5549576..5e7dc7d78 100644 --- a/src/csp_id.c +++ b/src/csp_id.c @@ -6,7 +6,10 @@ */ #include +#include + #include +#include /** * CSP 1.x @@ -37,18 +40,22 @@ #define CSP_ID1_HEADER_SIZE 4 -static void csp_id1_prepend(csp_packet_t * packet) { +static void csp_id1_prepend(csp_packet_t * packet, bool cspv1_fixup) { /* Pack into 32-bit using host endian */ - uint32_t id1 = (((uint32_t)(packet->id.pri) << CSP_ID1_PRIO_OFFSET) | - ((uint32_t)(packet->id.dst) << CSP_ID1_DST_OFFSET) | - ((uint32_t)(packet->id.src) << CSP_ID1_SRC_OFFSET) | - ((uint32_t)(packet->id.dport) << CSP_ID1_DPORT_OFFSET) | - ((uint32_t)(packet->id.sport) << CSP_ID1_SPORT_OFFSET) | - ((uint32_t)(packet->id.flags) << CSP_ID1_FLAGS_OFFSET)); + uint32_t id1_raw = (((uint32_t)(packet->id.pri) << CSP_ID1_PRIO_OFFSET) | + ((uint32_t)(packet->id.dst) << CSP_ID1_DST_OFFSET) | + ((uint32_t)(packet->id.src) << CSP_ID1_SRC_OFFSET) | + ((uint32_t)(packet->id.dport) << CSP_ID1_DPORT_OFFSET) | + ((uint32_t)(packet->id.sport) << CSP_ID1_SPORT_OFFSET) | + ((uint32_t)(packet->id.flags) << CSP_ID1_FLAGS_OFFSET)); /* Convert to big / network endian */ - id1 = htobe32(id1); + uint32_t id1 = htobe32(id1_raw); + + if (cspv1_fixup) { + id1 = htole32(id1_raw); + } packet->frame_begin = packet->data - CSP_ID1_HEADER_SIZE; packet->frame_length = packet->length + CSP_ID1_HEADER_SIZE; @@ -56,19 +63,23 @@ static void csp_id1_prepend(csp_packet_t * packet) { memcpy(packet->frame_begin, &id1, CSP_ID1_HEADER_SIZE); } -static int csp_id1_strip(csp_packet_t * packet) { +static int csp_id1_strip(csp_packet_t * packet, bool cspv1_fixup) { if (packet->frame_length < CSP_ID1_HEADER_SIZE) { return -1; } /* Get 32 bit in network byte order */ - uint32_t id1 = 0; - memcpy(&id1, packet->frame_begin, CSP_ID1_HEADER_SIZE); + uint32_t id1_raw = 0; + memcpy(&id1_raw, packet->frame_begin, CSP_ID1_HEADER_SIZE); packet->length = packet->frame_length - CSP_ID1_HEADER_SIZE; /* Convert to host order */ - id1 = be32toh(id1); + uint32_t id1 = be32toh(id1_raw); + + if (cspv1_fixup) { + id1 = le32toh(id1_raw); + } /* Parse header: * Now in easy to work with in 32 bit register */ @@ -182,19 +193,39 @@ void csp_id_prepend(csp_packet_t * packet) { if (csp_conf.version == 2) { csp_id2_prepend(packet); } else { - csp_id1_prepend(packet); + csp_id1_prepend(packet, false); } } int csp_id_strip(csp_packet_t * packet) { - packet->timestamp_rx = 0; if (csp_conf.version == 2) { return csp_id2_strip(packet); } else { - return csp_id1_strip(packet); + return csp_id1_strip(packet, false); + } +} + +#if (CSP_FIXUP_V1_ZMQ_LITTLE_ENDIAN) + +void csp_id_prepend_fixup_cspv1(csp_packet_t * packet) { + if (csp_conf.version == 2) { + csp_id2_prepend(packet); + } else { + csp_id1_prepend(packet, true); + } } +int csp_id_strip_fixup_cspv1(csp_packet_t * packet) { + if (csp_conf.version == 2) { + return csp_id2_strip(packet); + } else { + return csp_id1_strip(packet, true); + } +} + +#endif + int csp_id_setup_rx(csp_packet_t * packet) { if (csp_conf.version == 2) { csp_id2_setup_rx(packet); @@ -223,9 +254,9 @@ unsigned int csp_id_get_max_nodeid(void) { unsigned int csp_id_get_max_port(void) { if (csp_conf.version == 2) { - return ((1 << (CSP_ID2_PORT_SIZE)) - 1); + return ((1 << CSP_ID2_PORT_SIZE) - 1); } else { - return ((1 << (CSP_ID1_PORT_SIZE)) - 1); + return ((1 << CSP_ID1_PORT_SIZE) - 1); } } @@ -241,3 +272,11 @@ int csp_id_is_broadcast(uint16_t addr, csp_iface_t * iface) { } return 0; } + +int csp_id_get_header_size(void) { + if (csp_conf.version == 2) { + return CSP_ID2_HEADER_SIZE; + } else { + return CSP_ID1_HEADER_SIZE; + } +} diff --git a/src/csp_iflist.c b/src/csp_iflist.c index 448a2c162..2d121b367 100644 --- a/src/csp_iflist.c +++ b/src/csp_iflist.c @@ -4,20 +4,22 @@ #include #include +#include #include "csp/autoconfig.h" #include #include -/* Interfaces are stored in a linked list */ +/* Interfaces and alias receive addresses are stored in linked lists */ static csp_iface_t * interfaces = NULL; +static csp_alias_t * aliass = NULL; int csp_iflist_is_within_subnet(uint16_t addr, csp_iface_t * ifc) { if (ifc == NULL) { return 0; } - + uint16_t netmask = ((1 << ifc->netmask) - 1) << (csp_id_get_host_bits() - ifc->netmask); uint16_t network_a = ifc->addr & netmask; uint16_t network_b = addr & netmask; @@ -43,7 +45,7 @@ csp_iface_t * csp_iflist_get_by_subnet(uint16_t addr, csp_iface_t * ifc) { while (ifc) { - /* Reject searches involving subnets, if the netmask is invalud */ + /* Reject searches involving subnets, if the netmask is invalid */ if (ifc->netmask == 0) { ifc = ifc->next; continue; @@ -78,7 +80,6 @@ csp_iface_t * csp_iflist_get_by_isdfl(csp_iface_t * ifc) { } ifc = ifc->next; - continue; } @@ -101,6 +102,49 @@ csp_iface_t * csp_iflist_iterate(csp_iface_t * ifc) { } +int csp_alias_add(csp_alias_t * addr) { + + if (addr == NULL || addr->iface == NULL) { + return -1; + } + + /* Register interface for L2 filtering, if interface supports */ + if (addr->iface->add_alias) { + int result = addr->iface->add_alias(addr->iface->driver_data, addr->addr); + if (result < 0) { + return result; + } + } + + /* Add to list */ + addr->next = aliass; + aliass = addr; + + return 0; +} + +static csp_alias_t * csp_alias_iterate(csp_alias_t * addr) { + + if (addr == NULL) { + addr = aliass; + } else { + addr = addr->next; + } + + return addr; +} + +int csp_addr_is_alias(uint16_t addr) { + + csp_alias_t * alias = NULL; + while ((alias = csp_alias_iterate(alias)) != NULL) { + if (addr == alias->addr) { + return 1; + } + } + return 0; +} + void csp_iflist_check_dfl(void) { csp_iface_t * iface = csp_iflist_get_by_isdfl(NULL); @@ -133,6 +177,18 @@ csp_iface_t * csp_iflist_get_by_addr(uint16_t addr) { } +csp_iface_t * csp_iflist_get_by_broadcast(uint16_t addr) { + + csp_iface_t * ifc = interfaces; + while (ifc) { + if (csp_id_is_broadcast(addr, ifc)) { + return ifc; + } + ifc = ifc->next; + } + return NULL; +} + csp_iface_t * csp_iflist_get_by_name(const char * name) { csp_iface_t * ifc = interfaces; while (ifc) { @@ -152,7 +208,11 @@ csp_iface_t * csp_iflist_get_by_index(int idx) { return ifc; } -int csp_iflist_add(csp_iface_t * ifc) { +void csp_iflist_add(csp_iface_t * ifc) { + + if ((ifc == NULL) || (ifc->name == NULL)) { + return; + } ifc->next = NULL; @@ -165,7 +225,7 @@ int csp_iflist_add(csp_iface_t * ifc) { csp_iface_t * last = NULL; for (csp_iface_t * i = interfaces; i != NULL; i = i->next) { if ((i == ifc) || (strncmp(ifc->name, i->name, CSP_IFLIST_NAME_MAX) == 0)) { - return CSP_ERR_ALREADY; + return; } last = i; } @@ -173,10 +233,10 @@ int csp_iflist_add(csp_iface_t * ifc) { last->next = ifc; } - return CSP_ERR_NONE; } void csp_iflist_remove(csp_iface_t * ifc) { + if (ifc == NULL) { return; } diff --git a/src/csp_init.c b/src/csp_init.c index 5910b65c8..0d977ca3d 100644 --- a/src/csp_init.c +++ b/src/csp_init.c @@ -1,14 +1,19 @@ - - #include #include +#include #include #include "csp/autoconfig.h" +#include "csp_macro.h" #include "csp_conn.h" #include "csp_qfifo.h" #include "csp_port.h" #include "csp_rdp_queue.h" +__weak void csp_panic(const char * msg) { + (void)msg; /* Avoid compiler warnings about unused parameter */ + return; +} + csp_conf_t csp_conf = { .version = 2, .hostname = "", diff --git a/src/csp_io.c b/src/csp_io.c index dec585881..38c2b6911 100644 --- a/src/csp_io.c +++ b/src/csp_io.c @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,11 @@ csp_conn_t * csp_accept(csp_socket_t * sock, uint32_t timeout) { csp_dbg_errno = CSP_DBG_ERR_INVALID_POINTER; return NULL; } + + if (sock->opts & CSP_SO_CONN_LESS) { + csp_dbg_errno = CSP_DBG_ERR_UNSUPPORTED; + return NULL; + } csp_conn_t * conn; if (csp_queue_dequeue(sock->rx_queue, &conn, timeout) == CSP_QUEUE_OK) { @@ -66,7 +72,7 @@ csp_packet_t * csp_read(csp_conn_t * conn, uint32_t timeout) { } /* Provide a safe method to copy type safe, between two csp ids */ -void csp_id_copy(csp_id_t * target, csp_id_t * source) { +void csp_id_copy(csp_id_t * target, const csp_id_t * source) { target->pri = source->pri; target->dst = source->dst; target->src = source->src; @@ -75,54 +81,93 @@ void csp_id_copy(csp_id_t * target, csp_id_t * source) { target->flags = source->flags; } +void csp_id_clear(csp_id_t * target) { + target->pri = 0; + target->dst = 0; + target->src = 0; + target->dport = 0; + target->sport = 0; + target->flags = 0; +} + +static inline int is_same_subnet(csp_iface_t * iface, csp_iface_t * routed_from) { + + /* This check is is similar to that below, but faster */ + if (iface == routed_from) { + return 1; + } + + /* Do not send to interface with similar subnet (split horizon) */ + if (csp_iflist_is_within_subnet(iface->addr, routed_from)) { + return 1; + } + + return 0; +} + +static inline void convert_broadcast(csp_id_t * idout, csp_id_t * idout_copy, csp_iface_t * snd_iface) { + + /* Rewrite routed broadcast (L3) to local (L2) when arriving at the interface */ + if (csp_id_is_broadcast(idout->dst, snd_iface)) { + idout_copy->dst = csp_id_get_max_nodeid(); + } +} + +static inline void send_packet(csp_id_t * idout_copy, csp_packet_t * snd_pkt, csp_iface_t * snd_iface, uint16_t via, int from_me) { + + /* Apply outgoing interface address to packet */ + if ((from_me) && (idout_copy->src == 0)) { + idout_copy->src = snd_iface->addr; + } + + if (snd_pkt != NULL) { + csp_send_direct_iface(idout_copy, snd_pkt, snd_iface, via, from_me); + } +} + void csp_send_direct(csp_id_t* idout, csp_packet_t * packet, csp_iface_t * routed_from) { int from_me = (routed_from == NULL ? 1 : 0); + int via = CSP_NO_VIA_ADDRESS; + + /* Quickly send on loopback */ + if (idout->dst == csp_if_lo.addr) { + csp_send_direct_iface(idout, packet, &csp_if_lo, via, from_me); + return; + } + + csp_id_t idout_copy = *idout; /* Broadcast function procedure modifies destination */ + csp_iface_t * iface = NULL; /* Interface iterator */ + csp_iface_t * next_iface = NULL; /* Reference to keep track of packet copying */ /* Try to find the destination on any local subnets */ - int via = CSP_NO_VIA_ADDRESS; - csp_iface_t * iface = NULL; - csp_packet_t * copy = NULL; int local_found = 0; - while ((iface = csp_iflist_get_by_subnet(idout->dst, iface)) != NULL) { local_found = 1; - /* Do not send back to same inteface (split horizon) - * This check is is similar to that below, but faster */ - if (iface == routed_from) { - continue; - } - - /* Do not send to interface with similar subnet (split horizon) */ - if (csp_iflist_is_within_subnet(iface->addr, routed_from)) { + /* Do not send back to same interface (split horizon) */ + if (is_same_subnet(iface, routed_from)) { continue; } - /* Apply outgoing interface address to packet */ - if ((from_me) && (idout->src == 0)) { - idout->src = iface->addr; - } - - /* Rewrite routed brodcast (L3) to local (L2) when arriving at the interface */ - if (csp_id_is_broadcast(idout->dst, iface)) { - idout->dst = csp_id_get_max_nodeid(); - } + if (next_iface != NULL) { + csp_packet_t * copy = csp_buffer_clone(packet); - /* Todo: Find an elegant way to avoid making a copy when only a single destination interface - * is found. But without looping the list twice. And without using stack memory. - * Is this even possible? */ - copy = csp_buffer_clone(packet); - if (copy != NULL) { - csp_send_direct_iface(idout, copy, iface, via, from_me); + convert_broadcast(idout, &idout_copy, next_iface); + send_packet(&idout_copy, copy, next_iface, via, from_me); } - + next_iface = iface; } - /* If the above worked, we don't want to look at the routing table */ - if (local_found == 1) { - csp_buffer_free(packet); + if (local_found) { + if (next_iface != NULL) { + convert_broadcast(idout, &idout_copy, next_iface); + send_packet(&idout_copy, packet, next_iface, via, from_me); + } else { + csp_buffer_free(packet); + } + /* If a match was found, we don't want to look at the routing table */ return; } @@ -134,32 +179,27 @@ void csp_send_direct(csp_id_t* idout, csp_packet_t * packet, csp_iface_t * route do { route_found = 1; - /* Do not send back to same inteface (split horizon) - * This check is is similar to that below, but faster */ - if (route->iface == routed_from) { + /* Do not send back to same interface (split horizon) */ + if (is_same_subnet(route->iface, routed_from)) { continue; } - /* Do not send to interface with similar subnet (split horizon) */ - if (csp_iflist_is_within_subnet(route->iface->addr, routed_from)) { - continue; - } - - /* Apply outgoing interface address to packet */ - if ((from_me) && (idout->src == 0)) { - idout->src = route->iface->addr; - } - - copy = csp_buffer_clone(packet); - if (copy != NULL) { - csp_send_direct_iface(idout, copy, route->iface, route->via, from_me); + if (next_iface != NULL) { + csp_packet_t * copy = csp_buffer_clone(packet); + send_packet(&idout_copy, copy, next_iface, via, from_me); } + next_iface = route->iface; + via = route->via; } while ((route = csp_rtable_search_backward(route)) != NULL); } /* If the above worked, we don't want to look at default interfaces */ if (route_found == 1) { - csp_buffer_free(packet); + if (next_iface != NULL) { + send_packet(&idout_copy, packet, next_iface, via, from_me); + } else { + csp_buffer_free(packet); + } return; } @@ -168,43 +208,34 @@ void csp_send_direct(csp_id_t* idout, csp_packet_t * packet, csp_iface_t * route /* Try to send via default interfaces */ while ((iface = csp_iflist_get_by_isdfl(iface)) != NULL) { - /* Do not send back to same inteface (split horizon) - * This check is is similar to that below, but faster */ - if (iface == routed_from) { + if (is_same_subnet(iface, routed_from)) { continue; } - /* Do not send to interface with similar subnet (split horizon) */ - if (csp_iflist_is_within_subnet(iface->addr, routed_from)) { - continue; - } - - /* Apply outgoing interface address to packet */ - if ((from_me) && (idout->src == 0)) { - idout->src = iface->addr; - } - - /* Todo: Find an elegant way to avoid making a copy when only a single destination interface - * is found. But without looping the list twice. And without using stack memory. - * Is this even possible? */ - copy = csp_buffer_clone(packet); - if (copy != NULL) { - csp_send_direct_iface(idout, copy, iface, via, from_me); + if (next_iface != NULL) { + csp_packet_t * copy = csp_buffer_clone(packet); + send_packet(&idout_copy, copy, next_iface, via, from_me); } + next_iface = iface; + } + if (next_iface != NULL) { + send_packet(&idout_copy, packet, next_iface, via, from_me); + return; } csp_buffer_free(packet); } -__weak void csp_output_hook(csp_id_t * idout, csp_packet_t * packet, csp_iface_t * iface, uint16_t via, int from_me) { +__weak void csp_output_hook(const csp_id_t * idout, csp_packet_t * packet, csp_iface_t * iface, uint16_t via, int from_me) { + (void)from_me; /* Avoid compiler warnings about unused parameter */ csp_print_packet("OUT: S %u, D %u, Dp %u, Sp %u, Pr %u, Fl 0x%02X, Sz %u VIA: %s (%u), Tms %u\n", idout->src, idout->dst, idout->dport, idout->sport, idout->pri, idout->flags, packet->length, iface->name, (via != CSP_NO_VIA_ADDRESS) ? via : idout->dst, csp_get_ms()); return; } -void csp_send_direct_iface(csp_id_t* idout, csp_packet_t * packet, csp_iface_t * iface, uint16_t via, int from_me) { +void csp_send_direct_iface(const csp_id_t* idout, csp_packet_t * packet, csp_iface_t * iface, uint16_t via, int from_me) { csp_output_hook(idout, packet, iface, via, from_me); @@ -214,19 +245,12 @@ void csp_send_direct_iface(csp_id_t* idout, csp_packet_t * packet, csp_iface_t * } -#if (CSP_USE_PROMISC) - /* Loopback traffic is added to promisc queue by the router */ - if (from_me && (iface != &csp_if_lo)) { - csp_promisc_add(packet); - } -#endif - /* Only encrypt packets from the current node */ if (from_me) { /* Append HMAC */ if (idout->flags & CSP_FHMAC) { #if (CSP_USE_HMAC) - /* Calculate and add HMAC (does not include header for backwards compatability with csp1.x) */ + /* Calculate and add HMAC (does not include header for backwards compatibility with csp1.x) */ if (csp_hmac_append(packet, false) != CSP_ERR_NONE) { /* HMAC append failed */ goto tx_err; @@ -239,13 +263,19 @@ void csp_send_direct_iface(csp_id_t* idout, csp_packet_t * packet, csp_iface_t * /* Append CRC32 */ if (idout->flags & CSP_FCRC32) { - /* Calculate and add CRC32 (does not include header for backwards compatability with csp1.x) */ + /* Calculate and add CRC32 (does not include header for backwards compatibility with csp1.x) */ if (csp_crc32_append(packet) != CSP_ERR_NONE) { /* CRC32 append failed */ goto tx_err; } } +#if (CSP_USE_PROMISC) + /* Loopback traffic is added to promisc queue by the router */ + if (iface != &csp_if_lo) { + csp_promisc_add(packet); + } +#endif } /* Store length before passing to interface */ @@ -294,7 +324,7 @@ void csp_send_prio(uint8_t prio, csp_conn_t * conn, csp_packet_t * packet) { csp_send(conn, packet); } -int csp_transaction_persistent(csp_conn_t * conn, uint32_t timeout, void * outbuf, int outlen, void * inbuf, int inlen) { +int csp_transaction_persistent(csp_conn_t * conn, uint32_t timeout, const void * outbuf, int outlen, void * inbuf, int inlen) { if(outlen > CSP_BUFFER_SIZE){ return 0; @@ -331,7 +361,7 @@ int csp_transaction_persistent(csp_conn_t * conn, uint32_t timeout, void * outbu return length; } -int csp_transaction_w_opts(uint8_t prio, uint16_t dest, uint8_t port, uint32_t timeout, void * outbuf, int outlen, void * inbuf, int inlen, uint32_t opts) { +int csp_transaction_w_opts(uint8_t prio, uint16_t dest, uint8_t port, uint32_t timeout, const void * outbuf, int outlen, void * inbuf, int inlen, uint32_t opts) { csp_conn_t * conn = csp_connect(prio, dest, port, 0, opts); if (conn == NULL) @@ -382,7 +412,6 @@ void csp_sendto(uint8_t prio, uint16_t dest, uint8_t dport, uint8_t src_port, ui packet->id.dst = dest; packet->id.dport = dport; - packet->id.src = 0; // The source address will be filled by csp_send_direct packet->id.sport = src_port; packet->id.pri = prio; @@ -398,5 +427,11 @@ void csp_sendto_reply(const csp_packet_t * request_packet, csp_packet_t * reply_ if (opts & CSP_O_SAME) { reply_packet->id.flags = request_packet->id.flags; } - csp_sendto(request_packet->id.pri, request_packet->id.src, request_packet->id.sport, request_packet->id.dport, opts, reply_packet); + uint16_t dst = request_packet->id.src; + if (request_packet->id.dst != csp_id_get_max_nodeid()) { + reply_packet->id.src = request_packet->id.dst; + } else { + reply_packet->id.src = 0; + } + csp_sendto(request_packet->id.pri, dst, request_packet->id.sport, request_packet->id.dport, opts, reply_packet); } diff --git a/src/csp_io.h b/src/csp_io.h index a4030ed11..7e829ee4d 100644 --- a/src/csp_io.h +++ b/src/csp_io.h @@ -1,16 +1,13 @@ - - #pragma once #include /** - * + * * @param packet packet to send - this may not be freed if error code is returned * @param from_me 1 if from me, 0 if routed message * @return #CSP_ERR_NONE on success, otherwise an error code. - * + * */ - void csp_send_direct(csp_id_t* idout, csp_packet_t * packet, csp_iface_t * routed_from); -void csp_send_direct_iface(csp_id_t* idout, csp_packet_t * packet, csp_iface_t * iface, uint16_t via, int from_me); +void csp_send_direct_iface(const csp_id_t* idout, csp_packet_t * packet, csp_iface_t * iface, uint16_t via, int from_me); diff --git a/src/csp_macro.h b/src/csp_macro.h index 90fa08080..e98ada3da 100644 --- a/src/csp_macro.h +++ b/src/csp_macro.h @@ -7,8 +7,13 @@ #else #define __noinit __attribute__((section(".noinit"))) #define __packed __attribute__((__packed__)) +#define __maybe_unused __attribute__((__unused__)) #define __unused __attribute__((__unused__)) +#ifdef __CYGWIN__ +#define __weak +#else #define __weak __attribute__((__weak__)) +#endif #define CONTAINER_OF(ptr, type, member) \ ((type *)(void *)((char *)(ptr) - offsetof(type, member))) diff --git a/src/csp_port.c b/src/csp_port.c index 71cfc577d..cbec0e127 100644 --- a/src/csp_port.c +++ b/src/csp_port.c @@ -75,6 +75,7 @@ csp_socket_t * csp_port_get_socket(unsigned int port) { } int csp_listen(csp_socket_t * socket, size_t backlog) { + (void)backlog; /* Avoid compiler warnings about unused parameter */ socket->rx_queue = csp_queue_create_static(CSP_CONN_RXQUEUE_LEN, sizeof(csp_packet_t *), socket->rx_queue_static_data, &socket->rx_queue_static); return CSP_ERR_NONE; } diff --git a/src/csp_promisc.c b/src/csp_promisc.c index 223bdc0c6..a65696a81 100644 --- a/src/csp_promisc.c +++ b/src/csp_promisc.c @@ -11,11 +11,12 @@ static char csp_promisc_queue_buffer[sizeof(csp_packet_t *) * CSP_CONN_RXQUEUE_L static int csp_promisc_enabled = 0; int csp_promisc_enable(unsigned int queue_size) { + (void)queue_size; /* Avoid compiler warnings about unused parameter */ /* If queue already initialised */ if (csp_promisc_queue != NULL) { csp_promisc_enabled = 1; - return CSP_ERR_NONE; + return CSP_ERR_USED; } /* Create packet queue */ diff --git a/src/csp_qfifo.c b/src/csp_qfifo.c index 476d51315..ffb30d291 100644 --- a/src/csp_qfifo.c +++ b/src/csp_qfifo.c @@ -51,7 +51,7 @@ void csp_qfifo_write(csp_packet_t * packet, csp_iface_t * iface, void * pxTaskWo if (result != CSP_QUEUE_OK) { csp_dbg_conn_ovf++; - iface->drop++; + iface->tx_error++; if (pxTaskWoken == NULL) csp_buffer_free(packet); else diff --git a/src/csp_qfifo.h b/src/csp_qfifo.h index 31670670b..ef3cd9ae2 100644 --- a/src/csp_qfifo.h +++ b/src/csp_qfifo.h @@ -6,7 +6,7 @@ #if (CSP_USE_RDP) #define FIFO_TIMEOUT 100 //! If RDP is enabled, the router needs to awake some times to check timeouts #else -#define FIFO_TIMEOUT CSP_MAX_TIMEOUT //! If no RDP, the router can sleep untill data arrives +#define FIFO_TIMEOUT CSP_MAX_TIMEOUT //! If no RDP, the router can sleep until data arrives #endif /** diff --git a/src/csp_rdp.c b/src/csp_rdp.c index a860d86fa..d1315a37a 100644 --- a/src/csp_rdp.c +++ b/src/csp_rdp.c @@ -4,6 +4,8 @@ * delayed acknowledgments, to improve performance over half-duplex links. */ +#include "csp_rdp.h" + #include "csp_rdp_queue.h" #include @@ -17,7 +19,6 @@ #include #include -#include "csp_port.h" #include "csp_conn.h" #include "csp_io.h" #include "csp_semaphore.h" @@ -32,12 +33,14 @@ #endif + static uint32_t csp_rdp_window_size = 4; static uint32_t csp_rdp_conn_timeout = 10000; static uint32_t csp_rdp_packet_timeout = 1000; static uint32_t csp_rdp_delayed_acks = 1; static uint32_t csp_rdp_ack_timeout = 1000 / 4; static uint32_t csp_rdp_ack_delay_count = 4 / 2; +static uint8_t csp_rdp_incr = 0; typedef struct __packed { uint8_t flags; @@ -45,6 +48,7 @@ typedef struct __packed { uint16_t ack_nr; } rdp_header_t; + static int csp_rdp_close_internal(csp_conn_t * conn, uint8_t closed_by, bool send_rst); /** @@ -121,6 +125,14 @@ static int csp_rdp_send_cmp(csp_conn_t * conn, csp_packet_t * packet, int flags, packet->length = 0; } + if (flags & RDP_ACK) { + conn->rdp.rcv_lsa = ack_nr; + } + + /* Every outgoing message contains the last valid ACK number. So we always set last ack timestamp + * We do this early to minimize race condition between read() call and router task csp_rdp_new_packet() */ + conn->rdp.ack_timestamp = csp_get_ms(); + /* Add RDP header */ rdp_header_t * header = csp_rdp_header_add(packet); if (header == NULL) { @@ -132,7 +144,6 @@ static int csp_rdp_send_cmp(csp_conn_t * conn, csp_packet_t * packet, int flags, header->ack_nr = htobe16(ack_nr); /* Add a bit of ephemeral data to avoid CMP's to be deduplicated */ - static uint8_t csp_rdp_incr = 0; //header->flags = flags; header->flags |= csp_rdp_incr++ << 4 | flags; @@ -158,57 +169,10 @@ static int csp_rdp_send_cmp(csp_conn_t * conn, csp_packet_t * packet, int flags, /* Send packet to IF */ csp_send_direct(&idout, packet, NULL); - /* Update last ACK time stamp */ - if (flags & RDP_ACK) { - conn->rdp.rcv_lsa = ack_nr; - conn->rdp.ack_timestamp = csp_get_ms(); - } return CSP_ERR_NONE; } -/** - * EXTENDED ACKNOWLEDGEMENTS - * The following function sends an extended ACK packet - */ -static int csp_rdp_send_eack(csp_conn_t * conn) { - - /* Allocate message */ - csp_packet_t * packet_eack = csp_buffer_get(0); - if (packet_eack == NULL) return CSP_ERR_NOMEM; - packet_eack->length = 0; - - /* Loop through RX queue */ - int i, count; - csp_packet_t * packet; - count = csp_rdp_queue_rx_size(); - unsigned int space_available = 100 - (packet_eack->length + sizeof(rdp_header_t)); - - for (i = 0; i < count; i++) { - - packet = csp_rdp_queue_rx_get(conn); - if (packet == NULL) { - break; - } - - /* Add seq nr to EACK packet */ - rdp_header_t * header = csp_rdp_header_ref(packet); - if (space_available >= sizeof(uint16_t)) { - packet_eack->data16[packet_eack->length / sizeof(uint16_t)] = htobe16(header->seq_nr); - packet_eack->length += sizeof(uint16_t); - space_available -= sizeof(uint16_t); - csp_rdp_protocol("RDP %p: Added EACK nr %u\n", (void *)conn, header->seq_nr); - } else { - csp_rdp_protocol("RDP %p: Skipping EACK nr %u\n", (void *)conn, header->seq_nr); - } - - /* Requeue */ - csp_rdp_queue_rx_add(conn, packet); - } - - return csp_rdp_send_cmp(conn, packet_eack, RDP_ACK | RDP_EAK, conn->rdp.snd_nxt, conn->rdp.rcv_cur); -} - /** * SYN Packet * The following function sends a SYN packet @@ -237,7 +201,7 @@ static inline int csp_rdp_receive_data(csp_conn_t * conn, csp_packet_t * packet) csp_rdp_header_remove(packet); /* Enqueue data */ - if (csp_conn_enqueue_packet(conn, packet) < 0) { + if (csp_conn_enqueue_packet(conn, packet) != CSP_ERR_NONE) { csp_dbg_conn_ovf++; csp_rdp_error("RDP %p: Conn RX buffer full\n", (void *)conn); return CSP_ERR_NOBUFS; @@ -268,11 +232,10 @@ static inline void csp_rdp_rx_queue_flush(csp_conn_t * conn) { } rdp_header_t * header = csp_rdp_header_ref(packet); - csp_rdp_protocol("RDP %p: RX Queue deliver matching Element, seq %u\n", (void *)conn, header->seq_nr); /* If the matching packet was found: */ if (header->seq_nr == (uint16_t)(conn->rdp.rcv_cur + 1)) { - csp_rdp_protocol("RDP %p: Deliver seq %u", (void *)conn, header->seq_nr); + csp_rdp_protocol("RDP %p: Deliver seq %u\n", (void *)conn, header->seq_nr); if (csp_rdp_receive_data(conn, packet) != CSP_ERR_NONE) { csp_rdp_error("RDP lost packet internally, stream corrupted!\n"); csp_buffer_free(packet); @@ -304,12 +267,8 @@ static inline bool csp_rdp_seq_in_rx_queue(csp_conn_t * conn, uint16_t seq_nr) { csp_rdp_queue_rx_add(conn, packet); - rdp_header_t * header = csp_rdp_header_ref((csp_packet_t *)packet); - csp_rdp_protocol("RDP %p: RX Queue exists matching Element, seq %u\n", (void *)conn, header->seq_nr); - - /* If the matching packet was found, deliver */ + rdp_header_t * header = csp_rdp_header_ref(packet); if (header->seq_nr == seq_nr) { - csp_rdp_protocol("RDP %p: We have a match\n", (void *)conn); return true; } } @@ -319,54 +278,15 @@ static inline bool csp_rdp_seq_in_rx_queue(csp_conn_t * conn, uint16_t seq_nr) { static inline int csp_rdp_rx_queue_add(csp_conn_t * conn, csp_packet_t * packet, uint16_t seq_nr) { - if (csp_rdp_seq_in_rx_queue(conn, seq_nr)) - return -1; + if (csp_rdp_seq_in_rx_queue(conn, seq_nr)) { + csp_rdp_protocol("RDP %p: Already exists in RX queue %u\n", (void *)conn, seq_nr); + return CSP_ERR_USED; + } + csp_rdp_protocol("RDP %p: Add to RX queue %u\n", (void *) conn, seq_nr); csp_rdp_queue_rx_add(conn, packet); - return 0; + return CSP_ERR_NONE; } -static void csp_rdp_flush_eack(csp_conn_t * conn, csp_packet_t * eack_packet) { - - /* Loop through TX queue */ - int i, j, count; - csp_packet_t * packet; - count = csp_rdp_queue_tx_size(); - for (i = 0; i < count; i++) { - - packet = csp_rdp_queue_tx_get(conn); - if (packet == NULL) { - break; - } - - rdp_header_t * header = csp_rdp_header_ref((csp_packet_t *)packet); - csp_rdp_protocol("RDP %p: EACK compare element, time %" PRIu32 ", seq %u\n", (void *)conn, packet->timestamp_tx, be16toh(header->seq_nr)); - - /* Look for this element in EACKs */ - int match = 0; - for (j = 0; j < (int)((eack_packet->length - sizeof(rdp_header_t)) / sizeof(uint16_t)); j++) { - if (be16toh(eack_packet->data16[j]) == be16toh(header->seq_nr)) - match = 1; - - /* Enable this if you want EACK's to trigger retransmission */ - if (be16toh(eack_packet->data16[j]) > be16toh(header->seq_nr)) { - uint32_t time_now = csp_get_ms(); - if (csp_rdp_time_after(time_now, packet->rdp_quarantine)) { - packet->timestamp_tx = time_now - conn->rdp.packet_timeout - 1; - packet->rdp_quarantine = time_now + conn->rdp.packet_timeout / 2; - } - } - } - - if (match == 0) { - /* If not found, put back on tx queue */ - csp_rdp_queue_tx_add(conn, packet); - } else { - /* Found, free */ - csp_rdp_protocol("RDP %p: TX Element %u freed\n", (void *)conn, be16toh(header->seq_nr)); - csp_buffer_free(packet); - } - } -} static inline bool csp_rdp_should_ack(csp_conn_t * conn) { @@ -390,7 +310,7 @@ static inline bool csp_rdp_should_ack(csp_conn_t * conn) { int csp_rdp_check_ack(csp_conn_t * conn) { /* Check RX queue for spare capacity */ - if (CSP_CONN_RXQUEUE_LEN - csp_queue_size(conn->rx_queue) <= 2 * (int32_t)conn->rdp.window_size) { + if ((unsigned int) abs(CSP_CONN_RXQUEUE_LEN - csp_queue_size(conn->rx_queue)) < conn->rdp.window_size) { return CSP_ERR_NONE; } @@ -440,8 +360,8 @@ void csp_rdp_check_timeouts(csp_conn_t * conn) { if (conn->rdp.state == RDP_CLOSE_WAIT) { if (csp_rdp_time_after(time_now, conn->timestamp + conn->rdp.conn_timeout)) { csp_conn_close(conn, CSP_RDP_CLOSED_BY_PROTOCOL | CSP_RDP_CLOSED_BY_TIMEOUT); + return; } - return; } /** @@ -458,7 +378,7 @@ void csp_rdp_check_timeouts(csp_conn_t * conn) { } /* Get header */ - rdp_header_t * header = csp_rdp_header_ref((csp_packet_t *)packet); + rdp_header_t * header = csp_rdp_header_ref(packet); /* If acked, do not retransmit */ if (csp_rdp_seq_before(be16toh(header->seq_nr), conn->rdp.snd_una)) { @@ -470,16 +390,23 @@ void csp_rdp_check_timeouts(csp_conn_t * conn) { /* Check timestamp and retransmit if needed */ if (csp_rdp_time_after(time_now, packet->timestamp_tx + conn->rdp.packet_timeout)) { - csp_rdp_protocol("RDP %p: TX Element timed out, retransmitting seq %u\n", - (void *)conn, be16toh(header->seq_nr)); + csp_packet_t * new_packet = csp_buffer_get(0); + if (new_packet) { + csp_rdp_protocol("RDP %p: TX Element timed out, retransmitting seq %u\n", + (void *)conn, be16toh(header->seq_nr)); - /* Update to latest outgoing ACK */ - header->ack_nr = htobe16(conn->rdp.rcv_cur); + /* Update to latest outgoing ACK */ + header->ack_nr = htobe16(conn->rdp.rcv_cur); - /* Send copy to tx_queue */ - packet->timestamp_tx = csp_get_ms(); - csp_packet_t * new_packet = csp_buffer_clone(packet); - csp_send_direct(&conn->idout, new_packet, NULL); + /* Every outgoing message contains the last valid ACK number. So we always set last ack timestamp */ + conn->rdp.ack_timestamp = csp_get_ms(); + /* Send copy to tx_queue */ + packet->timestamp_tx = csp_get_ms(); + csp_buffer_copy(packet, new_packet); + csp_send_direct(&conn->idout, new_packet, NULL); + } else { + csp_rdp_error("RDP %p: Failed to allocate packet buffer\n", (void *)conn); + } } /* Requeue the TX element */ @@ -489,6 +416,12 @@ void csp_rdp_check_timeouts(csp_conn_t * conn) { if (conn->rdp.state == RDP_OPEN) { + if (csp_rdp_time_after(time_now, conn->timestamp + conn->rdp.conn_timeout)) { + csp_conn_close(conn, CSP_RDP_CLOSED_BY_PROTOCOL | CSP_RDP_CLOSED_BY_TIMEOUT); + csp_bin_sem_post(&conn->rdp.tx_wait); + return; + } + /* Check if we have unacknowledged segments */ if (conn->rdp.delayed_acks) { csp_rdp_check_ack(conn); @@ -549,7 +482,7 @@ bool csp_rdp_new_packet(csp_conn_t * conn, csp_packet_t * packet) { goto discard_close; } - if (rx_header->seq_nr == (conn->rdp.rcv_cur + 1)) { + if (rx_header->seq_nr == (uint16_t)(conn->rdp.rcv_cur + 1)) { csp_rdp_protocol("RDP %p: Received RST in sequence, no more data incoming, reply with RST\n", (void *)conn); conn->rdp.state = RDP_CLOSE_WAIT; conn->timestamp = csp_get_ms(); @@ -572,8 +505,12 @@ bool csp_rdp_new_packet(csp_conn_t * conn, csp_packet_t * packet) { */ case RDP_CLOSED: { + /* Clear ephemeral data added by csp_rdp_send_cmp(). + RDP flags are located in the lower 4 bits. */ + uint8_t rx_header_flags = rx_header->flags & 0x0f; + /* No SYN flag set while in closed. Inform by sending back RST */ - if (!(rx_header->flags & RDP_SYN)) { + if (rx_header_flags != RDP_SYN) { csp_rdp_protocol("RDP %p: Not SYN received in CLOSED state. Discarding packet\n", (void *)conn); csp_rdp_send_cmp(conn, NULL, RDP_RST, conn->rdp.snd_nxt, conn->rdp.rcv_cur); goto discard_close; @@ -686,10 +623,6 @@ bool csp_rdp_new_packet(csp_conn_t * conn, csp_packet_t * packet) { /* If duplicate SYN received, send another SYN/ACK */ if (conn->rdp.state == RDP_SYN_RCVD) csp_rdp_send_cmp(conn, NULL, RDP_ACK | RDP_SYN, conn->rdp.snd_iss, conn->rdp.rcv_irs); - /* If duplicate data packet received, send EACK back */ - if (conn->rdp.state == RDP_OPEN) - csp_rdp_send_eack(conn); - goto discard_open; } @@ -726,13 +659,18 @@ bool csp_rdp_new_packet(csp_conn_t * conn, csp_packet_t * packet) { } } + if (conn->dest_socket == NULL) { + /* User space has taken control of the connection, so RDP needs to "monitor" + * the connection by timestamping each time a packet is received in OPEN state */ + conn->timestamp = csp_get_ms(); + } + /* Store current ack'ed sequence number */ conn->rdp.snd_una = rx_header->ack_nr + 1; /* We have an EACK */ - if ((rx_header->flags & RDP_EAK)) { - if (packet->length > sizeof(rdp_header_t)) - csp_rdp_flush_eack(conn, packet); + if (rx_header->flags & RDP_EAK) { + csp_rdp_protocol("RDP %p: Got EACK\n", (void *)conn); goto discard_open; } @@ -742,12 +680,10 @@ bool csp_rdp_new_packet(csp_conn_t * conn, csp_packet_t * packet) { /* If message is not in sequence, send EACK and store packet */ if (rx_header->seq_nr != (uint16_t)(conn->rdp.rcv_cur + 1)) { - if (csp_rdp_rx_queue_add(conn, packet, rx_header->seq_nr) != 0) { - csp_rdp_protocol("RDP %p: Duplicate sequence number\n", (void *)conn); + if (csp_rdp_rx_queue_add(conn, packet, rx_header->seq_nr) != CSP_ERR_NONE) { csp_rdp_check_ack(conn); goto discard_open; } - csp_rdp_send_eack(conn); goto accepted_open; } @@ -888,17 +824,16 @@ int csp_rdp_send(csp_conn_t * conn, csp_packet_t * packet) { return CSP_ERR_RESET; } - while ((conn->rdp.state == RDP_OPEN) && (csp_rdp_is_conn_ready_for_tx(conn) == false)) { - csp_rdp_protocol("RDP %p: Waiting for window update before sending seq %u\n", (void *)conn, conn->rdp.snd_nxt); - if ((csp_bin_sem_wait(&conn->rdp.tx_wait, conn->rdp.conn_timeout)) != CSP_SEMAPHORE_OK) { - csp_rdp_error("RDP %p: Timeout during send", (void *)conn); - return CSP_ERR_TIMEDOUT; + /* Wait here for RDP to become ready or the connection to be closed */ + while (1) { + if (conn->rdp.state == RDP_CLOSE_WAIT || conn->rdp.state == RDP_CLOSED) { + csp_rdp_error("RDP %p: ERROR cannot send, connection closed by peer or timeout\n", (void *)conn); + return CSP_ERR_RESET; } - } - - if (conn->rdp.state != RDP_OPEN) { - csp_rdp_error("RDP %p: ERROR cannot send, connection not open (%d) -> reset\n", (void *)conn, conn->rdp.state); - return CSP_ERR_RESET; + if (csp_rdp_is_conn_ready_for_tx(conn) == true) + break; + csp_rdp_protocol("RDP %p: Waiting for window update before sending seq %u\n", (void *)conn, conn->rdp.snd_nxt); + csp_bin_sem_wait(&conn->rdp.tx_wait, conn->rdp.conn_timeout); } /* Add RDP header */ @@ -919,7 +854,6 @@ int csp_rdp_send(csp_conn_t * conn, csp_packet_t * packet) { } rdp_packet->timestamp_tx = csp_get_ms(); - rdp_packet->rdp_quarantine = 0; csp_rdp_queue_tx_add(conn, rdp_packet); csp_rdp_protocol( @@ -932,6 +866,7 @@ int csp_rdp_send(csp_conn_t * conn, csp_packet_t * packet) { packet->length, (unsigned int)(packet->length - sizeof(rdp_header_t))); conn->rdp.snd_nxt++; + conn->rdp.ack_timestamp = csp_get_ms(); return CSP_ERR_NONE; } @@ -1024,3 +959,22 @@ void csp_rdp_get_opt(unsigned int * window_size, unsigned int * conn_timeout_ms, if (ack_delay_count) *ack_delay_count = csp_rdp_ack_delay_count; } + +bool csp_rdp_conn_is_active(csp_conn_t *conn) { + + uint32_t time_now = csp_get_ms(); + bool active = true; + + if (csp_rdp_time_after(time_now, conn->timestamp + conn->rdp.conn_timeout)) { + /* The RDP connection has timed out */ + csp_rdp_error("RDP %p: Timeout no packets received last %u ms\n", (void *)conn, conn->rdp.conn_timeout); + active = false; + } + + if (conn->rdp.state == RDP_CLOSE_WAIT || conn->rdp.state == RDP_CLOSED) { + active = false; + } + + return active; + +} diff --git a/src/csp_rdp.h b/src/csp_rdp.h index 87802ce82..931030e27 100644 --- a/src/csp_rdp.h +++ b/src/csp_rdp.h @@ -10,3 +10,4 @@ int csp_rdp_connect(csp_conn_t * conn); int csp_rdp_close(csp_conn_t * conn, uint8_t closed_by); int csp_rdp_send(csp_conn_t * conn, csp_packet_t * packet); int csp_rdp_check_ack(csp_conn_t * conn); +bool csp_rdp_conn_is_active(csp_conn_t *conn); diff --git a/src/csp_rdp_queue.c b/src/csp_rdp_queue.c index ceb7576c1..08567c1ec 100644 --- a/src/csp_rdp_queue.c +++ b/src/csp_rdp_queue.c @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -21,7 +23,7 @@ static int __csp_rdp_queue_flush(csp_queue_handle_t queue, csp_conn_t * conn) { csp_buffer_free(packet); } else { /* put it back */ - ret = csp_queue_enqueue(queue, packet, 0); + ret = csp_queue_enqueue(queue, &packet, 0); if (ret != CSP_QUEUE_OK) { /* something is really broken */ break; diff --git a/src/csp_route.c b/src/csp_route.c index 2e4b76f3b..79cd23d24 100644 --- a/src/csp_route.c +++ b/src/csp_route.c @@ -17,6 +17,7 @@ #include "csp_dedup.h" #include "csp_rdp.h" #include +#include #include #include "csp_macro.h" @@ -27,7 +28,9 @@ * @return CSP_ERR_NONE is all options are supported, CSP_ERR_NOTSUP if not */ static int csp_route_check_options(csp_iface_t * iface, csp_packet_t * packet) { - + /* Avoid compiler warnings about unused parameter */ + (void)iface; + (void)packet; #if (CSP_USE_HMAC == 0) /* Drop HMAC packets */ @@ -61,7 +64,7 @@ static int csp_route_security_check(uint32_t security_opts, csp_iface_t * iface, /* CRC32 verified packet */ if (packet->id.flags & CSP_FCRC32) { - /* Verify CRC32 (does not include header for backwards compatability with csp1.x) */ + /* Verify CRC32 (does not include header for backwards compatibility with csp1.x) */ if (csp_crc32_verify(packet) != CSP_ERR_NONE) { iface->rx_error++; return CSP_ERR_CRC32; @@ -74,7 +77,7 @@ static int csp_route_security_check(uint32_t security_opts, csp_iface_t * iface, #if (CSP_USE_HMAC) /* HMAC authenticated packet */ if (packet->id.flags & CSP_FHMAC) { - /* Verify HMAC (does not include header for backwards compatability with csp1.x) */ + /* Verify HMAC (does not include header for backwards compatibility with csp1.x) */ if (csp_hmac_verify(packet, false) != CSP_ERR_NONE) { /* HMAC failed */ iface->autherr++; @@ -134,13 +137,15 @@ int csp_route_work(void) { input.iface->rx++; input.iface->rxbytes += packet->length; - /* The packet is to me, if the address matches that of the incoming interface, + /* The packet is to me, if the address matches that of any interface, * or the address matches the broadcast address of the incoming interface */ - int is_to_me = ((input.iface->addr == packet->id.dst) || (csp_id_is_broadcast(packet->id.dst, input.iface))); + int is_to_me = ((csp_iflist_get_by_addr(packet->id.dst) != NULL || + (csp_id_is_broadcast(packet->id.dst, input.iface))) || + (csp_addr_is_alias(packet->id.dst))); /* Deduplication */ if ((csp_conf.dedup == CSP_DEDUP_ALL) || - ((is_to_me) && (csp_conf.dedup == CSP_DEDUP_INCOMING)) || + (is_to_me && (csp_conf.dedup == CSP_DEDUP_INCOMING)) || ((!is_to_me) && (csp_conf.dedup == CSP_DEDUP_FWD))) { if (csp_dedup_is_duplicate(packet)) { /* Discard packet */ diff --git a/src/csp_rtable_cidr.c b/src/csp_rtable_cidr.c index 8dc9ed42b..551b58ab7 100644 --- a/src/csp_rtable_cidr.c +++ b/src/csp_rtable_cidr.c @@ -73,7 +73,7 @@ csp_route_t * csp_rtable_find_route(uint16_t addr) { return NULL; } -int csp_rtable_set_internal(uint16_t address, uint16_t netmask, csp_iface_t * ifc, uint16_t via) { +static int csp_rtable_set_internal(uint16_t address, uint16_t netmask, csp_iface_t * ifc, uint16_t via) { /* First see if the entry exists */ csp_route_t * entry = csp_rtable_find_exact(address, netmask, ifc); @@ -81,8 +81,8 @@ int csp_rtable_set_internal(uint16_t address, uint16_t netmask, csp_iface_t * if /* If not, create a new one */ if (!entry) { entry = &rtable[rtable_inptr++]; - if (rtable_inptr > CSP_RTABLE_SIZE) { - rtable_inptr = CSP_RTABLE_SIZE; + if (rtable_inptr >= CSP_RTABLE_SIZE) { + rtable_inptr = CSP_RTABLE_SIZE-1; } } @@ -97,6 +97,7 @@ int csp_rtable_set_internal(uint16_t address, uint16_t netmask, csp_iface_t * if void csp_rtable_free(void) { memset(rtable, 0, sizeof(rtable)); + rtable_inptr = 0; } void csp_rtable_clear(void) { @@ -127,6 +128,7 @@ void csp_rtable_iterate(csp_rtable_iterator_t iter, void * ctx) { #if (CSP_ENABLE_CSP_PRINT) static bool csp_rtable_print_route(void * ctx, csp_route_t * route) { + (void)ctx; /* Avoid compiler warnings about unused parameter */ if (route->via == CSP_NO_VIA_ADDRESS) { csp_print("%u/%u %s\r\n", route->address, route->netmask, route->iface->name); } else { diff --git a/src/csp_rtable_stdio.c b/src/csp_rtable_stdio.c index 6401007d1..ea5a03f2a 100644 --- a/src/csp_rtable_stdio.c +++ b/src/csp_rtable_stdio.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -21,16 +22,16 @@ static int csp_rtable_parse(const char * rtable, int dry_run) { /* Get first token */ char * saveptr; char * str = strtok_r(rtable_copy, ",", &saveptr); - while ((str) && (strlen(str) > 1)) { + while (str && (strlen(str) > 1)) { unsigned int address, via; int netmask; - char name[15]; - if (sscanf(str, "%u/%d %14s %u", &address, &netmask, name, &via) == 4) { - } else if (sscanf(str, "%u/%d %14s", &address, &netmask, name) == 3) { + char name[CSP_IFLIST_NAME_MAX] = {0}; + if (sscanf(str, "%u/%d %9s %u", &address, &netmask, name, &via) == 4) { + } else if (sscanf(str, "%u/%d %9s", &address, &netmask, name) == 3) { via = CSP_NO_VIA_ADDRESS; - } else if (sscanf(str, "%u %14s %u", &address, name, &via) == 3) { + } else if (sscanf(str, "%u %9s %u", &address, name, &via) == 3) { netmask = csp_id_get_host_bits(); - } else if (sscanf(str, "%u %14s", &address, name) == 2) { + } else if (sscanf(str, "%u %9s", &address, name) == 2) { netmask = csp_id_get_host_bits(); via = CSP_NO_VIA_ADDRESS; } else { diff --git a/src/csp_service_handler.c b/src/csp_service_handler.c index a9eb05c20..1947d5631 100644 --- a/src/csp_service_handler.c +++ b/src/csp_service_handler.c @@ -22,12 +22,22 @@ static uint32_t wrap_32bit_memcpy(uint32_t to, const uint32_t from, size_t size) static csp_memcpy_fnc_t csp_cmp_memcpy_fnc = wrap_32bit_memcpy; #else static csp_memcpy_fnc_t csp_cmp_memcpy_fnc = (csp_memcpy_fnc_t)memcpy; +static csp_memread64_fnc_t csp_cmp_memread64_fnc = (csp_memread64_fnc_t)NULL; +static csp_memwrite64_fnc_t csp_cmp_memwrite64_fnc = (csp_memwrite64_fnc_t)NULL; #endif void csp_cmp_set_memcpy(csp_memcpy_fnc_t fnc) { csp_cmp_memcpy_fnc = fnc; } +void csp_cmp_set_memread64(csp_memread64_fnc_t fnc) { + csp_cmp_memread64_fnc = fnc; +} + +void csp_cmp_set_memwrite64(csp_memwrite64_fnc_t fnc) { + csp_cmp_memwrite64_fnc = fnc; +} + static int do_cmp_ident(struct csp_cmp_message * cmp) { /* Copy revision */ @@ -130,6 +140,38 @@ static int do_cmp_poke(struct csp_cmp_message * cmp) { return CSP_ERR_NONE; } +static int do_cmp_peek_v2(struct csp_cmp_message * cmp) { + + cmp->peek_v2.vaddr = htobe64(cmp->peek_v2.vaddr); + if (cmp->peek_v2.len > CSP_CMP_PEEK_V2_MAX_LEN) + return CSP_ERR_INVAL; + + if (!csp_cmp_memread64_fnc) { + return CSP_ERR_DRIVER; + } + + /* Dangerous, you better know what you are doing */ + csp_cmp_memread64_fnc(cmp->peek_v2.data, cmp->peek_v2.vaddr, cmp->peek_v2.len); + + return CSP_ERR_NONE; +} + +static int do_cmp_poke_v2(struct csp_cmp_message * cmp) { + + cmp->poke_v2.vaddr = htobe64(cmp->poke_v2.vaddr); + if (cmp->poke_v2.len > CSP_CMP_POKE_V2_MAX_LEN) + return CSP_ERR_INVAL; + + if (!csp_cmp_memwrite64_fnc) { + return CSP_ERR_DRIVER; + } + + /* Extremely dangerous, you better know what you are doing */ + csp_cmp_memwrite64_fnc(cmp->poke_v2.vaddr, cmp->poke_v2.data, cmp->poke_v2.len); + + return CSP_ERR_NONE; +} + static int do_cmp_clock(struct csp_cmp_message * cmp) { csp_timestamp_t clock; @@ -192,6 +234,14 @@ static int csp_cmp_handler(csp_packet_t * packet) { ret = do_cmp_poke(cmp); break; + case CSP_CMP_PEEK_V2: + ret = do_cmp_peek_v2(cmp); + break; + + case CSP_CMP_POKE_V2: + ret = do_cmp_poke_v2(cmp); + break; + case CSP_CMP_CLOCK: ret = do_cmp_clock(cmp); break; diff --git a/src/csp_services.c b/src/csp_services.c index 8495f9959..c1d191c3a 100644 --- a/src/csp_services.c +++ b/src/csp_services.c @@ -128,7 +128,7 @@ void csp_ps(uint16_t node, uint32_t timeout) { break; } - /* We have a reply, ensure data is 0 (zero) termianted */ + /* We have a reply, ensure data is 0 (zero) terminated */ const unsigned int length = (packet->length < sizeof(packet->data)) ? packet->length : (sizeof(packet->data) - 1); packet->data[length] = 0; csp_print("%s", packet->data); diff --git a/src/csp_sfp.c b/src/csp_sfp.c index 1e845b658..27d75508e 100644 --- a/src/csp_sfp.c +++ b/src/csp_sfp.c @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include "csp_macro.h" #include @@ -48,24 +50,67 @@ static inline sfp_header_t * csp_sfp_header_remove(csp_packet_t * packet) { return header; } -int csp_sfp_send_own_memcpy(csp_conn_t * conn, const void * data, unsigned int totalsize, unsigned int mtu, uint32_t timeout, csp_memcpy_fnc_t memcpyfcn) { - if (mtu == 0) { +uint32_t csp_sfp_opts_max_mtu(uint32_t opts) { + uint32_t overhead = 0; + + /* If RDP is set, we must take RDP header into account. */ + if (opts & CSP_O_RDP) { + overhead += CSP_RDP_HEADER_SIZE; + } + + /* If CRC is set, we must take CRC size into account. */ + if (opts & CSP_O_CRC32) { + overhead += sizeof(csp_crc32_t); + } + + /* If HMAC is set, we must take HMAC header into account. */ + if (opts & CSP_O_HMAC) { + overhead += CSP_HMAC_LENGTH; + } + + /* Add SFP header size always */ + overhead += sizeof(sfp_header_t); + + return CSP_BUFFER_SIZE - overhead; +} + +uint32_t csp_sfp_conn_max_mtu(const csp_conn_t * conn) { + uint32_t max_mtu = 0; + + if (NULL != conn) { + max_mtu = csp_sfp_opts_max_mtu(conn->opts); + } + + return max_mtu; +} + +int csp_sfp_send(csp_conn_t * conn, const csp_sfp_read_t * user, uint32_t totalsize, uint32_t mtu, uint32_t timeout) { + (void)timeout; + + if ((NULL == conn) || (NULL == user) || (NULL == user->read)) { return CSP_ERR_INVAL; + } else { + uint32_t max_mtu = csp_sfp_conn_max_mtu(conn); + + if ((mtu > max_mtu) || (0 == mtu)) { + return CSP_ERR_MTU; + } } - unsigned int count = 0; - while (count < totalsize) { + int error = CSP_ERR_NONE; + uint32_t count = 0; + while ((count < totalsize) && csp_conn_is_active(conn)) { sfp_header_t * sfp_header; /* Allocate packet */ - csp_packet_t * packet = csp_buffer_get(mtu + sizeof(*sfp_header)); + csp_packet_t * packet = csp_buffer_get(0); if (packet == NULL) { return CSP_ERR_NOMEM; } /* Calculate sending size */ - unsigned int size = totalsize - count; + uint32_t size = totalsize - count; if (size > mtu) { size = mtu; } @@ -74,14 +119,19 @@ int csp_sfp_send_own_memcpy(csp_conn_t * conn, const void * data, unsigned int t //csp_print("%s: %d:%d, sending at %p size %u\n", __func__, csp_conn_src(conn), csp_conn_sport(conn), (void *)((uint8_t *)data + count), size); /* Copy data */ - (memcpyfcn)((csp_memptr_t)(uintptr_t)packet->data, (csp_memptr_t)(uintptr_t)(((uint8_t *)data) + count), size); + error = user->read(packet->data, size, count, user->data); + if (CSP_ERR_NONE != error) { + csp_buffer_free(packet); + return error; + } + packet->length = size; /* Set fragment flag */ conn->idout.flags |= CSP_FFRAG; /* Add SFP header */ - sfp_header = csp_sfp_header_add(packet); // no check, because buffer was allocated with extra size. + sfp_header = csp_sfp_header_add(packet); sfp_header->totalsize = htobe32(totalsize); sfp_header->offset = htobe32(count); @@ -95,10 +145,11 @@ int csp_sfp_send_own_memcpy(csp_conn_t * conn, const void * data, unsigned int t return CSP_ERR_NONE; } -int csp_sfp_recv_fp(csp_conn_t * conn, void ** return_data, int * return_datasize, uint32_t timeout, csp_packet_t * first_packet) { +int csp_sfp_recv_fp(csp_conn_t * conn, const csp_sfp_recv_t * user, uint32_t timeout, csp_packet_t * first_packet) { - *return_data = NULL; /* Allow caller to assume csp_free() can always be called when dataout is non-NULL */ - *return_datasize = 0; + if ((NULL == conn) || (NULL == user) || (NULL == user->write)) { + return CSP_ERR_INVAL; + } /* Get first packet from user, or from connection */ csp_packet_t * packet; @@ -111,7 +162,7 @@ int csp_sfp_recv_fp(csp_conn_t * conn, void ** return_data, int * return_datasiz packet = first_packet; } - uint8_t * data = NULL; + const uint32_t max_mtu = csp_sfp_conn_max_mtu(conn); uint32_t datasize = 0; uint32_t data_offset = 0; int error = CSP_ERR_TIMEDOUT; @@ -137,19 +188,44 @@ int csp_sfp_recv_fp(csp_conn_t * conn, void ** return_data, int * return_datasiz goto error; } - /* Allocate memory */ - if (data == NULL) { + /* Ensure the packet length does not exceed maximum MTU. We can't receive length > MAX MTU. + * Ensure packet length is > 0. We can't receive length <= 0 */ + if ((max_mtu < packet->length) || (0 >= packet->length)) { + csp_buffer_free(packet); + + error = CSP_ERR_SFP; + goto error; + } + + /* Check if the total size in the SFP header is zero, which is invalid for data transfer. */ + if (0 == sfp_header->totalsize) { + csp_buffer_free(packet); + + error = CSP_ERR_SFP; + goto error; + } + + /* Set total expected size. This is done only on the 1st iteration. */ + if (datasize == 0) { datasize = sfp_header->totalsize; - data = malloc(datasize); - if (data == NULL) { - //csp_print("%s: %u:%u, malloc(%" PRIu32 ") failed\n", __func__, packet->id.src, packet->id.sport, datasize); - csp_buffer_free(packet); - - error = CSP_ERR_NOMEM; - goto error; - } } + /* Mismatch in total size */ + if (datasize != sfp_header->totalsize) { + csp_buffer_free(packet); + + error = CSP_ERR_SFP; + goto error; + } + + /* Ensure the offset does not exceed the expected total size. */ + if (sfp_header->offset > (datasize - packet->length)) { + csp_buffer_free(packet); + + error = CSP_ERR_SFP; + goto error; + } + /* Consistency check */ if (((data_offset + packet->length) > datasize) || (datasize != sfp_header->totalsize)) { //csp_print("%s: %u:%u, invalid size, sfp.offset: %" PRIu32 ", length: %u, total: %" PRIu32 " / %" PRIu32 "\n", __func__, packet->id.src, packet->id.sport, sfp_header->offset, packet->length, datasize, sfp_header->totalsize); @@ -160,15 +236,18 @@ int csp_sfp_recv_fp(csp_conn_t * conn, void ** return_data, int * return_datasiz } /* Copy data to output */ - memcpy(data + data_offset, packet->data, packet->length); + error = user->write(packet->data, packet->length, data_offset, datasize, user->data); + if (CSP_ERR_NONE != error) { + csp_buffer_free(packet); + goto error; + } + data_offset += packet->length; if (data_offset >= datasize) { // transfer complete csp_buffer_free(packet); - *return_data = data; // must be freed by csp_free() - *return_datasize = datasize; return CSP_ERR_NONE; } @@ -186,6 +265,5 @@ int csp_sfp_recv_fp(csp_conn_t * conn, void ** return_data, int * return_datasiz } while ((packet = csp_read(conn, timeout)) != NULL); error: - free(data); return error; } diff --git a/src/csp_yaml.c b/src/csp_yaml.c index f77c2b6cb..2402546a6 100644 --- a/src/csp_yaml.c +++ b/src/csp_yaml.c @@ -195,7 +195,7 @@ static void csp_yaml_key_value(struct data_s * data, char * key, char * value) { } else if (strcmp(key, "promisc") == 0) { data->promisc = strdup(value); } else { - csp_print("Unkown key %s\n", key); + csp_print("Unknown key %s\n", key); } } diff --git a/src/drivers/CMakeLists.txt b/src/drivers/CMakeLists.txt index 8d3082971..109d9f55a 100644 --- a/src/drivers/CMakeLists.txt +++ b/src/drivers/CMakeLists.txt @@ -5,7 +5,7 @@ if(LIBSOCKETCAN_FOUND) target_include_directories(driver_can PRIVATE ${csp_inc} ${LIBSOCKETCAN_INCLUDE_DIRS}) - target_link_libraries(driver_can PRIVATE + target_link_libraries(driver_can PRIVATE csp_common ${LIBSOCKETCAN_LIBRARIES}) target_link_libraries(csp PRIVATE driver_can) if(BUILD_SHARED_LIBS) @@ -13,10 +13,10 @@ if(LIBSOCKETCAN_FOUND) endif() endif() -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") +if(CSP_POSIX) target_sources(csp PRIVATE usart/usart_linux.c) target_sources(csp PRIVATE eth/eth_linux.c) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Zephyr") +elseif(CSP_ZEPHYR) target_sources(csp PRIVATE usart/usart_zephyr.c) endif() diff --git a/src/drivers/can/can_socketcan.c b/src/drivers/can/can_socketcan.c index b5c456574..56b228e7a 100644 --- a/src/drivers/can/can_socketcan.c +++ b/src/drivers/can/can_socketcan.c @@ -1,6 +1,9 @@ + + #include #include +#include #include #include #include @@ -15,6 +18,7 @@ #include #include +#include // CAN interface data, state, etc. typedef struct { @@ -44,8 +48,9 @@ static void * socketcan_rx_thread(void * arg) { fd_set input; FD_ZERO(&input); FD_SET(ctx->socket, &input); - struct timeval timeout; - timeout.tv_sec = 10; + struct timeval timeout = { + .tv_sec = 10, + }; int n = select(ctx->socket + 1, &input, NULL, NULL, &timeout); if (n == -1) { csp_print("CAN read error\n"); @@ -59,9 +64,14 @@ static void * socketcan_rx_thread(void * arg) { struct can_frame frame; int nbytes = read(ctx->socket, &frame, sizeof(frame)); if (nbytes < 0) { - csp_print("%s[%s]: read() failed, errno %d: %s\n", __func__, ctx->name, errno, strerror(errno)); - sleep(1); - continue; + if (errno == EAGAIN || errno == EINTR) { + /* This is acceptable, since something interrupted us, try again */ + continue; + } else { + csp_print("%s[%s]: read() failed, errno %d: %s\n", __func__, ctx->name, errno, strerror(errno)); + usleep(1*1E6); + continue; + } } if (nbytes != sizeof(frame)) { @@ -105,42 +115,74 @@ static int csp_can_tx_frame(void * driver_data, uint32_t id, const uint8_t * dat .can_dlc = dlc}; memcpy(frame.data, data, dlc); - uint32_t elapsed_ms = 0; + uint32_t waiting_ms = 0; can_context_t * ctx = driver_data; - while (write(ctx->socket, &frame, sizeof(frame)) != sizeof(frame)) { - if ((errno != ENOBUFS) || (elapsed_ms >= 1000)) { - csp_print("%s[%s]: write() failed, errno %d: %s\n", __func__, ctx->name, errno, strerror(errno)); - return CSP_ERR_TX; + uintptr_t pdata = (uintptr_t)&frame; + uintptr_t pend = ((uintptr_t)&frame + sizeof(frame)); + size_t length = sizeof(frame); + + while (pdata < pend) { + int written; + + written = write(ctx->socket, (void *)pdata, length); + if (written < 0) { + if (errno == ENOBUFS) { + /* If no space available, wait for 5 ms and try again */ + usleep(5000); + waiting_ms += 5; + } else if(errno == EAGAIN || errno == EINTR) { + /* Acceptable, since something interrupted us, try again */ + waiting_ms += 5; + } else { + csp_print("%s[%s]: write() failed, encountered an error during write(). %d - '%s'\n", __func__, ctx->name, errno, strerror(errno)); + return CSP_ERR_TX; + } + + if (waiting_ms >= 1000) { + /* We finally got tired of waiting, give up */ + csp_print("%s[%s]: write() failed, we have been waiting for CAN buffers for too long (>1000 ms)\n", __func__, ctx->name); + return CSP_ERR_TX; + } + } else { + waiting_ms = 0; + pdata += written; + length -= written; } - usleep(5000); - elapsed_ms += 5; } return CSP_ERR_NONE; } -int csp_can_socketcan_set_promisc(const bool promisc, can_context_t * ctx) { - struct can_filter filter = { +static int csp_can_socketcan_set_promisc(const bool promisc, can_context_t * ctx) { + + struct can_filter filter[3] = { { .can_id = CFP_MAKE_DST(ctx->iface.addr), .can_mask = 0x0000, /* receive anything */ - }; + } }; if (ctx->socket == 0) { return CSP_ERR_INVAL; } + int num_filters = 1; if (!promisc) { if (csp_conf.version == 1) { - filter.can_id = CFP_MAKE_DST(ctx->iface.addr); - filter.can_mask = CFP_MAKE_DST((1 << CFP_HOST_SIZE) - 1); + num_filters = 1; + filter[0].can_id = CFP_MAKE_DST(ctx->iface.addr); + filter[0].can_mask = CFP_MAKE_DST((1 << CFP_HOST_SIZE) - 1); } else { - filter.can_id = ctx->iface.addr << CFP2_DST_OFFSET; - filter.can_mask = CFP2_DST_MASK << CFP2_DST_OFFSET; + num_filters = 3; + filter[0].can_id = ctx->iface.addr << CFP2_DST_OFFSET; + filter[0].can_mask = CFP2_DST_MASK << CFP2_DST_OFFSET; + filter[1].can_id = ((1 << (csp_id_get_host_bits() - ctx->iface.netmask)) - 1) << CFP2_DST_OFFSET; + filter[1].can_mask = CFP2_DST_MASK << CFP2_DST_OFFSET; + filter[2].can_id = 0x3FFF << CFP2_DST_OFFSET; + filter[2].can_mask = CFP2_DST_MASK << CFP2_DST_OFFSET; } } - if (setsockopt(ctx->socket, SOL_CAN_RAW, CAN_RAW_FILTER, &filter, sizeof(filter)) < 0) { + if (setsockopt(ctx->socket, SOL_CAN_RAW, CAN_RAW_FILTER, &filter, num_filters * sizeof(struct can_filter)) < 0) { csp_print("%s: setsockopt() failed, error: %s\n", __func__, strerror(errno)); return CSP_ERR_INVAL; } @@ -148,6 +190,39 @@ int csp_can_socketcan_set_promisc(const bool promisc, can_context_t * ctx) { return CSP_ERR_NONE; } +static int csp_can_socketcan_add_alias(void * driver_data, uint16_t addr) { + + if (csp_conf.version == 1) { + return -1; + } + + can_context_t * ctx = driver_data; + + struct can_filter filter[10]; + socklen_t len = sizeof(filter); + + getsockopt(ctx->socket, SOL_CAN_RAW, CAN_RAW_FILTER, &filter, &len); + + /* Current implementation has a defined maximum of filters available */ + if (len == sizeof(filter)) { + return -2; + } + + /* If only 1 filter exist for CSP v2, interface is promisc */ + if (len == sizeof(struct can_filter)) { + return 0; + } + + /* Add filter for specific additional receive address */ + filter[len/sizeof(struct can_filter)].can_id = addr << CFP2_DST_OFFSET; + filter[len/sizeof(struct can_filter)].can_mask = CFP2_DST_MASK << CFP2_DST_OFFSET;; + + if (setsockopt(ctx->socket, SOL_CAN_RAW, CAN_RAW_FILTER, &filter, len + sizeof(struct can_filter)) < 0) { + return -2; + } + + return 0; +} int csp_can_socketcan_open_and_add_interface(const char * device, const char * ifname, unsigned int node_id, int bitrate, bool promisc, csp_iface_t ** return_iface) { if (ifname == NULL) { @@ -176,6 +251,7 @@ int csp_can_socketcan_open_and_add_interface(const char * device, const char * i ctx->iface.interface_data = &ctx->ifdata; ctx->iface.driver_data = ctx; ctx->ifdata.tx_func = csp_can_tx_frame; + ctx->iface.add_alias = csp_can_socketcan_add_alias; ctx->ifdata.pbufs = NULL; /* Create socket */ @@ -210,6 +286,7 @@ int csp_can_socketcan_open_and_add_interface(const char * device, const char * i /* Set filter mode */ if (csp_can_socketcan_set_promisc(promisc, ctx) != CSP_ERR_NONE) { csp_print("%s[%s]: csp_can_socketcan_set_promisc() failed, error: %s\n", __func__, ctx->name, strerror(errno)); + socketcan_free(ctx); return CSP_ERR_INVAL; } @@ -224,7 +301,8 @@ int csp_can_socketcan_open_and_add_interface(const char * device, const char * i /* Create receive thread */ if (pthread_create(&ctx->rx_thread, NULL, socketcan_rx_thread, ctx) != 0) { csp_print("%s[%s]: pthread_create() failed, error: %s\n", __func__, ctx->name, strerror(errno)); - // socketcan_free(ctx); // we already added it to CSP (no way to remove it) + (void)csp_can_remove_interface(&ctx->iface); + socketcan_free(ctx); return CSP_ERR_NOMEM; } diff --git a/src/drivers/can/can_zephyr.c b/src/drivers/can/can_zephyr.c index 7730f6ad1..db5de1605 100644 --- a/src/drivers/can/can_zephyr.c +++ b/src/drivers/can/can_zephyr.c @@ -60,6 +60,12 @@ static void csp_can_rx_thread(void * arg1, void * arg2, void * arg3) { break; } + /* Drop frames with invalid size field */ + if(frame.dlc > CAN_MAX_DLEN){ + LOG_WRN("[%s] discarding invalid size frame", iface->name); + continue; + } + /* CSP requires extended frame format, drop it. */ if (!(frame.flags & CAN_FRAME_IDE)) { LOG_WRN("[%s] discarding Standard ID frame", iface->name); @@ -83,7 +89,7 @@ static int csp_can_tx_frame(void * driver_data, uint32_t id, const uint8_t * dat struct can_frame frame = {0}; can_context_t * ctx = driver_data; - if (dlc > CAN_MAX_DLC) { + if (dlc > CAN_MAX_DLEN) { ret = CSP_ERR_INVAL; goto end; } @@ -138,13 +144,15 @@ int csp_can_open_and_add_interface(const struct device * device, const char * if int ret; k_tid_t rx_tid; can_context_t * ctx = NULL; - const char * name = ifname ? ifname : device->name; + const char * name; if (device == NULL) { ret = CSP_ERR_INVAL; goto end; } + name = ifname ? ifname : device->name; + if (rx_thread_idx >= CONFIG_CSP_CAN_RX_THREAD_NUM) { LOG_ERR("[%s] No more RX thread can be created. (MAX: %d) Please check CONFIG_CSP_CAN_RX_THREAD_NUM.", name, CONFIG_CSP_CAN_RX_THREAD_NUM); @@ -240,7 +248,7 @@ int csp_can_open_and_add_interface(const struct device * device, const char * if * The following section is for restoring acquired resources when * something fails. Unfortunately, we can't take any action if the * restoration process fails, so we proceed with the remaining - * cleanup. In addtion to this, we've chosen not to restore the + * cleanup. In addition to this, we've chosen not to restore the * CAN bit rate. If this causes any issues, please open an issue * on GitHub. */ diff --git a/src/drivers/eth/eth_linux.c b/src/drivers/eth/eth_linux.c index 590526795..449d116cd 100644 --- a/src/drivers/eth/eth_linux.c +++ b/src/drivers/eth/eth_linux.c @@ -1,4 +1,12 @@ + +#ifdef __CYGWIN__ +#warning CYGWIN: ethernet not implemented - libpcap can be used if needed +#else // !__CYGWIN__ + +#include + #include +#include #include #include @@ -27,12 +35,12 @@ typedef struct { struct ifreq if_idx; } eth_context_t; -int csp_eth_tx_frame(void * driver_data, csp_eth_header_t *eth_frame) { +int csp_eth_tx_frame(void * driver_data, csp_eth_header_t * eth_frame) { const eth_context_t * ctx = (eth_context_t*)driver_data; /* Destination socket address */ - struct sockaddr_ll socket_address = {}; + struct sockaddr_ll socket_address = {0}; socket_address.sll_ifindex = ctx->if_idx.ifr_ifindex; socket_address.sll_halen = CSP_ETH_ALEN; memcpy(socket_address.sll_addr, eth_frame->ether_dhost, CSP_ETH_ALEN); @@ -73,8 +81,8 @@ int csp_eth_init(const char * device, const char * ifname, int mtu, unsigned int if (ctx == NULL) { return CSP_ERR_NOMEM; } - - strcpy(ctx->name, ifname); + + strncpy(ctx->name, ifname, sizeof(ctx->name) - 1); ctx->ifdata.iface.name = ctx->name; ctx->ifdata.tx_func = &csp_eth_tx_frame; ctx->ifdata.tx_buf = (csp_eth_header_t*)&csp_eth_tx_buffer; @@ -87,10 +95,10 @@ int csp_eth_init(const char * device, const char * ifname, int mtu, unsigned int /* Ether header 14 byte, seg header 4 byte, CSP header 6 byte */ if (mtu < 24) { csp_print("csp_if_eth_init: mtu < 24\n"); + free(ctx); return CSP_ERR_INVAL; } - /** * TX SOCKET */ @@ -98,7 +106,12 @@ int csp_eth_init(const char * device, const char * ifname, int mtu, unsigned int /* Open RAW socket to send on */ if ((ctx->sockfd = socket(AF_PACKET, SOCK_RAW, htobe16(CSP_ETH_TYPE_CSP))) == -1) { perror("socket"); - csp_print("Use command 'setcap cap_net_raw+ep ./csh'\n"); + char exe[1024]; + int count = readlink("/proc/self/exe", exe, sizeof(exe)); + if (count > 0) { + csp_print("Use command 'sudo setcap cap_net_raw+ep %s'\n", exe); + } + free(ctx); return CSP_ERR_INVAL; } @@ -107,6 +120,7 @@ int csp_eth_init(const char * device, const char * ifname, int mtu, unsigned int strncpy(ctx->if_idx.ifr_name, device, IFNAMSIZ-1); if (ioctl(ctx->sockfd, SIOCGIFINDEX, &ctx->if_idx) < 0) { perror("SIOCGIFINDEX"); + free(ctx); return CSP_ERR_INVAL; } @@ -116,6 +130,7 @@ int csp_eth_init(const char * device, const char * ifname, int mtu, unsigned int strncpy(if_mac.ifr_name, device, IFNAMSIZ-1); if (ioctl(ctx->sockfd, SIOCGIFHWADDR, &if_mac) < 0) { perror("SIOCGIFHWADDR"); + free(ctx); return CSP_ERR_INVAL; } @@ -130,11 +145,12 @@ int csp_eth_init(const char * device, const char * ifname, int mtu, unsigned int ((uint8_t *)if_mac.ifr_hwaddr.sa_data)[4], ((uint8_t *)if_mac.ifr_hwaddr.sa_data)[5]); - /* Allow the socket to be reused - incase connection is closed prematurely */ + /* Allow the socket to be reused - in case connection is closed prematurely */ int sockopt; if (setsockopt(ctx->sockfd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof sockopt) == -1) { perror("setsockopt"); close(ctx->sockfd); + free(ctx); return CSP_ERR_INVAL; } @@ -142,6 +158,7 @@ int csp_eth_init(const char * device, const char * ifname, int mtu, unsigned int if (setsockopt(ctx->sockfd, SOL_SOCKET, SO_BINDTODEVICE, device, IFNAMSIZ-1) == -1) { perror("SO_BINDTODEVICE"); close(ctx->sockfd); + free(ctx); return CSP_ERR_INVAL; } @@ -171,4 +188,7 @@ int csp_eth_init(const char * device, const char * ifname, int mtu, unsigned int *return_iface = &ctx->ifdata.iface; } - return CSP_ERR_NONE;} + return CSP_ERR_NONE; +} + +#endif // !__CYGWIN__ diff --git a/src/drivers/meson.build b/src/drivers/meson.build index a21d56cf7..72ad370cb 100644 --- a/src/drivers/meson.build +++ b/src/drivers/meson.build @@ -7,6 +7,8 @@ else conf.set('CSP_HAVE_LIBSOCKETCAN', 0) endif -csp_sources += files(['eth/eth_linux.c']) -csp_sources += files(['usart/usart_linux.c']) -csp_sources += files(['usart/usart_kiss.c']) +if host_machine.system() == 'linux' + csp_sources += files(['eth/eth_linux.c']) + csp_sources += files(['usart/usart_linux.c']) + csp_sources += files(['usart/usart_kiss.c']) +endif diff --git a/src/drivers/usart/usart_kiss.c b/src/drivers/usart/usart_kiss.c index bd9238862..e67ba07ef 100644 --- a/src/drivers/usart/usart_kiss.c +++ b/src/drivers/usart/usart_kiss.c @@ -4,6 +4,7 @@ #include #include +#include #include #include diff --git a/src/drivers/usart/usart_linux.c b/src/drivers/usart/usart_linux.c index 48aa3d2da..5587c5cfb 100644 --- a/src/drivers/usart/usart_linux.c +++ b/src/drivers/usart/usart_linux.c @@ -24,10 +24,12 @@ typedef struct { static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void csp_usart_lock(void * driver_data) { + (void)driver_data; /* Avoid compiler warnings about unused parameter */ pthread_mutex_lock(&lock); } void csp_usart_unlock(void * driver_data) { + (void)driver_data; /* Avoid compiler warnings about unused parameter */ pthread_mutex_unlock(&lock); } @@ -123,12 +125,14 @@ int csp_usart_open(const csp_usart_conf_t * conf, csp_usart_callback_t rx_callba case 3000000: brate = B3000000; break; +#ifndef __CYGWIN__ case 3500000: brate = B3500000; break; case 4000000: brate = B4000000; break; +#endif default: csp_print("%s: Unsupported baudrate: %u\n", __func__, conf->baudrate); return CSP_ERR_INVAL; diff --git a/src/drivers/usart/usart_zephyr.c b/src/drivers/usart/usart_zephyr.c index 12e108815..f4df75445 100644 --- a/src/drivers/usart/usart_zephyr.c +++ b/src/drivers/usart/usart_zephyr.c @@ -99,7 +99,7 @@ int csp_usart_open(const csp_usart_conf_t * conf, csp_usart_callback_t rx_callba } if (uart_rx_thread_idx >= CONFIG_CSP_UART_RX_THREAD_NUM) { - LOG_ERR("%s: [%s] No more RX thread can be created. (MAX: %d) Please check CONFIG_CSP_CAN_RX_THREAD_NUM.", __func__, conf->device, CONFIG_CSP_UART_RX_THREAD_NUM); + LOG_ERR("%s: [%s] No more RX thread can be created. (MAX: %d) Please check CONFIG_CSP_UART_RX_THREAD_NUM.", __func__, conf->device, CONFIG_CSP_UART_RX_THREAD_NUM); return CSP_ERR_DRIVER; } @@ -110,7 +110,7 @@ int csp_usart_open(const csp_usart_conf_t * conf, csp_usart_callback_t rx_callba return CSP_ERR_NOMEM; } - strcpy(ctx->name, conf->device); + strncpy(ctx->name, conf->device, sizeof(ctx->name) - 1); ctx->rx_callback = rx_callback; ctx->user_data = user_data; ctx->fd = device_get_binding(conf->device); diff --git a/src/interfaces/CMakeLists.txt b/src/interfaces/CMakeLists.txt index 50ca4c8d7..48b61b8d8 100644 --- a/src/interfaces/CMakeLists.txt +++ b/src/interfaces/CMakeLists.txt @@ -14,7 +14,7 @@ if(LIBZMQ_FOUND) target_include_directories(if_zmq PRIVATE ${csp_inc} ${LIBZMQ_INCLUDE_DIRS}) - target_link_libraries(if_zmq PRIVATE ${LIBZMQ_LIBRARIES}) + target_link_libraries(if_zmq PRIVATE csp_common ${LIBZMQ_LIBRARIES}) target_link_libraries(csp PRIVATE if_zmq) if(BUILD_SHARED_LIBS) set_property(TARGET if_zmq PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/src/interfaces/csp_if_can.c b/src/interfaces/csp_if_can.c index 3619f8580..a4a83e9ee 100644 --- a/src/interfaces/csp_if_can.c +++ b/src/interfaces/csp_if_can.c @@ -1,5 +1,3 @@ - - #include #include @@ -43,7 +41,7 @@ enum cfp_frame_t { CFP_MORE = 1 }; -int csp_can1_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t dlc, int * task_woken) { +static int csp_can1_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t dlc, int * task_woken) { /* Test: random packet loss */ // if (0) { @@ -60,10 +58,6 @@ int csp_can1_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t if (packet == NULL) { if (CFP_TYPE(id) == CFP_BEGIN) { packet = csp_can_pbuf_new(ifdata, id, task_woken); - if (packet == NULL) { - iface->rx_error++; - return CSP_ERR_NOMEM; - } } else { iface->frame++; return CSP_ERR_INVAL; @@ -85,16 +79,12 @@ int csp_can1_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t break; } - iface->frame++; - csp_id_setup_rx(packet); /* Copy CSP identifier (header) */ memcpy(packet->frame_begin, data, sizeof(uint32_t)); packet->frame_length += sizeof(uint32_t); - csp_id_strip(packet); - /* Copy CSP length (of data) */ memcpy(&(packet->length), data + sizeof(uint32_t), sizeof(packet->length)); packet->length = be16toh(packet->length); @@ -146,6 +136,11 @@ int csp_can1_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t if (packet->rx_count != packet->length) break; + /* Length information is packed differently for CAN */ + uint16_t length = packet->length; + csp_id_strip(packet); + packet->length = length; + /* Rewrite incoming L2 broadcast to local node */ if (packet->id.dst == 0x1F) { packet->id.dst = iface->addr; @@ -154,9 +149,6 @@ int csp_can1_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t /* Free packet buffer */ csp_can_pbuf_free(ifdata, packet, 0, task_woken); - /* Clear timestamp_rx for L3 as L2 last_used is not needed anymore */ - packet->timestamp_rx = 0; - /* Data is available */ csp_qfifo_write(packet, iface, task_woken); @@ -171,7 +163,8 @@ int csp_can1_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t return CSP_ERR_NONE; } -int csp_can1_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { +static int csp_can1_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { + (void)from_me; /* Avoid compiler warnings about unused parameter */ /* Loopback */ if (packet->id.dst == iface->addr) { @@ -267,7 +260,7 @@ int csp_can1_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int fr return CSP_ERR_NONE; } -int csp_can2_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t dlc, int * task_woken) { +static int csp_can2_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t dlc, int * task_woken) { csp_can_interface_data_t * ifdata = iface->interface_data; @@ -276,10 +269,6 @@ int csp_can2_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t if (packet == NULL) { if (id & (CFP2_BEGIN_MASK << CFP2_BEGIN_OFFSET)) { packet = csp_can_pbuf_new(ifdata, id, task_woken); - if (packet == NULL) { - iface->rx_error++; - return CSP_ERR_NOMEM; - } } else { iface->frame++; return CSP_ERR_INVAL; @@ -298,7 +287,6 @@ int csp_can2_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t return CSP_ERR_INVAL; } - iface->frame++; csp_id_setup_rx(packet); /* Copy first 2 bytes from CFP 2.0 header: @@ -343,9 +331,9 @@ int csp_can2_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t } /* Check for overflow. The frame input + dlc must not exceed the end of the packet data field */ - if (&packet->frame_begin[packet->frame_length] + dlc >= &packet->data[sizeof(packet->data)]) { + if (&packet->frame_begin[packet->frame_length] + dlc > &packet->data[sizeof(packet->data)]) { csp_dbg_can_errno = CSP_DBG_CAN_ERR_RX_OVF; - iface->frame++; + iface->rx_error++; csp_can_pbuf_free(ifdata, packet, 1, task_woken); return CSP_ERR_INVAL; } @@ -376,10 +364,13 @@ int csp_can2_rx(csp_iface_t * iface, uint32_t id, const uint8_t * data, uint8_t return CSP_ERR_NONE; } -int csp_can2_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { +static int csp_can2_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { + /* Avoid compiler warnings about unused parameter */ + (void)via; + (void)from_me; /* Loopback */ - if (packet->id.dst == iface->addr) { + if (packet->id.dst == iface->addr || csp_addr_is_alias(packet->id.dst)) { csp_qfifo_write(packet, iface, NULL); return CSP_ERR_NONE; } @@ -489,7 +480,9 @@ int csp_can_add_interface(csp_iface_t * iface) { iface->nexthop = csp_can2_tx; } - return csp_iflist_add(iface); + csp_iflist_add(iface); + + return CSP_ERR_NONE; } int csp_can_remove_interface(csp_iface_t * iface) { diff --git a/src/interfaces/csp_if_can_pbuf.c b/src/interfaces/csp_if_can_pbuf.c index 454e3c80f..faca94e1e 100644 --- a/src/interfaces/csp_if_can_pbuf.c +++ b/src/interfaces/csp_if_can_pbuf.c @@ -7,6 +7,8 @@ #include #include +#include "../csp_buffer_private.h" + /* Buffer element timeout in ms */ #define PBUF_TIMEOUT_MS 1000 @@ -49,10 +51,7 @@ csp_packet_t * csp_can_pbuf_new(csp_can_interface_data_t * ifdata, uint32_t id, uint32_t now = (task_woken) ? csp_get_ms_isr() : csp_get_ms(); - csp_packet_t * packet = (task_woken) ? csp_buffer_get_isr(0) : csp_buffer_get(0); - if (packet == NULL) { - return NULL; - } + csp_packet_t * packet = (task_woken) ? csp_buffer_get_always_isr() : csp_buffer_get_always(); packet->last_used = now; packet->cfpid = id; diff --git a/src/interfaces/csp_if_eth.c b/src/interfaces/csp_if_eth.c index adbaeec00..0d81d6246 100644 --- a/src/interfaces/csp_if_eth.c +++ b/src/interfaces/csp_if_eth.c @@ -34,9 +34,9 @@ bool csp_eth_pack_header(csp_eth_header_t * buf, return true; } -bool csp_if_eth_unpack_header(csp_eth_header_t * buf, - uint32_t * packet_id, - uint16_t * seg_size, uint16_t * packet_length) { +static bool csp_if_eth_unpack_header(csp_eth_header_t * buf, + uint32_t * packet_id, + uint16_t * seg_size, uint16_t * packet_length) { if (packet_id == NULL) return false; if (seg_size == NULL) return false; @@ -68,16 +68,18 @@ static size_t arp_used = 0; static arp_list_entry_t * arp_list = 0; -arp_list_entry_t * arp_alloc(void) { - +static arp_list_entry_t * arp_alloc(void) { + if (arp_used >= ARP_MAX_ENTRIES) { return 0; - } + } return &(arp_array[arp_used++]); } -void arp_print() +// FIXME: Function unused. Remove it? +#if 0 +static void arp_print(void) { csp_print("ARP CSP MAC\n"); for (arp_list_entry_t * arp = arp_list; arp; arp = arp->next) { @@ -88,6 +90,7 @@ void arp_print() } csp_print("\n"); } +#endif void csp_eth_arp_set_addr(uint8_t * mac_addr, uint16_t csp_addr) { @@ -132,16 +135,17 @@ void csp_eth_arp_get_addr(uint8_t * mac_addr, uint16_t csp_addr) int csp_eth_rx(csp_iface_t * iface, csp_eth_header_t * eth_frame, uint32_t received_len, int * task_woken) { csp_eth_interface_data_t * ifdata = iface->interface_data; - csp_packet_t * pbuf_list = ifdata->pbufs; if (eth_debug) csp_hex_dump("rx", (void*)eth_frame, received_len); /* Filter on CSP protocol id */ - if ((be16toh(eth_frame->ether_type) != CSP_ETH_TYPE_CSP)) { + if (be16toh(eth_frame->ether_type) != CSP_ETH_TYPE_CSP) { + iface->frame++; return CSP_ERR_INVAL; } if (received_len < sizeof(csp_eth_header_t)) { + iface->frame++; return CSP_ERR_INVAL; } @@ -151,42 +155,56 @@ int csp_eth_rx(csp_iface_t * iface, csp_eth_header_t * eth_frame, uint32_t recei uint16_t frame_length = 0; csp_if_eth_unpack_header(eth_frame, &packet_id, &seg_size, &frame_length); - if (seg_size == 0) { - csp_print("eth rx seg_size is zero\n"); + if (seg_size == 0 || seg_size > CSP_ETH_FRAME_SIZE_MAX) { + iface->frame++; + csp_print("eth rx seg_size of %u bytes is invalid\n"); return CSP_ERR_INVAL; } if (seg_size > frame_length) { + iface->frame++; csp_print("eth rx seg_size(%u) > frame_length(%u)\n", (unsigned)seg_size, (unsigned)frame_length); return CSP_ERR_INVAL; } - if (sizeof(csp_eth_header_t) + seg_size > received_len) { + if ((sizeof(csp_eth_header_t) + seg_size) > received_len) { + iface->frame++; csp_print("eth rx sizeof(csp_eth_frame_t) + seg_size(%u) > received(%u)\n", (unsigned)seg_size, (unsigned)received_len); return CSP_ERR_INVAL; } - if (frame_length == 0) { - csp_print("eth rx frame_length is zero\n"); + if (frame_length == 0 || frame_length > (CSP_BUFFER_SIZE + csp_id_get_header_size())) { + iface->frame++; + csp_print("eth rx frame_length of %u is invalid\n", frame_length); return CSP_ERR_INVAL; } - /* Add packet segment */ - csp_packet_t * packet = csp_if_eth_pbuf_get(&pbuf_list, packet_id, task_woken); + csp_packet_t * packet = csp_eth_pbuf_find(ifdata, packet_id, task_woken); + + if (packet == NULL) { + iface->drop++; + csp_print("eth rx cannot get csp packet\n"); + return CSP_ERR_INVAL; + } if (packet->frame_length == 0) { /* First segment */ + csp_id_setup_rx(packet); packet->frame_length = frame_length; packet->rx_count = 0; } if (frame_length != packet->frame_length) { + csp_eth_pbuf_free(ifdata, packet, true, task_woken); + iface->frame++; csp_print("eth rx inconsistent frame_length\n"); return CSP_ERR_INVAL; } - if (packet->rx_count + seg_size > packet->frame_length) { + if ((packet->rx_count + seg_size) > packet->frame_length) { + csp_eth_pbuf_free(ifdata, packet, true, task_woken); + iface->frame++; csp_print("eth rx data received exceeds frame_length\n"); return CSP_ERR_INVAL; } @@ -195,16 +213,15 @@ int csp_eth_rx(csp_iface_t * iface, csp_eth_header_t * eth_frame, uint32_t recei packet->rx_count += seg_size; /* Send packet when fully received */ - if (packet->rx_count < packet->frame_length) { return CSP_ERR_NONE; } - csp_if_eth_pbuf_remove(&pbuf_list, packet); + csp_eth_pbuf_free(ifdata, packet, false, task_woken); if (csp_id_strip(packet) != 0) { csp_print("eth rx packet discarded due to error in ID field\n"); - iface->rx_error++; + iface->frame++; (task_woken) ? csp_buffer_free_isr(packet) : csp_buffer_free(packet); return CSP_ERR_INVAL; } @@ -213,23 +230,22 @@ int csp_eth_rx(csp_iface_t * iface, csp_eth_header_t * eth_frame, uint32_t recei csp_eth_arp_set_addr(eth_frame->ether_shost, packet->id.src); if (packet->id.dst != iface->addr && !ifdata->promisc) { + csp_eth_pbuf_free(ifdata, packet, true, task_woken); (task_woken) ? csp_buffer_free_isr(packet) : csp_buffer_free(packet); return CSP_ERR_NONE; } csp_qfifo_write(packet, iface, task_woken); - if (eth_debug) csp_if_eth_pbuf_list_print(&pbuf_list); - - /* Remove potentially stalled partial packets */ - csp_if_eth_pbuf_list_cleanup(&pbuf_list); - return CSP_ERR_NONE; } int csp_eth_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { + /* Avoid compiler warnings about unused parameter */ + (void)via; + (void)from_me; - csp_eth_interface_data_t * ifdata = iface->interface_data; + csp_eth_interface_data_t * ifdata = iface->interface_data; /* Loopback */ if (packet->id.dst == iface->addr) { @@ -237,27 +253,26 @@ int csp_eth_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int fro return CSP_ERR_NONE; } - static uint16_t packet_id = 0; - csp_eth_header_t *eth_frame = ifdata->tx_buf; - - csp_eth_arp_get_addr(eth_frame->ether_dhost, packet->id.dst); - - eth_frame->ether_type = htobe16(CSP_ETH_TYPE_CSP); - memcpy(eth_frame->ether_shost, ifdata->if_mac, CSP_ETH_ALEN); - csp_id_prepend(packet); + static uint16_t packet_id = 0; packet_id++; - uint16_t offset = 0; - const uint16_t seg_size_max = ifdata->tx_mtu - sizeof(csp_eth_header_t); while (offset < packet->frame_length) { + + csp_eth_header_t *eth_frame = ifdata->tx_buf; + + const uint16_t seg_size_max = ifdata->tx_mtu - sizeof(csp_eth_header_t); uint16_t seg_size = packet->frame_length - offset; if (seg_size > seg_size_max) { seg_size = seg_size_max; } + eth_frame->ether_type = htobe16(CSP_ETH_TYPE_CSP); + csp_eth_arp_get_addr(eth_frame->ether_dhost, packet->id.dst); + memcpy(eth_frame->ether_shost, ifdata->if_mac, CSP_ETH_ALEN); + csp_eth_pack_header(eth_frame, packet_id, packet->id.src, seg_size, packet->frame_length); memcpy(eth_frame->frame_begin, packet->frame_begin + offset, seg_size); diff --git a/src/interfaces/csp_if_eth_pbuf.c b/src/interfaces/csp_if_eth_pbuf.c index cdf54ca5c..3d7473645 100644 --- a/src/interfaces/csp_if_eth_pbuf.c +++ b/src/interfaces/csp_if_eth_pbuf.c @@ -6,119 +6,106 @@ #include #include -/** Packet buffer list operations */ +/* Buffer element timeout in ms */ +#define PBUF_TIMEOUT_MS 1000 -csp_packet_t * csp_if_eth_pbuf_find(csp_packet_t ** plist, uint32_t pbuf_id) { +void csp_eth_pbuf_free(csp_eth_interface_data_t * ifdata, csp_packet_t * buffer, int buf_free, int * task_woken) { - csp_packet_t * packet = *plist; - while(packet) { - if (packet->cfpid == pbuf_id) { - return packet; - } - packet = packet->next; - } - return packet; + csp_packet_t * packet = ifdata->pbufs; + csp_packet_t * prev = NULL; -} + while (packet) { -void csp_if_eth_pbuf_insert(csp_packet_t ** plist, csp_packet_t * packet) { + /* Perform cleanup in used pbufs */ + if (packet == buffer) { - if (*plist) { - packet->next = *plist; - } - *plist = packet; + /* Erase from list prev->next = next */ + if (prev) { + prev->next = packet->next; + } else { + ifdata->pbufs = packet->next; + } -} + if (buf_free) { + if (task_woken == NULL) { + csp_buffer_free(packet); + } else { + csp_buffer_free_isr(packet); + } + } + } -csp_packet_t * csp_if_eth_pbuf_get(csp_packet_t ** plist, uint32_t pbuf_id, int * task_woken) { + prev = packet; + packet = packet->next; + } - csp_packet_t * packet = csp_if_eth_pbuf_find(plist, pbuf_id); +} - if (packet) { - packet->last_used = csp_get_ms(); - return packet; - } +csp_packet_t * csp_eth_pbuf_new(csp_eth_interface_data_t * ifdata, uint32_t id, uint32_t now, int * task_woken) { - if (!packet) { - packet = (task_woken) ? csp_buffer_get_isr(0) : csp_buffer_get(0); - } + csp_eth_pbuf_cleanup(ifdata, now, task_woken); - if (!packet) { - /* No free packet */ - return NULL; - } + csp_packet_t * packet = (task_woken) ? csp_buffer_get_isr(0) : csp_buffer_get(0); + if (packet == NULL) { + return NULL; + } - csp_id_setup_rx(packet); + packet->last_used = now; + packet->cfpid = id; + packet->frame_length = 0; - /* Existing cfpid and rx_count fields are used */ - packet->cfpid = pbuf_id; - packet->rx_count = 0; - packet->last_used = (task_woken) ? csp_get_ms_isr() : csp_get_ms(); + /* Insert at beginning, because easy */ + packet->next = ifdata->pbufs; + ifdata->pbufs = packet; - packet->next = 0; - csp_if_eth_pbuf_insert(plist, packet); + return packet; +} - return packet; +void csp_eth_pbuf_cleanup(csp_eth_interface_data_t * ifdata, uint32_t now, int * task_woken) { -} + csp_packet_t * packet = ifdata->pbufs; + csp_packet_t * prev = NULL; -void csp_if_eth_pbuf_remove(csp_packet_t ** plist, csp_packet_t * packet) { + while (packet) { - csp_packet_t * prev = 0; - csp_packet_t * p = *plist; - while(p && (p != packet)) { - prev = p; - p = p->next; - } + /* Perform cleanup in used pbufs */ + if ((now - packet->last_used) > PBUF_TIMEOUT_MS) { - if (p) { - if (prev) { - prev->next = p->next; - } else { - *plist = p->next; - } - } + /* Erase from list prev->next = next */ + if (prev) { + prev->next = packet->next; + } else { + ifdata->pbufs = packet->next; + } -} + if (task_woken == NULL) { + csp_buffer_free(packet); + } else { + csp_buffer_free_isr(packet); + } -void csp_if_eth_pbuf_list_cleanup(csp_packet_t ** plist) { + } - /* Free stalled packets, like for which a segment has been lost */ - uint32_t now = csp_get_ms(); - csp_packet_t * packet = *plist; - while(packet) { - if (now > packet->last_used + CSP_IF_ETH_PBUF_TIMEOUT_MS) { - csp_if_eth_pbuf_print("timeout ", packet); - csp_if_eth_pbuf_remove(plist, packet); - csp_buffer_free(packet); - } - packet = packet->next; - } + prev = packet; + packet = packet->next; + } } -void csp_if_eth_pbuf_print(const char * descr, csp_packet_t * packet) { +csp_packet_t * csp_eth_pbuf_find(csp_eth_interface_data_t * ifdata, uint32_t id, int * task_woken) { - if (packet) { - csp_print("%s %p id:%u Age:%lu,%lu,%lu flen:%u\n", - descr, (void *)packet, - (unsigned)packet->cfpid, - (unsigned long)csp_get_ms(), - (unsigned long)packet->last_used, - (unsigned long)(csp_get_ms() - packet->last_used), - (unsigned)packet->frame_length); - } else { - csp_print("Packet is null\n"); - } + uint32_t now = (task_woken) ? csp_get_ms_isr() : csp_get_ms(); -} + csp_packet_t * packet = ifdata->pbufs; + while (packet) { -void csp_if_eth_pbuf_list_print(csp_packet_t ** plist) { + if (packet->cfpid == id) { + packet->last_used = now; + return packet; + } + packet = packet->next; + } - csp_packet_t * packet = *plist; - while(packet) { - csp_if_eth_pbuf_print("list ", packet); - packet = packet->next; - } + return csp_eth_pbuf_new(ifdata, id, now, task_woken); } diff --git a/src/interfaces/csp_if_i2c.c b/src/interfaces/csp_if_i2c.c index bb66e01bf..68e740e58 100644 --- a/src/interfaces/csp_if_i2c.c +++ b/src/interfaces/csp_if_i2c.c @@ -5,6 +5,7 @@ #include int csp_i2c_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { + (void)from_me; /* Avoid compiler warnings about unused parameter */ /* Loopback */ if (packet->id.dst == iface->addr) { @@ -66,5 +67,7 @@ int csp_i2c_add_interface(csp_iface_t * iface) { iface->nexthop = csp_i2c_tx; - return csp_iflist_add(iface); + csp_iflist_add(iface); + + return CSP_ERR_NONE; } diff --git a/src/interfaces/csp_if_kiss.c b/src/interfaces/csp_if_kiss.c index b1b1e35f9..5b9dc33b7 100644 --- a/src/interfaces/csp_if_kiss.c +++ b/src/interfaces/csp_if_kiss.c @@ -14,6 +14,8 @@ #include #include +#include "../csp_buffer_private.h" + #define FEND 0xC0 #define FESC 0xDB #define TFEND 0xDC @@ -21,6 +23,9 @@ #define TNC_DATA 0x00 int csp_kiss_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { + /* Avoid compiler warnings about unused parameter */ + (void)via; + (void)from_me; csp_kiss_interface_data_t * ifdata = iface->interface_data; void * driver = iface->driver_data; @@ -28,8 +33,10 @@ int csp_kiss_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int fr /* Lock (before modifying packet) */ csp_usart_lock(driver); +#if CSP_ENABLE_KISS_CRC /* Add CRC32 checksum */ csp_crc32_append(packet); +#endif /* Save the outgoing id in the buffer */ csp_id_prepend(packet); @@ -93,9 +100,9 @@ void csp_kiss_rx(csp_iface_t * iface, const uint8_t * buf, size_t len, void * px break; } - /* Try to allocate new buffer */ + /* Always allocate new buffer */ if (ifdata->rx_packet == NULL) { - ifdata->rx_packet = pxTaskWoken ? csp_buffer_get_isr(0) : csp_buffer_get(0); // CSP only supports one size + ifdata->rx_packet = pxTaskWoken ? csp_buffer_get_always_isr() : csp_buffer_get_always(); } /* If no more memory, skip frame */ @@ -120,6 +127,13 @@ void csp_kiss_rx(csp_iface_t * iface, const uint8_t * buf, size_t len, void * px break; } + /* Should not append in this mode, but guard against possible NULL dereference */ + if (ifdata->rx_packet == NULL) { + iface->rx_error++; + ifdata->rx_mode = KISS_MODE_NOT_STARTED; + break; + } + /* End Char */ if (inputbyte == FEND) { @@ -128,20 +142,19 @@ void csp_kiss_rx(csp_iface_t * iface, const uint8_t * buf, size_t len, void * px ifdata->rx_packet->frame_length = ifdata->rx_length; if (csp_id_strip(ifdata->rx_packet) < 0) { - iface->rx_error++; + iface->frame++; ifdata->rx_mode = KISS_MODE_NOT_STARTED; break; } - /* Count received frame */ - iface->frame++; - +#if CSP_ENABLE_KISS_CRC /* Validate CRC */ if (csp_crc32_verify(ifdata->rx_packet) != CSP_ERR_NONE) { - iface->rx_error++; + iface->frame++; ifdata->rx_mode = KISS_MODE_NOT_STARTED; break; } +#endif /* Send back into CSP, notice calling from task so last argument must be NULL! */ csp_qfifo_write(ifdata->rx_packet, iface, pxTaskWoken); @@ -167,6 +180,13 @@ void csp_kiss_rx(csp_iface_t * iface, const uint8_t * buf, size_t len, void * px case KISS_MODE_ESCAPED: + /* Should not append in this mode, but guard against possible NULL dereference */ + if (ifdata->rx_packet == NULL) { + iface->rx_error++; + ifdata->rx_mode = KISS_MODE_NOT_STARTED; + break; + } + /* Escaped escape char */ if (inputbyte == TFESC) ifdata->rx_packet->frame_begin[ifdata->rx_length++] = FESC; @@ -208,5 +228,7 @@ int csp_kiss_add_interface(csp_iface_t * iface) { iface->nexthop = csp_kiss_tx; - return csp_iflist_add(iface); + csp_iflist_add(iface); + + return CSP_ERR_NONE; } diff --git a/src/interfaces/csp_if_lo.c b/src/interfaces/csp_if_lo.c index 7db823ce7..f4f17cfba 100644 --- a/src/interfaces/csp_if_lo.c +++ b/src/interfaces/csp_if_lo.c @@ -9,6 +9,10 @@ * @return 1 if packet was successfully transmitted, 0 on error */ static int csp_lo_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { + /* Avoid compiler warnings about unused parameter */ + (void)iface; + (void)via; + (void)from_me; /* Send back into CSP, notice calling from task so last argument must be NULL! */ csp_qfifo_write(packet, &csp_if_lo, NULL); @@ -20,4 +24,6 @@ static int csp_lo_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, i csp_iface_t csp_if_lo = { .name = CSP_IF_LOOPBACK_NAME, .nexthop = csp_lo_tx, + .addr = 0, + .is_default = 0 }; diff --git a/src/interfaces/csp_if_tun.c b/src/interfaces/csp_if_tun.c index 321aded13..c1660bed3 100644 --- a/src/interfaces/csp_if_tun.c +++ b/src/interfaces/csp_if_tun.c @@ -5,14 +5,27 @@ #include "csp_macro.h" __weak int csp_crypto_decrypt(uint8_t * ciphertext_in, uint8_t ciphertext_len, uint8_t * msg_out) { + /* Avoid compiler warnings about unused parameter */ + (void)ciphertext_in; + (void)ciphertext_len; + (void)msg_out; + return -1; } __weak int csp_crypto_encrypt(uint8_t * msg_begin, uint8_t msg_len, uint8_t * ciphertext_out) { + /* Avoid compiler warnings about unused parameter */ + (void)msg_begin; + (void)msg_len; + (void)ciphertext_out; + return -1; } static int csp_if_tun_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { + /* Avoid compiler warnings about unused parameter */ + (void)via; + (void)from_me; csp_if_tun_conf_t * ifconf = iface->driver_data; @@ -26,7 +39,7 @@ static int csp_if_tun_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packe if (packet->id.dst == ifconf->tun_src) { /** - * Incomming tunnel packet + * Incoming tunnel packet */ //csp_hex_dump("incoming packet", packet->data, packet->length); @@ -112,7 +125,7 @@ void csp_if_tun_init(csp_iface_t * iface, csp_if_tun_conf_t * ifconf) { iface->driver_data = ifconf; - /* Regsiter interface */ + /* Register interface */ iface->name = "TUN", iface->nexthop = csp_if_tun_tx, csp_iflist_add(iface); diff --git a/src/interfaces/csp_if_udp.c b/src/interfaces/csp_if_udp.c index a3993c579..ec88b167f 100644 --- a/src/interfaces/csp_if_udp.c +++ b/src/interfaces/csp_if_udp.c @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -17,6 +18,9 @@ #endif static int csp_if_udp_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { + /* Avoid compiler warnings about unused parameter */ + (void)via; + (void)from_me; csp_if_udp_conf_t * ifconf = iface->driver_data; @@ -35,18 +39,19 @@ static int csp_if_udp_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packe return CSP_ERR_NONE; } -int csp_if_udp_rx_work(int sockfd, size_t unused, csp_iface_t * iface) { +static int csp_if_udp_rx_work(int sockfd, size_t unused, csp_iface_t * iface) { + (void)unused; /* Avoid compiler warnings about unused parameter */ csp_packet_t * packet = csp_buffer_get(0); if (packet == NULL) { return CSP_ERR_NOMEM; } - /* Setup RX frane to point to ID */ + /* Setup RX frame to point to ID */ int header_size = csp_id_setup_rx(packet); int received_len = recvfrom(sockfd, (char *)packet->frame_begin, sizeof(packet->data) + header_size, MSG_WAITALL, NULL, NULL); - - if (received_len <= 4) { + + if (received_len < header_size) { csp_buffer_free(packet); return CSP_ERR_NOMEM; } @@ -64,14 +69,14 @@ int csp_if_udp_rx_work(int sockfd, size_t unused, csp_iface_t * iface) { return CSP_ERR_NONE; } -void * csp_if_udp_rx_loop(void * param) { +static void * csp_if_udp_rx_loop(void * param) { csp_iface_t * iface = param; csp_if_udp_conf_t * ifconf = iface->driver_data; while (ifconf->sockfd == 0) { - ifconf->sockfd = socket(AF_INET, SOCK_DGRAM, PF_PACKET); + ifconf->sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); struct sockaddr_in server_addr = {0}; server_addr.sin_family = AF_INET; @@ -132,7 +137,7 @@ void csp_if_udp_init(csp_iface_t * iface, csp_if_udp_conf_t * ifconf) { csp_print("csp_if_udp_init: pthread_attr_destroy failed: %s: %d\n", strerror(ret), ret); } - /* Regsiter interface */ + /* Register interface */ iface->name = "UDP", iface->nexthop = csp_if_udp_tx, csp_iflist_add(iface); diff --git a/src/interfaces/csp_if_zmqhub.c b/src/interfaces/csp_if_zmqhub.c index 903a477d3..b726decae 100644 --- a/src/interfaces/csp_if_zmqhub.c +++ b/src/interfaces/csp_if_zmqhub.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -12,12 +13,22 @@ #include +#include "../csp_macro.h" + +/** + * ZMQ destination size (for libcsp1 backwards compatibility) + */ +#define ZMQ_DEST_ADDR_SIZE_FIXUP_CSPV1 1 + /* ZMQ driver & interface */ typedef struct { pthread_t rx_thread; void * context; void * publisher; void * subscriber; + /* We must allocate filters per interface, as ZMQ does not copy the filter value to the + outgoing packet for each setsockopt call. */ + uint16_t filt[4][3]; char name[CSP_IFLIST_NAME_MAX + 1]; csp_iface_t iface; } zmq_driver_t; @@ -28,19 +39,56 @@ typedef struct { /* Linux is fast, so we keep it simple by having a single lock */ static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +/** + * Add one byte of the dest or "via" address to the beginning of the + * ZMQ message for the CSPv1 protocol. + * + * This extra byte is not used with the CSPv2 protocol. + * + * @param packet pointer to packet buffer + */ +void csp_zmqhub_fixup_cspv1_add_dest_addr(csp_packet_t * packet) { + + if (csp_conf.version == 1) { + packet->frame_begin -= ZMQ_DEST_ADDR_SIZE_FIXUP_CSPV1; + *(packet->frame_begin) = (uint8_t)packet->id.dst; + packet->frame_length += ZMQ_DEST_ADDR_SIZE_FIXUP_CSPV1; + } +} + +/** + * Skip the extra byte of the dest or "via" address at the beginning + * of the ZMQ message for the CSPv1 protocol. + * + * This extra byte is not used with the CSPv2 protocol. + * + * @param rx_data pointer to ZMQ message content + * @param datalen pointer to ZMQ message content size + */ +void * csp_zmqhub_fixup_cspv1_del_dest_addr(uint8_t * rx_data, size_t * datalen) { + + if (csp_conf.version == 1) { + rx_data += ZMQ_DEST_ADDR_SIZE_FIXUP_CSPV1; + *datalen -= ZMQ_DEST_ADDR_SIZE_FIXUP_CSPV1; + } + + return rx_data; +} + /** * Interface transmit function * @param packet Packet to transmit * @return 1 if packet was successfully transmitted, 0 on error */ -int csp_zmqhub_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int from_me) { +static int csp_zmqhub_tx(csp_iface_t * iface, uint16_t __maybe_unused via, csp_packet_t * packet, int __maybe_unused from_me) { zmq_driver_t * drv = iface->driver_data; - csp_id_prepend(packet); + csp_id_prepend_fixup_cspv1(packet); + csp_zmqhub_fixup_cspv1_add_dest_addr(packet); - /** - * While a ZMQ context is thread safe, sockets are NOT threadsafe, so by sharing drv->publisher, we + /** + * While a ZMQ context is thread safe, sockets are NOT threadsafe, so by sharing drv->publisher, we * need to have a lock around any calls that uses that */ pthread_mutex_lock(&lock); int result = zmq_send(drv->publisher, packet->frame_begin, packet->frame_length, 0); @@ -55,14 +103,14 @@ int csp_zmqhub_tx(csp_iface_t * iface, uint16_t via, csp_packet_t * packet, int return CSP_ERR_NONE; } -void * csp_zmqhub_task(void * param) { +static void * csp_zmqhub_task(void * param) { zmq_driver_t * drv = param; csp_packet_t * packet; - const uint32_t HEADER_SIZE = (csp_conf.version == 2) ? 6 : 4; + const uint32_t HEADER_SIZE = (csp_conf.version == 2) ? 6 : 4 + ZMQ_DEST_ADDR_SIZE_FIXUP_CSPV1; while (1) { - int ret; + int __maybe_unused ret; zmq_msg_t msg; ret = zmq_msg_init_size(&msg, sizeof(packet->data) + HEADER_SIZE); @@ -74,7 +122,7 @@ void * csp_zmqhub_task(void * param) { continue; } - unsigned int datalen = zmq_msg_size(&msg); + size_t datalen = zmq_msg_size(&msg); if (datalen < HEADER_SIZE) { csp_print("ZMQ RX %s: Too short datalen: %u - expected min %u bytes\n", drv->iface.name, datalen, HEADER_SIZE); zmq_msg_close(&msg); @@ -90,7 +138,8 @@ void * csp_zmqhub_task(void * param) { } // Copy the data from zmq to csp - const uint8_t * rx_data = zmq_msg_data(&msg); + uint8_t * rx_data = zmq_msg_data(&msg); + rx_data = csp_zmqhub_fixup_cspv1_del_dest_addr(rx_data, &datalen); csp_id_setup_rx(packet); @@ -98,7 +147,7 @@ void * csp_zmqhub_task(void * param) { packet->frame_length = datalen; /* Parse the frame and strip the ID field */ - if (csp_id_strip(packet) != 0) { + if (csp_id_strip_fixup_cspv1(packet) != 0) { drv->iface.rx_error++; csp_buffer_free(packet); zmq_msg_close(&msg); @@ -155,13 +204,14 @@ int csp_zmqhub_init_w_endpoints(uint16_t addr, } int csp_zmqhub_init_w_name_endpoints_rxfilter(const char * ifname, uint16_t addr, - const uint16_t rxfilter[], unsigned int rxfilter_count, + const uint16_t __maybe_unused rxfilter[], + unsigned int __maybe_unused rxfilter_count, const char * publish_endpoint, const char * subscribe_endpoint, - uint32_t flags, + uint32_t __maybe_unused flags, csp_iface_t ** return_interface) { - int ret; + int __maybe_unused ret; pthread_attr_t attributes; zmq_driver_t * drv = calloc(1, sizeof(*drv)); assert(drv != NULL); @@ -206,26 +256,107 @@ int csp_zmqhub_init_w_name_endpoints_rxfilter(const char * ifname, uint16_t addr assert(ret == 0); ret = pthread_create(&drv->rx_thread, &attributes, csp_zmqhub_task, drv); assert(ret == 0); - + ret = pthread_attr_destroy(&attributes); + assert(ret == 0); + (void)ret; /* Register interface */ - ret = csp_iflist_add(&drv->iface); + csp_iflist_add(&drv->iface); - if (ret == CSP_ERR_NONE && return_interface) { + if (return_interface) { *return_interface = &drv->iface; } + return CSP_ERR_NONE; +} + +int csp_zmqhub_remove_filters(csp_iface_t * zmq_iface) { + + if(zmq_iface == NULL || zmq_iface->driver_data == NULL || zmq_iface->nexthop != csp_zmqhub_tx) { + return -1; + } + + int ret = 0; + zmq_driver_t * drv = zmq_iface->driver_data; + const uint16_t addr = zmq_iface->addr; + const uint16_t hostmask = (1 << (csp_id_get_host_bits() - zmq_iface->netmask)) - 1; + + /* Unsubscribe from any current filters */ + for (int i = 0; i < 4; i++) { + //int i = CSP_PRIO_NORM; + drv->filt[i][0] = __builtin_bswap16((i << 14) | addr); + drv->filt[i][1] = __builtin_bswap16((i << 14) | addr | hostmask); + drv->filt[i][2] = __builtin_bswap16((i << 14) | 16383); + ret = zmq_setsockopt(drv->subscriber, ZMQ_UNSUBSCRIBE, &drv->filt[i][0], 2); + ret = zmq_setsockopt(drv->subscriber, ZMQ_UNSUBSCRIBE, &drv->filt[i][1], 2); + ret = zmq_setsockopt(drv->subscriber, ZMQ_UNSUBSCRIBE, &drv->filt[i][2], 2); + } + + /* subscribe to all packets - no filter */ + ret = zmq_setsockopt(drv->subscriber, ZMQ_SUBSCRIBE, NULL, 0); + assert(ret == 0); + return ret; +} + +static int csp_zmqhub_add_filter(void * driver_data, uint16_t addr) { + + int ret = 0; + zmq_driver_t * drv = (zmq_driver_t*)driver_data; + + /* Subscribe to an extra address, typically registered by alias address */ + for (int i = 0; i < 4; i++) { + uint16_t filter = __builtin_bswap16((i << 14) | addr); + ret = zmq_setsockopt(drv->subscriber, ZMQ_SUBSCRIBE, &filter, 2); + assert(ret == 0); + } + return ret; +} + +int csp_zmqhub_add_filters(csp_iface_t * zmq_iface) { + + if(zmq_iface == NULL || zmq_iface->driver_data == NULL || zmq_iface->nexthop != csp_zmqhub_tx) { + return -1; + } + int ret = 0; + zmq_driver_t * drv = zmq_iface->driver_data; + const uint16_t addr = zmq_iface->addr; + const uint16_t hostmask = (1 << (csp_id_get_host_bits() - zmq_iface->netmask)) - 1; + + /* Unsubscribe to all packets */ + ret = zmq_setsockopt(drv->subscriber, ZMQ_UNSUBSCRIBE, NULL, 0); + assert(ret == 0); + + /* Subscribe to unpromiscuous filters */ + for (int i = 0; i < 4; i++) { + //int i = CSP_PRIO_NORM; + drv->filt[i][0] = __builtin_bswap16((i << 14) | addr); + drv->filt[i][1] = __builtin_bswap16((i << 14) | addr | hostmask); + drv->filt[i][2] = __builtin_bswap16((i << 14) | 16383); + ret = zmq_setsockopt(drv->subscriber, ZMQ_SUBSCRIBE, &drv->filt[i][0], 2); + ret = zmq_setsockopt(drv->subscriber, ZMQ_SUBSCRIBE, &drv->filt[i][1], 2); + ret = zmq_setsockopt(drv->subscriber, ZMQ_SUBSCRIBE, &drv->filt[i][2], 2); + } + assert(ret == 0); return ret; } int csp_zmqhub_init_filter2(const char * ifname, const char * host, uint16_t addr, uint16_t netmask, int promisc, csp_iface_t ** return_interface, char * sec_key, uint16_t subport, uint16_t pubport) { - + + /* ZMQ will cause valgrind errors if `sec_key` isn't exactly 40 characters long. + For now we deliberately parse an empty string as if no sec_key was specified. */ + const ssize_t sec_key_len = sec_key ? strnlen(sec_key, CURVE_KEYLEN-1) : 0; + if (sec_key_len && sec_key_len != CURVE_KEYLEN-1) { + /* Is it bad to expose the detected length of the ZMQ key here? */ + fprintf(stderr, "ZMQ secret key must be exactly 40 characters long (got %ld)\n", sec_key_len); + return CSP_ERR_INVAL; + } + char pub[100]; csp_zmqhub_make_endpoint(host, subport, pub, sizeof(pub)); char sub[100]; csp_zmqhub_make_endpoint(host, pubport, sub, sizeof(sub)); - int ret; + int __maybe_unused ret; pthread_attr_t attributes; zmq_driver_t * drv = calloc(1, sizeof(*drv)); assert(drv != NULL); @@ -238,6 +369,10 @@ int csp_zmqhub_init_filter2(const char * ifname, const char * host, uint16_t add drv->iface.name = drv->name; drv->iface.driver_data = drv; drv->iface.nexthop = csp_zmqhub_tx; + drv->iface.add_alias = csp_zmqhub_add_filter; + + drv->iface.addr = addr; + drv->iface.netmask = netmask; drv->context = zmq_ctx_new(); assert(drv->context != NULL); @@ -252,53 +387,52 @@ int csp_zmqhub_init_filter2(const char * ifname, const char * host, uint16_t add drv->subscriber = zmq_socket(drv->context, ZMQ_SUB); assert(drv->subscriber != NULL); - /* If shared secret key provided */ - if(sec_key){ - char pub_key[41]; - zmq_curve_public(pub_key, sec_key); - /* Publisher (TX) */ - zmq_setsockopt(drv->publisher, ZMQ_CURVE_SERVERKEY, pub_key, CURVE_KEYLEN); - zmq_setsockopt(drv->publisher, ZMQ_CURVE_PUBLICKEY, pub_key, CURVE_KEYLEN); - zmq_setsockopt(drv->publisher, ZMQ_CURVE_SECRETKEY, sec_key, CURVE_KEYLEN); - /* Subscriber (RX) */ - zmq_setsockopt(drv->subscriber, ZMQ_CURVE_SERVERKEY, pub_key, CURVE_KEYLEN); - zmq_setsockopt(drv->subscriber, ZMQ_CURVE_PUBLICKEY, pub_key, CURVE_KEYLEN); - zmq_setsockopt(drv->subscriber, ZMQ_CURVE_SECRETKEY, sec_key, CURVE_KEYLEN); - } - - /* Generate filters */ - uint16_t hostmask = (1 << (csp_id_get_host_bits() - netmask)) - 1; - + /* If shared secret key provided */ + if (sec_key_len) { + char pub_key[CURVE_KEYLEN]; + + zmq_curve_public(pub_key, sec_key); + /* Publisher (TX) */ + zmq_setsockopt(drv->publisher, ZMQ_CURVE_SERVERKEY, pub_key, CURVE_KEYLEN); + zmq_setsockopt(drv->publisher, ZMQ_CURVE_PUBLICKEY, pub_key, CURVE_KEYLEN); + zmq_setsockopt(drv->publisher, ZMQ_CURVE_SECRETKEY, sec_key, CURVE_KEYLEN); + /* Subscriber (RX) */ + zmq_setsockopt(drv->subscriber, ZMQ_CURVE_SERVERKEY, pub_key, CURVE_KEYLEN); + zmq_setsockopt(drv->subscriber, ZMQ_CURVE_PUBLICKEY, pub_key, CURVE_KEYLEN); + zmq_setsockopt(drv->subscriber, ZMQ_CURVE_SECRETKEY, sec_key, CURVE_KEYLEN); + } + int keep_alive = 1; + /* Time in seconds a connection must be idle before keep-alive packet send*/ + int idle = 900; + /* Maximum number of keep-alive probes to send without ack before connection closed */ + int cnt = 2; + /* Interval in seconds between each keep-alive probe */ + int intvl = 900; + /* Publisher (TX) */ + zmq_setsockopt(drv->publisher, ZMQ_TCP_KEEPALIVE, &keep_alive, sizeof(keep_alive)); + zmq_setsockopt(drv->publisher, ZMQ_TCP_KEEPALIVE_IDLE, &idle, sizeof(idle)); + zmq_setsockopt(drv->publisher, ZMQ_TCP_KEEPALIVE_CNT, &cnt, sizeof(cnt)); + zmq_setsockopt(drv->publisher, ZMQ_TCP_KEEPALIVE_INTVL, &intvl, sizeof(intvl)); + /* Subscriber (RX) */ + zmq_setsockopt(drv->subscriber, ZMQ_TCP_KEEPALIVE, &keep_alive, sizeof(keep_alive)); + zmq_setsockopt(drv->subscriber, ZMQ_TCP_KEEPALIVE_IDLE, &idle, sizeof(idle)); + zmq_setsockopt(drv->subscriber, ZMQ_TCP_KEEPALIVE_CNT, &cnt, sizeof(cnt)); + zmq_setsockopt(drv->subscriber, ZMQ_TCP_KEEPALIVE_INTVL, &intvl, sizeof(intvl)); + /* Connect to server */ ret = zmq_connect(drv->publisher, pub); assert(ret == 0); ret = zmq_connect(drv->subscriber, sub); assert(ret == 0); - + (void)ret; if (promisc) { - // subscribe to all packets - no filter - ret = zmq_setsockopt(drv->subscriber, ZMQ_SUBSCRIBE, NULL, 0); - assert(ret == 0); + csp_zmqhub_remove_filters(&drv->iface); } else { - - /* This needs to be static, because ZMQ does not copy the filter value to the - * outgoing packet for each setsockopt call */ - static uint16_t filt[4][3]; - - for (int i = 0; i < 4; i++) { - //int i = CSP_PRIO_NORM; - filt[i][0] = __builtin_bswap16((i << 14) | addr); - filt[i][1] = __builtin_bswap16((i << 14) | addr | hostmask); - filt[i][2] = __builtin_bswap16((i << 14) | 16383); - ret = zmq_setsockopt(drv->subscriber, ZMQ_SUBSCRIBE, &filt[i][0], 2); - ret = zmq_setsockopt(drv->subscriber, ZMQ_SUBSCRIBE, &filt[i][1], 2); - ret = zmq_setsockopt(drv->subscriber, ZMQ_SUBSCRIBE, &filt[i][2], 2); - } - - } + csp_zmqhub_add_filters(&drv->iface); + } /* Start RX thread */ @@ -308,15 +442,17 @@ int csp_zmqhub_init_filter2(const char * ifname, const char * host, uint16_t add assert(ret == 0); ret = pthread_create(&drv->rx_thread, &attributes, csp_zmqhub_task, drv); assert(ret == 0); + ret = pthread_attr_destroy(&attributes); + assert(ret == 0); /* Register interface */ - ret = csp_iflist_add(&drv->iface); + csp_iflist_add(&drv->iface); - if (ret == CSP_ERR_NONE && return_interface) { + if (return_interface) { *return_interface = &drv->iface; } - return ret; + return CSP_ERR_NONE; } diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt new file mode 100644 index 000000000..6a29c23de --- /dev/null +++ b/unittests/CMakeLists.txt @@ -0,0 +1,10 @@ +if(CHECK_FOUND) + add_executable(csp_tests) + target_link_libraries(csp_tests PRIVATE csp ${CHECK_LIBRARIES}) + target_sources(csp_tests PRIVATE + main.c + queue.c + buffer.c + hmac.c + ) +endif() diff --git a/unittests/buffer.c b/unittests/buffer.c new file mode 100644 index 000000000..4a61f0f0a --- /dev/null +++ b/unittests/buffer.c @@ -0,0 +1,85 @@ +#include +#include "../include/csp/csp.h" +#include "../include/csp/csp_id.h" + +#include "../src/csp_buffer_private.h" + +#define CSP_ID2_HEADER_SIZE 6 + +/* https://github.com/libcsp/libcsp/issues/734 */ +START_TEST(test_alloc_clean_734) +{ + uint8_t expected[CSP_BUFFER_SIZE]; + + csp_init(); + + /* use all buffer and free them */ + for (unsigned int i = 0; i < CSP_BUFFER_COUNT; i++) { + csp_packet_t * packet = csp_buffer_get_always(); + memset(packet->data, 0, sizeof(packet->data)); /* clear buffer data */ + memcpy(packet->data, "previous_data!!", i+1); /* put some data inside */ + packet->length = i + 1; + csp_buffer_free(packet); + } + + memset(expected, 0, sizeof(expected)); + + /* access the data of previously used buffers */ + for (unsigned int i = 0; i < CSP_BUFFER_COUNT; i++) { + csp_packet_t * packet = csp_buffer_get_always(); + ck_assert_mem_eq(packet->data, expected, sizeof(expected)); + ck_assert_int_eq(packet->length, 0); + csp_buffer_free(packet); + } +} +END_TEST + +START_TEST(test_clone_frame_begin_fixed) +{ + csp_init(); + + csp_packet_t *src = csp_buffer_get_always(); + ck_assert_ptr_nonnull(src); + + /* Simulate a packet with no header*/ + memcpy(src->data, "hello", 6); + src->length = 6; + + /* Add header to simulate a prepared to send packet */ + csp_id_prepend(src); + + csp_packet_t *clone = csp_buffer_clone(src); + ck_assert_ptr_nonnull(clone); + + /* Verify that the data content is identical */ + ck_assert_mem_eq(clone->frame_begin + CSP_ID2_HEADER_SIZE, src->frame_begin + CSP_ID2_HEADER_SIZE, 6); + + /* Modify source data to verify that pointer not pointing the same area */ + memcpy(src->data, "world", 6); + + /* Check that clone is unaffected by src modification */ + ck_assert_mem_ne(clone->frame_begin + CSP_ID2_HEADER_SIZE, src->frame_begin + CSP_ID2_HEADER_SIZE, src->length); + ck_assert_mem_eq(clone->frame_begin + CSP_ID2_HEADER_SIZE, "hello", 6); + + /* Ensure that frame_begin does NOT point to the same address as the original */ + ck_assert_ptr_ne(clone->frame_begin, src->frame_begin); + + csp_buffer_free(src); + csp_buffer_free(clone); +} +END_TEST + +Suite * buffer_suite(void) +{ + Suite *s; + TCase *tc_alloc; + + s = suite_create("Packet Buffer"); + + tc_alloc = tcase_create("allocate"); + tcase_add_test(tc_alloc, test_alloc_clean_734); + tcase_add_test(tc_alloc, test_clone_frame_begin_fixed); + suite_add_tcase(s, tc_alloc); + + return s; +} diff --git a/unittests/hmac.c b/unittests/hmac.c new file mode 100644 index 000000000..bdcb15a83 --- /dev/null +++ b/unittests/hmac.c @@ -0,0 +1,64 @@ +#include +#include "../include/csp/csp.h" +#include "../include/csp/csp_id.h" +#include "../include/csp/crypto/csp_hmac.h" +#include "../src/csp_buffer_private.h" + +START_TEST(test_hmac_append_no_header) +{ + uint8_t test_data[] = {0x61, 0x62, 0x63}; /* abc */ + uint8_t expected[] = {0x61, 0x62, 0x63, 0x9b, 0x4a, 0x91, 0x8f}; + csp_packet_t * packet; + + csp_init(); + + packet = csp_buffer_get_always(); + memcpy(packet->data, test_data, sizeof(test_data)); + packet->length = sizeof(test_data); + + csp_hmac_append(packet, false); + ck_assert_mem_eq(packet->data, expected, sizeof(expected)); + + csp_hmac_verify(packet, false); + ck_assert_mem_eq(packet->data, test_data, sizeof(test_data)); +} +END_TEST + +START_TEST(test_hmac_append_include_header) +{ + uint8_t test_data[] = {0x61, 0x62, 0x63}; /* abc */ + uint8_t expected[] = {0x61, 0x62, 0x63, 0x3c, 0xc7, 0x49, 0x8b}; + csp_packet_t * packet; + + csp_init(); + + packet = csp_buffer_get_always(); + + csp_id_prepend(packet); + + memcpy(packet->data, test_data, sizeof(test_data)); + packet->length += sizeof(test_data); + packet->frame_length += sizeof(test_data); + + csp_hmac_append(packet, true); + ck_assert_mem_eq(packet->data, expected, sizeof(expected)); + + csp_hmac_verify(packet, true); + ck_assert_mem_eq(packet->data, test_data, sizeof(test_data)); +} +END_TEST + +Suite * hmac_suite(void) +{ + Suite *s; + TCase *tc_hmac; + + s = suite_create("HMAC"); + + tc_hmac = tcase_create("append"); + tcase_add_test(tc_hmac, test_hmac_append_no_header); + tcase_add_test(tc_hmac, test_hmac_append_include_header); + suite_add_tcase(s, tc_hmac); + + return s; +} diff --git a/unittests/main.c b/unittests/main.c new file mode 100644 index 000000000..5a4b6de9b --- /dev/null +++ b/unittests/main.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include + +#define DEFAULT_PRINT_VERBOSITY (CK_NORMAL) + +Suite * queue_suite(void); +Suite * buffer_suite(void); +Suite * hmac_suite(void); + +static struct option long_options[] = { + {"verbose", no_argument, 0, 'V'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} +}; + +void print_help() { + printf("Usage: csp_tests [options]\n"); + printf("Run libcsp unit tests.\n\n"); + printf("Without any option, it will run all tests.\n\n"); + printf(" --verbose print verbose message\n" + " -h print help\n"); +} + +int main(int argc, char *argv[]) +{ + int number_failed; + SRunner *sr; + int opt; + enum print_output print_verbosity = DEFAULT_PRINT_VERBOSITY; + + while ((opt = getopt_long(argc, argv, "h", long_options, NULL)) != -1) { + switch (opt) { + case 'V': + print_verbosity = CK_VERBOSE; + break; + case 'h': + print_help(); + exit(EXIT_SUCCESS); + case '?': + /* Invalid option or missing argument */ + print_help(); + exit(EXIT_FAILURE); + } + } + + sr = srunner_create(NULL); + srunner_add_suite(sr, queue_suite()); + srunner_add_suite(sr, buffer_suite()); + srunner_add_suite(sr, hmac_suite()); + + srunner_run_all(sr, print_verbosity); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/unittests/queue.c b/unittests/queue.c new file mode 100644 index 000000000..dc96c95d4 --- /dev/null +++ b/unittests/queue.c @@ -0,0 +1,46 @@ +#include +#include "../include/csp/csp.h" + +#define DEFAULT_TIMEOUT 1000 + +/* https://github.com/libcsp/libcsp/pull/707 */ +START_TEST(test_queue_free_707) +{ + char item[] = "abc"; + + int qlength = 10; + int buf_size = qlength * sizeof(item); + char buf[buf_size]; + + csp_queue_handle_t qh; + csp_static_queue_t q; + + /* zero clear */ + memset(buf, 0, buf_size); + + csp_init(); + + /* create */ + qh = csp_queue_create_static(qlength, sizeof(item), buf, &q); + ck_assert_int_eq(csp_queue_free(qh), qlength); + + /* enqueue */ + ck_assert_int_eq(csp_queue_enqueue(qh, item, DEFAULT_TIMEOUT), CSP_QUEUE_OK); + ck_assert_int_eq(csp_queue_free(qh), qlength - 1); + +} +END_TEST + +Suite * queue_suite(void) +{ + Suite *s; + TCase *tc_free; + + s = suite_create("Queue"); + + tc_free = tcase_create("free"); + tcase_add_test(tc_free, test_queue_free_707); + suite_add_tcase(s, tc_free); + + return s; +} diff --git a/utils/cspsplit.py b/utils/cspsplit.py index f2a0f901a..69d7b6bb0 100755 --- a/utils/cspsplit.py +++ b/utils/cspsplit.py @@ -20,7 +20,7 @@ def main(): print("HEADER must be in hexadecimal format") sys.exit(-1) - print("Priotity: {0}".format((hdrhex >> 30) & 0x03)) + print("Priority: {0}".format((hdrhex >> 30) & 0x03)) print("Source: {0}".format((hdrhex >> 25) & 0x1f)) print("Destination: {0}".format((hdrhex >> 20) & 0x1f)) print("Destination port: {0}".format((hdrhex >> 14) & 0x3f)) diff --git a/waf b/waf index e4c12266b..e968b6364 100755 --- a/waf +++ b/waf @@ -32,13 +32,13 @@ POSSIBILITY OF SUCH DAMAGE. import os, sys, inspect -VERSION="2.1.1" -REVISION="073e1606e6ae84d01a398086bd4053ad" -GIT="0d2f819d1e1127a8dc53fd61755ff4fb065bc411" +VERSION="2.1.4" +REVISION="72787ce48f227ac42c4b0da24e780694" +GIT="89cd97a8d823d797297592ad751beb678806f339" INSTALL='' C1='#.' -C2='#&' -C3='#$' +C2='#-' +C3='#+' cwd = os.getcwd() join = os.path.join @@ -98,7 +98,11 @@ def unpack_wafdir(dir, src): err("Waf cannot be unpacked, check that bzip2 support is present") try: - for x in t: t.extract(x) + for x in t: + if hasattr(tarfile, 'data_filter'): + t.extract(x, filter='data') + else: + t.extract(x) finally: t.close() @@ -168,6 +172,6 @@ if __name__ == '__main__': Scripting.waf_entry_point(cwd, VERSION, wafdir) #==> -#BZh91AY&SY,zx;#$P(B0u@b(ƨ#$#$#$#$#$#$#$#$#$#$#$#$#$#$#$#$#$#$#$#$#$lMյeZk{4mlږzY+foZ:{k.I뫖ե3羝r{.v#.AE;\^ۧM8l޾||2^VO]CןK}zlvlޱv{^wӁeJe׀#$#$#$ϱ z#$̔#$.`S]ӹ:#$wanI-LW MT( ITP=:R*@(#$BBov{>ޯ5{zYFٮ%)]G<m>fZ6<w=篷ws4ums{շ_x>(@vd#$%N.w^w,ͥnr5{1*=ƀUT @z@{{a+޻wKl#.i gǧ]/m"xn4wx#$}p[|v]#.}TvnE˵qڵptgvw{[Ofs.wk{e{o}m&S}U5簧2fӎ zjOu3ۻ}[nWml|o;ܷۧv}_n&=uk-uqUuǹ(ќ].Ѐ97=}C`:2#$@zWZ[GJ[4^(o%Nc=Y[۳颜u{x(۶hnn#$#$S#$U^sSx1׽}{=[mrq]_7l*fl9ȈXgh={{J.vWm{Y67S^hᧉ:nF;w;}_1x.]۲>.O[2њ-#$>ۓ(o}}V.=+`^| z-di}p*9{ok-cO};mS#&}v|]|.{wƾh #$&#$2&4&#.cHS=Cj6=&6H%4A d&j??SJghxSz@#$#$#$ @ @A4*7m55PhzP#&#$ #$#$#$ =R &='hOjOPMF@#$#$#$#$#$#$$!#$#$ hM D00OCI#L#$#$#$4#$Q@@#&S4==L)OԚ#$#$#$#$5? Ih QRDRy̋bD&)Ukei=ZjگT#$Bi?cz\{wV'80rf=;{^B#$6DBA@M GC:bZyROTWW=."1LUDU^&/09?V͵66͹]ҶmmUKUlkmT[O֢U"P$$DlEDM~캯:-dBEDDAFF"FfM2FEm3PXR(6&h #.`ж&4KD!L2R&Қ #$Jƥ M1eMDRlK-4Ҵb!e6h& 31IAj 6C6cI@4Y1SM5kf4Ic3&d mVԔMM3jm52bf"P%$ѤMR6B#h4TH!`I4T؄!E"HdFIcBd#  #&42H #J)DƒRɶM(IdjHdiM0MJLD2hi1 iHI#.Th,E2%3Ɗ ؉1M ͂ab6TJF$$K&D`E#PbIL# )%2E&!$["H[b2F̒2dJ3df fe"a`$Jhlb#RY#bE!2)j@F,40YfX imL%Ħ21(RH @X$J6D1`C,%L̵Jh"3hP()$$ccMM"4X32LR3iekd`K%4̂QjTd,"HQdR"(ت)$$Shj6I##&1&#"fM2C(2FƑ2dA)RYBZ͖lX i,mAPH‘&BăRXYiDс+HP%"Je&(%L)f)!BlH%Vkh2LM#$""hFRYdTZfLRSb!J"i!e6e0eF(2a#.TFihF5Yf2e0#Y12lhj)#.fMKLXd$ML*JѲ#F,RRٱձȊ4Meh*FHڋX)5Z2TVM%lTFM2ERhDQF-X#.ɓ#.0mbiR Z&%#.+4m1+H-mREC!2k&*F)&H4F֥clbV-YJj,BS)ذ*RYE!cI "B-"ņi(b$HbD&LP (d6i,Y#&)H"YdJbPL1!Fi#6Icll0YJ HhƌPFMAb1!1$ILh,@ S#$dhcf6HME!QM2MFCHbma4jMa#K $j(P)4h)S))e+2 blClQ6[FhJRl4Kbaj(% 2bRT5)ș%)#&(i,Fb&QIidlЩ e,(T4#b-M$)&FEEbљl4Y$Сf61MFQK6,e$(dɥ)*dEXkTI5E$ԚPd T#V-4d(Q$U2M"e5T+b%$Y4̡jDd#.2jlC6DڈEL&J4ZLIE%dJlCFTe*4 L66J*MXF"Chɂ5SS63Kcj6eU$i M$RE+IcdQVEZ5 j5QMFRȂljJ,R)2dfA1M53fLlm5&6[DR-DlldU4bk+cX٦#iR[(T#M&&P,QIhkbœlѢ؆6Lhcjmk#.(T0)M$ambhƵZK&ښd-4J4eDhBba)vlQlHiDi! b@?'cWl5{K4>gIM1 ?FAT۹ӥCQfDhL'eR ZJ{eŇ ĵ3YnVZAIprD-0Qo~^^mzV'v6URPѭJ@FzxŰf#&Gӻ5rNk"M#&6LQf=4cmO;ZM$D"ƨ0mxNwDFԣ+F&]rnQҹs&he4^oP&h?U%|ŧlzΛ.9GA_]JRL~֌m[_[ݙ#&(_E&4m뭖,?҅jnqjK\9恎ǨɘWT-@ýQp&Au:4n밵22V*^?<ϻ#.qZgvpDZaCG/5 ytr\Ae &u/K`'nnj\zm\EO&/9i-R|mWqe,YI#.N+ŠUDqY:E:-#.7pi)$W^4dlrޔcs?ud/ިY|h-#&[`Dl`*PDҩtC)ZbZLWѕ7eSZP( b[A[FTt`A|Z2ZPcmVNPnm(`?lFiwnW{]6"<꼗y˛n{. Es\bv!Y5kbQQ}tR (*0FZrFɭ~&JDm#.XꝗMj_9N)*-J_:[B}Tw#kf ڏTAXBcKua Y<{RfX:\\.oĉD=iP'mҊeґ]UBYNNYzwY`l&t__-&x^M?|QDbmZDEO#.\*͖݇"al|~#^pc>NXtA)\s,1Paosxʪ5@I81x4cWʱT3 RFwqX%4PT.t< zU^ 9VY j")\~.Os*a06'4Lg yq!gx =x=~Mqx鑭X߲DgFnVM~u6LmGSNeNNMjRޟǾ:pZa?Xף_+T,xY̟SI4B(v~Xj8!P^^SY^)#.ҫQK%Ǔ P'x11N9#&B饧w9C+l\VÆm"ZL9jv׺ϳ!*n>}>:f%IݥDnSMKq#.u뺟F/Fkn f8kݐnHܐlأ!/i~)~梾Ԧ}Oڿ?]#ADj?3s|dUsJ tŧ:zDoc[$8Xvԡ#. gS>U5iKΐ J#.*w)9q-=QEFv'x6IXϾb:qp3H>"#&TN7ߝՁɁB}LV?4dftQHK(XE# NuwE{ٌ߷\/g2Ҧ[Sr'uJgI3߹mvw~!Z32 5MdT3iZfDj>IqwxuvS2M?HmT#&hzh;x^ݮ~HV5 U%44*,(-fMY-Ui;CWQlzxl:#.Vc^Yw*4~tkGWY֋Xn2_FH@R.mog>u֏5Me(+vseEuм5+& @c~0Z7 ePoF7^?ƅƎ.3yѡ;>r3T?k?D|]5"+Eoᝏ?uJ̶Pc"'<oU#.IISpdY2b8@cBigsB(76!L .DV,b#.7SDHa"tPv UBo=11"'J+lMKgÜd9:EdQ{ͳW9^ #.eRvrVڡbsJ=?~\e\3}*a+_ r94b=8OcDMp?7B'c}|bSRwMâ_U&B{/UK޼>um m*FRWSk>( 4)g% \U4UuA|"s9CZ͞ӮF_?ǽm-:u=y㙑ôSuǢ"uĞ^lR|$ s0k҃12H5'X&Z`)9~#$#.McU#.c"cN%1u8#'K~R߲e-Ahc&^EGɾ)ֆۓ¼AVj%nwˆcMZ?w:b6"r7vPqrtOVZ< փܛU"4[z>J0\or[ 7yy\(5#.FG:͒`\^^x<(f`YTuTwdq36:KA@II2Xd"G?N<hXK4A#.*>񂢕lqp~>Ve&Ppd[ X'1R#f^wD#$:I/#&zX,j<Hh(,lM7TmXtլ`Ҍ&e1qLb(>/ǐB #.dR 5)#.?ȯ]^$8T"O9Nibrwt ӷCuË9"_nnQ#.4+_Dx{U'4 Na%X`j:#&.Y|+D\~VTGo(ʔɳovM6}3ȳX#vF[l}U+7kZؤ4F,bNMDRP⾶E`iF8,Wﺸ{[`>&0#$qNy2y#B#&%⊠}%ץnrѡ&"hdߵkW6>cj ~=0̂$#&{د-Yj/g;EӣF,j~eWTe鱘$dG.r4|15Yv8 $qm};sSj8{hΕD-^9 ޚ#.U%IߒutѾK`,,مJ3NmT:5UtIe[ZitY@mʀ]u\g]:K@4SCW=&U#HOiDE|Qt1QKP4ULocB lHQ睕6D!#$% y ftÆrbJ;f8hAUTp bfR116;Eś'X6i[KF2uf\1,Q]5_Ew#.W3?i$:s=]p/,T^WJvְKy5a;87QV˝ԈaG唜5 _7]kg$>;Ԛ}7ZSS@uXy٢SIQgÌ t[M!FzTj rYtm{Z#.Ճ9kJ>Ɇv @ U.>\U֤f&0(:}3aJ351Dy?Lo?W3ݟ~_iφx?cH#$&Msy:&A: ֡ڳXLYⲾW-% #&Vv0?3՝/#&QWR_kugepXoa2տ>\=Zzaڨp|K}eOw @ i-6#n۪ Q*zwVB $$#&|P[F&,~k#.f 0?#.='F5nD+~}O#$k6J"MCԕc;Ix\tCR f,"pSe[Qa=XuNnnv=YVv ^w+5)Z0@S SZM0dFC!KX lzO%l6[JC7ZO .xyY Ž-2DK1%54lYYH qee?N -ƥY*X] sI#)W@#.(D^r|r1fQTz9 DJoa /#.*N?byz;u2`L -z٭蠽d᥵ljŅ;f#Fg6X$)].|+XDysy^@lH-%n|߄YOiĞkSf#&ֽG(}AŽX[NJ閚&(7qTX$;g@!#.%h^".U9%H38lH\f-n2.vΰO&OgY&5;<|t@?kwӇ[bz(%k$42[ޜ4Yc/#.~~M'%Nߦ)IorV7/Ek3Hk78ԛPEGƑC{*oK0CHr 7 $ym,ʹˍȪ׶~[^8]BGYgY_ٖhBB*R wϕ1{,Iئ2#;Ÿ%SGaԽ`Y7cQs`hng|ՃT2n1F(@5(ե #$dRdnAYv{}haD:o(fLc632$ )THM"&rK]ꍪH[Ѓם]#nr郭cHR WXrf`a^,ͅwTxfnrmYBqq=_+d M#BBDZwC;3}o(~}w6uv甐L@ LVV=#.}#&g'>&CUVChRv*>@zQNUHH?zܭ1vReCQ-0BhkuGm`N WG;a:2 2{*լ(#&#.,*'80ݧ:!ʿ|)F>;ߤp>%mOm6C~+eI#&Iрӝ[-F`V6vN{ʒAMMhe#JJhE6-,U <÷۵ďyPЙF:ݸB:b7aEklOe49phr39z)9FaX=UԱov@=KkE) Ru[q*kr$z`;%yO8IF'Fx\„@!{x52K61^й}zufiWr 7l|OE1afsNM@ڀb-:F!ݮ<˵i376墣GO鉳8w閙__+5K:7E ii4&j"#&)2hDBR 0#$z@bpj5Pl MٶlۊFHĵT`qʊNLD}U*/XSC]"KVblx}ɹRt?rڡ͖拆#$ȴ#.d*ꂞo".LFT Flv醴4(eC% #.D4E,34(n eI8V?hǺ#7͚2S.ihli ڍZ9Nn:YY*b{G%ԗzm#R4&W1b{G b#&LB) F A= }w|%<6I)(PūW`dQF{$ݷd|0njVǨꥌK'"BSk$jk*I,t,Tt)~lOȾHn{\ #. R0JfMMV^TN}[0J`l`]&DѧJf zRY!C!NXCR@pdl%"t(1ZV0QY H(F4KZesn 3-(j#&CnFu *5 F=3#& ##d ]Ou1C3BobP\pq!LhAu5:W LGt\ :)`V#.z5׍ k={rx5ƍ/9]6r0jD&vG#f'# shPf :"]e#&^ 㼻pf|#&8c)lz܈F߉ V$J'n,5Gv)OS%)"f˲>Odqܳ¹MB|56>xR^H$ZɼNFm[f*"g<V;|gkg3/֟y=NcUF$Ɏ6y@`싷E?0͇zǘ|p-`5IlEe5P`r2̭Mk|bFxq){l7ۈq׸"oH:SR$ûbc3/ϛ^ }sV¶4&`3m˓*4[*PyJ.va!+RcaFzV(O\f#&B[f)yzcG`nP#$&Lǻw3^N?^',jC-!|vdOýyƴlIrֺTmܪ[M-ݦ`f#Vc#5l{AweNl$]g^Clhq$o7{74޺:1 5e=XT>g ' պt/Kkrm8@4h>:#&gsoAj)vcC:iXwnqtt T:z$-=ijCo_.?G>(%ƍ vrd?g{i?i%ZU5A+2'a:ޖˏ—gCd~#|7ÚُN#pk84||#.[Ê` =!n?(qLE]6wfwYd[t~wu}rw=^{Fjgp#& F0Ork`1_4Y&.H{$<\ϢP]pxP$Y,i ]S,F[`1O/X=#.«)TeeSNuTDiuoZB(䀘j5&?vΊ @ Ky#&}TxaҝOɞ.dS""*#.H=NS6hE3F!z;n6,s*(P4[W{?$P#@>NeF7l CD16MFWte-s\eH']нWN[glQrhӶuF/oOM~&PFbo-U~GO?3(`HCX(ERL ]c[_t^EPIP2>܏^M*MK05H[aO;v"ZQ[Z}AN[ÇvD*n77,u//ڥ)`XKثl}v o@isGzEdKacOK[e ; &?0$#.r:z7Yo0IοtqBl2T5#.ɕ(&,TaDC*$|KKdp_lNN}HKyc='g)):dɿѧa{}E\8$F #.(`Ъ$'?>BU^:P@Ś1oR}s짥& EqehLba#{nO_'Cv.yLr)vaai`,831 Jz`#&t"?vI*A+-mλ.wd󿷇-chѸ̦hw*M+ZPL!rK%1o#$U gu뾂FX(xtĢ姫nwoǿ^¶-zbouϢ]ċ6r^t>Y.zzޣݍ?~V\z!~1X8;UmǛVC̼$Ѻ 2#.;K+Iݤ\:t->UQ`w-|l(d~:Dy+m9GOW#e 4{fscmG/pSd<1k3*^I6<׃S{t&ek>#&|(GGW6FI͆;cjw}n}{4F_Aj]8ܷyұѭ^iE3x6ZUvDX/`6򔧺ݝ[D{}1oM1*as'# 0g1W4St击L]M]nrzۢNN-zU 9ȋ!dбߢ#Ʈ)"%b$h|U4l1tcus7[t:Uߥ'&νٰh}'SXǷtm 7"i}ڟ; qqyi}dP`t}gos.Kʨxy~0v*olwuh}}E?zT{O#&}Y:pOtg? gs~0w:={{M=ԤTp,>[hփhEx+Qۇe㥆_o}:;}0v{m4Яgγ=_B8_S@#ÿ0 yzl}ƶuG6Ov+wT g[?_rN_?!#.=5/s#&^Yx(ʅ{P5X0ѨϴK ;n1YOrEmfWf߳eRvN_ֽ;#&=v8Mg_hG?H:G_ .[GClʠk;j(?}Dg>_ҽ7iojwƣ`Np5vL>7|B|Pr6w${C,?P(?o}TO7(Ռ9U}E|u35rqY7^xߒ]ϻy|,?Ab|;{U=2؞O4Gۅ$AAtttJ"z-ͥ*!|lT ]sP y+f@Hs@.$/Ίrԫ YLTҼo?ў6ÞA 6*DGK'^$N%]T|F"#(O#MoF SgrSg}^7#&V~ա@&a#$pӑ(b |W+Y(y̔9z3Z#.rkdrʪ#\d85qNWM,ļL_7XWܨjӆ;qd#C/GVrvҖ dH׃( BI~`'irJސ-ա?1#&Ӕ Fc*¾@aWZS[u/|SؿWNA%@#&~lU7{8 :ۿ8tc:כYGa75;Tإ]~M?j47eg?էb,_/-p:#.t#$JtS{q;cFmw1[-DNG%cQ!2ƨpQ-1lŅ41#.#(XbV7d#.lȠA61EPdQZa+5Y#.gTh8>2hdJ5hlJF2#&Ӥf#J& `B#&Qf(&0yͽ^JhTCiı !BQi> ;e-g8o Q-(H#.` 4m΍AAIeCN"GDtdA*56*EG!4`Q]iF8cdΏ8'qB I$PA&AD=Rze99ZeߤԊ#.#.Sh}E®NO( ds(HNzX!<=34z\4B&Ȉӛʽ3N#&]~K~hO=nq83tE=u{9s #$vA#$]~{0pyP}j8UKK2]ܢpHК@rX<$u!Pt/yDC o}ީ60ߵ95KcD,Y%ө Hȑ#eP(Eĉ* m4 W(/:ibT8cE{/F4]YW`BEaB#&>4.ZVF#ltvsX&-:0,#CcPqcF<]i24Hn3,1i+ 쥁FbVǕsµpXraDžF0О&ɞl SÑn`yl(B#.`I0!p ֘oƕ(FFRPC!YJ(KL*m8V6#&Pmx&ޤAlF&s b'!:8i֫M3C/J6B1gS9Lm>7^E&Y#FS%}27k컹^rHBw |q] x~=wq|'9M[ m#&eS= CX:ۈE#.H ^(#.qg#.T;lo3}io!vy :T lc'<34G#&KqΟ6m1(Fܛ*UҖcjI[2D\s|66^Mdtv(ظxTjnɱ#.d5Eb'$a$epc;\(_Bf(ͱH x4U=,8! O*A"f6Iv/-O&sBLRsLzC gX/DJA<㊆';#ưv}@竇LQ7kr:l1WKϙ#.(ٶBL@Ǎ/[ֱF:Ƶ-#$鯾4M.w0Uʨ.Ԕ+B"wW; ߒ#& AsP)5QoŲmZ\$H5#.;+hvۆ婸iއ^볺o1,D\psQiqR틎`'AXpZC.Y91MKNLeNƦY!cP6YctNf)LkBÛ"H6J) bWK`XVCm7l8iMIO3cF256J#&.R%AN~DqwL~^鴼\) CmҞ\@/q֘8Z9<ݟ 98!Uk6ݝ6{nDzrs槀wTM_Mۜ+DF&aߚhXN/uRM{arBj5S9m*K:FXZH5=-q(YW>[T2gJE]R$y*}V,w&M]4\6#.$8ʂUTB&M}DLCXv]>uZU; #&^#.S;td8i|qgw16NOd嶃fxFǗ~d4ٿ{7Aw B.\-.U6KZin$/VUfnM=5fW- ѲX|#BalǸ;%HtΚ؊Lz;rբQ՚f]Dwp8cBak>qGsi=4S>?ֽ0Yʮhbׯ [`3քNg=x9U`{½~oOx2?#$0>(bKJڊo`s9>eGOa~Goݵڵk7 <=`tQ&(XP P#.u<@7;T XNX&-5!i^'~g\= !;5eA~RKI&8-C[rۨƒpl[hRոK)^_ O޾,#$̅F-2 @N�Nv p#,x7IQ=@F3dDxb,QIZ&}$#;;"st]nO[Z=AI`r興.'G;O-ɼ] 'f/[BQr#.VMNuAR6,4Pf|k5zO2gq +Ae#4$s;NDDLts&<`>?44Ъe0',kdtB$yLjo,k.˞N[.&钒cx06LGWГ0L8ɣ>s^g[n7J >"z6#,S>/euw?uE#&743R}uѲSj?]tzSNMM?+KY'uG4rPEA-wݚ +(],;'0kӏ} Rt?7CpC#Gi@'p&LÜi^S9DXw|6vΐˆ|5*;’ДDGyp=MEBidFT[|o+ h^hh{PɘjaglЂ1_4CR,%]ç=€>ܛ \ #80h\%B[iϦO_Gǁ/;d u v#&a;O/#X!h})rXY.,~B=('qP}ԝ6Xn+r\Uq,{{FjsQY#&#$F2|c5,I Yg])#CmfUc;}s2oۙC3c+YQ_FuKDvvryZu/Ldo\c,s!mQt2NFݮ腜`A+s,}-E+#.S[Jo~!'*%Nىy䐫]4:[$UVa:|S:0*EDt/um+j-I[}Uȓl1M}:xʗɺEzPv l#:=T_DlykA(Ɉ\4q U1w~Jlkh*2!B?%`p^awwCqƮScS)n+nI=Uu4oucЯ[lov{U#&"ͤws[OR5X>C|!YKMQbIc<ڃBŏibЂ oQȃa%K9ۓGyx--k"Vo%*a|r~ͿsnGtp6p4M/qѳp8c cbz0x>h"۷#&"1ީi^QrJ#B/[n<T8>L?Jn~o҉(r߯íz!2,9)yC#&KG3֣&n/; 0*xX뢀&iy<2xG!&tx>Z\MM72G5^pb~f:}CMK5ĚCd环%GΘ*崀_W:J:]'v8OG N[^,lE8X,/"BF\m {X]Ul. -OXS!VXrr5͠B#$靍'sUɄơHQb~ק3;\+--C(%U"7['J=2wIn[LW~*Q*VXzBp]0 3;..uZotUfj0N)+㈰;bj#&Ufpڶ9:, vLs^霮;uۥe<=S+38HO6#&W4{| +K#.&PI d9a lc`WcmC497Ax[}EKdFF$wci} 7zƱKYdh.W/!74sˣcYbe'c&OlϺxnI]:LԢ٫ U.np]ViT#.3XﱁW (]Jldggv>||u07P0F~f&,o"+5dC',uX;f݅CrM,sMK>qnW@*脴.NQuSnQ| x`srɻiR \0([m6vna`HtI;@K5N۩~р34pGl614{Rǖ˗ GUwJ#.n\#\>Vg~#q#&? #.~J,@H睃^}Y:gi|=z0MѢ4jIaZq&qtR!ժA#&3́aIɅ Nc˝#httx~?HWHzܝ_i.YC;w^ JЫE6X'l3}~d/Lp֮acފ8+$@GrX'cKVIHr=qյϡyt{ѯ:-YhTݷ^^zșyP~\WOV8v½1CmXoV5#.Vn+7kNKWޣ[,?gxEFEwJ/~eE>\`ڠN=#."v|EE&XdžUWN\(I2Th۟os76ZLu@Gcsɭf+'n,AO4p<ۅ۰Z[iY44̓^emB:D-SRsֿ 2cUo{dI˟b!R)dTFӧE?p_Ki՘Ac|adpb[ I0dlԓZB:l`ǝ7.ŒE_K!X19!9އuPBrghsm®Z/#jm{A7p:%JZ2 dav)nS/`|"Ĺ!Qs^&VqǭS$(/q.9硟QR#.nKӘ>\ǩBxq48xwe"e#.`^ɐa3mF`][N=!w$H/aFl!N$ogG]"G(i#2ཪ!]=VҨQyQ*wХAr &_t tc&lڎBKʰuVP!yfl:un-({.>(:m^߯.^27vJ=zUopz85&eaQ#&GrnQvQs8u:q~1/ m(^6لk9grDH E5| p錆Cv]55Mt0ôzlxo*Tt#$[HujISepe?:1aɆs~wK#.1@#6Hlmi:":a#.zߙyDF̅ 7]ǽV:yFk ӳ"4T4!/(~%-+Ժ#.M -!fH%Vu-p',l] VlZiAxb@Ais=C#ܨR76 *j9#6iH%}Ctƅ)=5g&{0ik!1Ef %Z{CP#.^@ݘ;l~c#$DxvWš&U6DRadge Nٓi,3S5_˓1jjN.:qn6˿̯t ^Jw>'>0l&Zy̡R-eQzf"02LM[(#?:/{όdT#.f6!KG$Ewt^m5~#.R xN[ߓոFsu5Z:ad*uۿnNw(t|)#J9x1>s={#&ubTtn[rߛ%.n#.-%{|j:Tb'$N9A5[`ï;P #&NGlcCգ)Eӛܻ0u~pO^W5xgs٣4axA)&/sO"8|7^DNEGv{Wr`)o &oLs#&*I>h<77A$N#.Dʕ5.Zg6F+ك+j3G;TP@H},4l50D XdntEz<Yf5Lsyûgx6|:BjNOSn#&cF_v'CGjL'\tP%Ϳ>SNלK:in}Oyyۑyi2vx<6$Z;,E@/MPy70,&8Grλrt@Ϯw\i8Y EBM+;)9zv\|~Y[YM@{.F #.ga>}7#${3#.Y$˭PQNӀ#.$d_kh^>hHmA6 'U<3i$~#&ZGt9vN a )rpּnC_@o(nkf*A#&厑[f4cqo>'YG:E,Pg7aNE^pl1}dFVu[;_tTށ3˂4%h[%ERQ#&T#$tҷ ȊCfYMևkm:&h+j{!P6f ?j[w|@xzܝ>ã'Rҽ|x#$0 j#$#$t6Y{% Ihj 0 <: k%ў(AF~S]尺oC#.hID<=W"+IMUbں}(bq2"Z"@xiLX3s[>3ؿ &Z!E P`#$MSBzu6?tZLOl#&8y[h2$YzH +G#$f`|=0p(p4g6c~>mQV-Q]ʿ? ,dU,2A,/60o֥-ykΕ$NIe|u#Yb 9C#¢]\޺ - { O ߵеOUZF$4%6.֗7TKńa\ 㯾aRֵ͍:8<A_t5'VH#͖9DkP1nH?An:u{{7E UVe,AXv:|s<7=ag:& zD}|qw hmzfM3g1P?ic@naW4w:(E%E#&PΌ6d?cO L0:]#$pCkPPfPn5PFU}{hd=#&" P{ZRh_3Q 啌0\TvC6­Cb@12k:3'k٪hC)Kz#&5}%za0+|qͰ"S8_f4˂zpCL{8\]DIuQljS+ +$yզ60`֚bOgm\*_Smo?A^ZY 7gʮc"g?{{1vRq h>UvTA gT>z%U(J_:ߨY%zLJNlk r1|TQ Sg۝`nTEoz=11/:{ɀflL s۽X|:2qkfp#&yLƵHx!>8f1NWnMP"Zm(AP~5;U#x#.|==M$-pۋ̪& m!2rSuyqNQ(8ڄ'9ڳ눢 Dk Z{I޳"d;3{䛶ʒ|:Tn`fffې:```% H <[z6xn_oֈ ^`I##$?aчZQIWqnpK@Qo(ؔ>s" iZ铵#$;@GIبhCuLmȄWƁ8fIEPۭ{ɎWCVJ!:M+M׃Zַ#.s*2)p4gX6f1x^AGiW;QbϤ*P;?OИPӊs#&y=g1S@D̨*&wNδNa2A`~+@av!^|ܐۈaNf|}UFԙ|7+B&ï!m#.u*5%#&#$0k(!4 95bɞ`#$ r-,Paԅ,>P—cꂶM(m),dEI:R`P@gYRJh;xX]C@ZQwfLf A$K#$8ْVC2AT%_mx_?ǧ?S8{4:e!bwxV-$_]_P({lpQ1wqt1nAL%C#$mz x*23l k\?DE[g7&O=5eD@:l3r/uJ̑[Wcx)oKp6ύwE&h\%NsL"&w&$D0OZ{B8ȿNKl0>VQG|MTx4XL$Lz4NmJJ{ĺq@f4C:uLE`;7;6 fYuֻwYZީ@#&6%JS: DW'A`6bT$!~"{rŸijY==J4)("0D3JkLS>ǟkF%UQ#$,r79C9iZq LZf;i#E$ c"(lHg$" E:E}Zl޾p۷&4v1!L;#&4Fϥ6B@jT- K,:H˶Kс;Ũv've&#&(a@UMݙ% ,<$6G=em#$vۙ込 ToȦJ%dCc}#`+t$85X65R-@Ҭۦp REdPZa #-:ueX#& 2#$99Q=-EMU{<Р0b1TBӴ7dnhO:+^#&]^LԬxA#&{ goLL |fI0mPQŴ'橢WRJ!Q!#&q蜍|L@tssaN*o^Ht=z#.fL&t+i7jI.~ib=^cၛX_jL%96*)#.VdzM}ݜ#$.T${I֥Q+o,ZƝώsWN p@G*)Txy98q&`Q1ACS85@Bslǯv$gU,w TJWujWh$YD`, v)+۸XƘ7ա]S{6袐Vaӈ}ov"_lD !tRWtw댪wT FR H#.^'^ÄP%#$łD4jv!r&#gNh#&h=|wKc{ {Cݍb7N#.Oɾ]d^{AjXs]e#&u0u\ -_|͢ouEש ϜbL[m #(-ݗnq}eN⏑@_KfH*x3!R+'d撿`O{}KzTwcU $ꉸLc4a#&.MQT*5 (bCNhbˆu'fGu#&}ˡ#$@vd(e ݐ>O|룚JxgˆSEf5 מF/K#&NQ3@##$wAק!D)B/ZlinE1uوb;?j㒖G "UɆPiIyqxE>,m#.'bЪRK}!+cNCHξL[#$zP)qE:NuYs?0 M6{Bg3>+eGq/y]eGlfg{ٹvDf A%h G]_塒[Z|\ hGo\H~?vW `pM$Y}b@DaόF9!?S)+7sΓ@WL)!![V6it)G?Fzd.ZH;mȽw".206`>o\Rg#.wuho8$Z|Af`C5އ~?>:4G8@LcfC#&!3s~M{/ϵ+9JlԷ6ʺi*5G^p-%+wc S?,~'㡣?Ϫ3~`MfQZaTJEwrO5D[`g!+:fak54Ҩ1*4]FzD~/Ex|D6 R*ek}pm~Y`#&CW#.GT]Um8|&%hݦYA Z$;e.y#&gB)t<"z$4(CmyTN3j G~=3|-i cǞ~27ƀXDȔ ߳o:=>܀A9\k-#.QĐ#$JaAS#&FM=CYч$чmXJE!(J$Js2ݏL[aK-PH1:ua$/|wİda"kp#$z=~#.n*s,<:ssK}HP~%S3U ZE.B@=̮#.tʖ3Qepw~~ܐ(W?ϫ"a=D $PE=8{I|SR#?GW<[5sicy|$fO#V>Nc?Vm6C9seT>OwؿYBG*g3pžbĶɜe#$#.T:Wl1s95zf뗠؄ (/;.>o2Wn;S8!NoNM-GW;}YT <^:uyǛ0i/ߩ?,"'JUϢ#&nz6mCXP#&:߬tkz.P|^f$6/tC#$2Hi,ϟ8 ܖpͣB#&+:lVjZ*|+uD;49ﻫ}/,:[*Fi]fK3s:=/1m5VQxu18JD!5aM{_^8eP?(0D% E|<S/F&N Zc5{ե?sB!ThpNP(N[u{r%碜N;7=#$ܢå 1CQV+! y*נi6se9P|(#$AYUC_>ZfB#$B=<ce `@HTV?ƷzΆ>#$nsdM:@P#&߃@DFw@2Uc|JP+U#${W I%aՀC_$9]]jH?n!xWOwu 9P*/zǫ\dc[1\ظ(8;d]&.^{9 t w.'~139?oIIL=A#&]mdmInO):^8}{zrqIr;HHɍ/#&T_Z=5ՇJ7sg܃*1s"e2"I.?JaG4_KHîdюAg3֫L!Ч9<*uxk !B]%z_oV]UU쮻zj_!RTn~#&j0.#&ŀSpn"ح]UA*1^M^QfM峤zk#&acHȖ|ڊm.]'E"2}$ :S_N|\p݉q8_s6s(tȵޝG&H^76#&Fٞ|:b-h&3;_|׶eTK?M?%ivˎ ;uS86gʞ[uCo|VK8NBg du#>S.:'f2> ''m4Gg1BLzs|sKxIEw" M!@x59(#O㶧>8|񺌲h=b1Z_g,45'j\oW8=`EsssabŜz&:%OXE2i1Gv1w"M.#.̆_EW@]ѵXL̅/+AL.ȩ(>̒㶉c.#&g?v<x,|䍟AMTAC[{K*.||ZwKvZ<tgVA\VrpSz4Y.E6!{Fr+Ҝ\TETs9<:8 {\[=wq4[DtωST?l\( #.p DXBt1tT#&#&1FF@3z8#&n3(U~~ޠw b}3'k%~l",q#.h+Ωg z(EZ/[PK $#{oM#fy;-}S>Ai&oqGZ3 9_cv~!PM|Ț#.e~oT2'uKi7pRl5yu:D]d*35az ]2c#&#.r@({h[iv.1:AD/L6s;AgqWtՊ\& >2x4GuN2iҶ|_|luݤY&~t0YZ,eǸt5SQ ;0sCdwq(YAg/҉h$VQL6+|Yub&kwC)EyTu Y5sP+'%͚ѡ-sᤸ)/T#.b#_#.Jl&uI"x(tA/*O约 4c#$#ݛb#&C(D4w!O?XxfJ$g֣#$D1jc#.H_$&GʪzA=ٲ1 ~屧tNyY?B腤>]ޟNӅJ+ +? nҘ"u[g}0jp=eC>(^"uu9VHPhCU#$6PQԠ4ҠZ±Ӂ_D:#}kÇ7~OˤlW>} Eb!V7:W>pZg(SW#.s/>i?& :9%OA?=>0yP?J#&q`YʱG?9;@p>LK#.QE$?\lPCyo $J(#.#H%5$j|F#$A&P`H#&M< <>mR1+1Z ŠI7M{D^/Py@v/#$۔3#.Fq䖏#.Ps1(H9Ȑ߿=a/q&o'_hR0#$#$=lJ^. #&JCn#&_=>*7K?3( ҡJ #$MVY˅WS!}n8 6P$#$1MbK!VSa`>W;9CTӐ39c'zk@6ǝWvd6NWUW/TBi@#$uR`xDMu=K%k^SXԊ^m%tk 9ü7)~n`5I҈7X髖rxn܁R(6e=os~DpV\_"'(PvVR6 !O-ꮫ^~n.)D*SJyҏA M*Oe<L|w$DHSz5#.;acm' \"bb~/W, Kc j.]'{߬Z:{| gMkGpɄ`vx~(0kb:)Ə"4omyڎۿs^nT9i*?PmJ?rhֆZIϽtBBW[z4+u#&$^1mkl:L*B_8+u8x\.6iP+ b>_^D#&\YYr{;|9~n.Ѡ#&).|q󐄎gTiѶpVxHHE\l^" [gnLh(B0rjC#S тqZARR[+#$`] ]OU.ցp-ۅ7Օ +}&^aq]A!sƝZA)S!K~U;l,{{,#$P6BzG0@QHM#&saؐ H$y\, Z;b^|w3۳:ATrm\m"nx\܏;UkSXAeAv#$LQ:$d0&Hl[oyrP;l,Q-G[qK) 89hKPu*^s }p;HͽYnGWk'_:A$-E߇&|0TxCÄAix]N{JD(-8|P54Lq+<:^zm_C3JћNӤ[W -Fs\]SxhմxQ; f.XOf@4PȌKCV$^"݀({iIߞ琋LVK#.fB dTLP/"r=ɽ.cvTj=a[Iڴ,CƂ""#&3w"E12/ ȺLdoH<›c9Q[5k=~rŝѬ<2aQFa-qƨ,:"55wtFӓz2:(kAgDH%|,}`I[`^5=5{Drsf0H#%'s= ^%Hmdqm89[zк,|7߱v4!ƢSeIm~w;Xh$$1Tɟz;&E%t9·\";_mQ:#.shmp:4olZl n^QFmkfq91o9˳%$vǪp*0hPq֓V?;0BUDoq"fjy#&|}\0l;i -B #. 1b`BP_Q}`n>]8#$APLk6%-'ێ[zMl\EL _kGᆴa@H#$aAlDQKtEE |!H-A!z#.RG~^/=K˝w>knO???c^-6d[>Iݲ$5|iP>/r@})2ߧW5.iB:Ig.` Mճ>_Ht0tQ#&} '+-aꩰ J#$vr;rv ~Ծd85C#.ȭG)ب\V>O;ԖZ6s59=mAO P5A>lnf=u]Y" Cö#>LDeSeq߉1f`]M'|OPdu0|[+ m6[T}B9||>}[k lʀ*6*"}u%)|#&ekcC-1իLC=i#.vB1>p~Fq'ǘ߼L0-,#&\hxtoO\ڨh!f K㍻ㄭØQ?3a93JY ҆h722ha6ބêJOzq; #&*Uqh{ }ay=;9/ޚVizRnmQ<و^nhPn1 )D.׊Ҕ`Bl"^bs\$tV6Gak@űЫIW'yx, zʄzَu`t LV2 b+e@GlmG;_Gц@VC#+0?ep݇^FŁŘ/A`B#|ꌜ*Pt4qsI/ Vr\Ih@ $#&ǁv)X`U|-5>5[vQǩ~!dVrEflT,|J(!M&+(Y"{p5>zmSs}]\3R>YB0GnN#(4y\kKU 4#7[B kYmz}*"NXwL"T\ՎkGXC[#&7Bz`E\ae kK6')"٬9:K%];I w\C#$Y,܏6pi3xbs/zd~5.<͛rYN3$vzgIg ;#&A.o۩'>gqbRğȔ0S7_Hgf;sU+L#F%jXe J;Ά#k%?Phl죮')d#.3 %D7V ;^ܚxNA7P?)*NSjƍ(Li4@XSQ#.D[>[5:aG\(x)n("GS3*/љu2bhXLC#$:~_xy0_*~a;Z*93|2P~_v0S?uK#.`#. (ARDQnN:i+$%m3WFFhiZLlHzqx#&1l+EӮFjMb2SRZZiM~PySLjup-O B[!6Y! #$m6nwm_io|&#6@NUެX0IA#$)l>Pd:n5g\#.[cMT" #RKDux#&w6INsneaJ#.S7cΨ`%=,u>jfprJ`;ؤ$" ;N3RRQ$H2jl}|yȰT9omc~,k툞Ww̭9N߹[A%/H]>57;=M!H)M4!Ljx|8`z@ ݚʅ`ĥK:Pd|ݦm7`h }xv=!jUO1!P*bG i@:CrS Y p/0!x]žJѠ.L(W[8`·"#@#$  g/󒻛{K#.gAQ_3%|["%:PZ lDo(p|cv`􍀰?v ! 4pn'=4"Ɯ_CmP}&~ {^[x8u'D #&H˼&S$Xd@=;+80GӖЅj <IS_Pi˺!,H`u| #&_t wȤg9r#$@t '/i.~$,'(DԼgiLOM5%p`.=*fYxI=j<?^0H#&d>~?">SDf<Bޕ7Kq();s4?bx"V z¹{*J5wt`D.ϙUbCQ08.'fp) X-C1qNnER p5#.0k!*>R\r$ q6N75]4%2]*'#&j8{> CTF2 "!g^m f9Hm{WDzgNNlS0Sd:K̝AwQKR|OE=P짘{^!AA|fF`#$f=5!ؑ4 pp~pnM,A1T!@"źz(w{ZO?Tؼ=5* $?u+#Zii隺4Y xL!(yddG|ϳZAMR* ˂^B#&Z*o}h>DmB=tsɆNHgdAlؤ#$*`HD{~:e3qٻj<;,F\vġpIGbOo_/r?ꄏ a<FuNd/Y9tAYTv 02 (F#&8?A~3CqP d4#qg0)Opv3b(* U¥1L{SuT +Ojv&6V%8f*0NCaF(=S%g1ER Ukݰ0B 0:".ƝT-/XJMRY.rX,x#$qtpHLWQ9dPd:ì;l$9ZIPT*dS6ة<0sCb*9 eyw|}AW|6F-'u<|NOǴPQW"*G?ڬLT\_c{Av/ѴHI*ȩ⨨8(g($'GCb}9ަk0l넷p,579?ev:ߟ5#&v A'sCP#. ZYqa66,G恻}5@:0$i@84BGu`~;#.2!EBEFDf:#.&]ɗ}`X:4γQfAsOBۦZrbgxCJ^T*DdكOM#$[iZõ#$mb;B`,8 XG3Fj(Z|!}eKWybH&]o;u6#.PI_&hE#&ed#&V6]oA$:rPoji{LW~x?kPkN~\f&v1O y mֽEhe#&:OHB<"4 O_}0k>sOc@\D>Iu{z3pF,Mc&vl|xSEm6pF#&BBM#.I#.kv QY8pơ}m35Y_o=}O<$"XȄOU/L&ˑݬUA)(4~8hhB{}nt#&4-슴3gkPhM`뺓2|^Wऊ\?AV,|*}]u Mz{}Jga >'{͔JwQrR @#&fr: K|-9/}@1#$4!46qsάm#a+V=p*f ڄS#EN=e8>Қ$4̨gJ^=ׯTCC&v 4רtd?(DEFN4TIQR<ʅ ,8Y#&YR܀:m{UְfBh_}~jz~VD#&}KY}Ȕh !7=nj]hD#.h#&+4 nDlzږy:pqr/ew9QB{< ~o[#$RPju7eyHjI@*,YV L2l!"#.K(J#.+5]MʽZumYVt zfbOmOYPleFgRu -F?ٱ% QV'{|f j֖| ]bOcVlwrJ܀+jz$ hT?;>5myo"s^QfOUzNԽ3g`OJdӲrX/4esmu<&o>1νgo<̰?PhRZ[ HKG5.Wsl0Y}<<3OƧ/_)Lt+wQ걗#&7d.2Gq^ϡ 0EGD+pE pu;ZI 97AG4zo&DY-8Bu_ʜAʽ7X9@9?P|=;~D>ha"ǛY9I 8r3{#o$'r;?b`l#qcl˦gjzeueh|%Ig biYMCPXF8[9J0>>cֳڸ9˷uf8S7[=GQ/AJvE~Ir8LF?[mK.V1){jY[ug#&\Yl8ݳzbyiwc^j446Y^.NgGS*zCELѠjHhg4ΘE˥7yX}&,.J涐Ó=+봘[x#$/Ļώt1ARc"f/3%v+UUhq-U;r95$en蟫#.J!&0a.Aҝ3b+&W­ݒ:Π}-wKHI , 1k#."kԢD\s_|{%[kRF r#$G3WVMMQ8xvDzcvnTwdE_io }|ڌFnѶe0TD0T3Fy,4Ͻ9E zїs|29AK}_CnYu8Lv}cXaNͿGҷ8$MKs{tKfԳ$P&_#&1;nT2fC ȽLvG~mn穐p" #."AW;E5^YQ@1|==B_6WiyTz4'"/@WyF~ }3 ڤ?ɲSy7@? >NM z*?9 A22K0ڻ[@Uɘk;#&[Fm&aN8a\e٤$HlK:MNgMQpɦ*C[&պu^BbZ2l]е1 ˉ87U^04a8pnGxuma^Í9몐m,p]TXT[3sDc;nj7,ف=#&/dP;E:`0j#&nUK =+¿ :aeL$D (8zCB#.BzLŋ40>W\CaO`lDl~#>1JU8GZ8I #&@ cX`DRAZ,/w:D 4ln>3AfZJm/w'.k^ 8Gw]VVi#&S/@fO'V,#$$y7$)՗gv.P{zMQ?c=5hgfu#$4TyOLJT'*jAm\)ZcljJ&M+PuI  @!TFB%w8 c@܊Y똅d@ $Dޟ.DBUmgPa"#.C׼ɯHPw.}ݍB./"+ON}<{>I. D<F؉Ea=V =R.1pN^Xgd2Xl]kbT̕ɲoX$}9<#$ӵ#&C]RXnuʤJ.bL1tR6&Ӻ۽ONk%%c`;wm5BZ΢wp#Dd]4;r:i`#V&ǦaSqSiL`,*uKn&e%Pp&W{<V/9᳭1ѥ⽫SOtqvɄy]n# vi3GyRqK-x!f|^sDR7ckfk#.څg#$utImuwM#$t((CIܷfyy1յM #"6#.j&^I#$pVf!rGQ`SjpP?g٨Ϸhq,gx6{ FђJMvwkoǩTRT%Q"ZXox=besz)E%'Y|U(_#.t g&?cz>͹{38XI>޾ι#y1CaC`*< yn{2ᵁsB哪AQimu:|#$1-U6S7C%(3&MtTHaܵw tw5jI!6Mðwl^JOrǏ)"Ν#$ ٮ[3.sJ 0E4!$0d&fk#&8/R)z:#zd!#&lwI E*4po&"i9u%uQX0G][uL88+|吝>Nk)F#.R4-+УΜEr]9%Цf%L̼eʳ,̒UٞKéVֽ[B8\c˗ɪbIJ|jX5OI+͞v^'Ֆ-_B8 cDx&|*sh5 b2&0A {d$MLVs =WSM|Zݑ|@GYG3ȹZCbv.MԎrv#$aCJeʋ(}|r;,@9ZT[;Ϻ{#$)#$nW\De_;ŕ剱XaCڵ0$zBhynůlb_,$" &9w"&ʲI.>j~nNq>fF1'C$n>1#IY>qN5D|Gv BP@:d#.toK'5V.s#$ Cͪ(j@p*BŠ#$^S˽XR~6/.kϫ#/el6i[?#J")?rAۮڮ?_VU=Ԧi$:l;zz@7zor$̼>! E$I4"!)ߴJD59J >\0#&+i}pdoZ^-υ.v@RYo9HC1Nfe)tUKyR+j:I #$"$z#.dDjAT7 #$Ra#(o C\ȕ/LSK6$$L-] J+Pʝ{m|#%=\5c80.H6D[)YQ㗾0=|NdQTAA`3:XEELQuj%:#$hYC@c:ewm}uӆ?UT]Y T`#$$iÜ ڹZK-*I{#@킋"2"(H#$b#$Ȁ pOG_;`z8GKUϬx bu XS r,!7Fb#.=eOi4שxעI1oWux6SzJ7wh%/G]snl\1rRVWvėǧZjJ/hD1eg!]k~Ѐ'dOt47KPV=f̞/ѓ+TS}TAj2QR_;xsLX%3nCvE.}.zD [ǀuQ?n`CV. syҨϽcC"{~oRa'z`BvnjN|Vඕd0j LÍ th!t{~)̠V#& oc*S 5'V?M"XŦ"a0",Ywo|mB ^^1@kL {5Y>jxNS2! F%юȥJ!E0p;Uc;(#ǁ@mj2WD*{3ߴYA|B*7{=ox~h#!ܿwhw|n~Xz`[Ͽ.Bw?tqO ͼ$\ypZ3JO2c.sXC؀:#$Nq#&=v(.}%422eo~gbɅDx;Z” 82+GxzǡSmtI[A6cmvr<$o$Zڽ&63t'*@܇PQECRT(z1"BÙ })#$A3d4xϕv^Hn[1&܇r)eR7Ƚ;wvu%Wwo[G |z,E|GuqzDmyбo{k;1?Noe=P$}2筥_;/lih,xrqjDB@(F=k_OM{ tGXn1]|9[Se<AțK[:m4ڸr8l6 yO[j(6ƦS6&hջ(t؏-f}MH#F"rdf#.gѩGٳ,#.}#&3o>pF!d d[#&ϻ5wx4:/#.(D{{8rhSҀP[T 1#$BV%QDR/Ӏ^զZsNW<'c8D<ν]}56#.zʷ#.Y܊n [fyD|#eu:È6kh.=˾ ~7z9d$#&g]j7?g'xn68un6CAllKr#T,z䖶~~8b!Jp :cÐ]th2tz$8QFJi!r֟#&Osk͗A9(5eKH4`ǓM)=ՌO^#.ꍲDZd tQ#&𽥚B 4b8 xEt;/wcݧ2lm]j&:#.:| limtGmmL!xCz{~5#&Y1,.ŽW.kj7g+,_8!Q*'xSR6%w%sUύخ1N\tj# l6Y}۸B#/L$:IɬmP5' ;s)SUETW1K#C6Kk͔ȹ,]Kk建lJ #.(Zq#&CC(DP#.4*(e"PÑ!$FF %)Bm(Yդ #$ߴ(#&(%8*VCd>Vʕ)W)P#&#.*z(=HQU^7MO #.#$@RY#$Qt|v+АLme=&ފoŶl/cah1PSׄ9BMeUʑٴP5Eh) .2YA=0TDiiM\v[DH'$'Ӄ>^nEʣTbQH 4I T%Y $@"OD `#&'Emc'i%[Z!EÙь#&FTӾ)ABDI=pG'tCФLBvO].Z$Q#&2JZM>}]&car~ Q4˃\"bBQ\mUB3{'@]k[j;ie.x/n&jټ^5&eҊ}50n' NR+<*kitFE3:#စR{ bH [iu#&^BB #&xeY KtR0t4h,$9lS5F* #.Zt$%#$A!`H\70ύ܆CH%ulHJP#$*#&.ef#$y0[FYeI(B\#.!F]L410BşQyE0&p0ii[rSeemwԚ%ԥ-܂/p)>OÏ4d^ll7Mxs$g, S*^<3}&22MOc#.a5E g1[RIYo\OA!s. rIMIKٿ^irM6]֦V'vg{Fiʸf׷_ZK5K >0Eu6%HqܱYg B&KIEU"bzV2wh}6~77#.?_P=xꨤ#.Gqyg#&L RSn'CLua` @C0R7@)"'JUP\T -5lf `([RT&LIaj"#MIyh-#$#$-Xy=Pғl&7eNmZUf/c "xZc#CLKy5;kg$Ny6  pȕmUȒ\19A&Rz/U >4L5V;bSu_2WtCvد$Ȉ2iK#M$g!S`bsI{4z5 elT"g} >WL|ل(j e^B*-5G25!=1ڌmY#.b-*M#74>P.#&cHE5\Ӓ(jr62Xºt#$ƭ}\/vΎ |V{gC[iDӉbˌ,4iܐyM*$Opxc4uXgds;ɻ9Fl_M%6Bp㉺xi=Z6A-q5i"H4 1pB!#.S|1R;UxF&c(u24Y5zy,l8$; 4h#.A&"S&kFA#.!hCS 5\ ^b}C;kSir6qHx3+  dB3IT8Dd,1`(S[WXtB7I.9LL暑ƒb/W]Hl7%9k癑6 67-HJKJ'9 Df%1!`P&Ըi WvMl&_MN3J)ª.E$9#&$Lef0)r! .6xXR:q#$e- 3YE52fwc͏cD225'#웘L.qcC9mRþSnKS[d`:3#&t'k%ȳB;Ӹn؟t:z'vFY6 ),#&oϬ!סA6("(bshI=k@Pafb5BƎ$`"iH#$Fo#&xK2`{P]4@H=8jLJ7C1 S$( #&I0d|C ]œ`V˺ Hzjp :4pgKw"Mvg?H(3nޭTX[L9̾'1x? EiQGW5 81?1ˊ)_` #.POrD$Ju܆Z+I" Q@@U -⬀Q#& kD"_#.);d7W*J30zY4k=GŲB#.abFJd̦Rʙ2&5ZҡMJ)F%#.5{vMi1dJfЦLL2E0R(d#i,0(a iQ2"&Mik!2HSPe*2 Am4i)dHCe;l-y'L[U_c>ƾ_4#Gd7.q*7#.Oxa#$}y`윰bo~3jb#&ޒ&yBLufsuf.hHgo۴&\;7Q.X agJ㵢40Mֵَ߮ǫ04 npK{ Pߤodtm8`deS HMl#&V^ѭuLcef6k&QDleHdOo{:x`J@ !&v|f\ =zKC/Up{"G}Ω gIUaը6#. D"H^ɏUoK%G Q,oJyK3q"JkJX9>HTFc#&yv@WMmU?sZRKm7Ϯh]tg&S4WJ짝ߴVln]vߝKωr57iÞ1QAJWŭNCHǗ 1-t:p+qwhעIthcd%A\? T5{̼$a8L'vIC{(NI"!UCVeR{#&`@1"gd6ESjۑ{= oi=TTd9kȊ#$E$?FVu,r*ڛИt%ɃF5"quJy#&Ja#.UmeP쇴I .T۳]3f;V8gXiwoTlm`YlRCgK5-]jԊ(hM֊Ascc#.ۃS%)C&զybꟑYq&6%(fV0ܰ Y}`^fh!(kؖ7F7ww.E-ra2".2ߛYhԫqYN.cq>i#F\ER!o[O45mi3e)|#&+33uɩG_#$fS@32L>8tlQU#ӨBYfHkMsYkMh5(q.LWŨs=Hg)Rp^ndmYxh1K=-1 WķMux[/W:zebbmvG#&`»Ę с#.fLƊ$BA&nur&v,F BHNo96 [8P9DSbML 1fyW\az96kfjWԱȑHSYn$m#&:lSgkZ)+vgo/vmZ|WIdmP)-XP 7Ѫ>::R#$hx:2P37:jRȶɢVJ%vʃG ؼ7 ШiP]x[nCG'(̎6(Q9cx|h5b#$Dw@5EȽw;4EWyۜ_Uh=(Ѷ#m/&ǔZ a݆ٮ ej@#&5djfe24҂[%!Rr:g[ۮo>Y+`Il'-kG(uII (윒ZEodlt-*N, h)ц$5@%ql*qJG"st| wDpуPg$ʢefuY&b`B $I3/TE"J@SHDMNBcBl!)/D#.a2[B:c@QZ.KDMӫJC 56:z8bBMr7]ȱ05J;@H]z/XTKduS5(7TM#&4P6vG"(a8 & m26$@34X7#aulJDCst11p #$s4@;ĨxhF+ݍ% Gi@#$LNg`i i- KāȂIƖꎃ (΢T]Ph}qҶH `eJr,ͦ`ӵ`v\ˡ7nfadԔqkĜ sӰd ..%oͧ`b[T; h[@#mAQH đbARE!W1x3iSYB%" TF_%> ;U۪hՌ0KR-~qcQ \9ѴN5):U`1B0 `@RrnmymC@e$Ӷ0gAxG؇5rQMC5ޖc G^w{fdKԗcc3t[TD&M<)#.H97a~&͚;B*Wnӕó#jz}L?P.+c̾B6Ȟ˸9VMw橥ijwQ]YB=-{bh1D) S@ %˵&t#qPLX|r3,;((ؙ#$D5SoaGA5M;UjmL[Ѩm&-2M4̴j"lhZ0RO:gp#.֧#&]d.;` ڞ,Yԑ$Z"=]Y 1LaƣDd\0#@6,#&u`zdK v0?5bTɫ#&'9j b#$lx\exfVM#&1YK-#éR#&c"1dǻXZWJoRЛ@4+vLhٔ4PHZMS& *&;g:ъ>6ON5|;뺷#&y/&&#&p%1r0s|## qe]rgRrE0N퇷Lk;ZBCZyU3(JÈB(oE9aC2&Ȍ4 Zjhql"h##.Q3 BŎϹuNij:t(uCLx-gAtX#&nç[q.E?`v:IG4dN<6b 0ᬦ*U)TIBc;5Ck+,5A_I8fSXjwؚ.!#J ·x!Dz4(,[ي #..`Yc+7nbC#$ktA,)"#.`Phf`\/]h TXI0u@!o,pJ5340@ER<:#.P{ھ">zm2]#$(OYqS 2 ŇO٥#&SC5bSj#.=2#&7 zB#$Dt8WͩL'*1W[hQj[I[UD!"BC_C;?=yʪ1ws\c'Ph zk|tzmVVrr$(lN+d/&Uѕ*\RYNVi7f7C%ok֣E]͜qM$iLi[xR@G%0omEfiڞx$KJ4Du#$˖ :qrF-N72D3hu^1Oz4_ F4qӭ:cu8ejS)1kc#$$ tSq mVfubiFy4V./|MrQ3&=\dV\C4(wMc&IUB t5.n#.2C(ii깤,c$"1a9Ha7unծeR۳fm QF]ڥER<+zh#CBC$yQ6ukCM66ޭagD q`D@?dĥ:/<$OXԃ\[kJUv;`#&&nH8d7E,(a[@li(1425tKukWuwr׮-;O7]U䨘ҷS[]hXOdH#.QAb6j@Ԋvj.ʤ"l6ƴԊf-)*eMZ>+zMF`S4m@$URog9zuMSgR/d1Tl`#$Q8[@bQUت ,DvYPz.zQK9*llxf)|-A`.=W0k6,[tD<,[(׫\l:4gƧdny76N *(hH P СlFHYKRqs\RQЖ)g?E?"9yp݀2!MiK٘ˆ?d[¯,qL&OE9Lz_sOc*a=NZe2ňX]xI$tZm,ҚѶ&W佼6LjEE0BDVj٬h^6Z򖮚sba+} %RiMum)#$rUoۖ&Fiim"()֛fmJ(a)j+*ERBl)Dԙac(X#&IV-JTJJKIڊٴв$Ƃ)04ɩfmm"FRͩ2eɓEJJmͲd H)R`K{k]6lԥe 1#$*"] -J[-"UhQDCRXO@qvׇ[c0eR:9W~pcZvƁ0zNcRzH#.'{QO,%I#$yFoʖp<VU~*߷#mshnt#&URwP"zvi\g{FE1SB^-Ӭ,{kOiyΤ5OR`;I̋Izqh*v#&%[S@x|BNf;EuYr$~D3N}eH#ŖQV]UՋR48NI1d30>"i^AA,ޯͳj=/8H<#$b0mSBQ-=a E$BA9Ez]()~9t`1a}[x " dΆya&oD:\w8:Q)l#$@K'îq7͎TBFW=#$P?Wmܝ bb\,P’_*?) a@"BvT?v셳M*أhee$Hѣ9IcX ##&xjI]`#.i0FiDiԜlG&y"m xH&Ucq`۴HFsba2d S[q,TCFm,*ETk@ɂL*cR⑿J#&PX5#$zwQ+ѪQ@5jKA(zv9!mPj" 5RlFjzdЇ&J7gxןզ3pg8#&a޾ iMñD PHDo++}տ@6mS]ROiDyعݭ]'e_c_=*LDwq6U) &3&w։!L@KU#&-y97&|7qu[b1}Mo9#&`W͐ʁY.h@-nWaA5(FRҊV>PvUh9nfV(5@&O>ZlY~i1T&eK'$^_oRW2Mm^<4@iII娊47  F2e4{&pp}ln?=>K|Հt``1Y1#.jwO9m_grpsr虢ѩ!2p|hyȜ08$rtu>3 UD=9c[eJXwҘז²3ަ|(`Eb@dP [[(vZbtQ4ǭPc/㿣;A<#.|, uGTw,y*ZCO@F ԑr?g14̞Nqi9ۤ#&iAޘ̈Ѩ™4L56l*7d7ظlz󒃈0Fsgh#&#.2w&#&5jぉ`f4^q퉦;[=r%quj&a rtNX'x}:l%BN7=#$-xwbB;'n356@/Sf@AiT+#&%->>N}֗#ޒ@<Nծpv\%B9mFsB;dZR8dTR\kAPR8eɋ"MnOMܺW5rMuwlm2*DRA%@U*#H 7t\Mfk(<*CpW\-[zA  Er }M|r-uREEn4d,Ŭ ݰS8g8@0ka 358h$MFH#$TAC0kL2opb 'fR@LTH8 ()#$A#QDx%ᘆlh <@306wZ|#.#.- Ӵˋ#&u1}AӷP G #.jlAgB/Ng̀I 2mR#$*,"TT 3Gz6,PFHArZjueH`n0fJXUSHQo]VGq:][ؿڼ+c=)J4:qxq7R@RA -Os%B)ѥШ>'Ւ]F&+?Wˋ ƒ.HfE}m#&<6|Di_DJ!հӖ]o:#.=CUwP;2$#&ƾ;)k%#Ԓ%TEi>8gdqQT*)p%9`L$aYԁUh2^aWZ*@ZTlwȒll3PruȔbmq2anL`VIkc#46*k-.ת^6띻d+jyGh+CcbMLcjըv!Fm#n2¶)&Y#.p2SPa DXczInP"hc^ڼXkFFsbF#&pimcKm`֙lhR6V|cxl1ڭ&Rzʰ$'Au#&n$lmr.Zһrܪ+o֕{yײlّ#.DIxDf cV`aF:vN#&2F8kV&4w7D=z!&jֱJûTp#.AP@DѨܼƥK%FWji%ʨƱNu6ƬcUvmF& A:#.%RD@Q 'yO"ðQ!c!/yepEx8wV=X3\ԂQ#.E} ZJ#.Wqn"T`nmA#$baA}˴p.#$&T#&zH#.& 4-⼥%ԥK5\5m}ךEV#&TmJ껪-zjՉ PCTEYT10jMIVLAD#&"ci&`(vA%Dⶉ.I~3@!x!&Xe!(BJ#k'I9@ΣF`*aQ׍cFBܰ)"Or䀹"X|z<|3/n-UT |ry3ҡ J4\T-\d5-x2X~3Kf~he$ƆnRu1(ἙV;qh åQKbшښθl2P|CQ8%xi}7QHQhEf -IIM3i-$dkWA&̮Wer3-mEIlK)fV.iDU6Ͷ&ͱj -Zb9 j?~TS^[% Ž@dSJ$Q""Q9*صԫkݛH/R%&A'"v'QDN#$(mdT)Qd !#.x #.pO%u_өwEU}R|%>`LݠIFࣳqx& B]P?0$)P#$#&jsC`~ d{C$ Q+hDt/j#. , #&E-pSO-Ay4GHII3ss!LI6#$'Gy#$ b5fQvA:J~.#&T\;aG, ZEihh8F xZ3GWaQ`Kar~qѳڇK}J3Ց Cj~~>'Цj6OnqQ:v`U42j*:c_H(I{xq|<ԁ!#$56,L#&90'5vNz^QM7.يHR:ZԛE ,Qm5eIe'纩È0At#$O VژVYd`$c<@7%r8T #m4#E#.cX"QJ`,-"$c+ i% F X A5cYi}{fMj "Cd "e#.dʆ JO$Rvevm5QR$d@4Bצ1QU-TK%ԥg;Ck.&j#&0ÒF#$1ɁunA8bTK%\iVh ) dxa,H2~kv/&yvhYH,L Rh,ye_WƗ5:˷udcIB8|dM}+{]..?;Q"m"&F`VaZ`tctר盬Rԋ/p&Ra"lzkd)m7}D9,D%$)O;˽nTV1l B/zZy'yJcÌ!MSEyۦ^yҺ㶺Ҭդ6YxQm<]/Z-HGuJ(-!Wlԑw0`+i(B~7g{V"44#gJ֝0 3U&Zxo#.,ѽ4]u Lf5VB}`pa*ehiR \j $c@iRin5-l^k{^8LL61k7rKwL:Y!PMAȥ8;#&=e#$!D'SL&ZJ;h'#&|uUy LK ȶ=s<j6I4NJƯb"NNd=~baX k7 #sĠur88DY!fy߳<|CD0IKXrc|DPt _g) ҺOOK.Ź 6B}#&#czxv9٭Đs.%RȢ`j ݤճ;b/݂!p6m==m#.N##Mls44+Jn,нXRLeFi5G:$SoNo>˜#.yq#&>JV!Z,̐7:tRyjoA$*#.LhP ӄny -V_/&F}Q"Ֆ5oC>\MBohHCGp346u΢yDĘwJF#rvUv#&q\ۻsnji4ggVMUT[nbrxyweWmed""Tγ#&@=j=a}Գv<+ҏ~#.!/I:߿y'_붊+0)Stٛmd&!ia0kw̛}&8r5G~_eZЄK9#.ڈs`FNkPX҂J$}xV`B`Z AR^ DX76\k#&khsPBL4#$jҊ^@8oPÂD #6NX#Kciְ}vt;cɣ9ebcs^æ }=}s@N?KI%:Pd0Z͆xQGH_2b6rdfM.׊`@_#$5.aH8{,8 pph3ԂoyM?F!w^}q"nt&#IF|:I N>]T,#.$ hgJrlgOp{`%%i&b\ 7BO[5hޥt,d4qv4tؖ&Ac}k;@F8EOhF0,a֟^IA3* WUeCy#.X^zԁ?Q@:W!r`j(8׶㐧8IN{=Okq]zƂɰdܶ̕(NqT;w\Zr&TfBqTr١PGSw"Šb;rb,UXwR!jӂ<ݠ9#$cy#f*OY^XContG$»cLU#.J5@F%2cjHG|Fe'Б~u5#ݑOTTP JB!Hyh]uzm^zqYEanE4*V"1p`24KsCrQMA 7^JCZ2%FG$Q6ƴK i#&i[1# 3K`#/!N 6ՔJtȲMs7]dPAH!\88ԗ2ۭ٠{+ϜU4lkVVMh7n"{.WiK'\^ؤ(BE 3ix-eFbI%մm)A Vj­a#.#$`0RD#$!BD\bb M0"t)`ް(#.\D#& ц) 1 #K*#.4>w^#$pIt}$0m#.>BU}LۦCmߦ{h$#m%ݫOw #. "#. OB#$+$3ω}C)Z~! d^$T^‡(+LЇtǹb? zXӐcm t3rШ΀"tH\eڵh#$iӯ\^/ *:. x!(M!#.Ihe4`MaD#5ݚ6Km@F &k8(KDl@d@#.B#.k  2LEUK̑#$c5{/2PGFǨj7g,Q:^xSz<6̓z||?aUM@tŪ[Z"#.)$Hdw&23ݻ(e/!)*)`%1l#1`ҽ{WzhlR5+M-Ȩ6#.5ij\("9P\5Sw)L(p=RiPHF1X&!5qw#.EH X(ĊV@R#$łGzr@1c:FP#$UBHxN5i/}E%&ZTț` #.D!*0\4ΔE֯(œ)#$#.$QG6ۻ_{Ё@~.E!s{QV5?ʆLdH8s@F-V#$II81!=#a~PGxyZJs=  LEJvօM$/0aUsW1u1--F#U9ܗw|==~L[5m;p#hz1.`F bƚ% h-,8XA c20x46lֳ#$OA&F*Td;3f:tAc)!'4ogmµzΰ/ZylKIhcSplnG~s`(8kc.һ_ׁ{H16@:bWBD`_#&#&i_ԱtXLW8H{J}p#.f#$,$19xnb86#&[V[戅 CU+?5Tj>HLƵUVUU3{*T P}ђDH؄FIJ`S^,R]-KoEAQr vKq$5UD%xu!ʇJ IU&vg Nj44ɷc,Z$cggHNX)me,(] A&g<]BUU֠d!AV#.bB _JjܴL XrDy"pKZh'!}:5;li_:)#.pfɔ6I$G (%jz1% jt*lb,B,V9#&}[0I%{sZ]IإY ݡ/v\"'}#$كf3^jT#&%1"/ȌH#$K!E7|H#$ H Hvt?#&TewmzOJd33u&Q8dCuC2z#&3Ko9m:q\:k hvA#.V#$Yn%GQC^p8{#$|(;öETsJϩl gz&ONvkJ>glsʳŮR&6lw("0@A#$Ɂ3OfDC5~ I(Zo)D܄W8̬~Ҙ{#$R[-@O.h#$#&TbX#&CC|i) Yv/rU4"y+EELͨim-B{jhuC-F%\m)M;yu|Wɑv`?T']~NuyHiP9J A+c\4!f_,7(֔#&lp2p "h$դDǦay*D10~Ua߷F4z&ޘ26*T9I#MTQ8Sÿ,Cߊ |Ȗ$v=dvuP=W~Zb\x ͱnQR0',|)x@0_[:`e;'H9Nl1e0'FwZ[JQ>OxbxqCg&Ai<{9H6$j'.h[X[]4NB7TRznD0;9obkN|ja4B j5 1p>pr>ç93dp(%횷ٚ^6pR5I"8@ɗ:"5&j HZʬJ2UJO9s6Q]zv1RA49`CJm7WbםfM4 hYC#.1/#.#&aKP)#.nhD&DC۩#TH$F6Vl 0]#p6m{|p̨2u!F`"2HwEpiRdOŨsp!4\1E~/SiM`tC1#.*9)Tqa(hj#.B\UPƦM͕c@l٢=GěI" 7ܮB JjPRR46B{y! ڸ0&ArSmQ ERm68#Ym eh@L#RVeEH5`VT UZt b\!Ր#.#&MwLh,(CQGؗo]B~ffu.LSc%pٱ^6Y$myjN+6xNCXPQ 6#L_Hp3m:n#&@ F(ވ NB9O׉ex(a(Č%g&h>~_ذXPĹ {1b6aʧ˯_`?|b]=}w:?aחp pG`RKݱmy4q̴ZobOֽx:wbK|.4'? >疊)TRY#.0#;&P,u#.SGm#UT17'#}ril2 (!oÌR5NYwYci f1/@$)#h=;@;9#&Z!ۺ#&O9w'nb_w8 ɗI9xoSI>%+횎K!u!!mi9ә"2r=Nxm@_#.^oҩ1|(j_wyϦb[ڜoIF2L#.%#.6Vl+vMq#.]ҁ0!l6[Zζ=šB#e天t!UkQ{3Gy\]W#/6k|j~N>r_ -䴂Lpomyl=%ϟEbYE KeGNA$Z"Kl/qG0l1ZqFX d'؝]ႆ0dv1Ԗslw&vE"!Qt #$%S$Bٽ2>yEI$̺i0I.n.22{]Q7ףmؚ6QIyNJ戨iAAAuBЎiB7X*6p8x᠖h&$2@#$꙱(HgC`F-]˭'=/Akf &{.wy]yY8'%By4QS%#$p(@@Fn6AaBԎ8BoL8` 9ܘjC4ib#HN(FMh]J@Hǜ\9tLC@Q.fQzMw0#p}U[vM JI /=4#$IeaPuʻXF#.41P|TUG(AzR>U![et{ec]+to)<(5z:癎zpC~'6KK1l,nq6\;-ڻE m]^vY߫I1Ḩ%m=9W}%CȃF%khf{-ysS1 dvxwu~JkZkN21D ceK+Ɵn-TPĠ^ZiZ.Xh#{4Fбډ"#&;?Gr^{r÷_LM088㹙/9 W^yKvCi^uRzsGcG#.:GG< 2nw7 )ѷ]/^gZ}a1J|ʨKO%#fm!:. W߷#0a$T0MO2"Cm#.XBiF#$3HK̠!h^hu-7${qyNpz$#&,[6,hty>Os;W#Ӷd=lG)G:߭L8Sb G=I F\d]l#.R̮#,omPj,Yc#.5CjTU*Q[OgHtYZMCs0R8a!eô#. 3@؝RHpbBIIDGRkCFaH#$&Hp}#&F~å]6cMZ>vj^?䮈}=ɱ?r[cp\\XE{v,߳oKâv m#.jh|w^#.;(DZ,!艼s*$XB}շ-G[EM#.W9KRdi!(l#$# 6&@<ƅ$iiVQ@YP$U+F$ǦJjmjƭ-ۂ@ATMH""AFPTcJ*"EJ‡sݜosߩ_]#.{wxFZБ5^6Uȹ66mk_V6ab#$l#$ƣ#$k#YӝDUUt3_e7Rjt->V1lX[!5X>юf-+W"(ެδF$1#.Rwy4*!!5SA婕AWˍ=HwMXDL*fiq͐‰m65$R]V6&cD0J墣ܧ%#.|'Xt‘HUe>m0BRR@4č7.)qcn5)Vs7v94 #&ZǶֵdƳ%-&1qVamLlbq ygP,EfUK6@&s-~7[s]Ld39N+8`CǔWjnMNrdקv1q9W1^2V%+0[0qLL`ΰ4݀ٺC#&:gXsI'Ӗz8#$"j5kcF1.@/;ew4f΃%cFѸUPTVĦ#&!iiA\\Z6qmeӝml&A #&Ruxqkd500( ݌n*13T1A-h8%k/sb<,*FeNm1&R^Wzd¦@HBFL"T[ %k2U.rRGBڎ;:ьеr3y虜TDˌzFV=N8FVumb [ڟފ=GҼ}@e@F"#.=ȴa#֥0Qs zXrYLrCƐ~h MX[2]L5c3m>+Wcx;9KZ:t+hT H<s2[fEZЈX,)I@ZXpEFE,V#&b:X3g#Y~z}K䏋[Ū 0X(C$·E_??`@ ҡYĐ攴”TN?(y p}LH`CTU:U֭4*Sm%* V@zU0'ueb˧Lf12*)HxG (jɻ2$GlE;|A7Xΐdv?W=<_< 5NӛA~?]IBWc8zm𑍡;ۺݻ6nn Ktx(T:C[n/翷r4XMP-m@M Z2eXclVIhТ**1fЦ&(٥$$ iFR2!LR"6al%(I1X ڧ{:y=s5G[ =h˪읬AuW|wS!#8z8_ k1+[}RNum#׊#$Uj}#&p3QI݄Ķ1)!`|>7?b]XpKp7=δ,3$%!I!un[z|sӺ1/UvtNzW2鱵Zs i[VTdڤD&H&/MZ6QQ+b2.̭K˵VYAd7~A3M22Me -YLs/>Qb0rcD>N/PΘ1dLE"dݒdzWzxk@kJČش7 ;w̷wQ2i^RoMY5hrEpxcC(E- E(IQ&珅YA}N&{YH--R%'LHQc0peȤS#.Lܵě9 L6m#xyR'IPB<8Ns$&Q 6iI#&@(8P1H R\2G"L$I sb#.#& &y)\3n)iXHl<_.;HV~I8J6gUz&/f:q.@.}g5FAjffX q`{JRJZK`ݕ(Z0?I)ˌ42dKڋCrvK+NAB/{luy&NImrS{R9z=d#$lx[t~RnCp#$vb?U!F,DFVj{fQAN^$G 33=]^w5Չx3!%Uj}FS8?Y~9oVV-$/F8Gq1/i#&qPhh'txܫ+|KCXgΤ1c5,WMp nʆ@tC zGc/S1 |~`~8s(%tYJV ITSyzd,٬#i:Rm#."|YJw:"`~xN#.G x((`YvfF@5~uVPK!h.y2ۑnE٩ #$c#, 1H6s(pc#&҄QF‚`Mrs"i&%7gdfifבا]$\4&H_B nӱ%#iEd !xQ \B(*D`#.#&3 @JdDK,p!Lj1Adᙚy#&#^-QBi CC7#.AB2Аb-mh Z`&m僂43T#&D1zQ:3wx +}k@иUȋ|ʌ"0S!20ɼ窚j"e0/ J!3-}S̮L'Ԍ2eq]A>~ `.]#&٭)K ``7\0mݚDLYI1g]iS4d,Lhda]".nʯ֚S-+c=XffST}g^0P(S5{X%^.dM"zNڑk @eƷxB˕¬mvMȗ0N4I EG@/JfkεlWs}:+64K✕;]uQȫ:I XE#.56\ IXӠTNNaX{Aә88qWR݈jQµM vaiC5 S`Yl0!Q"F//VVIipe^!&a@GTBN\cGclcpf6p6haDgjc̪\6vl?=nMرBp%Q2g w𡨕&%0vJf[Qv3lQ'(˙pt#&kM'2qҶ41&f9MGnxu%맡5;竦iX | y)t.z—y| +dA߈&X]Y)5!%#&aqV p0+?u~]wFQ0Qv)#.yh6Ԛ"myy#U@TDX kG8AÖǿ~D37XDT*̊m@O#[Ίpd*1N /ϸj\>R! _X rI s'<.8xIhGuЮmMX*Fpr#Wm חiX` (=@lW%UE`!Du"#.pS_MNaǮh y8\%DQʑ8rACXn4\g$^#_IQX{Dy̰xZWapD1PC4Ah\Y}5Uktr6^0ۚ>+;YQd\y#~yCȐʿ'#$R">2BYo2wղ'r(6~H.ۡ\ώ P֣^Wۢwg@h;7qdcj"6"0v8Be(wKy6o]e1 #Bd`7@@65fAhpұ̟@|s$$HuTMq NPsWhՇM g]I6Q  F읯!<Z H}@H"ra#$BٟHbT)biI@&1:C5УCH5Ɇ ]1Ӂ65:pqBzyا#DfG{-;#.w:KX6,$_C\NG?#$qhIe D,2yɹ5،͉ۖըqd,*npNM]S5!")=S[6]!#.'^#&]4>{Q#&i1ûmZ~^I&Ic$a>0hzp3#^_D*vڊJ+IFR$N%mjfE-*"^,TRD$Ep$E5`'g2(iLa#$j#.~?Tu 5Pߖb8Ӥ ըB1ymdqmӈy !0"boϖKRK K>Ec4]@Q֤LBiƂ X0/9a`i`;LHW.&<򣀸T,3S#.%끑G܎z6sqG-YLu4)B^WзY5YӍD.4ʃ@} B iHlBI{\6>iߵ#qEF7o$bD`$ef1Y&PmV-f&Л4T֘i-_\g-lٵm&➩hAWۭ;MSSw@:}]}H;=5vA$FE\,!٭==}S"*;#L$v5)Xxt*H܎iV"[iiz#.o4-J[hyqme;;p#&DM<7"#YJܖ'ΔMaWT#.`jC-,9Ctȑ$eݶw#&_u$p)#&5Za3]aF_XVƬJr je@X&13.6?F{g>(1@B"Lp=Yd݊h7խ ,ŪK,~kdXȾV9pKLgwygD @w} )q҈$ALpIF;͉t`!BF dV1$DAΜ(<4O.FI ,I#$$Jb#&z=`߿(b #&(C)@JB#$&RPR.@$ Ђ`Rޯsa(wk=ZIw (#$߇r{ QoSnˁcꚀ  }%5CGSf(xq_) 0d٭P`J0`Hddxhe]&iOunU˞yCd)[TKt#.%FѶ[cn[+'+mJֻvPԪ~l(&-[>(Wc{0y%f#4P9&,U!TY[6ԄByB@R#.dX)E4Ilٵz꿃}#&%#.) 1jj6QLUVLK`1#$#.d5 \z9"fH;QW[֛ȦƨVʹb21XL`[ɭJRIJr^Wx'30ȒwMzÀQܾgѲ2$?E~{06`y #&xH.Ȳ/o,,a!HHIͧ>EW{+&4Lq " dp5#$Do6=h;22РpBfuZ (H/D(Ju#&ƂۤRmKitJ lMEݽ:EkUѰDXyR+K,>\.N'#&8s8ww)}Ua0^Z.% g]_K#.Ci#Xvl#${hӰ5y,h+A׆+.viq!~D<)gWCRC"H,&Sos&#&DZUqMaU-xPhIfPB/WCDŽcc%h Y=#$ܫ|R @T*LQ&ͥ BpyAI3#&d#&u*Y(Q! *Kj1iCThMl,EjfY[ۯҤ6a@ _kLKPFxPcQa9TeY7^#.TdR-߂Yl32$ؘתMkKvۦ-4)R޳a]]J7i5*䨷ݙ]svU["ͫ˻hn֓eIS"Sc[i:"%N#.U" QX0^\F)4I,bm!єͶ[)kw-XV!(Q hDQFlx3h VMIB6˂)E` dTb&H,p#.>( kgސ&h&X#.r|3HrhT#.U6$7UJ35s ]:;MEHvzG@09$ccv ۮ`@:C#xoGibNjP4L4WZd LJ(ZoWi{wzC/):tMr/Iu5f0 g:c1kt%ƒvKvיL@rs<Γ[t jX6`y)L{:`=fOiU[am_:j#&ROX"qxLaWNA - Ѥ#&!Z^#:DB4DbCT/}[%Qhds$(q3J膅UP#.#&om[F1+BG_hzg"VC`psX)M(=}m-%AB#.)>E-?lL TR*9F!B'̐]C =>7`n6Gʵr2#$01Py~o=/v=Gs|O?}Y_o^#(?C'( KԦq!y@p5`WH#$!Pv#&%"Z.hUh`Ix'#ILvʷc\#&LeSRr? \LSJ?rdXvqh̠;Gn  2$z(,H#&?Da߃K%+;*Ύf>Aj'])٪fiha$#.LɘMGZ#&uZtM*fkcAl,<#&kLM |.97S]uu*č#zgw85wHBCeayPCAm3^-m&XI"Z/!4㎤xe:j7}]G}ݩN#&@̖U߇b'w4"?"#$1To0X" d{%@P: ϛI#$[~ƽ,W_jzm ?N]',DQn:rcoo=>c3 !-!s #&yaw#$@ch7o&???18(͖hjD\:i#$5͋#&D'VN.! "I^FM6[FW #&#.'THH-Qnbۚs[ҍ^VXIb"#$ JBYk 3v m#$.Q#&$44ޅ $/#&lH.#$d#. v:.=ыAS d^=6)zɒnJ xR$Y#$pR'rwѩu>Br((xz֊#.Ŋj;JQ%#.TX#$)ݭ]X5&OI E$@ooW؎ ë<|G&26bg`SDKdO "?AlEKkM:*, =d1@B|l z]ME@-} ]ΜCN<\$%OÔq' ʿmO70( }%p#.?dYӯ'_ZUK7run8eTk[lԣһxULqs8iӶeecs&X߇hTK^VӘH0>#&iO?uD f8SB#.inrgP'A]?U#.H"ە#&T#.oF$;f['/#$H#.@ +#BZh91AY&SYfyzqPM"B#.0u@b)\_mȀ#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+#+m&q.=إlԵ=sNѥJJ2&)M]VEv y˂oUѱĪ5F=#v妛=8l}5E;c{] pra@;z}aYwcwz#+#+@#+c@Me(2۶Yl#.C.MM#-h3E;XvÀP( J(c*(D+A@AJ퓽Q@sG/D6ټ;@mJ彸R-}=OXz6trނz۽޽{3yofgwؖo+ϵ޷vyVݾAA_]* j=QZngצ7z:]7$٣"úT#+TT)#-#-e{5蹴:Q]^kۗeIZBH5B7>#.Oo8;<#+-^zh˧tϝw;wp鶅}6b\duӓ6[[.ӳs;}y]uu>o{vӰnݻO;f4Q魳VwRLw&ɶ9͛vr{ӌvܧ{Zl\{u,iTo/X#+wzsm;]#-JE#+@TGU ow\dٝ1=ݶ^{qٕV9O^=oUޡк.ƭ#+ jƀ#+wg7%y7xkǣFknU|vk^nz3f#-_VXMD@{S}vꫳ7V+{z=ý^y5Cjݝ4}{n;ko|m֐׻G_.hNOea{ͬ m#+#.s(o[;OSUO]w[OgS{m@ ]cwew6s8k]l`S뽬=Kv˰績nFà=hV 7v<:Ӡe#-.'7 :ݣ#+O=Jwc#+4%"G,sxۺs>6۶wJ@(Pݚm5N܎9;dc-Ӹ{}t|Z'_i#+&#+44hȘLFI&Д̐4mFzOS@ @&M*~4?J~$@Sh4=@h#+#+#+#+#+H$D&e3"'ڌTLOMCiGI` #! =R!&&SLڧ$P5#-=C 4#+#+#+#+#+I #+M#+ L=hh##-)4#+0#+$Dɦ@ 4'CAOʏҟz#+#+#+qS<ʅ ZwQ6+RL#.TT@OPD/ӑk+{w ⨵xqs1ĉ,a)̽{S1UY:p"#.BP@Ch7CNNxz9Io2fiTb"<x\I%:¬eZƅpq$(Blەڭ6bfZhQVEh"*(@PK>0#`Pb(bY E QzkjԘ,fjI4$a$Y*635%M(Ҋmabf)(MSQEc`ж&4KD!L1@cCLYSh%j(M4Yi#- (bFPZ&liM(MP46ZF0"ji&lF,fd̀AMJImMfZLLPjU4PS# *FЈTYm,)6`f#.b1(IL#lhLab!!e#&F$S2)ee3D% dbFK) 6$єA& IF(āF"Q3)YS0Lh͈&#i-%@h HIPbM5%R,0CJaL0I(Y-$I1"E؀YR6dMdTDʉE(͑&mbHEE#-)Jd%Ȧb l XP‘PeF4SMMjBX#6c Qd44hX$J6FR LAD!&ZfZQTQ4ĎԋLB4(VPIQM1&i,XSlFMblIdS&R"٢4أMJ,dSi#.,QE*I4I&4HLl$F4DFe6Q2) e"d4ȂSd1J"L6 Li,D4Ld6$MaH!EFbACA,j,4hM(bԑI$3I#)E,ѥ14PhM)CbUi(MLє1M2"+AM)f1Ie!e6EEmd!E6"&Sk6jb 1Q1ƪ*6L4B$Ѥm!Z6MFD6$YL!*HF Lf[3Z(ĤڊBR,,Y 2i!lHѩB+d"TT6lf35lh"M%a*ek%Yl)fF#.))QbZC%6ѫFJѪɢZH[D$U&4El2ѵ0, E 1d+HFѴĬb" MkfBe3RMTR$M6hKY)Me-bV-,5MM!m )lXh)l1Tl!b41$Ch1"c&mfK2UI4FLJ,l#-R1(RX٦hh M16ARJ (1!2QCLI5Ć `f,PQ%IFʔ#-P0&6a)dZ2EQѳ)$ѐ4d4,؈ɣF642FbF22R X 6,1iefhԥ< ɵ cJId*ICČPHdLkؔ 4ME1M(ڲ6hT2H*Q DERF&LBăII I# EJFV1h#-&I4(lSDFE͋!IJ1iJJV-ƨh2QAb 5&0#B()HR)ILh5EJEMؤIM3(kH@d؆lڈEL&J4ZLIE%dJlCFTe*4 L66J*MXF"Chɂ5SS63M25IBI5QcDjJŲX1h"0dEC+DTmQi5 hLLSdF%#-LLlm5&6[DR-Dlld)M+bhضŭ&c)D%ĵ6iT53I FZ,أ,#j+bɶRehlCLE&4XڪDʅDĔF*HZMTU0ImLմcZISC-lmMMԚ%X2"4f&(6͒ݛ,EdQB6mi6Xps"[\#8q}o _(~L~#-?(jƆ4{: bvU ˥7\Xp!tosF]%iH*&*qnh/aIe;Lu*L k(U+RP8&ql-+YSmӻ5rNk0"M,OFܜ#B6;#.JL Uy%E0()vN zzmj,U,'RHLYh#")͓*P*RFI"#Gk] :F1,#.2yl2*FoM#-|"0Q30\,.9ZUPF'E|h r*bܼY*w"a)hR).Y*/5֔ 2,#+GܹͅaZ4A1GF[16Z6d}ܗp::Hc7j[J$UcZhJW6REE\ۯ]|E*#.+kLW(!pS)Sߕ(QϞ0PqX%&,n$%{<^UJw\ӔoxIoR#[a@q]v7(\)"+J3Ar*MDV)Bw팺d2 F6KkM7)!Iե`xQJ+=1kbUru֍\C相4R_xM>FH!oXzdXnta1KF>R"60L(V"iTeN$iӀkRũ/*5ĥG?.wEpu00\l`~ ^8JP9O#-g2||#+@pXp>Ҭ{S0dAQ rґÜ8ΘqH\^/WaMX#- `8FW_fU9Zp N3&~ /L1ONm&DPz^$#m-|Qm#-Om#CF,Pc9k9]Gmj7al2561AP#zFE#-*x72.aTiFoMo]$ /*z'Fm w'j(TG/Poᬯ]yiU(//'NP:cc%xr@ӻGyZۍol\MRꈈbh?OƧҙ}so G ;kLgًlOOپ$;SJ 0Т8rOmѡ#- M+U@bR +:ZW7.b|jw2%v}V}*<.GrɕS:zDoc[$5#-JB <SQ\@υ|HmiKΐ J#.RRuoFSmǿJ26Ҡhj'6ʴOb\eojbk?Iqa,Fep&*鹦~wC L#.Jb#6,E"*ִ߽0o~5i#.n.PmӞ Jn;YSr'uJgI<׎k˼#.9]}|$*^1ueLs::dMgwYa83/4kM"&埨և|ug#VmQa9p1AEZK:`TYL*P[oeU!̚Z(Ovɓ썋p;\t XYR6r3T?G詵<(t}W|+8#-H^ :kv<\/N땺7l6GI>_UH:vl7U:u1 DL5:1OvxaB(76!L .D1\;tU-n&~N0D~|Ȫojx*“|P%rtȏG?T1-׬΍6#.0Lܞֆ3y=;i#k\j5[~tzöTn\4# u蕼5?9ѷZ;9P忶̼[/򓼰1j*D-'L}uTׇ#R|&s :MWSk>(4ykϽ0ou} H!:7'1DE5=0^=f`cʾ[BLZYx19F2\(1&DD<Ҥ%H8`,M$ˇcvW2JE>~Z`i PexRh:NDJdL`IĦ>6<ՕS#dd)ru/՚[R}2}?q+ ;;sMYBGɾ)օZ{(:xaj8p;y1yq̿[Ë|`{|(8VzGljof[R)9:sIǪ# U Kd`=k#-onW6%c¨fμsdo(`4,;t)UQ9c:N#.WA@HKᘡ6D$DgG E QAi$ MlxQJj6J8M b?_ngo<9aݑȷ(+'1̲UM#-6〰KOw߲~mł~:p!K[rr'mJ&ծM7TʔmX6s018QͥYX"Sհ_=*_-P-MZ(: 0R]s~SNhcX+ztqC1OoWg:imf=P2*/AAvKfln6JРDbcL`Mm"ь`\EX]#.=զb5ɤ}:t$0ͪ&-!ZUsLN:,wJ%4,w1]a; qN~6n1ަP&u4~٬Fפ*{#-GbR&iwxAm1as>^n5,?HJ:tֱD1󻌏?U& TTM2`)H6YXlT FfJPF^>XUv R7R듮Հǿq !HTߞ3qRB Gws)h1Op`G(δv1 & ٗӁIH.Pu%l0SFg˚o-1'S}֯(zW=p4M=TouDvLl6'dЃꄆk0yìf#-rc#NMZc㾪#{c_Nqv4F,rםuCe=cPȰQ+,(C-}/`o*y06@3x\1F MжF2EP >m(&"XhdϩS8>Cyb =AȽWyKxtq(ōGf^E_#.(?-lVa6S(ӹ\d{ ڷ$bŴ9."ڛQtFt!vSݜurƺGw>Μ_{6w*bLxDC aFTlR.ն.vx"V n/=6g#-ĸdﮍϩp/: p=;-c@nO=gEbk]ܬ[ekl;xE!dUqtbueC]t$,V-,+}v+6Y!{mҶ#-XŻAz"Z)>&Ue@u ßX" }TAo:&<YU^muaf#+C_#+/A$:~o,f GmR2ZbR?*$l_~t㟇(՝H iz~#.?Rl\$R\ӣөïA!NC>1Ƙa\P! @H_#+a[6}>|k$6#-w&ʓ\1~8p5~mw%L#+Žܱ|iU7t!CDRI>VB!#+ΟujxRFzLj>l%>^}:얒=c:D#u7+Ǭ8tT&1ToŒw&u*d&d.w_S PYIq&Bx椙eb`f͞nW2|jw.[4ʂ7R/!r@#+@L6$tL?J쳠mj#-5L\>)nHL럷|f &`An YΗ嫩OMǝYY(g|__Ѿ-Z=^r_Fp(O9ä0map'&6[Pwpw"R߅}?I! lk6"/kн7}~'걅"Œ$&i0x:5r!\>1x#-XɲQHM#ҕW1R`&$4} T 8)}=XuNnnvvG^HX#.@z -I |NSr~*Y;>a*^ #-8d7wPcCl5#-o;e5#- )@U[Dd"b$#.Jk!gZȚ1.>(pL!m5*P7sIp8:2j VIX![^vƯX[0H]hݬv 8JW|8˟5?%"tgƹO,ôbOrb5)كul`qPs}`$5𣬖Ws9gUjBrgEj(RKod(0D\"-TD$TvLR'O+|6}%z_q\q;'T'AɓYi0%IbM{#8~`qӏ\#.zQIc5sfzlF^1oޜ4Yc/#.~b!Iz?KЛJ;DZLS}IorVB37/e켫3=NYt_341\v(s=41]#+pqk 5Bwn#++[b/]̻]֞;#1#snZ*Jgq-3-⾜jtn>k#+DhMD)$MSXY2h=`ҡq)E(he364 ':ZEhbp6ţSӋ ýzc`ި К4Pna ,w)[!&KZ]o4\0pPE8U&aWTCyp2b2#+{hwc^Κ[q̋Rt':K[*L/QrNG#.@E`9AjJ;ɋp[l3]_\1&k[p괌`ߣ6hN%7斍6Ƙ"[gLE"V&rg tT7.ěܺƑ4p5#-5)ƫ`0h?{" X1,q`͔.@#.4`=z}w}Xv/fA IL(WM^}*߶܌L1bkۻ]չnIWo#+l4Y 1cymxŹE^H$gͺ|pq$Ϸˋu^ޱETVbW#TQLj`|]CepsgtwC 1k/yj|dMy_:y^/|8\x"xO}s(Zl$<`_KFlu8dN)Gv/KVR&tDaR(j5mʷܾƟ"tEm Ɓ2!qw"F)dXUK@md d ::a#-Iu6#FJEQc޵ !`XY] +J:c(IHގ؅)ڊSjŚ *07XL5ա#@F5j# CLDezϯݻ4$_pド 7B[u[SGbK(7ivg@lX/xQ;D#ǘg08#-qKnM6Bi?'h+yb9iȦ`b":|s|X;z"]eBqXт:\Ϝ p7lR!6<ע#-Iu NݮXkz#S!KXR+M7E+_f˲>ʋ28NY\&!>Pr~C#.6^tmkTkp|&R@_5,R~όMlFe7jy?F~dpL< e0NsvEۉ" ğ3a޳?&lf=͈m&*S1pJӷic8.,g_"uh@#.v@3֑y"/9/ϛ^|sVj0tfۗ&Ti"1E#-:#-7+«xهʣ'?#.ԘxQ+*09ǿ#+ތ"B(Rl//L/3A#+1?lݻmcnT&-,x:䛛Mht9+tPZf!XJjgmVM`XAdQEҤ\KhUHhb0h+#-1C,!K]S40(3 אfmj (#-rɶCb59H @O =:~۠xrMhw#F)j6".u(pUl4 mG8YAY8t` 6.mWPK~f>+4Le!+_68i¼{S?>*Y[nȧS⊑s P}bry^%j~pF]ap5q@ "-k]]rfggG%wmr~Faլ߯W-Uk:axaSRJX8!G?tzr~DqXW5̦ꨈjiw@wxC8(|: ET];~>^+CC||-4ޖBMBb3E3jK&1*X_sѦ_k[Ң#+R)wRPi/4eOZ {#ݲ;3MtkQƐHS#.X5Ӗ-'ͫY%;f?5}zV (瑉"ȤPΐx=-ԏ+b%Gh&Tw@O{4mkk{$bA6BU LU20S~= ;nm6#E,OD!otѮ+wt (x~:;t";s; EJpN[2<"e|̈aw%},h>A/EkH.ӛU;ǿl5J9D#-Vp+{%u|j1,Ϻǒ`ǍzC+~J[aAWJ2pgz e5 "&]+vnst?ɻ#6{Z ~NCab/RRNQF7DN$_mfk #ҡ$:x?>lV-,dcq*$|KKdZD0EozW!AĩBvy3TűW&#-^ߵ"3ܩrҎC_ɟmdc/ IJēEo𢸍2A1BA0U&#+5yZܪ(agG!eufly) CebK4 PcCUR(StH7PKHQQʓcЌ?,#-e8tCrrh#."DzN%1o#+U o?iiٲ s_}[+2Mܹ]>{Ծ~U{9:$kocYCV<Ce/-1h+rpV}O\O_oރoۅ>ͣN߆\y+6×U3^nhTݯ/.NCdEãOla[zcTV^Ygl(b^oyÚ/0"?jz?H9\ _tW'O#-UNEq}c5K AC-r-taWU9?+,e]Œ4st&q9o4ݿ#ƙ_CV}eǎ-z:T#5P컚5s19:$Vdn*k's>*+RycXߦơxT[~'K./+fo_P4Ɨ;}&#-ُ*z]{>wsyD.({w::pz_Ww`^=D|Ot짒hw}q$ÿ l5!H n h l,#+(iP-AR^ -s`IBfH&Qx Zj]]I_DW]3>yqߧ^_#-,,5\A(mjT /V$F%N/'kHyeIࢂ"a)n${ml< n&aN#+ps4P|ܯVꪔG+(!ID'1hύVqjի B`:ǿG't$7#.N߷4W4H~xpG^}kwgcJkYuWU%N-ö22j#.#$pw]mC^N҉a o9E5~.NzX7ojNǣ{ k?KnT֥[5y55poimƿ>ً$ayB(+GJr)=;cFmw1[-DNG%cQ!2xhǖs̥PQEmp! Vse"1EPdQZa+H)YlJGUZڌF0T-ЃMHAty c] D܄0RA#.4lF9Mcm#58Vt-'e)ms8$|3D"*?63#+!¯]oYH.%#.#.H**q=9qG#-֖$ثFuFFZF1h.Yw*ĐRI*9%{{Bn{.DO>p@ՀSoA NFWodj"=@gx}{}I{~E-ӣBb)^/+tNćYW3%.]A0/H#Ǩ=zY5)R#cމ!aAg*H^W_WD"k8rۢޜg64ksL&\׮!>}i#.ZB<ƻ!W$‘ n]AqM4.5U.BI,ʪoj# fupyp$wU'#."0s~H譇G툃 5w{mnA.V{7+k}PYN͓:1G'֙)ma!]}s&Vxp ^Czzeo>%Q!U($HV:ETn#r#.5#+F<`Ozo%ѕQQHPK*)b25y}R^ߏWH{XU~߬>? me#.#.X<Qs(^#.A3#pkvH)1=jo\[1A'PzZ*0d+DU3ceL2Р*0be#.h/]M|["FcVգsS&!}fw0 o*̩H86Do9 W2Zի!Vcmș)ޛ62A4ab0jBd^7@XllX -#.TL$wg5ŊĦ)ER.j(,Ps/$la1fnx0iGHr> [AJXf}`>cjQ#+Oц:~#|Vy=p`Q$XUD qnnEtH^ZQO|tUdNG#"DCLȱ:EAWHh'3[P6qzL=5Cv#.'E0ltY"0)ExL2\*cB#+JSM@Qo" 6J3%QJx&A.;I5h #+̓BpቖDNؠŁx*;HT+ɖcarR4R2N#-T:+R,)BLF0JeVA#AuŔHBw |@m(#-(׷o/|޾=xaEQѥȈ҃5Eb'¬SUkx՘ij3V4Qb@h{LYqÊB.>}U@Dmcn_ZMN0" 7HrDuAda%a-0vI<4nu#+=\<(D C%s?F5e1mqcy,#-PQFڦ7 [o=&/Jz,T>;柠!uDݺfiZ"13BX-vE_FWDM"%PAiT]6ZFЧpY8B:>L(dΜ#-[oY3qp)׍qKEXx(m!%[cd&&HrS.#-E˧w17yX=>3#-ag9w+iT' '6x*L5㝤}jbmVfX|FHGZ1HWXzu.ٺ ^ drz:TnTnq0X6c"oLF]Tǫ㼁 -Z "Rn*gv)(լXq\o~(D΋2@88B/Wl*B0Yʮhbׯ [A:_*2b9:C'dv7H-cx9{(] @(#Bþ҆bw6o#-:#-1OQ_ȣF:Gԝ`6~ad(۶KsG)+kw!xlK0@;]0oJ6~s?2@PwT˃@n{YErظ>]ydC> @Z4$`n#+ۈpKXgD+q?<vJSHiiW?'{Lۇ^LҀdB#B̠A8?뻔mdwA<{cbOPcѭ}~Bqq`-2 3y!܍0#â-sqlF??j%`R8 <9yofM刾g5OLH4B~b)㏭noϡwp|Mb F]P;N&:J{F`M^̿\aەMftqdʊcu28c"l(.Bm0JɋaGhZ+R\Ô!@νpdGU^`(anC0sYLdUVgnȷL4=>L+8|Y.=S^-`l5IrT> +Ae#4$s;N(F./_#-koռNȆ7Hdݙye[X^aϞjBBP\yuh4tWA \pk A%S9o3'Ѧy^H[BG5$(dW/`hAT%gH!ĽG\Zjm .r+sd*Sdݔ<7ImPsbmC#Ꮌn]'pk4]mϿ~tusJ,r/DN5r蘘 ty3=c/3Eor %EG?qNtLEEJ,9rKr~  T~h==5P`O^ב IsXę2 u0rR?M3#-:?_2Y&",gxpg05V+-'}{mU*V +󸶘CGR#-͎؅`A+s, `MQgE+#.S[Jo~$Iʢ}b^j~8y$*GV7SYte>s?f5uÔw\S5qCktC];e-(QbS2njޔ!at`ΏdZJ2b<#-Gk]|ZNwe4?[{/gq1$7j65?`sZZ0ZFQ-M=#.EѠ6gk]Q߂,Gq=W5z4/mc n|nvllτ$BroN)pN0 rEE(܃@J)rZRLz[+7NP>Sw9K߅&o۠#9#-?(0v}\tl޷?>|Nvly!Z7cU44#.-IDhW#-[LGY^"#-9zOW=cH&?[ӕߕѫ(z+/2>ZU.pgT9.P(uœKjb+51UutZO/ݥ聰,g#.ŤrxKVYJx8X_/-i@V6Ua*mU2TYB#.,j䔍]K_$ l5EٍycZY$'+#-O3/*BAFo^($0'/qcMN6Fʃ496QxYuB"#-nϮta##;FP}{l6ƨp%,ű4z+yZpۖ}YGg1F ryO.O|0ay/{>߷pLl纕ig6dV{D,T͗uy4 I&fw[.*ĔXI%o ]0g1^afp.а~v#+hdkřMc 6.rWNl'4ԳGt  ~KBI7lզ//z NQ97vS ][!5SEk#.BˮͦF][X==R+=p2dN!gSv_4`.9w+;=1͐x9Nꬁa9򤣦R-Ŀ[aY売Cqw?)P:"`ןg> uM+#+9;7j7KJ3$4njDBYM_{YeRC(I #+[Z֕.N#rߪ\u>{~5Prhٿ|N2}t6~`8OoV;<-r||P6Q۫}󭀥qVY_cHn{cϡyt=th,xMKW\E$#.csFeiDmm8U6T~'T*+7jʱrĺX[_ Ӯ b*dܦ~ht[( >.qY_j8t(TFτEBI,vbuHDUptG$#.r2OѳuV7 CqlQG[LYYlM2NFH= jTMM%s^Upc5yd4ٳ{zo~NaݷXU*Z ײA4i9rӪ-P1#-J箢U ]56z-cS1Ac|adpbZIa\kԖ2-yj6blM˱dnbA NH{tDžbscǔ^2(TE#.Yr6smT 5k;xyMc]yw9.U ʧng,Ֆ5m5Tbј{d[S/Mzưo|/bLU#-&'HF2[Cc'-2KWz7*=&#-bMY17*YQH,2UPWU#.Y@宁7nhl2|.d AXONƣ]rC?r_\AzGnÎ9B[=twe(){&!ηTMS*P4_|"ągɍ^$|b>GOԎ'vG][J^TvMEeW9m2fŨXEYB(MWtn-rtq#+Y{3~T\e8nLzuXqëjL¢~3#-Ģ5)0ܸz`"ܭx]|۳<Ѵ|jZF$E6颚,깁5h=pR9TbO=obS(GQbauMG KM)U;c#-aG>1;Yi %|Asu9/#-x͇U)¨ehMq`9#.?ܣ$.(osy;{w=M o- +#-0T&^o&vΥ(b4SHi{b_e5Zτ*,Yaɾ`|r[~9,vY~#DqGF#-@ $zqO+{Jljbq!?T>_r:=T#.K _43M_VO+5:,TVDsF6Z7'6ϙ]QN i mCx*;^i#.eOlYL=7b]'EKpsQ:J9֙~Xb8>#.m+}xA%yF{oo=`fʐ[~k1g{>hEf0 #.`;#.@RLOeFC%v9 @85ń`te%ru;TbpljB2#.ཱུ1ʻRtdr(ÿ=[trY l =@x$(j<"vBta1!7_ں486S62PǫuKgZ>#-kct_D>}^tcÓT )YF(zfbԓNH-8@uBGS/ s!Bc#-X/CF0| +Z+Fj˻dӯ4TkP?$Qn]I ̋YïE NuCp \5B<&4#-7ypj҂,9ğ}sRz_T(^{EF4NmH͚`w G]^0:#BsF=`ѱϐYnZ|>U".d/ߐ¬`Hv=$ۥMqY)#Qmc'4q"lJԋI~'!d/*+7 H -J*8['!Sr@L2S=.oALهg-skaCi["˝Bs_ƺNb2V+ѪFP"F~t^[ȩlSŮҺ6)H󚠠_z>yU[ъ<}<}[g='QxΑ<˒غg:tᢰ4 C*M`1NAo$E<E$}s(yy^㡞&f xҵavĘbDp]jvb3{lF}O#uP& 8[ 3wO98 ^dMиASxpo;]<iq`+_ٮ- +Qj!=0881^;HT&F ?Nq 0 )J<eFIv("Y4-H#.juR"\X '`&DHK2Hmjމʅ<اi'^`ӇB /EwDCP%Ϳ>SN9FX8rB3ۑyi>ũ/{QԫuZ;,E@/MPy70,͂5\ S~N@@gf6P$r|2@Be%שR4sLj̐q#+P˜ŏ٫o:3]+cԨ(PF*r%VqhOOw! muN"x>:੕I%k{{i{ZC=՞qB֯_~0 {:-nxjfxoOc#-#.Qv HB;ߘD+|r&V+ysoQVU`< c*|EYbh^xRgHģӓMñEI-yp_apZlYEIDW,4 Fkj +ɥ4n:6db v*`rhP@3c4at,Ͼ <p|Uv'tOp)f\x#+0 *#+S#+t"5Iz ITsC&4÷զfB#.3 |j-dx}*D)$QI~_6Whؔ+IM@2#.Pk<?V;e!a@P؏@׽wԉ.2}wmclZ,SM_#..6(;% 3(V'J/HNDH̫&Sh1$Yz #Ff {'|"&|#+Oߟ|;l*E6WO *C!QnWCTO#-nVHFM .$?#+蝻Zt 5-Ń;bI;τ>҈j#.r9V뫂]\}n[x2~znnimuC>t3Z0>8+>B$iXXiRύ4>\3gD}o)˩!3P1nH?An::yװE-:V@UVk5a_OIW3q*ЃsVs94/(KvZ#Gh`ݖ#.cj2ly38@8 ߦL9䁶'a\Կf@4#.2,#-N ͝l~O C P~[d#.32ϕT9o_n<0 ´wrPϡ%&<:'RCR 啦.*;!Lh lKQj,:Ђe\'sۭˉhC)u\af2vثs} ]ҵֈ` qmh[*Dq#.:SH{(߆h[po㲅iGTqtEʮolݘ*H( -$Z㤽#L5}MLH+n^UOƮc"g?s 9T;H#.;o#-p8$,M'kiȂQXQGU>\.!ҼO}We`Cx0oavenX룺wjj+$ҹn2֡{oQ9F\(Px"ޥ"UYZ޿kêߕpa99.zrRH2dGy@$P~h_e 0!H7Hqb -7Ctȩ]lP8[#-eqI׉{ iӂA;u[pc#.bôǁg8#+b(j#.Nj'cӎ,J^wDG}0I#.0H@&(d~AA:TVP)jI_#+y>z<6甩ĜTp;dx"%4jPB!=s o@Onՙ|TZ@EßPxJrU冗X@YCr`Ҏ$J 99@M]%$%s6Jᜀt#v3@2+Ŏc!tޑЃUr5#-:;R~ziJ^7zQC* @d [\?.|')#."N_tR}9uZm_T1'ʘ=G*H-t&_ӎs;i$KDF2|B $4weC4[;A0jR%`JzoWcC#- 9q5Ա&!H!=E,nw)&&ϖ.kѕoh/+A#-(Vq=ΣsdUbN)b{l=5BVvLŦ#- 9ky6@/&0>%ĨceUB|8"m7%E>0l@/#+LYuIPB@f 0Mj/fR$=g=Qgƹ-efIK/E:!uĒY>Qs;|U̫7v"^4RRZ"B_̊rO&Wa;e/ [|u'?0]5eGgd(g:#-&Mvn=5NUZV5ީKPV+]oa vCCN9^dyj]2R?4Ntp" |#{{R"ґ`%b:zLYLzdKRy|= jZ**6]xr^,~ұ{b1$VV(JTO|{xe=\c Dv38#+v1h)S^!돻|?G<~FF=#Ο{bh$Tu8 ;|ZP[R gf'"M&<A-JmW!/):т9w IP?)ϯ,TURևr}(9![8~Gc?jxM<0o6Dݖp ^GOn^oArx;410LA恈;Qpd57!)6w9Skϣ(x5MW!)` .Y;=ЧS%F#+䡠#+5â"ab+AudD#+HF0[j,>P—QA[&E72"\)]@`P@gYRJh;]Cҁ&ģMK͘A1,8H#+q%/[ G$APC8BW~< ,겴J#-XQ}5>5wLP({kpB;X8]( scflS8wрC ߲K!T/{Ldf7٘eswtT;ymGXBW{E>T(oQ-?#-U#+m`>+2GGoa_eMQ-dW^>5usEBp3a7[5H)%N[d<֮'^1/' ڈҡ4Zo]YG%M6mQ0b12뀒29Pa)*3Tj }]ע2Y902yeeWӺLh;<-ig3^n@~6 h_#+6OWa! @9ߕMů?-߻m׾bi,K5a)cϪ`UUFTF?,狼 滍h#-]6+7M,Δ!ײ3,շ#.B#+j#FxB(SdWigݹ5=G٠ԛ#.alq6}T:j8#.}P& gaF]]h£Jݴ7psI"8`CJm)*@(lUeYVHaةFvM6QaF@vz_fq*Birq)RYIƹ"5ay9Y'9HBOP.|"D,ֲݹF?~FpWo |:=#.9CA)G{8wC* #-bսMj[ck\sj-rם84i#-1았h4J]5dU)#.QZfyĎēӼ7EGÂHrI! 5ʲ$f4xA$F];Bϯ&{47pV<:8ϧutH-hV*RJfO_-3iP<^_PEܚ4 zʸ5[b$RJK_#.#.Lƕ`³b"OL͟5Y@-@*(EȻlj򯀨j%,BTN#-;6Iٻ'@۴Ք IO%@xFL0 6*±}oW񏭧=Bd,D[[6X);ԼJ<m$>}rCwyzoH*)tWmvv[eTmH 0Q1ACS8qAx&^&v8uY{cs #$l=#+j |J*)sTt،Ax0p8fG<@`P!slS,Y#-F! __;HtH)m//a$x{"O#.5rc\Vz#YA}禅4VaE *C(4^)`s5R$\5FG80G8N#.PP`ȢPBI#+[@]<$|kW\ :WqG;:ڛA{^qMܼN( #+s`nȦ#+48: b+7eۼyf_qD{ٱTC8wJ ]xEߩI(0[ʉ0p#Beu*_figEu&N1lKfr:npB=U#.n>Ӛ$XIƷYQ}:U&!7R6^Ã#+,;2R2 anlu %Gࣔ B@q^"&XsN R`c$CqB]m;#-K;FW-#4MeRi4Kbޘ/nl#fhfWC%lGͦՒD!HTsC9M͙qk:laOz50|(#+(kƇYiSdUd݅b:tA}[k~ Ա&k@#-!$RfCht侁0Nsҗ|?;#8%<~:?% =o]2}4NH`!ĔiXٌٯ?/$tܺQ/@; #+o(#+U$,i#+#+ ]H(=QO,Y|RƋ[;1|bWi~4?7]#0 M6{&Ҕ9_^ߎTwWu^#.h?YL&/:t#댖K$?i_fn HCG!Hn$ @ A*T#.Cj'I3"*y$U#.7k\*m3|H]+*{E;aa &ѕA9:q/.fglg#.tѳ?(Z~9 *ggcOs:$J)yPY~#-~ zⳛؕNi) P#+,F IJaO`)xoGI@#{ #+=#=}+LOײC]e'%-]tV7<GElߠn&MZiR #.m1ѦK87G#- lUH>>{|.a/1/V6NWRljTHNN#A._ٴyNZZ'o$#+=<뉙/#_PÒhË?b3ҥ ,'q:%UZUDE Nz Ey=eWqK05d8"zֈU<<}zG77>wN2'sN}V;D&#;wQ}$rwCsE{xq#'.\Tb\QC;@O3ǪvޭQ[rZ#.,R~D@5-_k 36Drh #-@lykC.'@4v@^e t#.^ޘbՑ DS/j[?Ӟw#-_<4 ؟@eڣXbI_9o(()HV9}NkK:1udk$I0*P)>.'mWaLD8n9݀b#.OP04t3n.ǂ  A"(a~=8 lN~B#+|{UVh>WО}cOM2)Ҫ.AxoǷ>T#+u$#+vV8'oXE^keom"EuMQ˟#+RL%i)qA#+(JOucG#+pPP/^Hc4BA#+]?]ޫƭ ]ÇN`(Dnu#+_'ko84!СU>b>W@>w@-pBQa*)J9/jQ^=7@<{eC5Ž,$);/򾙽i'2j\I~z\§T\c}Ep\8Qޏ.Yp״\<(s!@\NbfsT ?N8Jey#-jo^#mvJKt/J}H9~CҙՕF!W6\P߽ϯP~+ۜ&<28GW?~pG|j͍6b#+Si*whkeS,pUnTsw{r3tvn+qQ$2eC0R/﹔2å)H>/o9Gn:$WoUOrѤaiVZV5`H4Qt5lK$$5l,.<βc6l" ˥ ww#.zP?)_Zo_lzd&}\h/h*+=}Sqqxu5x\L1?l|]`*Ƽ|S=Ht)#}O#.k1Zb(;gFK^ ^թrUt{+F4F+*Yʜ#-\jxMÀՠ.ɲ!b1c7UOG>E$ g%CڛjIHLIƽpĨ)Δ#J r oS!\V86NftK,]xkT|cki-gǪv"c0C{mvUN_D_5nqtn*gRf;>oEȦ"/rQhQzS+0}sX(:8 {\[=w(~O nwt*{yʇ͋%=G4CmG|gxV X'G@2xhS/CX[)YGuIWN8pɖ,M43-|3 mfWpYR8 4]~5N=CY"=:6i#\3~~o,#.0m/pqEGЅv|6}V=j)>Ҝ$]C"pgTwLʟ韺m4#& gW_#-!Ӟeå[S^cPx'A9ae,%$fт*M  ؍K$ÃDCE~k3r޿x?.utzb3l*Nu]D 9 YD!yh|_2ӒHʄ\I\yA qJ`zG$6Gz#-#+#-JzyKFRObe TrEM|ĂRQ1}f`|!3Ryox}\٭֔,q\"LDk@b$D6$k,0#.SpDQLG+PC(uU3#.5ٻNxXۡo#-E`GBm-#+K;#.&M6ޗ¦b:>!ʮ5J1xĐj o8$zMyw_Ɍ B#+ޠ#+@x  oxSWu@\}|A$T" f#.+έI =N&$lwzhѮ##- =#!#TbE\sˍmq%#+ ypsAN#4hNWdHh#%}7J~88\줝/D1\TOiu *$b$&1eUE5J. G#+-PP#+)\#_=*DK/3( СJ z#+M6Y\S#+|b܂dɊmD $`'})yXY:@e63sD5K?Ɯ8x_)<(z!r{yEe{' 0WT όI^WkݖZFU5)SIo{oSFoqM|,țPnY[σ0v8: "+!u=Eq!)A"ysA]K)Cƌ+on7Ghl:E:FIC;W)|@P&J#+R.^Xj#LZs&J z[hc;jH׎_,?Ĉ4~DNPRyZgզFA P:lidiweJ|G@'M&\>cN6MR2WΡGs|Y6Pn!f1Ctd?cNaJ#.;˄ƑS{ޏy}iJDig%>M(5*`{yM+?Pȿ$,~靏ϫIV([*xH=[`a}47\8[ftEe;HTLB9Gvg{:q&] 2b9YQ#.Mv `2Ǘ6tV[]QL6hi.|q򐄎WT3ݣdᠭXyK0R"SB~enxWNKq=_:8;-#-!1(fqc݂#uKՎ$D#.+$v?C0!8D]$Dt4C'U#-+΋IVb "hr:dz--9Wkm-6Kp+!}l!ˇ Fߣ\z0H&T=GF9[)s3Q#wI+Nx_P~}TptTpc#-&j캢:=*d=UO#-#+.qޯăBDMWs2-·NV'3 Q#+C;Xtc#./a++pZ#.#.菢ۃ_'9V, jՆp@u[PrJdѾOM͛l1cK]=t:vhu ;G(Y)ב+6ی4ߧ#bMfl#+Aj2#kZjEf=u9\ՃV}k57먖V=Z#-.#-e\>#-;U ]q,A[_;4(+OxgIt9.M x nIX6d1TrU}#+pZ#.bmh6m3X4iя(p}]G#+#.TҵGs];3m(U P I!_?τHB|el zrJQ *b+UPIu,zJG]6%7#-S `* 00tFbYvѢ}|L8;,6 nP(f8 8#.&hm;RD`˻W3jrQMwZU>ǪGbѓó_E8#0Li/@3U6]@hwh)>_o"ؾ(#-alVT.+w'ȝjZ6s69=O P6HA>mnf{נrDuSjxW[=ʶnq~\5d-#.#+XH)8&\B6&#.{\pޒ6+`#+P#-]#-Lgqe`-N-I#(ӳR=Z'66s%涇S,%lSى>#7cZ5P]ؗDXb D|=]6Mh!f K#àQ?Ka93JaꂝDCLs QX$ Ö(L:IQ@SRqO!f= {w!v;:Ngl[%JoѵGCg~^'3allu,"H Xt.Ҕ`BĶl"1vkpC#.$aihloBEZuOzW _"C)Ïd=os`@\⁍Kc![\ꇳ#+vXHW6#+wI!v${BقO~: J F:6,#-,ćzdx_?elmmkM}eK5CO"@Due $^=_+dowZzW, Gy6;MQ[=$'i>gǠuZj_D[)'Q T%@K/]/ئQsy'rz`E\ajvDWpqZsAkN:D X3stKBwI$2lJؑ?^C r?g|kI=C|  |N :>YzYN3$v{ao~#.BNtK;z??b# ҆T|sRjy;CsJUF5PTVnM\kZ'*.gʏz\[Be$#6(Hx;Gt_p#-$Mr?WcFEDd`\Ke9`?E@b#+$̆qݼ?GQiS\(4!AJd@ئz~BQMN>c#+@D|hd$GB$%W 7 aE81eATr#.vA:08 [#.h0?.jdst M.͐'/,~@XSQ#.D[⿛ߴ77C@e&PtRPEz͖l`b0 UD0G?86 O XiZ*93dJn0#.AғT"M1uX?]ydcpJ=kP#-WN>/&4cer@huגHTiRb]jiZiM~6yE%9aBtTXK<ǖ__M#-46LGb!im 2E'BYf` RAI|ɒtn5вVT%tqȣ0hNJbF1-Q6i޴z#-8ayF'=tPRpwdm+.^Jq3xZś\CHdNUdDߢq)P.n$@R-KX9WKáϪ>:/eЂJJ֏кpXŴ\b#M@/*>KAQ/ډp@}>kO;s=`,!"@D?>܎;|MXӘ[7UiKC߿>!>(<@$dl=VHïzӓ#.sC83giLOM7<~KR_l.Kid]MDf Hz${U*y}/j¦q 6P#+r?E>SȭMzFtjBe7KH);s4?! CTF2 "!g^m f9HG- %xSoW76)D^#g<q{$.Ow?4W"hrOqQIB:ozq8B|1p*4PTU1x>{:;TiOzz+omU[TP1W0QAGD6%#.(r[#-> Ćߧ>[$%m}_lCK[OMI@nqJv;Q̝!vKJO5l=;#-7#΃>#.9hakE]~L xLo6;R9xxuwwf~hBK D,"#+l&#.!,#.u%QRA8y8W7w6EݧyهZ hC/A܅ kwޟ}RNU 8vauA:@@`)Gz!}7wD+Qt=mcoB4Nc@!A`wmxOQ$>EfRbK~jHNz#.ާyGJ59y0i?`9ZZfczfB#J/BB*$#7C3f!@.* ˂^B#-tUO9QH}$nB=tɆ,ϳ/TdAmڤ#+U!5HD{ YRmFo;wGticvD;HB;R~P?t3{9z|$x#-)B0>p/Y9tAYTv $Kh^XE45ܩq}&Fh~^?VM#+PCB?~2b~{?;/nHJ{\]*sH-ʏ[* Fڝ SyI@9#+Nc8H.b&?_֑@q]zǯKej"E "ޖ &f@ \z #+ ll0x)Јxg^^#+Av_}U0讧i%:'ۻհ{T/\MӜƤpI%* :`r t8#-[P@D$ #-~ 4xzp>^ڪI6#+UP!h漸||bpSR"#(#5W#+X/ <#_5sf>BT;,0kC۵!}dj/=p_벓WNhmFp=;9 =Aݐq.dH%VeU-Ëó˔tu],'>o#--' v⥍'J䩜?1}F#.DlV BBͽwPߠg(O>րзMe#AkLRyc[^ڦ#.&*-/6s[7|p$dTEiDߌ_o)s3M0l넷srgo{ޚxu -Qύ7SdSX-,ICcbT~Ah3\ !AERT2ܙ6 BGv*aSR@˩Cz+!#-/vpM:}闅`X;4=GaշFn9'۫-psbgxCJ8׉2*DdOM#+[q[#+nb<2B`Y7#-t,2D.#-1Pt!YW|{Ha@#.$QdIuCxݺOpѹxm#W͕7[mB2Ό'dpֱx.Rx"5Z77JF敧^a^B'WM%d@42}'HT#.cvZ,AI$;g}pJ뵇zTX~!s1 !.waa"X"MW۷#9&wl?g;~i8GyI?jn=@(a"=wBHD wu+KyϖDeȿXA 4-+U k:mh*l ACZK6E.fy?ۋOC)Ospң#.~OW~gQ&šR#-u1.Q`|C~iZ|3goyg'YG )>J6ϺSIdsml$H~/@A*q/9Uqh!!eC:U1u<M;t=RkhĚvZ+ ]'xH1"x ̨ZȪ#+d7Ҕ mB6nnWc^.̯pzm`rF<@mֵgމJ@s{櫸-CW#-"%wm̖& s#Щ}%/Z˒.K|ijF+,D5`PejM:lRne`*~b 4a5'З485ТNLTR\)I?c(f@M!Ml^ ccb3KLAfO2JapGXo᪚X91WĞƭ䕷p?0YR)OG1MDqR*wԝ6w9s/ y[z~@Y#-i^30BA_O#.dѮr4gMuqa/癖T,ӕLou~Hs [ͯ \9wr#.<#-Jvw3t#+T[_ـU{_Ui!pWiJ;&?@̔nC5~=Q[wa䞦9'/!`?}񁟉q*}4 Y#+ P#.D'^bMI)`$n3?@X|FD=#.6BE/>r0(pe돹Ҏ$蠟#.Ջŏ&{go.y?$eꝗ}כ_*OK?GO^x\+N<~RD13F%E^[ud s_[{k*k0(662uKC,4u2ot0SQXNLg5n#-,2^0qp:)0L]Pdr۫8l.ax%KK~Q/?FSqs8.#.Sw=#f߷`;`N؂ -OZsj[xP;Sw|w-umMnsኞ9-@Aa4T _fhg4ΘE˥7ykAJヿ-0ݖbuXL ,#+>^\7T0AJ%1~o&ٟGY+_V37UqƳOT}wƘj6Q9DK'ǂSc/Cw7w9N#.3P;:۲_<!"~D~8DpCK "D\w}ʫKf): n\x~ ra'Lj-Qo;Q`S^[ڨDi.zylkǝ+:VeH=Gۀ^#Xg`k=s5zCl" aJ#+d?bBٴ7DZRw-b]C:ÏJVw85-!q+MSggAHL|csw.ݘd̆A.t#-{P?k-!ò9Ƙ=>-S!/D3\r>[zV"QGA,g.Hv`9a$`tsMS薭zph5T''sy,h"TalzLT0#-Y%)Vs()DT/Oq|{ ǁe >" L'V M#.Bjn~-ۄ#L?#.8롡9E_|,2jMO>yK7@?5LAth[Ҫ3S#$c#-˼v;M$eYy!ᛇG`S@P~#lRbF6Oz\β9նIUI:*``:[u<@U(%fǴ,fe䦿t=N914?/ [v[eIjwtPaM{#-.k.>DŽ*3AfXWKu6/ G| #->˪-!vs,oBwNbH@owHS-\\`?_`2i7W:CO6IJUBMY*gBRb"X\UꢃAj2c.rI3DcR2.ьR?̓"ͳ4fbo[J0ްn@ln?1~䮰AԯCOaQj۳>0ȗ8B$cAĄPwNPoqwi.vHῖ73=  Y C5QIs45#-h b+r dP+mXHSк|s(Wޛr(Fs;!ò]SbT$ ɲpش}9<#+ӵ#-C]RYX9a1MA&ØlM11u{! J#.J&vv q5BZ΢wp/ܹ%\2a}Gnm#+dޞXޖV<*o9B#.n3ɑ }öʼMzcIAxyQ8_+͞ +pd^)CB:{;dY<זtݎ;&h7Zuo*S)t;ZYw[,ζa74E#v8bBm6$>5e*Zmnz:m9#+p(yOz9Ǔg^42OXyX*R`'ЭAg$S;N§{"feCڦ?v}fSz0 $TۯgvvzE..%HUD|sFxc,{J(//)1 [7pAk7y\L:\ g9;͖ K]փ%qg#+fx`RA"@sэ`TBe8ufjWC.oDNsz #.=(==AbMTjnz,r#+rxdG檈p@ᒔM9yG{t'Sl* zw]Nŵ $#-&;6/r%'ckّgNNR-k9 d!iXP;!ǒj<&&H v^B,tǐĄHoCj6;Y"T 87 2wq3jHflNJ; 64×fVA8S#N ,No"Eh,D(EF#.R4-+УΜEr]9%Цf%L̼feʳ,̒UٞKé#-Vֽ[8\c˗ɪbIJ|jX5OI+͞v^'Ֆ-_B88m_uPlEV,W^OXk&b9M [t/='aAb_ :["zC45D=` ;MmSLQGRdLȉB3y#.Jd&a<Uy@cN>#.F#-#-#-/SFq֡esHwRWhX~#+tDTj-*㶐3{iŵ7x#.YI!v#+pJP80-6^S)#-mZ#- Eır}x# l}AP`,*R#-Dv)P8#+z!b @D#+#+2%gK?1͉  B:~UwV.0rnp!x V3鄍"#-yʏ,6>9؈7qobt'e8EhB ]l@$ @a,}m3eFB ש(@>2_LR>M־܎:m4w#+\C*0@4d`EW|E#.H,`B(!G#.kV*eknd@K~'$nvdG(ijI#+\R:#-a)`c4 C#+C`x(b4+iR"ELbޮzm^75ؔnJ^عcsr0L$CXsNZ,2*?#.͊0H4 BZ?_NP:@m&dc"x{}sU-!%%4_r昱 K>g /vE.Ϧmn8d:D=x?fӹGH"2&ǰ0$Yө·+'V9^BG{Vky꯬|Ts2S/%s`(`e\#.25LXIDJ MҧC4 1@1"v@;&2^?y~NU4}zD YǀA<ZmmrUU񽰅.י]m*=<6}Q_ ۳orfޚxAׯL8C̖ɫn lq7Rs$Pgdbg5cYҘQ|OЉ*T͢ #.mOm&SQ"5>P Ew Ud@bD@#+WwU*ÙhuHYw[-'{x"؛_3f3$oR,Ax#.ݯem>l!IK#)QB =k@WpbZ mJ-Y(Q%IcYYX*KlkI&Ti)jVkl\KIj:꺡Ӏ'8 AAIzh| hLLӹˆD[؟WU̅ ]6M,F"7/C|(InQY)o8d:gYTƢ0$ED42V tM#-%hkIb/X_Jșfw#+)U>p@E#.f`zoI;#.2iڂEᢁ+*D`X-uA~A΋ٜ]`@4!8@(' ** H,#+mr-EjԕnS6U3ƙd9%oo~'zsr8U!$@5Q}__X7qŹM%20OcЃƽ;)"m$>Fi{}=?P'ˆC!":/b*0)#+B4P@#+!DSҪ7w}},dmZuiC1D\?~4d>>O#.#-<#Ņ<Ka]J&2;H[ԝy'] nbd |1`rǖy'[nK(#-O[AOއ fQC7vZ1D(jkc$bwgyۥĥӘBˬwaoS㬠?L[eu{zߟ#+D9AwslQݰݏ1q:R>JZal4.aC|dso .\kP~zt.lc jtnuCh`2L>!%422eo?b Dx;Z” ؝ZqdVC)G#.wf!!28:b I J7SȀB")*T=Tu'{sO)gJ͒_.??R(Nɣ W~*ϣ&䫻ܢhkoDY3@2zv8-v28C6r@F^nԐ#+뚾Dhp/`~p9@/R&p "<)4eʺ/Z xCLQv[.!v_~#.x |.^11T.W ɱ1MPXpe^3xdr-[;#k΅{^1&~-\Q#.{@ueK#-?nURUxr8H[dFP%;G|uZk4= H()}QIeX1zg^MW@g#-~0 vVE&۾}jvڷevujS%)>#+036pSuafXgVv}x ,L:pC #-ϹƽǶ#--pEfGvw#.!p|}C#.\˙ kllvT6d!u9,C4O"qW ϘwdO#S`x@ВHn [fyD|#eCM6p.nnCbT"Aa a0GCjcf@9vպ ^,IRhDד%ϒZb^=ku9016V <:WỷƎ|9N҉ qh0jnd!9>}02%xƹB|HX\:FȌy4Y,-c:l&dC|/if2#LV,GWC{~wNd۰Luzt#P!0#-7b0]SӾ&-k9[z޳P|c84CVMH?_/s"'f3BKĶBC#-FTƒ)ABDI>p&&ftCФLBhջ' xLwX[I)gn:\/Y/с{a^).u))h"W$RƍuNF#+zy#.p?鬙|;.cn. #.J`x`5<3X]˜[!`#`7I&ooig[&4S$.wy}3|ߪ#+ Q&P*#+-BmR´z ]?92&,DA #Ctcήj\sf:'3AQ4QKC}>u9NXff`;~ո`V%[j;ie.x/n&ӵl/QE>z7@Eg[lmdNv)?lr9I"Kq11zGy"@Z4Klfk%[m##+ 4-C;#-a w4@QJxV @HX&;D6+83DAfw!eWhXEU*#.@#+4:mM7c#!d(oYEu0&D}FG  Pv2–Ž,f7LZ켤[rSeeHV#.;FEAsdW[&'0"3|dG%duӱF(L}摔ӆ#C Ouh Hxrd8'#. PF^ۻ,wqyg#.0&(}eKLsשw~xd #+c,Rl`9f.>mLy>Î6tx'5|aXwtѩjwve 2#ٜHo;Y.EF7F=!J1ro7e(dNy`nw^fҍ֊#+QanX(bshI=k@Pafb5BƎ$`%b&eX;;0e-@r1#+权G^N7DPA##+!FBsn91wLMZ]ZCۂ5jA$Eԕq9# ڲ0mG?96L[U)dqts/⨌^q2{6:H'd2⡊r v@nxN(N#LSߵh$J<[rhh^*-sZFd$(h]V#>KZ!H{$"`Df_k<<+C!SL$hILe+L,iMQi3PJL%*1Q4ԡbPZX׷nhd֓IE)B13LJ4"RDFY)4dIE0AhDFFlb&5Dd)2i6f49vIxX @L(|-ٍ|?~0hG){ŒL7Z1&heG^X`I-~&(GLuN]hHWpiq.*C.) 6#.f]q$8h}.']kZ)7"qdNNu`\GA6cFU2#.Ti&EEdIR35h6KLQȞ:u311Ek2A,?Gg LR߃;>#-{3TTޯ#.ϐiWL׻n39S1%Jgrgr_$F0&>z'ӽBCnSᐻTUS`%w #-%e~vn]GzgסbsH3uUXuj0 T(U$I#+ܙ1 ?#+Jtv%Y;6gDm>x1Jw#ۚdq;Ï19|xu,g![UO֮7TMag]ڍo#.{bq3obN2]Ha+VTCuCN(*iÞ1QA:Wŭc(ӯvF]stih!9-FK6A]LQJz $=2𒕇Nȭή٢aAiR{#-`@1"gϺ#,٭h(vFE &z=XgS{ #+ۅ&xWnC#+#+P(# :sMD׳>gA8]rii:ҪC3}{fJhޚUFP+F8/?DoJJ<#+]薪U:,ؙu5'YɼG̅MYo.? ` iANEq+DlQm2#QP9vڔDө=O;#Za\kr1-ֆٓUŒf#-3#-s:LQ桦ek/9r8t,M5TԦq[ޑĭo˭S1V2AfĬn>d8̭=7KŘ4mYEMpS1R~n,+mfyp"5mS'S2s%gn^3ut[ۗXVjr:C/hr]um1 5#0MZ;}$9ܑUN81* L\Q#O<T}`es_%4#-pj`|$;0Iґ0W4U2ѹM6W7:ri(Iz.Ѻs}#.3>-jYE aH$Wɭ ]-o W.{01H#-d8Mej#˯i̹Ԟz[Q3#wqV44.QoDJNb1\>*u7q3;mz|B7}1mCr#;B~t%%١H\XFv·38pbѫ%QiK#!zB4q[Ι%QNJڴ&F Lc R-ki5(}m\}Ch7jҹ#.a#+H RK\3d`ݑ/MD MT^ aE1d})y[6ۍr6+8g!v{QZ0V/Q ;d3$! Ed5\nn^SL&˝,ߎ]̕O,Kr6pE&.HNLܢ7L:8FMb퍴oTsٵr$q favfS.Lt4׹"S΂Bb%0MTvCu !W#. qIQ ̾[pD& MrإDaakZ9[>#-PЛ.#.J%S"ML>?#ڳMZ0lJQJaaB>s!]390B P!أ;x.E-Vtߒ#!#-nޞpsN-j95Y9Ŵ`۬}x7[*OfLF!ȟڣ kuaD)&&;rje×5< ̇#-[4M۸JlwZj:٦o:{[Tц!My|oZG3Ԋ-͡q2U(*bOӽ$yἜRب6=:uNBI82-ZAĺ6,LmI$,Giܛm{lvSZq8hAN(3Qu29'.efpBd!$җ"UW#-n`:9Q2Mv˔l7;v#-:[!͎ @:4cC;Nlh}%`V#[A8ю0`pGEjh7#+4aZiLv0"8o|ӓ2sY59e IJȠ Avn͓]{"J@qb fa#.ͦ!1!6^lЛ#-20hGZ q1dZ EhɺqoP6K.f&3PSBpz{#Z[x8o2A8TX$GaJt=#-Hǥo3duSq5(7TP#K2jJ3"q,#.5pQ #-N8HPhC3K5l3p>L6.-Hurf"2eIYH. t!(F+ݍ27C`!d2ppvbIvgN1lf2\f$#-Dhݐ4l:S1`e뎕5r@Z6Sp#..] mѻs5 &HGM&Iĸ -;#-;MA)#6l9p 8pNNM#-Ψ #R|#.$B(bf5(h…#JDg0C>\ w+$$A2HA A>UN2 9#+AtR~:F@ JP $&@0-YyLB976<6! Rؒ|iuT|3Jhss81] u,ťdL]vmR^Xb:(yq.\gd鲶Mr0\`uA㉷yĻ2fU! +gji;#-?P5o.qĐHzHUֈmF@;a0A@(PE,Xb_ZDN׫R!5C]K1TFE.P#+!#.|]M>yrɂ#+DU,Zv.Ʒ]ٗ7]7lWO/˰x C'"16.Gz'#+8#.(ęy!~$CPj(fpE>&X0#-痏nC^o?-$\?3<ܟ}69n#l#dޅ$1"؅H4ejm Ft e Lfkv M1ȅ4bޢUJhtpas0Y#$<;,JJ!h?K1O~yy˖%.3#.٠G|ha 2#."1l[cFkEDD6 2Ѩ%dI !J#>ci*Wbv.Id #+MI(`v"=]Y cBK#.N)E#+ Hn '9n #X:vLvQ83IIF53pE­rh&E՜,$z>i:/&GNz{nhEGK]7ѡ5-j[(Bb554CT\oC6BTLAc98tg7Bn{i,39g-ȑ[q.NBiIxt9+PunguHTJH2c-rgB,5AGN2I< \9)'p #-PNt;= Ҧx#.d29Yuv3c exaLhS(҃cS!N T$ 2+#-\ F "?@"T˟;kֳ2!_̓>1Qdd&MPj@d Ň?񹂤{f`mJvCbbFJ=Ms0# #pYlӗ͖| 0EK[#-[pM#-8kSk;( i&<> U" ~5u N8Υ>?CBoȢ#.[OkWjvKQ&n‰YDMmr,&+{ծTie-FŤE4%dQ)f#.Qj#.Ij3#-Mf4ĕmjkjhQYuj6Vjѩ)m)I[jiUjWr%F~퉲(ɭHͲn]ihRMJ6מvIiBfcS[#.Mե1FeRڙ45uf+j]Q1*&&ٵ V.?ʬӗRVc|:K#+G(^7 9#+KtڽkÊ(=Z0KT @tN+),DF13J"I*0H$R @A#+&y2ZB"4{[;5M7u)j魱W6*mFOjS0QM)@jkYllƖ(RJMRlٛY-Қ)R|V`"JZʢm%Qf6f5&hi1A3Ck(-aԕbڔ RJS%RVLk%҈mElhY#.RcAIJddԳU6j#Z)bei2eɓEJJmͶ{ڵݫiYmYjCIoyk͚ڔգUOeW5Yh)lJU֬k_QRX׸ۓȭ Qb\ [pm3=uǎ m`0SRX#-OAf)zIRdyREՍr;lfޙ|C#-#-J~έ`qxRVvOc«w)5e;;$d$`Qrس!BFrdܴ5X6Hj#.+-1VRD13T520+IEI1IoM5\6pmC#-y¸#-Tz-GSp|<~P:N35P p͡UYSh]%LE_8Ю7yzg֍T 1ۉvg;+Ů E 'o6{݌՞[F$#" Ùb|sl~]K-m *$RFgA,} #W x*&^۩$RQ )EU! Bw5jvگk+IkSKӧv␚s" q. )첊ݝ'$z_1㮈m3gۻ%P`]e=]Qs}KwI̚/K_u WMғk 2_Q&Y8#.P:U2h3q#+-(MP0+RmZE*i4]*ɉ,ŨVF5R[J,XoAU"T`(Z("#+( 'kYƖI]L(AKP 2Ӊ;"FCX3iߘh%lc0WZ¢Ȗ5BpRiϣ4,~X(']W4AE#iJNWHl#HL8dTR\kAPRȹ!rblAcF@m{JVi퍦EF!j P*2Jl#-[|ɡ1ڨoF¬Fq8J셫zTB#+:+eeFޫ|M-uRE"ȥƀ"B,eE0>ަ#+a$e#-s``#+#+:30#+ђ#+P\2L>ئwFXn"Q#-DW |v%( "Hd8 ܠ{Bu%9be0/|OyARӟ?Ԙ!;LOBf5ߣPϬ:v#-F}"lq bQR|ǡ!}azC "H T@( *" 0/"!\V3n0DYq0~73%t`Q̅[~TGyԝGc7/=7ğ`7'=FXNOQiɬN$dTCDh!~%6ރ=‰dR(Z"4v.xQˋ ԨaA3K}c#+ߢ>b[x{?LH#m+[#-9eߗƏ`bT܉!$DP#ae-d~]QdmI %0:8/ƵDp%9`L$aY .#.:{~#.XAiH6sb;I66f UxኒeJ|m0tHjLK0VIkc#4.h|^{ۮvl`EPm#-fVJr{wonT󵹪64i .+jHAbek7 i%8H%&7-׻x6*D#rcB HXj1kL4I)+>L䱼xaEXVmVz=eXeU1wW5\r[bL{ZUJX+e&̈XR"K'+PApjӬ3L tgR0e..3u,PyAH\؇oD28#-@ 8Z)YXwj"mDn,e>! :PP2;h|imOUI 0؞\݌pܗݲg+Lʞ+nnQfue7^LXhbP.O))cn+r9=Cxx-#-$#+e#+:шM?VF5*Y*6jTcMD/UF5wjUKp L% Yt#-KR$OO"ôR(*B"BJ~gBkPEyK8x},`yHhyQ#.E} ZJ#.W7i*078  Ci}J$D-U]Vɰpp9AoUQ A_mWܷ/KTR,s\ךEVԆ6ץu]W"B*k,VD]^te*9=\M}R#J4\T-\d1(*Y It ޙTA5CP#+E>88Jv%RYfz>w-*PӻJ4ҕ5Xچm564fy+cAFIQ,O."yM@wb \ wj#.).՝F0wrZ#-(}?'}#FiBdߪj!y0YV%0mM+Jd+D.نCQC[͐#8=ɉDDaVdUC`,r%PJ];D#+9τ?%&$@mubQ0nz|i(u'#-Y:DcMҸ2P~a8Beʌ9LQ0%&JiMm-Ih&3#^ 6er-,J-S2lؤSY&V,mSm,Ym]b X҈mL%k4M-bض A$DHBM0`QQu [-a5p#DF6E]Jټ£kҵ-A'"vYDN"nlT6(K~kK{mHZ5#+62)#+{PFBA2*-4eQaH Y`2?n|<\ 1kԪ=<#+Nq@J"#+|@D6#y!ĄJQ*^!t4#yxE<˟2XflHhGwT6JXF̻ >D##+Xdf d-p[܏z^dM#+g&J+'a'FxA@8-͸ZNv#.ƞ`%j58whar1hѲ?<)uW#.>-M=8A;/6W4' N9OY Y8FH$Qm͸k?QE/6 p)ߑg%x*gOnC|__*#N#.)%#.d=O:Z|lW(62"Y) _+#-T@w(@Ɔ1bi|m#G#.T2ʥc|߯bLcD]blN9anPN$mW XcI( 0J6RH КKȯC6kT^ͳ EĈ)Bc*1ThwF'T*cu)"KDL(r@GN3#L$vEHϿ櫎8KK98wƆ\M$(#"aIb!xFu:#-!p"cyLha!e?B;^'XO%J~ݵKɪd6)hYH,L Ro`P# #+l=~staqoL8B9 ?,p#+0:1ݾl雰R3&Htmj RX@2QHK'e'EEꄤ RP,J,VQX~^J]7ytJB<[HHc-]g((|DCT)#DQ]eK#+)vs2jQCB_Ć' h2FZ8٤[HazwTyS-m"c*ZmT6sl-޴wkXC(zTMuq0#|7r_|1똊jr#+KB#-3ZPd-)rމ 5,9# 8@#-$W=ƙj]SpDM҂4t356SxuND3U #-tF!tʬmPd40jʥ=vEH=JlmE:KRM.MWC\7 Lkd 'eQaM T=ϟLY65ЁYT`#+I#+گYn]T^5Zޖ)DTyePykL#+Y:xzgު#+{ a/v>u1c (,#.^9p:BGOB]@nYGC#+rG8Cj|#-:v]!i}gz}眦oKO*۪z)l A0 !h)\HlzU&K/HlHtDTJ1!(0O7P[CEƒ`M@CET "w_1 QE0-Hشl+BlMHѓ~#.mu Lf7mgֹt̼jKUT I-1xsvԚ`MKdc3DDBB /{&؄D)H֩P#Xl圲Ȕfb,#.YujY2IYl#.`G&į=.B4K y1 !f#k.MהiLRBcevJM$ˮUo#-IN\eʵ;8V%PϠ)UM+VY5huFJBR'd\I{`b$jTlS5Ы6ntkmF0Va7fZf`_eZLآ'XT$*t (̊+ğ'~_~mVJ)|I}c=<|Xd!Ђ {+kRd( [bay*C5B!V#-Z.1ŗnŐ"0#H2|#-,nk\O'S3AbsAŰJ+QlG?&xf,vM=,s*4#I6!Y\Zr&TfBqTr#-pKL>1fE@ ~rb,Uwe:_Ymʻu<#+~ߘ^@\x0{8ɴP n܌ZkaF4 #+,ˆЃRjPHVmtE#_#G;5**Ջwwn#! bȘǧDD5i@,A6b{MCmJ"Q67H1ᅸDa4ʜ͌c#+fL hc3 jco.q## o C+vH!p @67v:ʇ%ekEko=mMI/Qu[f#+Œq(ɷ,<#L'<։,]%c)Y<]Od#-Y`ㅳ#blY[f7Īl)b #.X"LZ)NlpL̘>|#+ǩNewbIe>nO\tz7i6NVS!YJcBnF& jMVopPJqKƵ!L:*B芩#/ C!`&/ =zϟ5L0FH0DN[`uAH܎#.),-nZP|%; 0X] BE) DH!A$LAm: #."ИTAE4`iT`ۢSob:r:`vGoo: FBd}2ÊN~#P5Cc襥!3Ht|31G}("SlZVH]=˺Y8gmRrr5x;]uّʲcԻaKoCCmc4^1Cfmlޕ]sǡ 0l;4pЩ:kEoƆX, ɒ>D@ʦl!bn^t8#6"n: k3 o49:"Hdnƻ;:M#.=sPX_$>ZqeDMlH$(#Hx]WdȂ!IN jpN,l=V ّOc{q}k FMvmu~lkm.#%>آER@<6rOQ4;W;x*+n[&;-kιtmͻL:V<3LHjjwuXJMzZM&vm*7B,BKI#+4!7XQQHQ3[EjaeQ#R0#+"I.p)#+6r]5s ʮ׵ F.T1R|[e’nMufQR/TK*4ZѬmfmiYL%R#jfM%WoVH~0DE$U2#+X,>÷|}yiy9+Ԁ66BS)X o-@@,jpkIkFWPF?,kp_7(|cɯ\BpA^*6`*DTb=Y! lyGg#-k4)ec+iLH1'ftpI<ܛ0Qqu'.o]^^u [Ǭ9{w5Ai]%}NFЦ6d͇0s"\ӲKPցxMðhtKEH *Ӹ #-1.gEXd^w\+ a9 (Tٜl6C T&*ᢦp*ΚBr|M(!\pj"rVdPR[|JcI@2lB^  6 殴0x rOՁ @[_ʈpL_#+Ed$ (mʫJU2 %.ST 5U2 ɑ?0\+qA!LEkjEɼm=xw}k=t^]IjS[4 g8Z8ʍRKh5jR ՅZ#+ (0RD#+!BTi/Dbf Mp$oXF. "t g{^0!81D`zeBFK[|4d uP9hd~?ȼ07C&zN'#.@ /-_0^wBxt9<>%cNA@YvoLFѱ Y~1:>TAUuިX(#ŷ0覙#-2 h!;ex'uYѲ]t*0CZxJ#- TRK؀"3H#d6 *Hl="xgw2X7sL*["PKF썶oF N3\l?d!~>RktŪ[JZ]URI* D 3v~OfIА4pRհYZ7~>,R*ȰC#-ݑ#.J%5QwlJss/76$l)Ԟ6f(E3ca.m\.q]t.Wwno,o'n]dۼڦ֌:srM%jRgr2d4Ӛ:k)p]YQhZ۔i#.AH#+nlUܞv"Dvp;Od8#+;OxxB$E j"4 's,-PSul4P>(8 `} P *}{/#.Ҵ}E$ ~ώqn;6#+z+2HH緫ZV-EwV.]x"s*` ,"@ID2"{Ot/+MVWzhlR57-Ȩ6#.6D\("9P\5;'o1;3 )􈦓GÔ.ҡbJ!?懺6&qwH X(ĊV@R#+łGˢ@1c;FPɚm#.~}ٙ g3 $D߳kZmWqP@-L꧜!_dQ9nPx*m#-J=wCw;=}u>Y'#.<3o\ax`UMOgr2#- #+V$J`^HOu`i]]mqI'?U8~X-ڈu$Y@w#+Z =Zl?k XQ ԑ,I1P`."DP. Z@GL6ٵ]${mZ#+ekDWAx4c0f+M#-1UaPБjQ*\mxX(&eIvlUWy kyՂOoPʫBЁ"X`\0h6<.SL$yc]ɣ2Lj R,6Phم`!Q@bTZڭ[knn\Mh?OrqߗA?D``""Ϛ'XM͟8(y tQ8x?2/bP)41ǂ:S$[bDR!'쐘g(F?JƌdJeAGam!Xt4"#- !%Kŭw4WiyfQf-M*{71T F-sDCku?S~xsT1@f5ʒIUbQukY6.j~k|5vŕ܅;䆰Ģγ=9PD'Iy943cb;SDQȵDž벉 YOzv4 PRUU֠d8<aM2tkPwnOXVZaÒ&cۂZF}=ԥt+$'aO9!PHb^qZ7I$S6DžvA.S,-]SUOHcbbtoSRzs7׆0=:A$x́NffLJq "8#>Cn>ҁ=#-3Kp9/mˆ:ێ(7r1rqBx)l|&[hM2{AˆC;hTUI|Һsu>HAo4zXހPQlt;ZQt;Q*@ j1+cELE"#R0(@=)VnqI!/ƎAZo)D܄W8_̬~˜zAF؊qݳ2U#+!0Ev@1?NWpS$dKˬJc4:ݚi(ߙOt }~U"#- M>M&?NbaG#.QDPQ]J`,_gm<3(ʒHǻ3MQ5IӗLhb#M5#%Bfɰ¢,b?4sC,{crkm376ƶstd Ҋi3ͼaiw/LwˎҠBɱ(!^8Ъ8%'4 dG* 1hHf)tmx--M Vc%a{8i˃`j'ΩvO"zH]@.[t}7frL?c:R`ۊ]mh7ӄ u'kEPjM7@0XLdq#- 9%#.(+L 0RĕfTUrW+ntt#6ٚïbd  +lƚMc vI/B*% |j6H) ƒO#jirJrU#-DUi1QYfl3sT#.DW FQEh9iIa3&7ҥRG*62pB.f0Χ ]٤EfL%B#.D3rz#U%\mpXoE+0$WE nT:(񩤆b o0a)PY#+06)-#.c#-*JFB/co$"$2siDª!VaMD-Ha㐎( e+So=k^m=7c۠5`VT' UZtx&(Ю`1@Qk9ƌ hi8EJ"dBC[SR1F׊fk*<X4f^Phr#f 6!Zդ"ȍ084[!IL0ҹ҃A&LM:W$azV#.+ұ53V5vdjP†C 5҉z]=WȀͼX2if)l(`6)bH(]aֹdA"|"(ZƐhQ^ݘkA`0bШhk45Ed{f}o\RL Q59ED0ڼ.{2:b1(ۆB7ޱh+9٢[Ueb-Ke6468$P܃%T\T!š" (@ K `#.6$2*LHؘ@/i,܀HB.?"kWW.⹋wpݼO#-Q^wWK~\OW#;w[毯i(-ءjYlI|]Cjn8tΏF#+$$B>{}+WiV'ŭ!ە#'6gwZ6괚%41RS@K٥ "CQgz1|/ȅID\R+':DQX%f9Pi #`fH؛$7omu҇9GwJ .P0n@b‘fAF(SR˄:A-%MhggNC60F:/%* -&mvwƸJCrlدxk}HILԜ!YVl5|#-5Bgtyy#L_Hp3m:o_ F(>利 >\rq%C˂PTPᓡ%g&~φ_+ءLM3 4}ߌSzG{S)t v]hv`@~o.$a7V(FsWCbO1347#.zDO1|2a#-4z(u PPxiL״RDz:r/EtmGVR tvY#+M}kYmФxEĪvf[dP-CmA~#&3$U)"*TI.ZRQgoR\FHE,@0àcqb6E~XksS#-%n݆,&)(21" l(&kӖ[Fb[wkoMn^*+mk!ljU]7+rۛ[;TU&[rUucUoꋡkX6 o'6E~8!УB#($=ɿ$/eX<4pE"؀ӊṔ̈Z0xxUfZo#.#+_ϳU?y>t&L1SW2$]wq6zm`6U#+A#+[UZ6#.ʡձ[Ȗn )2V*.CjjYFlIklfD?>@K+uR/';:QT+L=("e/U*zkJ(Q KAA_vL;SI$TOY+&t:`=v H*emk)Z ͒Ie#. Q$eI%)AI32Bm*VV2ԴjeJd  {-l[C2lFFHp(41fPA#j#fQA (ŦL E#.Q c"7#.*#-qX1*B*6 `aJZQ@D!D#."E0$n_%PAj,`ş&w (;KBnӻ1u+[vZ5]MjmImch$m8T7D B3ry:}W}U-R{$>+-ߡ!L9U>҉@S#.hC7RK 7 6-rrbx98( CT?˺{{H20Pb_[a#+YHA)wDE`E0n2tW.JfPmwjEcZڎW ծ[agډw?ά*cIe+"}M9"Vna!%y"TlKy{?C`{ՁU)}(zvTT&J\r\K5ilQ߄moi쵁͜px~ &~;ddǪn>ַN'4&GdqwYGh&/VŠ`#-p Q\/akF{Z4@{ETR!#+ҋ` =X:bh:՘1U,`[N$w;ju0OS[6(K Gl52kgoۼڮ_V^&9gsP=ĵ2=ʷA9Ql. 1WD]2NC=vŤ{N=,}2j'ZV')bQ$覓J`MّlT'$ xƼkÐиA5"CwL=v'K4zzBH3nJHDF7(/%-D!YMo[]FrIvqĝBFz;x[tH*d΅]\.6=c#^IFQc]K=%#-zV3Ĺq<ԯw#-ZgP Ǖ.RL8۰JkBHyRv4"$D'/OHܙ:,3Tt 9aZL#-xy)#Cg6*%Q9jJig! Ehq+.]pid#-]f+2 ڧ+]4Rw[Ai3lC92I'/i5Ľ%}Qd:]{Τ9d3'8zs:y&tAx`/lY0Lu%[1#ɥnHHilTf@%= pT:y5жoLQFy#+ݫ8є^PXS9J`]#+#fZ4BcGl7cY7r:(eIyNJDTU0k-IesAIfnpk B9#.)B>|#-t.1 іZxYRdB8Aor!ף]AǓ0<^ Gmz3#-c1Jz616#.&4yOܨvf 0,"!04;#-0!8` QнAo0"4D| dn*uhlG@4:RB(XK3ݻDʄ4 iumsi՜G몼l@BҒPPR9M9uI##-V k*U`rw1xATrLW*0R]'AǶ[Pq]u쪍tR;ϛ`q yH؛q̚{3w)qg#+L5<+E#-)bR ե#+ϣ1Uh/4[:̖=#.Ӹ=g DX\ኆ#.yd4T[:Cˑ,!+qGq`Xxb(Qm?tM`a) @)su筵VGK1m#-]`EfadR崈Ab: vp |#-f<`t.,18b.z!'ɚ!X"tdȍ[GjюFZ$WaF &DG{cMU \l`nxdoЮ5)!PF#n761$:%-%D ClBl5{}d$I#-Ԏ ؋wyjODS  x@C#.ƶՒ2VdA#.#h6((Go#+Y(EHMVwuAFjkl(2h)ٔ-S*4dMDFٌZ1fЦ&(46JFQc$)*SdSTцL6#-D &5kUr̹홨fg;PS.Λ4Z>#+s6~$$gAD86LJVu][A- 2#+6:jI#-I݄Ķ1)!``ޚIwv Ac$G.`}A{@0bzr#-גI tu{AAlx=]z4q ۨ/_WV66K]am;C3U-[ETdIDI&/m65)&(640(HT,%d@#w hxeBk(dQl͂`SaxJQjf##-.V4Cq}t`ɌS$Rb) h=_\ƴ`HZLH/>I-C4o:;pqo:wuNS*&t5[}t9f"<1!a BQcD#."RTB(Ig-dPj4 =$bl$(82dRRK“17-q&;k{z'r6m$G:f6r1JN2Er&IL`d:LK% eU,х#.*g#+P8J)d#.KH[͒DI#ACå44@+m-=({;8˯/ ?C㻯gQ=%#-z$#!?FϪZW\12 UC5+a9d`# Tԕ &*PS?YLm1X#IC5jMWs"!Ym䢍fJ)R͵ZPS|KQ|k֙&#-7 Ҷ]/`mirZ|t~7C6a$25dllp*5%4&M#-AS/өq\i2Ћ:vl'46)Хto{ "/״۟DN37}2oCx@wABRdc"#b+6=Ga( 0^$G33=Bb}Am IDG4Š~킩[~U~׿#.mI%z@Gq1/#-qPhh'tx*=,f/_8R3>^Ɲ԰WMp#G:ݕ 8C!uCc^=f#4_@OOIg4 :{/1 K,+}a#Ё'4ESNO/mFmnd[wofXqBKB\+kf%)df~xN#.f^J*(VAn?iB%p+ɖv܋w.H`URb2Bt7ڲ0(A]kA\(&'=13 /Ha\eXfǝm#-bZPC*a,R῁[iؒ4ԍ}\pƓ6kƠ"Jc3#+ Liv1C|e1#.`uSQ ' 04hݭܤ*d!!̐I:80Haww#.7P'MHHQ#.ӴSH`Al0e7t6"3ô0S!20ɼ窚j"e0/ J!3-}oqU/0LgF2Â^z۸pNrIvE00,0nߚDLYI1^:iS4d,Li:% ZZBB<⤚xDKΡM[[4E̫ӊ4̡&\es nDм,9u@oqppJВ-s凉$E;1FGSk9XN|e(͆t{OA5YF=#-;;-#.t9&+LFe%oݑ=̙,u`LCpÃ78ڃdԟw5͍%nWGp=]sr*΅ii+BFčW'@47y%64..0E(Xw0t8:tc,iA,;؆+_ dhZΉ -3f#.r c,#+-#.7xzfM+K(y̐>&a@I *ǒ+tll(ބca*-)E\6vl?=nMرBp%Q2g w𡨕&%0vJf[Qv3lQ'(J4Kg)g|7>Cw ld9>>K3"#.kU<6Ѧ%b&&]Y[,qS%##.Ti moX,.̓師a-kzmTBRBlUh4(LI[_&L;$t M7#+L;vvZ //SJ 1 Hq!Yc;LYI ,e JJBSx1MX(`L9bbmh ˁ=1#-mTV w:bnVG)p.8g:c3Ό:N,#-2oeg{*YĐO\lN2A˫& ݻ&oK q`kCTڦD-YYffu!eRf4smHHdhXt`AnC: XHIj(D{ XWJjwnٔZ#.Rtҡ4#.5CE[vKAK8Bݙh6"#-@`l34&C! 1nI#-'@)]D͆f\¦@c`q#+๖*P K#.A TP`綋.T1fKF(d9#6.r*AoDA@a1(kXZEWF90 h3h@0KYact𵅱P0;#-I50J8,,ǀ0SCc'X$ds61aE jm)ĸYGt&X&CMC#."b 'O/>1mA5TUew6oDCӶOp2 |!,"B@M*PEO#.l9+Q\VV1N /V5]/?>#+܆OSy x\ql6̎\ڛAR4SD3hfHC r>)G׻nڹ*=֢/ ܢ%|)WHs=Jh(Gfv0k=vD~^NW Q'v\fR'W(6+m(%iD&ONl~#4ytw:YR_FWZ:mv*l#.9sm[畝g1Y7z^H߅^PIEGF\x3S *}vCد o×C@sF:wuiv"pd&Ӻ]p3wfM0*vZ0b#i`#c#.I-jwn#-p\&X 8K$)AمFƬ-4l~U#-HE#-ҞQus\vӔ1\5aBD{R,Q 'gl~=al1xkpAjcbV[4h1Y#-#.44[թeѢ;CLd4D͡hN"Po(O_@TrȃHeOThۇlfڒ?({/l~$h1+$ khdA-aNKM8`wvܶlOVU)f!P|\KtgL.غD,@#-!#-)OiQN滒BN+NT8i|1z(Ř݌6C-|MM1Xؔ|M3ǦSZ43vߏW.(*amE%[kI"KjoԛdVVeeƑD.@KJ(n\0b16*'dQH.#+46e[#-^],V8;B(H2;/?/ܦXߖf#6}8ݻ@˕uC'~]Rr+{ū{H5J pDd2h#-" #-A*Hi.ZDKYP+uDr+`Fa (o#+ILj*x;uZd#. tzB)H*R&܊`9ZON7L#+0J^,2 FCG- ԦAΫ#nsM@KKP|MW!jRCh㈤l)ؙۅj'iḠjW.>tl#.SVRmfAD&-3o:9$!Ni * 5Jp¶5bVƗkcUՓ(܀#+(,lE*Ł7əu5x;qaz"/]əw%YAبփZbZa1`3[&2E2'  vlx Q\xu!#+#++KE Bd|Hx0a'WV/Q:< ȬbDAAIF|#.#.pмBĐiHTxьV(׷vކ" ҈2t#.!o~gϯoo|,JNh /#H`Rޯsa(OQx#.$d'g7 OpnSJЍm&C  %53_`[T<Я@vEno`n2Eب0 ;0`Hddxhe]&io*<{|l!e-*%nyhx-1w^-V%_kve?rE6q~ϑ(+0=IY}F]➥huib#.TٶQhZld**R66$ifٳj#Q~%#.) 1jj6QLUD06!#+2)'cpH]+aMFma$$-MsdScT+f1QebQlRIzʭ֥MM~]4!{qDMsWq=ó"IீA6l!FOz/mLdI~˨p ôGaw 4,Be=r2o0$XܟIUyƀJɮ&Sd*BbȃY?GVm qK"76;A22РpnBfuZ (H/D(JuM$![RmKiwWd*(#-ͷn쒹NA%#.xʔYVD!,]lm}]# \f 5W4&cBٚVAm!Q,XT\#+{hӰ5y,+PuO}bopȇs3EeFB #+@HSᒀƶr93E(~ 20U {ɝ52&CbR!Tflyp`.D##+l pKs}݁vI1t IOCmg& ccżBӓݒ8_ ѝb#--xcMf#.v}yAG)ļ.#-Lo~a$#MfUsQ Є2"IQGBsGi[ҚC@#.:(ޖ3w3;-\(uKFGACM4lh0#-6;t(h{Ʉeӽj̅G|5ɇNeJXbApC GGd 9]#+eC#+gAF/hm&dF+#-2PLJ(ZoWivM_nuui/Iu5f0 g:c1~N`qwmRaA%k٦v,\Q67@J%hf R$Ǿ`|#-k1*cXiA'dWέmڃp8Ti/0i+GqmtTPcpkCliFLctñ,#..0XżRUGA!!yCKD4/=zEn>&#-ڟa#+ۿv#-V.kh06#+Ih#+V?Wo?Oz}e??z9͏p??W}~?Z?RB0m)H~>gne?)MX?#+*"S+\m4 T*Y40v~9wh@3>ol_vָJ#+Hd,\LJ?zjo4 Cmh#+̢<Gvu:DzLd܍]D)Yl)/@ȭ UфK.*G,Q۬1ym^#-((X⽫#+#4wl*;ǖ>6ivϷb7[JN3^}[#-#.2JS=[efH#-?Da߃S%+;*Ύf>Aj'\R3U4u4H6/&a6h4m8ui4JqUj*:X7Y>8=]4CVM3*č&ޤ4A#-b7PXglT:͕S[K|D"?D?@1To0X" d{%v Eb(}gi2~Ȗc5Ied~pU}BShwtL&_htuo&؅hlXS-XhOQ8hoh$q%y_=7 #-#.'THZH-E5QoJ5{-[bV@4Zw݂u.$9bCfI#-#-!?^YS\( -$26Eo33ocVUy {ZZ7?Aq/H#.ORP_4TH8!'wfbKV}[/uЛ+8GvB?R]xr$ςr/'kTe~Nl?sT#.?aőp7J0{(WQ6g# 3H!*\oR4ZJqTE3oYw=N gAX7 ^[ c~qQ-y[Na ;fL5HK}<5$aO|=)ۜi{8ˣ7F2H_<8[_7/Ɗ)„#-[3 #<== -#-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEjH6y+TsMRfVzL+XRG6xXHc13IpUFAmZM7vsACgkQG6xXHc13\nIpXIVxAArMHbyJFk/VDRXYhnTCz0lPBKkdqMcaK3JhQzKHjPjgTCHEBffmaIq2hz\ndMs2fgjDUmMlmdO5sEeRGgfDHo+ILlRDWOZF/7120aMeSxtS6JaNCzm5W97bWYUP\nJT67cJFanzcUC/HodaUPjUni3FZdjla4CAC99f3DA2thMLTN06WtAdUnOGsfl0vg\n7Aho57LYtv1nUsW23T9IyF4MjhwVR07dGmV82LYFhZRUtaGiBBxQLXOlxshIWczt\ne20fVerHJ6towSEW/fXTY+xOIPs3PaB5/GqMf9SvvdS+33uMrHJIEBOeoFPbBap/\n6bZuonnubCW2bOwCri7pI8cMbXvGHEnKq5iwZFefJfBMkS+eVTuzq2Cy7Q9/+e6y\nrVZ5pPX2yxvNi0kdDhq9DbZ0hNm+wrnqYS/44zPOVJFBRA+pjTyM14dL/pwnFwQ0\nTN47DVElkAnMs8qjul3rIbT37LJe5hr5QgF/ph4bfK7IYbGPbaHOFkqgPiMipATk\n3zpZvkQwMGx1iQiqvBUgnQabbaX+49GjIqYFv0DnHabYTzJjjn+f/xKxqx37K0a0\nthLK/9Kt4iRlVNz3Xa1osPZKHjK4saeHQQV6geWSu8FjTn2WQSV6QACxTbjYtRdW\nMiM3qXn1rNZAhspHW7uCPifC2JheR5435V7OpEk3T8zqPcne5k0=\n=2de+\n-----END PGP SIGNATURE-----\n +#-----BEGIN PGP SIGNATURE-----\n\niQIzBAABCgAdFiEEjH6y+TsMRfVzL+XRG6xXHc13IpUFAmczp/cACgkQG6xXHc13\nIpUETQ//V9rcegq5fXQ4wJpTMVvKwtldiObdr7R/ZfMHgHvp6LFQ3XwCWsh+Qqcp\nXWAmQNtPfgxQcfGnGHEupp6d1XsLGHWi/zqFQx43qE6XIBxnNAO9SKLTRlJrWuUX\nq09jkP7qrV96SVKqpFf5bIS9Pb0ZtvFsnawIBgWZY8Da//W9eDCBSE0cD0lAhsCE\nwTBIO9avqRlaxCbGUr0UF+p3T4A5Qv2qVFfX6qGCKvTMcg9/uSUU3LTn4FUXVBeQ\nwJmBzZN1IRH5t54b1PHjCjGkLkD6Wr7cqecjEyPvJUzqad75MBLrj4qR+QtzMb40\noWX5zg2QwVcdWOtbR6R0toYE2d2PgvgSHtyvcNutIKlcOm8Me2xzFy8KS1hhK3go\n3cdlz57onWftgAyU0OKTz8FXlPDnQOtzANLRTSd8oaPceF/FdHTPVZNLMJlmvBC8\n7CHP245o+iYEiM0BfV2CM4w6pmXwK9kU5DHlLN9oM0jd6jTYntP3+zwqTRw9drg6\nMDXijWLPFiNMUegz6isOrLnilHtjss3GSQ5SC39fmy94QoqI0k9wQRSQt2adZj7v\nSlWE4Cm/png5L2bLFg4cykDRKmnD0NFjWkNb5fprUM7Xnm4TfgUSCusPQe8gox2L\nGj/8L+0OIq5+1eN/7CVwf+Wmu7c3+yYtXlEzNVK8wkX8lz15TBM=\n=GL1q\n-----END PGP SIGNATURE-----\n diff --git a/wscript b/wscript index 12c9787f0..b7e6bea20 100644 --- a/wscript +++ b/wscript @@ -4,7 +4,7 @@ import os APPNAME = 'libcsp' -VERSION = '2.1' +VERSION = '2.2' valid_os = ['posix', 'freertos'] @@ -23,6 +23,7 @@ def options(ctx): gr.add_option('--disable-output', action='store_true', help='Disable CSP output') gr.add_option('--disable-print-stdio', action='store_true', help='Disable vprintf for csp_print_func') gr.add_option('--disable-stlib', action='store_true', help='Build objects only') + gr.add_option('--disable-buffer-zero-clear', action='store_false', help='Zero out the packet buffer upon allocation') gr.add_option('--enable-shlib', action='store_true', help='Build shared library') gr.add_option('--enable-rdp', action='store_true', help='Enable RDP support') gr.add_option('--enable-promisc', action='store_true', help='Enable promiscuous support') @@ -50,6 +51,10 @@ def options(ctx): # OS gr.add_option('--with-os', metavar='OS', default='posix', help='Set operating system. Must be one of: ' + str(valid_os)) + # Fixup + gr.add_option('--fixup-v1-zmq-little-endian', action='store_true', help='Use little-endian CSP ID for ZMQ with CSPv1') + + gr.add_option('--disable_kiss_crc', action='store_true', help='Disable the extra CRC in the KISS interface (legacy)') def configure(ctx): # Validate options @@ -76,16 +81,22 @@ def configure(ctx): # Setup CFLAGS if (len(ctx.stack_path) <= 1) and (len(ctx.env.CFLAGS) == 0): - ctx.env.prepend_value('CFLAGS', ["-std=gnu11", "-g", "-Os", "-Wall", "-Wextra", "-Wshadow", "-Wcast-align", + ctx.env.prepend_value('CFLAGS', ["-std=gnu11", "-g", "-Os", + "-Wall", + "-Wcast-align", + "-Werror", + "-Wextra", + "-Wmissing-prototypes", + "-Wpedantic", "-Wpointer-arith", - "-Wwrite-strings", "-Wno-unused-parameter", "-Werror"]) + "-Wshadow", + "-Wwrite-strings"]) + if ctx.env.CC_NAME == 'clang': + ctx.env.append_value('CFLAGS', ["-Wno-gnu-zero-variadic-macro-arguments"]) # Setup default include path and any extra defined ctx.env.append_unique('INCLUDES_CSP', ['include', 'src'] + ctx.options.includes.split(',')) - # Store OS as env variable - ctx.env.OS = ctx.options.with_os - # Platform/OS specifics if ctx.options.with_os == 'posix': ctx.env.append_unique('LIBS', ['rt', 'pthread', 'util']) @@ -118,6 +129,7 @@ def configure(ctx): 'src/interfaces/csp_if_can_pbuf.c', 'src/interfaces/csp_if_kiss.c', 'src/interfaces/csp_if_i2c.c', + 'src/interfaces/csp_if_tun.c', 'src/arch/{0}/**/*.c'.format(ctx.options.with_os), ]) @@ -192,7 +204,11 @@ def configure(ctx): ctx.define('CSP_USE_HMAC', ctx.options.enable_hmac) ctx.define('CSP_USE_PROMISC', ctx.options.enable_promisc) ctx.define('CSP_USE_RTABLE', ctx.options.enable_rtable) + ctx.define('CSP_BUFFER_ZERO_CLEAR', ctx.options.disable_buffer_zero_clear) + + ctx.define('CSP_FIXUP_V1_ZMQ_LITTLE_ENDIAN', ctx.options.fixup_v1_zmq_little_endian) + ctx.define('CSP_ENABLE_KISS_CRC', not ctx.options.disable_kiss_crc) ctx.write_config_header('include/csp/autoconfig.h') @@ -232,23 +248,32 @@ def build(ctx): use=['csp_shlib'], pytest_path=[ctx.path.get_bld()]) + ctx.env.append_value('CFLAGS', ["-Wno-missing-prototypes", + "-Wno-unused-parameter"]) + if ctx.env.ENABLE_EXAMPLES: - ctx.program(source=['examples/csp_server_client.c', - 'examples/csp_server_client_{0}.c'.format(ctx.env.OS)], + ctx.objects(source='examples/csp_posix_helper.c', + target='csp_posix_helper', + use='csp_h') + + ctx.program(source='examples/csp_server_client.c', target='examples/csp_server_client', lib=ctx.env.LIBS, - use='csp') + use=['csp', 'csp_posix_helper']) - ctx.program(source=['examples/csp_server.c', - 'examples/csp_server_{0}.c'.format(ctx.env.OS)], + ctx.program(source='examples/csp_server.c', target='examples/csp_server', lib=ctx.env.LIBS, - use='csp') + use=['csp', 'csp_posix_helper']) - ctx.program(source=['examples/csp_client.c', - 'examples/csp_client_{0}.c'.format(ctx.env.OS)], + ctx.program(source='examples/csp_client.c', target='examples/csp_client', lib=ctx.env.LIBS, + use=['csp', 'csp_posix_helper']) + + ctx.program(source=['examples/csp_bridge_can2udp.c'], + target='examples/csp_bridge_can2udp', + lib=ctx.env.LIBS, use='csp') ctx.program(source='examples/csp_arch.c', @@ -262,6 +287,10 @@ def build(ctx): lib=ctx.env.LIBS, use='csp') + ctx.program(source=['examples/csp_sfp_server_client.c'], + target='examples/csp_sfp_server_client', + lib=ctx.env.LIBS, + use='csp') def dist(ctx): ctx.excl = 'build/* **/.* **/*.pyc **/*.o **/*~ *.tar.gz' diff --git a/zephyr b/zephyr deleted file mode 120000 index 0f0b58ede..000000000 --- a/zephyr +++ /dev/null @@ -1 +0,0 @@ -contrib/zephyr \ No newline at end of file diff --git a/contrib/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt similarity index 50% rename from contrib/zephyr/CMakeLists.txt rename to zephyr/CMakeLists.txt index aec543e75..9c7f9e11d 100644 --- a/contrib/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -1,5 +1,5 @@ if(CONFIG_LIBCSP) - set(CMAKE_SYSTEM_NAME "Zephyr") + set(CSP_SYSTEM_NAME "Zephyr") set(CMAKE_BUILD_TYPE None) set(CSP_PRINT_STDIO OFF) @@ -9,6 +9,16 @@ if(CONFIG_LIBCSP) # predefine it set(HAVE_SYS_SOCKET_H OFF) + # Cache entries. see the top level CMakeLists.txt + set(CSP_QFIFO_LEN CONFIG_CSP_QFIFO_LEN CACHE STRING "") + set(CSP_PORT_MAX_BIND CONFIG_CSP_PORT_MAX_BIND CACHE STRING "") + set(CSP_CONN_RXQUEUE_LEN CONFIG_CSP_CONN_RXQUEUE_LEN CACHE STRING "") + set(CSP_CONN_MAX CONFIG_CSP_CONN_MAX CACHE STRING "") + set(CSP_BUFFER_SIZE CONFIG_CSP_BUFFER_SIZE CACHE STRING "") + set(CSP_BUFFER_COUNT CONFIG_CSP_BUFFER_COUNT CACHE STRING "") + set(CSP_RDP_MAX_WINDOW CONFIG_CSP_RDP_MAX_WINDOW CACHE STRING "") + set(CSP_RTABLE_SIZE CONFIG_CSP_RTABLE_SIZE CACHE STRING "") + if(CONFIG_CSP_USE_RTABLE) set(CSP_USE_RTABLE ON) endif() @@ -17,7 +27,7 @@ if(CONFIG_LIBCSP) set(HAVE_ZEPHYR_CAN ON) endif() - add_subdirectory(../.. build) + add_subdirectory(${ZEPHYR_CURRENT_MODULE_DIR} build) zephyr_interface_library_named(libcsp) target_include_directories(libcsp INTERFACE ${ZEPHYR_CURRENT_MODULE_DIR}/include) diff --git a/contrib/zephyr/Kconfig b/zephyr/Kconfig similarity index 62% rename from contrib/zephyr/Kconfig rename to zephyr/Kconfig index 44672e8e7..736b060af 100644 --- a/contrib/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -5,7 +5,9 @@ config LIBCSP bool "Enable Cubesat Space Protocol Support" - select POSIX_CLOCK + select POSIX_TIMERS + select POSIX_MONOTONIC_CLOCK + select POSIX_SYSTEM_INTERFACES help This option enables the Cubesat Space Protocol (CSP) library. @@ -39,11 +41,70 @@ config CSP_MUTEX_TIMEOUT help Waiting period to lock the mutex. +menu "Memory Tuning Parameters" + +config CSP_QFIFO_LEN + int "Length of incoming queue for router task" + default 15 + help + This value specifies the length of queue for incoming + packets that are used by csp_qfifo_read() and + csp_qfifo_write(). + +config CSP_PORT_MAX_BIND + int "Number of port to bind" + default 16 + help + Number of ports available for an interface. + +config CSP_CONN_RXQUEUE_LEN + int "Number of packets in connection queue" + default 16 + help + This value specifies the length of the queue for packets in + a connection. + +config CSP_CONN_MAX + int "Number of new connections on socket queue" + default 8 + help + This value specifies the number of new connections on socket + queue. + +config CSP_BUFFER_SIZE + int "Bytes in each packet buffer" + default 256 + help + This value specifies the number bytes in each packet buffer. + +config CSP_BUFFER_COUNT + int "Number of total packet buffers" + default 15 + help + This value specifies the number of total packet buffers in + the system. + +config CSP_RDP_MAX_WINDOW + int "Max window size for RDP" + default 5 + help + This value specifies the window size of RDP. + +config CSP_RTABLE_SIZE + int "Number of elements in routing table" + default 10 + help + This value specifies the number of entries in the routing + table. +endmenu + +menu "UART" + config CSP_UART_RX_INTERVAL int "Interval (nesc) at which the receiving thread runs." default 1000 help - Interval (nsec) at which the reveiving thread runs. + Interval (nsec) at which the receiving thread runs. config CSP_UART_RX_BUFFER_LENGTH int "Size of buffer to be stored when receiving in the CSP UART Driver." @@ -69,6 +130,10 @@ config CSP_UART_RX_THREAD_NUM help Number of thread stacks for CSP UART RX. +endmenu + +menu "CANbus" + if CAN config CSP_HAVE_CAN @@ -103,4 +168,6 @@ config CSP_CAN_RX_THREAD_NUM endif # CAN +endmenu + endif # LIBCSP diff --git a/contrib/zephyr/module.yml b/zephyr/module.yml similarity index 100% rename from contrib/zephyr/module.yml rename to zephyr/module.yml