diff --git a/.github/workflows/abi-checker.yml b/.github/workflows/abi-checker.yml index a472fcbc6..3ede0c15d 100644 --- a/.github/workflows/abi-checker.yml +++ b/.github/workflows/abi-checker.yml @@ -22,7 +22,7 @@ jobs: sudo apt-get install abi-compliance-checker abi-dumper - name: Checkout merged-base - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: ${{ github.event.pull_request.base.ref }} @@ -32,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@v6 + uses: actions/checkout@v7 with: fetch-depth: 0 ref: ${{github.event.pull_request.head.ref}} diff --git a/.github/workflows/build-test-freertos.yml b/.github/workflows/build-test-freertos.yml index b9060a9ef..54e308b0e 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] +on: + pull_request: + push: + branches: + - develop + - 'libcsp-*' permissions: contents: read concurrency: @@ -17,18 +22,18 @@ jobs: sudo apt-get install ninja-build meson tree - name: Checkout Test App - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: libcsp/libcsp-freertos - name: Checkout FreeRTOS Kernel - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: repository: FreeRTOS/FreeRTOS-Kernel path: freertos - name: Checkout libcsp under subprojects - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: path: subprojects/libcsp diff --git a/.github/workflows/build-test-python.yml b/.github/workflows/build-test-python.yml index 8bbd830af..bd138cfe2 100644 --- a/.github/workflows/build-test-python.yml +++ b/.github/workflows/build-test-python.yml @@ -1,5 +1,10 @@ name: Python Bindings -on: [push, pull_request] +on: + pull_request: + push: + branches: + - develop + - 'libcsp-*' permissions: contents: read concurrency: @@ -25,7 +30,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} @@ -39,7 +44,7 @@ jobs: sudo apt-get install ninja-build ${{ matrix.buildsystem }} - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Build libcsp examples run: python3 examples/buildall.py --build-system=${{ matrix.buildsystem }} diff --git a/.github/workflows/build-test-zephyr.yml b/.github/workflows/build-test-zephyr.yml index 2d9d51c5d..9f26582eb 100644 --- a/.github/workflows/build-test-zephyr.yml +++ b/.github/workflows/build-test-zephyr.yml @@ -1,7 +1,10 @@ name: Zephyr Build and Test on: - push: pull_request: + push: + branches: + - develop + - 'libcsp-*' schedule: - cron: '0 10 * * 0' # Run it every Sunday 10am UTC @@ -26,7 +29,7 @@ jobs: steps: - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} @@ -35,7 +38,7 @@ jobs: python3 --version - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: path: libcsp-zephyr repository: yashi/libcsp-zephyr diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 5784ac126..91f84d7f7 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] +on: + pull_request: + push: + branches: + - develop + - 'libcsp-*' permissions: contents: read concurrency: @@ -10,66 +15,85 @@ jobs: strategy: fail-fast: false matrix: - os: - - ubuntu-24.04 - - ubuntu-22.04 - buildsystem: - - meson - - cmake - - waf - compiler: - - CC: gcc-10 + target: + # Ubuntu 22.04 only has GCC 10, 11, 12 + - os: ubuntu-22.04 + CC: gcc-10 CXX: g++-10 - - CC: gcc-11 + - os: ubuntu-22.04 + CC: gcc-11 CXX: g++-11 - - CC: gcc-12 + - os: ubuntu-22.04 + CC: gcc-12 CXX: g++-12 - - CC: gcc-13 + - os: ubuntu-22.04 + CC: clang + CXX: clang++ + # Ubuntu 24.04 only has GCC 12, 13, 14 + - os: ubuntu-24.04 + CC: gcc-12 + CXX: g++-12 + - os: ubuntu-24.04 + CC: gcc-13 CXX: g++-13 - - CC: gcc-14 + - os: ubuntu-24.04 + CC: gcc-14 CXX: g++-14 - - CC: clang + - os: ubuntu-24.04 + CC: clang CXX: clang++ + # Ubuntu 26.04 only has GCC 13, 14, 15 + - os: ubuntu-26.04 + CC: gcc-13 + CXX: g++-13 + - os: ubuntu-26.04 + CC: gcc-14 + CXX: g++-14 + - os: ubuntu-26.04 + CC: gcc-15 + CXX: g++-15 + - os: ubuntu-26.04 + CC: clang + CXX: clang++ + buildsystem: + - meson + - cmake + - waf 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 }} + runs-on: ${{ matrix.target.os }} steps: - name: Setup packages on Linux if: ${{ runner.os == 'Linux' }} run: | sudo apt-get update sudo apt-get install libzmq3-dev libsocketcan-dev socat iproute2 + sudo apt-cache search linux-modules + + - name: Setup packages on Ubuntu 22.04/24.04 + if: ${{ runner.os == 'Linux' && (matrix.target.os == 'ubuntu-22.04' || matrix.target.os == 'ubuntu-24.04') }} + run: | sudo apt-get install linux-modules-extra-$(uname -r) + - name: Setup packages on Linux 26.04 + if: ${{ runner.os == 'Linux' && (matrix.target.os == 'ubuntu-26.04') }} + run: | + sudo apt-get install linux-modules-$(uname -r) + - name: Setup build system packages on Linux if: ${{ runner.os == 'Linux' && matrix.buildsystem != 'waf' }} run: | sudo apt-get install ninja-build ${{ matrix.buildsystem }} - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Build env: - CC: ${{ matrix.compiler.CC }} - CXX: ${{ matrix.compiler.CXX }} + CC: ${{ matrix.target.CC }} + CXX: ${{ matrix.target.CXX }} run: python3 ./examples/buildall.py --build-system=${{ matrix.buildsystem }} - name: Run Loopback Test diff --git a/.github/workflows/check-compile-options.yml b/.github/workflows/check-compile-options.yml new file mode 100644 index 000000000..a6a8982be --- /dev/null +++ b/.github/workflows/check-compile-options.yml @@ -0,0 +1,39 @@ +name: Check Compile Options +on: + pull_request: + push: + branches: + - develop + - 'libcsp-*' +permissions: + contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true +jobs: + check-compile-options: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-22.04 + - ubuntu-24.04 + options: + - "-D enable_csp_print=false -D print_stdio=false" + - "-D enable_csp_print=false -D print_stdio=true" + - "-D enable_csp_print=true -D print_stdio=false" + - "-D enable_csp_print=true -D print_stdio=true" + runs-on: ${{ matrix.os }} + steps: + - name: Setup packages on Linux + run: | + sudo apt-get update + sudo apt-get install libzmq3-dev libsocketcan-dev ninja-build meson + + - name: Checkout + uses: actions/checkout@v7 + + - name: Build with compile time options + run: | + meson setup ${{ matrix.options }} builddir + meson compile -C builddir/ diff --git a/.github/workflows/check-merge-commits.yml b/.github/workflows/check-merge-commits.yml index 310c9fbd8..4ab184a32 100644 --- a/.github/workflows/check-merge-commits.yml +++ b/.github/workflows/check-merge-commits.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout PR head - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 219f55141..7c0a8241e 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -1,8 +1,11 @@ name: Codespell Check -on: +on: pull_request: push: + branches: + - develop + - 'libcsp-*' permissions: contents: read @@ -12,7 +15,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Install codespell run: pip install codespell diff --git a/.github/workflows/develop-build-sphinx-docs.yml b/.github/workflows/develop-build-sphinx-docs.yml index 8e5af2159..f46f00fdd 100644 --- a/.github/workflows/develop-build-sphinx-docs.yml +++ b/.github/workflows/develop-build-sphinx-docs.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout the repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Setup packages on Linux if: ${{ runner.os == 'Linux' }} diff --git a/.github/workflows/gitlint.yml b/.github/workflows/gitlint.yml index 318b3cb9f..8598e7b68 100644 --- a/.github/workflows/gitlint.yml +++ b/.github/workflows/gitlint.yml @@ -16,7 +16,7 @@ jobs: pip3 install gitlint - name: Checkout the code - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 diff --git a/.github/workflows/linelint.yaml b/.github/workflows/linelint.yaml index ee6190415..375a8a149 100644 --- a/.github/workflows/linelint.yaml +++ b/.github/workflows/linelint.yaml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Linelint uses: fernandrone/linelint@master diff --git a/.gitlint b/.gitlint index 87587e6f4..bed235a85 100644 --- a/.gitlint +++ b/.gitlint @@ -3,3 +3,7 @@ regex-style-search=true [ignore-body-lines] regex=^(?:(?:Signed-off|Acked|Co-Authored|Reported|Tested)-by: |\[\d+\]: https:\/\/) + +[ignore-by-author-name] +regex=^dependabot\[bot\]$ +ignore=B1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 2752465aa..4621671c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,11 @@ add_library(csp) set_target_properties(csp PROPERTIES C_STANDARD 11) set_target_properties(csp PROPERTIES C_EXTENSIONS ON) +set_target_properties(csp PROPERTIES + VERSION ${CSP_VERSION} + SOVERSION ${CSP_VERSION_MAJOR} +) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release MinSizeRel) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "No build type selected, default to MinSizeRel") diff --git a/include/csp/csp.h b/include/csp/csp.h index ea190e605..f8d20ac45 100644 --- a/include/csp/csp.h +++ b/include/csp/csp.h @@ -510,7 +510,12 @@ void csp_hex_dump(const char *desc, const void *addr, int len); #else inline void csp_conn_print_table(void) {} -inline void csp_hex_dump(const char *desc, void *addr, int len) {} +inline void csp_hex_dump(const char *desc, const void *addr, int len) { + /* Avoid compiler warnings about unused parameters when CSP_ENABLE_CSP_PRINT=0 */ + (void)desc; + (void)addr; + (void)len; +} #endif diff --git a/include/csp/csp_debug.h b/include/csp/csp_debug.h index acaeaaf0e..4d4da8d59 100644 --- a/include/csp/csp_debug.h +++ b/include/csp/csp_debug.h @@ -73,7 +73,7 @@ void csp_print_func(const char * fmt, ...); #if (CSP_ENABLE_CSP_PRINT) #define csp_print(...) csp_print_func(__VA_ARGS__); #else -#define csp_print(...) do {} while(0) +#define csp_print(...) do { if(0) csp_print_func(__VA_ARGS__); /* avoids unused-parameter compiler errors */ } while(0) #endif #define csp_rdp_error(format, ...) { if (csp_dbg_rdp_print >= 1) { csp_print("\033[31m" format "\033[0m", ##__VA_ARGS__); }} diff --git a/meson.build b/meson.build index 5c5d44b64..6b9b68b1d 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('csp', 'c', version: '2.2', license: 'LGPL', meson_version : '>=0.61.2', default_options : [ +project('csp', 'c', version: '2.2', license: 'MIT', meson_version : '>=0.61.2', default_options : [ 'c_std=gnu11', 'optimization=s', 'warning_level=3', @@ -101,6 +101,7 @@ csp_lib = library('csp', c_args : csp_c_args, install : true, pic:true, + version : meson.project_version(), ) # The following dependency variable is for parent projects to link diff --git a/src/bindings/python/pycsp.c b/src/bindings/python/pycsp.c index f0c2d8118..90ad92483 100644 --- a/src/bindings/python/pycsp.c +++ b/src/bindings/python/pycsp.c @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -617,6 +618,117 @@ static PyObject * pycsp_rdp_get_opt(PyObject * self, PyObject * args) { ack_delay_count); } +typedef struct { + csp_iface_t iface; + PyObject * py_nexthop; +} pycsp_iface_wrapper_t; + +static int pycsp_nexthop_trampoline(csp_iface_t * iface, + uint16_t via, + csp_packet_t * packet, + int from_me) { + + pycsp_iface_wrapper_t * wrapper = + (pycsp_iface_wrapper_t *) iface->interface_data; + + if (!wrapper || !wrapper->py_nexthop) { + return CSP_ERR_INVAL; + } + + PyGILState_STATE gstate = PyGILState_Ensure(); + + PyObject * iface_capsule = + PyCapsule_New(iface, "csp_iface_t", NULL); + + PyObject * packet_capsule = + PyCapsule_New(packet, PACKET_CAPSULE, NULL); + + PyObject * result = PyObject_CallFunction( + wrapper->py_nexthop, + "NHNi", // PyObject (steals ref), uint16, PyObject (steals ref), int + iface_capsule, + via, + packet_capsule, + from_me + ); + + int ret = CSP_ERR_NONE; + + if (!result) { + PyErr_Print(); + ret = CSP_ERR_INVAL; + } else { + if (PyLong_Check(result)) { + ret = (int) PyLong_AsLong(result); + } + Py_DECREF(result); + } + + PyGILState_Release(gstate); + return ret; +} + +static PyObject * pycsp_iface_add(PyObject * self, PyObject * args) { + + uint16_t addr; + uint16_t netmask; + const char * name; + PyObject * py_nexthop; + + if (!PyArg_ParseTuple(args, "HHsO", &addr, &netmask, &name, &py_nexthop)) { + return NULL; + } + + if (!PyCallable_Check(py_nexthop)) { + PyErr_SetString(PyExc_TypeError, "nexthop must be callable"); + return NULL; + } + + pycsp_iface_wrapper_t * wrapper = + PyMem_RawCalloc(1, sizeof(*wrapper)); + + if (!wrapper) { + return PyErr_NoMemory(); + } + + // Fill iface + wrapper->iface.addr = addr; + wrapper->iface.netmask = netmask; + wrapper->iface.name = strdup(name); // ensure lifetime! + wrapper->iface.interface_data = wrapper; + wrapper->iface.driver_data = NULL; + wrapper->iface.nexthop = pycsp_nexthop_trampoline; + wrapper->iface.is_default = 0; + + // Store Python callback + Py_INCREF(py_nexthop); + wrapper->py_nexthop = py_nexthop; + + // Register with CSP + csp_iflist_add(&wrapper->iface); + + Py_RETURN_NONE; +} + +static PyObject * pycsp_qfifo_write(PyObject * self, PyObject * args) { + PyObject * packet_capsule; + char * interface_name; + if (!PyArg_ParseTuple(args, "Os", &packet_capsule, &interface_name)) { + return NULL; // TypeError is thrown + } + + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + + csp_iface_t * iface = csp_iflist_get_by_name(interface_name); + + csp_qfifo_write(packet, iface, NULL); // todo: handle pxTaskWoken != NULL case? + + Py_RETURN_NONE; +} + #if CSP_USE_RTABLE static PyObject * pycsp_rtable_set(PyObject * self, PyObject * args) { @@ -701,6 +813,34 @@ static PyObject * pycsp_buffer_remaining(PyObject * self, PyObject * args) { return Py_BuildValue("i", csp_buffer_remaining()); } +static PyObject * pycsp_id_prepend(PyObject * self, PyObject * packet_capsule) { + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + csp_id_prepend(packet); + + Py_RETURN_NONE; +} + +static PyObject * pycsp_id_strip(PyObject * self, PyObject * packet_capsule) { + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + int res = csp_id_strip(packet); + return Py_BuildValue("i", res); +} + +static PyObject * pycsp_id_setup_rx(PyObject * self, PyObject * packet_capsule) { + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + int res = csp_id_setup_rx(packet); + return Py_BuildValue("i", res); +} + static PyObject * pycsp_cmp_ident(PyObject * self, PyObject * args) { uint16_t node; uint32_t timeout = 1000; @@ -967,6 +1107,27 @@ static PyObject * pycsp_packet_set_data(PyObject * self, PyObject * args) { Py_RETURN_NONE; } +static PyObject * pycsp_packet_set_frame_data(PyObject * self, PyObject * args) { + PyObject * packet_capsule; + Py_buffer data; + if (!PyArg_ParseTuple(args, "Oy*", &packet_capsule, &data)) { + return NULL; // TypeError is thrown + } + + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + if (data.len > csp_id_get_header_size() + (int)sizeof(packet->data)) { + return PyErr_Error("packet_set_frame_data() - exceeding frame size", CSP_ERR_INVAL); + } + + memcpy(packet->frame_begin, data.buf, data.len); + packet->frame_length = data.len; + + Py_RETURN_NONE; +} + static PyObject * pycsp_packet_get_data(PyObject * self, PyObject * packet_capsule) { csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); if (packet == NULL) { @@ -975,6 +1136,14 @@ static PyObject * pycsp_packet_get_data(PyObject * self, PyObject * packet_capsu return Py_BuildValue("y#", packet->data, (size_t)packet->length); } +static PyObject * pycsp_packet_get_frame_data(PyObject * self, PyObject * packet_capsule) { + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + return Py_BuildValue("y#", packet->frame_begin, (size_t)packet->frame_length); +} + static PyObject * pycsp_packet_get_length(PyObject * self, PyObject * packet_capsule) { csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); if (packet == NULL) { @@ -983,6 +1152,62 @@ static PyObject * pycsp_packet_get_length(PyObject * self, PyObject * packet_cap return Py_BuildValue("H", packet->length); } +static PyObject * pycsp_packet_get_frame_length(PyObject * self, PyObject * packet_capsule) { + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + return Py_BuildValue("H", packet->frame_length); +} + +static PyObject * pycsp_packet_get_pri(PyObject * self, PyObject * packet_capsule) { + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + return Py_BuildValue("B", packet->id.pri); +} + +static PyObject * pycsp_packet_get_flags(PyObject * self, PyObject * packet_capsule) { + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + return Py_BuildValue("B", packet->id.flags); +} + +static PyObject * pycsp_packet_get_src(PyObject * self, PyObject * packet_capsule) { + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + return Py_BuildValue("H", packet->id.src); +} + +static PyObject * pycsp_packet_get_dst(PyObject * self, PyObject * packet_capsule) { + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + return Py_BuildValue("H", packet->id.dst); +} + +static PyObject * pycsp_packet_get_dport(PyObject * self, PyObject * packet_capsule) { + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + return Py_BuildValue("B", packet->id.dport); +} + +static PyObject * pycsp_packet_get_sport(PyObject * self, PyObject * packet_capsule) { + csp_packet_t * packet = get_obj_as_packet(packet_capsule, false); + if (packet == NULL) { + return NULL; // TypeError is thrown + } + return Py_BuildValue("B", packet->id.sport); +} + static PyObject * pycsp_print_connections(PyObject * self, PyObject * args) { csp_conn_print_table(); Py_RETURN_NONE; @@ -1026,6 +1251,9 @@ static PyMethodDef methods[] = { {"rdp_set_opt", pycsp_rdp_set_opt, METH_VARARGS, ""}, {"rdp_get_opt", pycsp_rdp_get_opt, METH_NOARGS, ""}, + {"iface_add", pycsp_iface_add, METH_VARARGS, "Add CSP interface"}, + {"qfifo_write", pycsp_qfifo_write, METH_VARARGS, ""}, + #if CSP_USE_RTABLE /* csp/csp_rtable.h */ {"rtable_set", pycsp_rtable_set, METH_VARARGS, ""}, @@ -1040,6 +1268,11 @@ static PyMethodDef methods[] = { {"buffer_get", pycsp_buffer_get, METH_VARARGS, ""}, {"buffer_remaining", pycsp_buffer_remaining, METH_NOARGS, ""}, + /* csp/csp_id.h */ + {"id_prepend", pycsp_id_prepend, METH_O, ""}, + {"id_strip", pycsp_id_strip, METH_O, ""}, + {"id_setup_rx", pycsp_id_setup_rx, METH_O, ""}, + /* csp/csp_cmp.h */ {"cmp_ident", pycsp_cmp_ident, METH_VARARGS, ""}, {"cmp_route_set", pycsp_cmp_route_set, METH_VARARGS, ""}, @@ -1061,8 +1294,17 @@ static PyMethodDef methods[] = { /* helpers */ {"packet_get_length", pycsp_packet_get_length, METH_O, ""}, + {"packet_get_frame_length", pycsp_packet_get_frame_length, METH_O, ""}, {"packet_get_data", pycsp_packet_get_data, METH_O, ""}, + {"packet_get_frame_data", pycsp_packet_get_frame_data, METH_O, ""}, + {"packet_get_pri", pycsp_packet_get_pri, METH_O, ""}, + {"packet_get_flags", pycsp_packet_get_flags, METH_O, ""}, + {"packet_get_src", pycsp_packet_get_src, METH_O, ""}, + {"packet_get_dst", pycsp_packet_get_dst, METH_O, ""}, + {"packet_get_dport", pycsp_packet_get_dport, METH_O, ""}, + {"packet_get_sport", pycsp_packet_get_sport, METH_O, ""}, {"packet_set_data", pycsp_packet_set_data, METH_VARARGS, ""}, + {"packet_set_frame_data", pycsp_packet_set_frame_data, METH_VARARGS, ""}, {"print_connections", pycsp_print_connections, METH_NOARGS, ""}, {"print_interfaces", pycsp_print_interfaces, METH_NOARGS, ""}, diff --git a/src/csp_debug.c b/src/csp_debug.c index acb67c02e..86a2e1b25 100644 --- a/src/csp_debug.c +++ b/src/csp_debug.c @@ -14,10 +14,10 @@ uint8_t csp_dbg_rdp_print; uint8_t csp_dbg_packet_print; #if (CSP_ENABLE_CSP_PRINT) +#include "csp/csp_debug.h" #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); @@ -25,6 +25,8 @@ __weak void csp_print_func(const char * fmt, ...) { va_end(args); } #else -__weak void csp_print_func(const char * fmt, ...) {} +__weak void csp_print_func(const char * fmt, ...) { + (void)fmt; /* Avoid compiler warnings about unused parameter */ +} #endif #endif diff --git a/src/csp_io.h b/src/csp_io.h index 7e829ee4d..16f8c6a8a 100644 --- a/src/csp_io.h +++ b/src/csp_io.h @@ -3,11 +3,29 @@ #include /** + * Send a packet, resolving the outgoing interface(s) from the destination + * address in @p idout. * - * @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. + * The packet is always consumed by this call, either handed over to one or + * more interfaces or freed on error. * + * @param idout header (identifier) to send with + * @param packet packet to send + * @param routed_from interface the packet was received on, or NULL if the + * packet originates from the local node */ void csp_send_direct(csp_id_t* idout, csp_packet_t * packet, csp_iface_t * routed_from); + +/** + * Send a packet on a specific interface. + * + * The packet is always consumed by this call, either handed over to the + * interface or freed on error. + * + * @param idout header (identifier) to send with + * @param packet packet to send + * @param iface interface to send the packet on + * @param via via address, or #CSP_NO_VIA_ADDRESS to send directly to the destination + * @param from_me 1 if the packet originates from the local node, 0 if it is a routed message + */ 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_port.c b/src/csp_port.c index cbec0e127..b23a1b0ba 100644 --- a/src/csp_port.c +++ b/src/csp_port.c @@ -74,6 +74,10 @@ csp_socket_t * csp_port_get_socket(unsigned int port) { return NULL; } +bool csp_socket_is_conn_less(const csp_socket_t * socket) { + return (socket->opts & CSP_SO_CONN_LESS) != 0; +} + 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); diff --git a/src/csp_port.h b/src/csp_port.h index 5ee3dacd3..79410312a 100644 --- a/src/csp_port.h +++ b/src/csp_port.h @@ -6,3 +6,4 @@ csp_socket_t * csp_port_get_socket(unsigned int dport); csp_callback_t csp_port_get_callback(unsigned int port); +bool csp_socket_is_conn_less(const csp_socket_t * socket); diff --git a/src/csp_route.c b/src/csp_route.c index 79cd23d24..ee83da74e 100644 --- a/src/csp_route.c +++ b/src/csp_route.c @@ -109,11 +109,103 @@ __weak void csp_input_hook(csp_iface_t * iface, csp_packet_t * packet) { packet->id.sport, packet->id.pri, packet->id.flags, packet->length, iface->name, csp_get_ms()); } +/* Deliver the packet to a bound callback, if any. Returns true if + * the port is served by a callback and the packet was consumed. */ +static bool csp_route_deliver_callback(csp_iface_t * iface, csp_packet_t * packet) { + + csp_callback_t callback = csp_port_get_callback(packet->id.dport); + if (callback == NULL) { + return false; + } + + if (csp_route_security_check(CSP_SO_CRC32REQ, iface, packet) != CSP_ERR_NONE) { + csp_buffer_free(packet); + return true; + } + + callback(packet); + return true; +} + +/* Deliver the packet to a connection-less socket. The socket is the + * endpoint, so delivery terminates here. */ +static void csp_route_deliver_conn_less(csp_socket_t * socket, csp_packet_t * packet) { + + if (csp_queue_enqueue(socket->rx_queue, &packet, 0) != CSP_QUEUE_OK) { + csp_dbg_conn_ovf++; + csp_buffer_free(packet); + return; + } +} + +/* Deliver the packet to a connection. The connection is the + * endpoint: an existing connection, when given, was matched by + * identifier (client or server side), otherwise the socket acts as + * the factory for a new server-side connection, which is posted to + * the socket rx_queue for csp_accept(). */ +static void csp_route_deliver_connection(csp_conn_t * conn, csp_socket_t * socket, csp_packet_t * packet) { + + /* If this is an incoming packet on a new connection */ + if (conn == NULL) { + + /* New incoming connection accepted */ + csp_id_t idout; + idout.pri = packet->id.pri; + idout.src = packet->id.dst; + idout.dst = packet->id.src; + idout.dport = packet->id.sport; + idout.sport = packet->id.dport; + idout.flags = packet->id.flags; + + /* Create connection */ + conn = csp_conn_new(packet->id, idout, CONN_SERVER); + + if (!conn) { + csp_dbg_conn_out++; + csp_buffer_free(packet); + return; + } + + /* Store the socket queue and options */ + conn->dest_socket = socket; + conn->opts = socket->opts; + } + +#if (CSP_USE_RDP) + /* Pass packet to RDP module */ + if (packet->id.flags & CSP_FRDP) { + bool close_connection = csp_rdp_new_packet(conn, packet); + if (close_connection) { + csp_close(conn); + } + return; + } +#endif + + /* Otherwise, enqueue directly */ + if (csp_conn_enqueue_packet(conn, packet) != CSP_ERR_NONE) { + csp_dbg_conn_ovf++; + csp_buffer_free(packet); + return; + } + + /* Try to queue up the new connection pointer */ + if (conn->dest_socket != NULL) { + if (csp_queue_enqueue(conn->dest_socket->rx_queue, &conn, 0) != CSP_QUEUE_OK) { + csp_dbg_conn_ovf++; + csp_close(conn); + return; + } + + /* Ensure that this connection will not be posted to this socket again */ + conn->dest_socket = NULL; + } +} + int csp_route_work(void) { csp_qfifo_t input; csp_packet_t * packet; - csp_conn_t * conn; csp_socket_t * socket; #if (CSP_USE_RDP) @@ -175,123 +267,42 @@ int csp_route_work(void) { return CSP_ERR_NONE; } - /** - * Callbacks - */ - csp_callback_t callback = csp_port_get_callback(packet->id.dport); - if (callback) { - - if (csp_route_security_check(CSP_SO_CRC32REQ, input.iface, packet) != CSP_ERR_NONE) { - csp_buffer_free(packet); - return CSP_ERR_NONE; - } - - callback(packet); + /* Callback delivery */ + if (csp_route_deliver_callback(input.iface, packet)) { return CSP_ERR_NONE; } - /** - * Sockets - */ - - /* The message is to me, search for incoming socket */ + /* Socket delivery */ socket = csp_port_get_socket(packet->id.dport); - /* If the socket is connection-less, deliver now */ - if (socket && (socket->opts & CSP_SO_CONN_LESS)) { + /* Resolve the endpoint: an existing connection first, otherwise + * the bound socket. Drop the packet if neither exists. */ + csp_conn_t * conn = csp_conn_find_existing(&packet->id); - if (csp_route_security_check(socket->opts, input.iface, packet) != CSP_ERR_NONE) { - csp_buffer_free(packet); - return CSP_ERR_NONE; - } - - if (csp_queue_enqueue(socket->rx_queue, &packet, 0) != CSP_QUEUE_OK) { - csp_dbg_conn_ovf++; - csp_buffer_free(packet); - return CSP_ERR_NONE; - } - - return CSP_ERR_NONE; - } - - /* Search for an existing connection */ - conn = csp_conn_find_existing(&packet->id); - - /* If this is an incoming packet on a new connection */ - if (conn == NULL) { - - /* Reject packet if no matching socket is found */ - if (!socket) { - csp_buffer_free(packet); - return CSP_ERR_NONE; - } - - /* Run security check on incoming packet */ - if (csp_route_security_check(socket->opts, input.iface, packet) != CSP_ERR_NONE) { - csp_buffer_free(packet); - return CSP_ERR_NONE; - } - - /* New incoming connection accepted */ - csp_id_t idout; - idout.pri = packet->id.pri; - idout.src = packet->id.dst; - idout.dst = packet->id.src; - idout.dport = packet->id.sport; - idout.sport = packet->id.dport; - idout.flags = packet->id.flags; - - /* Create connection */ - conn = csp_conn_new(packet->id, idout, CONN_SERVER); - - if (!conn) { - csp_dbg_conn_out++; - csp_buffer_free(packet); - return CSP_ERR_NONE; - } - - /* Store the socket queue and options */ - conn->dest_socket = socket; - conn->opts = socket->opts; - - /* Packet to existing connection */ - } else { - - /* Run security check on incoming packet */ - if (csp_route_security_check(conn->opts, input.iface, packet) != CSP_ERR_NONE) { - csp_buffer_free(packet); - return CSP_ERR_NONE; - } - } - -#if (CSP_USE_RDP) - /* Pass packet to RDP module */ - if (packet->id.flags & CSP_FRDP) { - bool close_connection = csp_rdp_new_packet(conn, packet); - if (close_connection) { - csp_close(conn); - } + if (conn == NULL && socket == NULL) { + csp_buffer_free(packet); return CSP_ERR_NONE; } -#endif - /* Otherwise, enqueue directly */ - if (csp_conn_enqueue_packet(conn, packet) != CSP_ERR_NONE) { - csp_dbg_conn_ovf++; + /* Security check with the endpoint options */ + uint32_t opts = conn ? conn->opts : socket->opts; + if (csp_route_security_check(opts, input.iface, packet) != CSP_ERR_NONE) { csp_buffer_free(packet); return CSP_ERR_NONE; } - /* Try to queue up the new connection pointer */ - if (conn->dest_socket != NULL) { - if (csp_queue_enqueue(conn->dest_socket->rx_queue, &conn, 0) != CSP_QUEUE_OK) { - csp_dbg_conn_ovf++; - csp_close(conn); - return CSP_ERR_NONE; - } - - /* Ensure that this connection will not be posted to this socket again */ - conn->dest_socket = NULL; + /* The packet itself does not tell connection-less from + * connection-oriented; only the bound socket does. Without a + * conn-less socket, the connection pool is the last possible + * match. */ + if (socket != NULL && csp_socket_is_conn_less(socket)) { + /* Connection-less delivery, the socket is the endpoint */ + csp_route_deliver_conn_less(socket, packet); + } else { + /* Connection-oriented delivery, a connection is the + * endpoint and the socket, when present, only accepts new + * connections */ + csp_route_deliver_connection(conn, socket, packet); } return CSP_ERR_NONE; diff --git a/src/csp_service_handler.c b/src/csp_service_handler.c index 6503af1e7..035dcb9e3 100644 --- a/src/csp_service_handler.c +++ b/src/csp_service_handler.c @@ -8,6 +8,15 @@ #include "cmp/csp_cmp_internal.h" + +static void set_u32_reply(csp_packet_t * packet, uint32_t value) { + + const uint32_t value_be = htobe32(value); + + memcpy(packet->data, &value_be, sizeof(value_be)); + packet->length = sizeof(value_be); +} + void csp_service_handler(csp_packet_t * packet) { switch (packet->id.dport) { @@ -15,8 +24,7 @@ void csp_service_handler(csp_packet_t * packet) { case CSP_CMP: /* Pass to CMP handler */ if (csp_cmp_handler(packet) != CSP_ERR_NONE) { - csp_buffer_free(packet); - return; + goto discard; } break; @@ -27,20 +35,13 @@ void csp_service_handler(csp_packet_t * packet) { case CSP_PS: { packet->length = csp_ps_hook(packet); if (packet->length == 0) { - csp_buffer_free(packet); + goto discard; } break; } case CSP_MEMFREE: { - - uint32_t total = 0; - total = csp_memfree_hook(); - - total = htobe32(total); - memcpy(packet->data, &total, sizeof(total)); - packet->length = sizeof(total); - + set_u32_reply(packet, csp_memfree_hook()); break; } @@ -57,32 +58,26 @@ void csp_service_handler(csp_packet_t * packet) { csp_shutdown_hook(); } - csp_buffer_free(packet); - return; + goto discard; } case CSP_BUF_FREE: { - uint32_t size = csp_buffer_remaining(); - size = htobe32(size); - memcpy(packet->data, &size, sizeof(size)); - packet->length = sizeof(size); + set_u32_reply(packet, (uint32_t)csp_buffer_remaining()); break; } case CSP_UPTIME: { - uint32_t time = csp_get_s(); - time = htobe32(time); - memcpy(packet->data, &time, sizeof(time)); - packet->length = sizeof(time); + set_u32_reply(packet, csp_get_s()); break; } default: - csp_buffer_free(packet); - return; + goto discard; } - if (packet != NULL) { - csp_sendto_reply(packet, packet, CSP_O_SAME); - } + csp_sendto_reply(packet, packet, CSP_O_SAME); + return; + +discard: + csp_buffer_free(packet); } diff --git a/src/csp_yaml.c b/src/csp_yaml.c index 2402546a6..d1ab695dd 100644 --- a/src/csp_yaml.c +++ b/src/csp_yaml.c @@ -104,7 +104,7 @@ static void csp_yaml_end_if(struct data_s * data, unsigned int * dfl_addr) { iface = malloc(sizeof(csp_iface_t)); memset(iface, 0, sizeof(csp_iface_t)); csp_if_udp_conf_t * udp_conf = malloc(sizeof(csp_if_udp_conf_t)); - udp_conf->host = data->server; + udp_conf->host = strdup(data->server); udp_conf->lport = atoi(data->listen_port); udp_conf->rport = atoi(data->remote_port); csp_if_udp_init(iface, udp_conf); diff --git a/src/drivers/eth/eth_linux.c b/src/drivers/eth/eth_linux.c index fdbf17d6b..8cbe03de3 100644 --- a/src/drivers/eth/eth_linux.c +++ b/src/drivers/eth/eth_linux.c @@ -149,7 +149,7 @@ int csp_eth_init(const char * device, const char * ifname, int mtu, unsigned int ((uint8_t *)if_mac.ifr_hwaddr.sa_data)[5]); /* Allow the socket to be reused - in case connection is closed prematurely */ - int sockopt; + const int sockopt = 1; if (setsockopt(ctx->sockfd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof sockopt) == -1) { perror("setsockopt"); close(ctx->sockfd); diff --git a/wscript b/wscript index b7e6bea20..6d96af371 100644 --- a/wscript +++ b/wscript @@ -124,6 +124,13 @@ def configure(ctx): 'src/csp_services.c', 'src/csp_id.c', 'src/csp_sfp.c', + 'src/cmp/csp_cmp_clock.c', + 'src/cmp/csp_cmp_dispatch.c', + 'src/cmp/csp_cmp_ident.c', + 'src/cmp/csp_cmp_if_stats.c', + 'src/cmp/csp_cmp_mem.c', + 'src/cmp/csp_cmp_peek_poke.c', + 'src/cmp/csp_cmp_route.c', 'src/interfaces/csp_if_lo.c', 'src/interfaces/csp_if_can.c', 'src/interfaces/csp_if_can_pbuf.c', @@ -238,7 +245,8 @@ def build(ctx): name='csp_shlib', target='csp', use=['csp_h', 'util_shlib'], - lib=ctx.env.LIBS) + lib=ctx.env.LIBS, + vnum=VERSION) # Build Python bindings if ctx.env.HAVE_PYEXT: