burst-link-protocol 0.1.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ version: 2
2
+ updates:
3
+ # Maintain dependencies for GitHub Actions
4
+ - package-ecosystem: "github-actions"
5
+ directory: "/"
6
+ schedule:
7
+ interval: "weekly"
8
+ groups:
9
+ actions:
10
+ patterns:
11
+ - "*"
@@ -0,0 +1,38 @@
1
+ name: Pip
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ pull_request:
6
+ push:
7
+ branches:
8
+ - master
9
+
10
+ jobs:
11
+ build:
12
+ name: Build with Pip
13
+ runs-on: ${{ matrix.platform }}
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ # platform: [windows-latest, macos-latest, ubuntu-latest]
18
+ platform: [ ubuntu-latest]
19
+ python-version: ["3.12"]
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - uses: actions/setup-python@v5
25
+ with:
26
+ python-version: ${{ matrix.python-version }}
27
+
28
+ - name: Set min macOS version
29
+ if: runner.os == 'macOS'
30
+ run: |
31
+ echo "MACOSX_DEPLOYMENT_TARGET=10.14" >> $GITHUB_ENV
32
+
33
+ - name: Build and install
34
+ run: |
35
+ pip install --verbose .
36
+
37
+ - name: Test
38
+ run: python -m pytest
@@ -0,0 +1,82 @@
1
+ name: Wheels
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ pull_request:
6
+ push:
7
+ branches:
8
+ - master
9
+ release:
10
+ types:
11
+ - published
12
+
13
+ jobs:
14
+ build_sdist:
15
+ name: Build SDist
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ submodules: true
21
+
22
+ - name: Build SDist
23
+ run: pipx run build --sdist
24
+
25
+ - name: Check metadata
26
+ run: pipx run twine check dist/*
27
+
28
+ - uses: actions/upload-artifact@v4
29
+ with:
30
+ name: dist-sdist
31
+ path: dist/*.tar.gz
32
+
33
+
34
+ build_wheels:
35
+ name: Wheels on ${{ matrix.os }}
36
+ runs-on: ${{ matrix.os }}
37
+ strategy:
38
+ fail-fast: false
39
+ matrix:
40
+ os: [ubuntu-latest
41
+ # , macos-13, macos-14, windows-latest
42
+ ]
43
+
44
+ steps:
45
+ - uses: actions/checkout@v4
46
+ with:
47
+ submodules: true
48
+
49
+ - uses: pypa/cibuildwheel@v2.22
50
+ # only build 3.12
51
+ env:
52
+ # CIBW_BUILD: "cp312-*"
53
+ CIBW_BUILD: "cp312-manylinux_x86_64"
54
+
55
+ - name: Verify clean directory
56
+ run: git diff --exit-code
57
+ shell: bash
58
+
59
+ - name: Upload wheels
60
+ uses: actions/upload-artifact@v4
61
+ with:
62
+ path: wheelhouse/*.whl
63
+ name: dist-${{ matrix.os }}
64
+
65
+ upload_all:
66
+ name: Upload if release
67
+ needs: [build_wheels, build_sdist]
68
+ runs-on: ubuntu-latest
69
+ # if: github.event_name == 'release' && github.event.action == 'published'
70
+ permissions:
71
+ id-token: write
72
+ environment: pypi_release
73
+
74
+ steps:
75
+ - uses: actions/setup-python@v5
76
+ - uses: actions/download-artifact@v4
77
+ with:
78
+ path: dist
79
+ pattern: dist-*
80
+ merge-multiple: true
81
+
82
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,7 @@
1
+ build
2
+ .venv
3
+ *.whl
4
+ poetry.lock
5
+ **.pyc
6
+ .coverage
7
+ *.tar.gz
@@ -0,0 +1,23 @@
1
+ {
2
+ "configurations": [
3
+ {
4
+ "name": "Win32",
5
+ "includePath": [
6
+ "${workspaceFolder}/**",
7
+ "${workspaceFolder}/.venv/Lib/site-packages/nanobind/include/nanobind", //To be fixed
8
+ "${env:PYTHON_INCLUDE}"
9
+ ],
10
+ "defines": [
11
+ "_DEBUG",
12
+ "UNICODE",
13
+ "_UNICODE"
14
+ ],
15
+ "windowsSdkVersion": "10.0.26100.0",
16
+ "compilerPath": "cl.exe",
17
+ "cStandard": "c17",
18
+ "cppStandard": "c++17",
19
+ "intelliSenseMode": "windows-msvc-x64"
20
+ }
21
+ ],
22
+ "version": 4
23
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "name": "Python C++ Debug",
6
+ "type": "pythoncpp",
7
+ "request": "launch",
8
+ "pythonLaunchName": "Python: Current File",
9
+ "cppAttachName": "(Windows) Attach",
10
+ },
11
+ {
12
+ "name": "(Windows) Attach",
13
+ "type": "cppvsdbg",
14
+ "request": "attach",
15
+ "processId": ""
16
+ },
17
+ {
18
+ "name": "Python: Current File",
19
+ "type": "debugpy",
20
+ "request": "launch",
21
+ "program": "test/test.py",
22
+ "console": "integratedTerminal"
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,86 @@
1
+ {
2
+ "python-envs.pythonProjects": [],
3
+ "files.associations": {
4
+ "algorithm": "cpp",
5
+ "array": "cpp",
6
+ "atomic": "cpp",
7
+ "bit": "cpp",
8
+ "cctype": "cpp",
9
+ "charconv": "cpp",
10
+ "chrono": "cpp",
11
+ "clocale": "cpp",
12
+ "cmath": "cpp",
13
+ "compare": "cpp",
14
+ "complex": "cpp",
15
+ "concepts": "cpp",
16
+ "cstdarg": "cpp",
17
+ "cstddef": "cpp",
18
+ "cstdint": "cpp",
19
+ "cstdio": "cpp",
20
+ "cstdlib": "cpp",
21
+ "cstring": "cpp",
22
+ "ctime": "cpp",
23
+ "cwchar": "cpp",
24
+ "exception": "cpp",
25
+ "filesystem": "cpp",
26
+ "format": "cpp",
27
+ "forward_list": "cpp",
28
+ "functional": "cpp",
29
+ "initializer_list": "cpp",
30
+ "iomanip": "cpp",
31
+ "ios": "cpp",
32
+ "iosfwd": "cpp",
33
+ "iostream": "cpp",
34
+ "istream": "cpp",
35
+ "iterator": "cpp",
36
+ "limits": "cpp",
37
+ "list": "cpp",
38
+ "locale": "cpp",
39
+ "map": "cpp",
40
+ "memory": "cpp",
41
+ "new": "cpp",
42
+ "optional": "cpp",
43
+ "ostream": "cpp",
44
+ "ratio": "cpp",
45
+ "set": "cpp",
46
+ "sstream": "cpp",
47
+ "stdexcept": "cpp",
48
+ "stop_token": "cpp",
49
+ "streambuf": "cpp",
50
+ "string": "cpp",
51
+ "system_error": "cpp",
52
+ "thread": "cpp",
53
+ "tuple": "cpp",
54
+ "type_traits": "cpp",
55
+ "typeinfo": "cpp",
56
+ "unordered_map": "cpp",
57
+ "unordered_set": "cpp",
58
+ "utility": "cpp",
59
+ "variant": "cpp",
60
+ "vector": "cpp",
61
+ "xfacet": "cpp",
62
+ "xhash": "cpp",
63
+ "xiosbase": "cpp",
64
+ "xlocale": "cpp",
65
+ "xlocbuf": "cpp",
66
+ "xlocinfo": "cpp",
67
+ "xlocmes": "cpp",
68
+ "xlocmon": "cpp",
69
+ "xlocnum": "cpp",
70
+ "xloctime": "cpp",
71
+ "xmemory": "cpp",
72
+ "xstring": "cpp",
73
+ "xtr1common": "cpp",
74
+ "xtree": "cpp",
75
+ "xutility": "cpp",
76
+ "crc.h": "c",
77
+ "decoder.h": "c",
78
+ "stddef.h": "c",
79
+ "stdint.h": "c"
80
+ },
81
+ "python.testing.pytestArgs": [
82
+ "test"
83
+ ],
84
+ "python.testing.unittestEnabled": false,
85
+ "python.testing.pytestEnabled": true
86
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "tasks": [
4
+ {
5
+ "type": "shell",
6
+ "label": "pip install",
7
+ "command": "pip",
8
+ "args": [
9
+ "install",
10
+ "--no-build-isolation",
11
+ "-ve",
12
+ "."
13
+ ],
14
+ "group": {
15
+ "kind": "build",
16
+ "isDefault": true
17
+ },
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,90 @@
1
+ cmake_minimum_required(VERSION 3.15...3.26)
2
+
3
+ project(i_think_this_name_does_not_matter LANGUAGES C CXX)
4
+
5
+ if (NOT SKBUILD)
6
+ message(WARNING "\
7
+ This CMake file is meant to be executed using 'scikit-build'. Running
8
+ it directly will almost certainly not produce the desired result. If
9
+ you are a user trying to install this package, please use the command
10
+ below, which will install all necessary build dependencies, compile
11
+ the package in an isolated environment, and then install it.
12
+ =====================================================================
13
+ $ pip install .
14
+ =====================================================================
15
+ If you are a software developer, and this is your own package, then
16
+ it is usually much more efficient to install the build dependencies
17
+ in your environment once and use the following command that avoids
18
+ a costly creation of a new virtual environment at every compilation:
19
+ =====================================================================
20
+ $ pip install nanobind scikit-build-core[pyproject]
21
+ $ pip install --no-build-isolation -ve .
22
+ =====================================================================
23
+ You may optionally add -Ceditable.rebuild=true to auto-rebuild when
24
+ the package is imported. Otherwise, you need to re-run the above
25
+ after editing C++ files.")
26
+ endif()
27
+
28
+ # Try to import all Python components potentially needed by nanobind
29
+ find_package(Python 3.8
30
+ REQUIRED COMPONENTS Interpreter Development.Module
31
+ OPTIONAL_COMPONENTS Development.SABIModule)
32
+
33
+ if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
34
+ set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
35
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
36
+ endif()
37
+
38
+ option(COVERAGE "Enable coverage reporting" ON)
39
+ if(COVERAGE)
40
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage")
41
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g -fprofile-arcs -ftest-coverage")
42
+ endif()
43
+
44
+ execute_process(
45
+ COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
46
+ OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR)
47
+ list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}")
48
+
49
+ # Import nanobind through CMake's find_package mechanism
50
+ find_package(nanobind CONFIG REQUIRED)
51
+
52
+ # We are now ready to compile the actual extension module
53
+ nanobind_add_module(
54
+ # Name of the extension
55
+ burst_interface_c
56
+
57
+ # Target the stable ABI for Python 3.12+, which reduces
58
+ # the number of binary wheels that must be built. This
59
+ # does nothing on older Python versions
60
+ STABLE_ABI
61
+
62
+ # Build libnanobind statically and merge it into the
63
+ # extension (which itself remains a shared library)
64
+ #
65
+ # If your project builds multiple extensions, you can
66
+ # replace this flag by NB_SHARED to conserve space by
67
+ # reusing a shared libnanobind across libraries
68
+ NB_STATIC
69
+
70
+ # Source code goes here
71
+ src/decoder.c
72
+ src/encoder.c
73
+ src/python_bindings.cpp
74
+ src/crc.c
75
+ )
76
+
77
+ nanobind_add_stub(
78
+
79
+ burst_interface_c_stub
80
+ INSTALL_TIME
81
+ MODULE burst_interface_c
82
+ OUTPUT burst_interface_c.pyi
83
+ PYTHON_PATH $<TARGET_FILE_DIR:burst_interface_c>
84
+ DEPENDS burst_interface_c
85
+ )
86
+
87
+ target_include_directories(burst_interface_c PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
88
+
89
+ # Install directive for scikit-build-core
90
+ install(TARGETS burst_interface_c LIBRARY DESTINATION ".")
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.1
2
+ Name: burst-link-protocol
3
+ Version: 0.1.0
4
+ Summary: Binary Utility for Reliable Stream Transfer (BURST) is a library for encoding and decoding binary data streams into and from a byte stream.
5
+ Author-Email: Floris vernieuwe <floris@vernieuwe.eu>
6
+ Requires-Python: <4.0,>=3.10
7
+ Requires-Dist: cobs<2.0.0,>=1.2.1
8
+ Requires-Dist: numpy<3.0.0,>=2.2.3
9
+ Requires-Dist: crc<8.0.0,>=7.1.0
10
+ Requires-Dist: pytest<9.0.0,>=8.3.4
11
+ Requires-Dist: pytest-cov<7.0.0,>=6.0.0
12
+ Requires-Dist: pytest-benchmark<6.0.0,>=5.1.0
13
+ Requires-Dist: scikit-build-core[pyproject]<0.11.0,>=0.10.7; extra == "dev"
14
+ Requires-Dist: nanobind<3.0.0,>=2.5.0; extra == "dev"
15
+ Requires-Dist: pytest<9.0.0,>=8.3.4; extra == "dev"
16
+ Requires-Dist: pytest-cov<7.0.0,>=6.0.0; extra == "dev"
17
+ Requires-Dist: pytest-benchmark<6.0.0,>=5.1.0; extra == "dev"
18
+ Provides-Extra: dev
@@ -0,0 +1,69 @@
1
+ # BURST interface
2
+ Binary Utility for Reliable Stream Transfer (BURST) is a library for encoding and decoding binary data streams, a packet format.
3
+ It combines a 16 bit checksum and cobs encoding to convert packets into a format that can be sent over a stream.
4
+
5
+ This projects is written so it can be used both in python, c and c++ based project
6
+
7
+ # Installation instuctions
8
+
9
+ ## As an user
10
+
11
+ Simple installation
12
+ ```sh
13
+ pip install -e .
14
+ ```
15
+
16
+ ## As a developer
17
+
18
+ Fast build
19
+ ```sh
20
+ pip install --no-build-isolation -ve .
21
+ ```
22
+
23
+ Auto rebuild on run
24
+ ```sh
25
+ pip install --no-build-isolation -Ceditable.rebuild=true -ve .
26
+ ```
27
+
28
+
29
+ ### Python Stub files generation
30
+
31
+ They are generated automatically buy can also be generated
32
+
33
+ ```
34
+ python -m nanobind.stubgen -m nanobind_example_ext
35
+ ```
36
+
37
+ # Publishing instructions
38
+
39
+ ```
40
+
41
+ ```
42
+
43
+ # Test
44
+
45
+ ```sh
46
+ pytest
47
+ ```
48
+
49
+ # BURST protocol
50
+ TODO
51
+ * STAGE 1
52
+ * Convert cpp to c files [OK]
53
+ * Formalise naming [OK]
54
+ * Add c encode functions [OK]
55
+ * Test c encode functions [OK]
56
+ * Update README
57
+ * Improve poetry.toml [OK]
58
+
59
+
60
+ * STAGE 2
61
+ * Add CI/CD on github to compile x86
62
+ * Fix dependencies once compilation succeeds
63
+ * Publish on pypi
64
+ * STAGE 3
65
+ * Add a way to get C test coverage
66
+
67
+
68
+
69
+
@@ -0,0 +1,56 @@
1
+ [project]
2
+ name = "burst-link-protocol"
3
+ version = "0.1.0" # Choose one version reference here
4
+ description = "Binary Utility for Reliable Stream Transfer (BURST) is a library for encoding and decoding binary data streams into and from a byte stream."
5
+ requires-python = ">=3.10,<4.0"
6
+ authors = [
7
+ { name = "Floris vernieuwe", email = "floris@vernieuwe.eu" }
8
+ ]
9
+
10
+ dependencies = [
11
+ "cobs>=1.2.1,<2.0.0",
12
+ "numpy>=2.2.3,<3.0.0",
13
+ "crc>=7.1.0,<8.0.0",
14
+ "pytest>=8.3.4,<9.0.0",
15
+ "pytest-cov>=6.0.0,<7.0.0",
16
+ "pytest-benchmark>=5.1.0,<6.0.0"
17
+
18
+ ]
19
+
20
+
21
+ [project.optional-dependencies]
22
+ dev = [
23
+ "scikit-build-core[pyproject]>=0.10.7,<0.11.0",
24
+ "nanobind>=2.5.0,<3.0.0",
25
+ "pytest>=8.3.4,<9.0.0",
26
+ "pytest-cov>=6.0.0,<7.0.0",
27
+ "pytest-benchmark>=5.1.0,<6.0.0"
28
+ ]
29
+
30
+ [build-system]
31
+ requires = ["scikit-build-core>=0.10", "nanobind>=1.3.2"]
32
+ build-backend = "scikit_build_core.build"
33
+
34
+ [tool.scikit-build]
35
+ minimum-version = "build-system.requires"
36
+ cmake.build-type = "Debug"
37
+ cmake.args = ["-DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCOVERAGE=ON"]
38
+ build-dir = "build/{wheel_tag}"
39
+ wheel.py-api = "cp312"
40
+
41
+
42
+
43
+ [tool.cibuildwheel]
44
+ # Necessary to see build output from the actual compilation
45
+ build-verbosity = 1
46
+
47
+ # Run pytest to ensure that the package was correctly built
48
+ # test-command = "pytest test"
49
+ # test-requires = "pytest"
50
+
51
+ # Don't test Python 3.8 wheels on macOS/arm64
52
+ test-skip="cp38-macosx_*:arm64"
53
+
54
+ # Needed for full C++17 support
55
+ [tool.cibuildwheel.macos.environment]
56
+ MACOSX_DEPLOYMENT_TARGET = "10.14"
@@ -0,0 +1,67 @@
1
+ #ifndef BURST_INTERFACE_H
2
+ #define BURST_INTERFACE_H
3
+
4
+ #include <stddef.h>
5
+ #include <stdint.h>
6
+ #include <stdbool.h>
7
+
8
+ #define COBS_DELIMITER 0x00
9
+ #define COBS_MAX_CODE 0xFF
10
+
11
+ // Status codes returned by the encoder.
12
+ typedef enum
13
+ {
14
+ BURST_DATA_CONSUMED,
15
+ BURST_PACKET_READY,
16
+ BURST_OVERFLOW_ERROR,
17
+ BURST_ENCODE_ERROR,
18
+ BURST_CRC_ERROR,
19
+ BURST_DECODE_ERROR
20
+ } burst_status_t;
21
+
22
+ typedef struct
23
+ {
24
+ uint8_t *data;
25
+ size_t size;
26
+ } burst_packet_t;
27
+
28
+ typedef struct
29
+ {
30
+ uint8_t *buffer; // Output buffer for encoded packets.
31
+ size_t buffer_size; // Total size of the output buffer.
32
+ size_t out_head; // Current offset (number of bytes written).
33
+ } burst_encoder_t;
34
+
35
+ typedef struct
36
+ {
37
+ uint8_t *buffer; // Output buffer for decoded data.
38
+ size_t buffer_size; // Size of the output buffer.
39
+ size_t out_head; // Current count of decoded bytes stored.
40
+
41
+ uint8_t current_code; // Current block’s code byte; 0 indicates a new block is
42
+ // expected.
43
+ uint8_t bytes_remaining; // Number of data bytes left to copy for the current
44
+ // block.
45
+ bool pending_zero; // true if a zero should be inserted before starting the
46
+ // next block.
47
+
48
+ bool finished; // true if the packet is complete and available in the buffer
49
+ } burst_decoder_t;
50
+
51
+
52
+ // Encoder
53
+ void burst_encoder_init(burst_encoder_t *ctx, uint8_t *buffer, size_t size);
54
+ burst_status_t burst_encoder_add_packet(burst_encoder_t *ctx, const uint8_t *data, size_t size);
55
+ burst_packet_t burst_encoder_flush(burst_encoder_t *ctx);
56
+
57
+
58
+ // Decoder
59
+ void burst_decoder_init(burst_decoder_t *ctx, uint8_t *buffer, size_t size);
60
+ burst_status_t bust_decoder_add_data(burst_decoder_t *ctx, const uint8_t *data, size_t size,
61
+ size_t *consumed_bytes);
62
+ void burst_decoder_reset(burst_decoder_t *ctx);
63
+ burst_status_t burst_decoder_add_byte(burst_decoder_t *ctx, uint8_t byte);
64
+
65
+ burst_packet_t burst_decoder_get_packet(burst_decoder_t *ctx);
66
+
67
+ #endif // ENCODER_H
@@ -0,0 +1,4 @@
1
+ from burst_interface_c import BurstInterfaceC
2
+ from .main import BurstInterfacePy
3
+
4
+ __all__ = ["BurstInterfaceC", "BurstInterfacePy"]
@@ -0,0 +1,41 @@
1
+ from cobs import cobs
2
+ from crc import Calculator,Crc16
3
+
4
+ crc = Calculator(Crc16.IBM_3740)
5
+
6
+ class BurstInterfacePy:
7
+ buffer = b""
8
+
9
+ def __init__(self):
10
+ pass
11
+
12
+ @staticmethod
13
+ def crc16( data: bytes) -> bytes:
14
+ return crc.checksum(data).to_bytes(2, "big")
15
+
16
+
17
+ @staticmethod
18
+ def encode_packet(packet: bytes) -> bytes:
19
+ packet_with_crc = packet + BurstInterfacePy.crc16(packet)
20
+ return cobs.encode(packet_with_crc) + b"\x00"
21
+
22
+ @staticmethod
23
+ def decode_packet(packet: bytes) -> bytes:
24
+ # decode and check crc
25
+ decoded = cobs.decode(packet)
26
+
27
+ if BurstInterfacePy.crc16(decoded[:-2]) != decoded[-2:]:
28
+ raise ValueError("CRC mismatch")
29
+
30
+ return decoded[:-2]
31
+
32
+ def encode(self, packets: list[bytes]) -> bytes:
33
+ return b"".join([self.encode_packet(packet) for packet in packets])
34
+
35
+ def decode(self, steam: bytes) -> list[bytes]:
36
+ self.buffer += steam
37
+ separated_packets = self.buffer.split(b"\x00")
38
+ # Add last packet to buffer
39
+ self.buffer = separated_packets.pop()
40
+ return [self.decode_packet(packet) for packet in separated_packets]
41
+
@@ -0,0 +1,20 @@
1
+ #include <stddef.h>
2
+ #include <stdint.h>
3
+
4
+ /*
5
+ * Calculate CRC16-CCITT (polynomial 0x1021, initial value 0xFFFF) over
6
+ * the provided data.
7
+ */
8
+ uint16_t crc16_ccitt(const uint8_t *data, size_t length) {
9
+ uint16_t crc = 0xFFFF;
10
+ for (size_t i = 0; i < length; i++) {
11
+ crc ^= ((uint16_t)data[i]) << 8;
12
+ for (int j = 0; j < 8; j++) {
13
+ if (crc & 0x8000)
14
+ crc = (crc << 1) ^ 0x1021;
15
+ else
16
+ crc <<= 1;
17
+ }
18
+ }
19
+ return crc;
20
+ }
@@ -0,0 +1,4 @@
1
+ #include <stdint.h>
2
+
3
+ uint16_t crc16_ccitt(const uint8_t *data, size_t length);
4
+ #define CRC_SIZE sizeof(uint16_t)
@@ -0,0 +1,150 @@
1
+ #include "burst_interface.h"
2
+ #include "crc.h"
3
+ #include <stdbool.h>
4
+ #include <stddef.h>
5
+ #include <stdint.h>
6
+ #include <stdio.h>
7
+
8
+ void burst_decoder_init(burst_decoder_t *ctx, uint8_t *buffer, size_t size)
9
+ {
10
+ ctx->buffer = buffer;
11
+ ctx->buffer_size = size;
12
+ burst_decoder_reset(ctx);
13
+ }
14
+
15
+ burst_status_t bust_decoder_add_data(burst_decoder_t *ctx, const uint8_t *data, size_t size,
16
+ size_t *consumed_bytes)
17
+ {
18
+ // If the decoder was finished, reset it.
19
+ if (ctx->finished)
20
+ {
21
+ burst_decoder_reset(ctx);
22
+ }
23
+
24
+ for (size_t i = 0; i < size; i++)
25
+ {
26
+ uint8_t byte = data[i];
27
+ (*consumed_bytes)++;
28
+
29
+ burst_status_t result = burst_decoder_add_byte(ctx, byte);
30
+
31
+ if (result != BURST_DATA_CONSUMED)
32
+ {
33
+ ctx->finished = true;
34
+ return result;
35
+ }
36
+ }
37
+ return BURST_DATA_CONSUMED;
38
+ }
39
+ void burst_decoder_reset(burst_decoder_t *ctx)
40
+ {
41
+ ctx->out_head = 0;
42
+ ctx->current_code = 0;
43
+ ctx->bytes_remaining = 0;
44
+ ctx->pending_zero = false;
45
+ ctx->finished = false;
46
+ }
47
+
48
+ burst_status_t burst_decoder_complete_packet(burst_decoder_t *ctx)
49
+ {
50
+ // Ensure we have at least two bytes for the CRC.
51
+ if (ctx->out_head < CRC_SIZE)
52
+ {
53
+ return BURST_CRC_ERROR;
54
+ }
55
+
56
+ // Calculate the CRC over the packet data excluding the last two CRC bytes.
57
+ uint16_t computed_crc = crc16_ccitt(ctx->buffer, ctx->out_head - CRC_SIZE);
58
+
59
+ // Extract the received CRC from the last two bytes (big-endian).
60
+ uint16_t received_crc =
61
+ ((uint16_t)ctx->buffer[ctx->out_head - CRC_SIZE] << 8) |
62
+ ctx->buffer[ctx->out_head - 1];
63
+
64
+ // Check if the CRCs match.
65
+ if (computed_crc != received_crc)
66
+ {
67
+ return BURST_CRC_ERROR;
68
+ }
69
+
70
+ // CRC check passed, we can remove it from the packet.
71
+ ctx->out_head -= CRC_SIZE;
72
+ return BURST_PACKET_READY;
73
+ }
74
+
75
+ // If byte is a delimiter but a block is not complete, return COBS_DECODE_ERROR
76
+ // If the buffer is full, return COBS_OVERFLOW_ERROR
77
+ // If the byte is consumed, but the packet is not complete, return COBS_DATA_CONSUMED
78
+ // If the packet is complete, return COBS_PACKET_READY
79
+ burst_status_t burst_decoder_add_byte(burst_decoder_t *ctx, uint8_t byte)
80
+ {
81
+ // Check if there is space for more data.
82
+ if (ctx->out_head >= ctx->buffer_size) {
83
+ return BURST_OVERFLOW_ERROR;
84
+ }
85
+
86
+ // If the byte is a delimiter, decide if it terminates the packet.
87
+ if (byte == COBS_DELIMITER) {
88
+ // If in the middle of a block, a delimiter is not allowed.
89
+ if (ctx->current_code != 0) {
90
+ return BURST_DECODE_ERROR;
91
+ }
92
+ // Otherwise, the packet is complete.
93
+ return burst_decoder_complete_packet(ctx);
94
+ }
95
+
96
+ // If a zero is pending from a previous block, insert it now.
97
+ if (ctx->pending_zero) {
98
+ if (ctx->out_head >= ctx->buffer_size) {
99
+ return BURST_OVERFLOW_ERROR;
100
+ }
101
+ ctx->buffer[ctx->out_head++] = COBS_DELIMITER;
102
+ ctx->pending_zero = false;
103
+ // Now, treat the current byte as a new block code.
104
+ ctx->current_code = byte;
105
+ ctx->bytes_remaining = (byte > 0 ? byte - 1 : 0);
106
+ return BURST_DATA_CONSUMED;
107
+ }
108
+
109
+ // If not currently in a block, this byte is the new block code.
110
+ if (ctx->current_code == 0) {
111
+ ctx->current_code = byte;
112
+ ctx->bytes_remaining = (byte > 0 ? byte - 1 : 0);
113
+ return BURST_DATA_CONSUMED;
114
+ }
115
+
116
+ // Otherwise, we are in the middle of a block so treat the byte as data.
117
+ ctx->buffer[ctx->out_head++] = byte;
118
+ if (ctx->bytes_remaining > 0) {
119
+ ctx->bytes_remaining--;
120
+ }
121
+
122
+ // When the block is complete...
123
+ if (ctx->bytes_remaining == 0) {
124
+ // If the block's code is less than COBS_MAX_CODE, a zero is pending.
125
+ if (ctx->current_code < COBS_MAX_CODE) {
126
+ ctx->pending_zero = true;
127
+ }
128
+ ctx->current_code = 0;
129
+ }
130
+
131
+ return BURST_DATA_CONSUMED;
132
+ }
133
+
134
+
135
+ burst_packet_t burst_decoder_get_packet(burst_decoder_t *ctx)
136
+ {
137
+
138
+ if (!ctx->finished)
139
+ {
140
+ burst_packet_t packet;
141
+ packet.data = NULL;
142
+ packet.size = 0;
143
+ return packet;
144
+ }
145
+
146
+ burst_packet_t packet;
147
+ packet.data = ctx->buffer;
148
+ packet.size = ctx->out_head;
149
+ return packet;
150
+ }
@@ -0,0 +1,91 @@
1
+ #include "burst_interface.h"
2
+ #include "crc.h" // Assumes crc16_ccitt() is available.
3
+ #include <stddef.h>
4
+ #include <stdint.h>
5
+
6
+ // COBS encoding with CRC appending.
7
+ // The input to be encoded is the raw packet data followed by the two CRC bytes.
8
+ // The algorithm works by maintaining a "code" (the count of nonzero bytes)
9
+ // and inserting that count at the start of each block. When a zero is encountered
10
+ // (or when the block length reaches COBS_MAX_CODE) the block is terminated.
11
+ burst_status_t burst_encoder_add_packet(burst_encoder_t *ctx, const uint8_t *data, size_t size)
12
+ {
13
+ // Compute the CRC over the raw packet data.
14
+ uint16_t crc = crc16_ccitt(data, size);
15
+ uint8_t crc_high = (crc >> 8) & 0xFF;
16
+ uint8_t crc_low = crc & 0xFF;
17
+ // The total number of bytes to encode: raw data + 2 bytes of CRC.
18
+ size_t total_bytes = size + CRC_SIZE;
19
+
20
+ // Initialize COBS block state.
21
+ uint8_t code = 1; // Code value starts at 1.
22
+ // Reserve space for the code byte.
23
+ if (ctx->out_head >= ctx->buffer_size)
24
+ return BURST_OVERFLOW_ERROR;
25
+ size_t code_index = ctx->out_head;
26
+ ctx->buffer[ctx->out_head++] = 0; // Placeholder for the code.
27
+
28
+ // Process each byte from the raw data and then the CRC.
29
+ for (size_t i = 0; i < total_bytes; i++) {
30
+ uint8_t byte;
31
+ if (i < size)
32
+ byte = data[i];
33
+ else if (i == size)
34
+ byte = crc_high;
35
+ else // i == size + 1
36
+ byte = crc_low;
37
+
38
+ if (byte == 0) {
39
+ // Write the current code to the reserved position.
40
+ ctx->buffer[code_index] = code;
41
+ // Start a new block.
42
+ code = 1;
43
+ if (ctx->out_head >= ctx->buffer_size)
44
+ return BURST_OVERFLOW_ERROR;
45
+ code_index = ctx->out_head;
46
+ ctx->buffer[ctx->out_head++] = 0; // Reserve placeholder for new code.
47
+ } else {
48
+ // Append the nonzero byte.
49
+ if (ctx->out_head >= ctx->buffer_size)
50
+ return BURST_OVERFLOW_ERROR;
51
+ ctx->buffer[ctx->out_head++] = byte;
52
+ code++;
53
+ // If the maximum code value is reached, finish the block.
54
+ if (code == COBS_MAX_CODE) {
55
+ ctx->buffer[code_index] = code;
56
+ code = 1;
57
+ if (ctx->out_head >= ctx->buffer_size)
58
+ return BURST_OVERFLOW_ERROR;
59
+ code_index = ctx->out_head;
60
+ ctx->buffer[ctx->out_head++] = 0; // Reserve new placeholder.
61
+ }
62
+ }
63
+ }
64
+
65
+ // Finalize the last block.
66
+ ctx->buffer[code_index] = code;
67
+
68
+ // Append the packet delimiter.
69
+ if (ctx->out_head >= ctx->buffer_size)
70
+ return BURST_OVERFLOW_ERROR;
71
+ ctx->buffer[ctx->out_head++] = COBS_DELIMITER;
72
+
73
+ return BURST_PACKET_READY;
74
+ }
75
+
76
+ void burst_encoder_init(burst_encoder_t *ctx, uint8_t *buffer, size_t size)
77
+ {
78
+ ctx->buffer = buffer;
79
+ ctx->buffer_size = size;
80
+ ctx->out_head = 0;
81
+ }
82
+
83
+ burst_packet_t burst_encoder_flush(burst_encoder_t *ctx)
84
+ {
85
+ burst_packet_t packet;
86
+ packet.data = ctx->buffer;
87
+ packet.size = ctx->out_head;
88
+ // Reset the encoder context for the next use.
89
+ ctx->out_head = 0;
90
+ return packet;
91
+ }
@@ -0,0 +1,80 @@
1
+ #include <nanobind/nanobind.h>
2
+ extern "C"
3
+ {
4
+ #include <burst_interface.h>
5
+ }
6
+
7
+ namespace nb = nanobind;
8
+ using namespace nb::literals;
9
+
10
+ struct BurstInterface
11
+ {
12
+ burst_decoder_t decoder;
13
+ uint8_t decoder_buffer[1024] = {0};
14
+ burst_encoder_t encoder;
15
+ uint8_t encoder_buffer[1024] = {0};
16
+
17
+ BurstInterface()
18
+ {
19
+ burst_decoder_init(&decoder, decoder_buffer, sizeof(decoder_buffer));
20
+ burst_encoder_init(&encoder, encoder_buffer, sizeof(encoder_buffer));
21
+ }
22
+
23
+ nb::list decode(nb::bytes data, bool fail_on_crc_error = false)
24
+ {
25
+ nb::list result;
26
+ uint8_t *data_ptr = (uint8_t *)data.data();
27
+ size_t data_size = data.size();
28
+
29
+ size_t bytes_consumed = 0;
30
+ while (bytes_consumed < data_size)
31
+ {
32
+ burst_status_t status = bust_decoder_add_data(&decoder, data_ptr + bytes_consumed, data_size - bytes_consumed, &bytes_consumed);
33
+
34
+ if (status == BURST_PACKET_READY)
35
+ {
36
+ burst_packet_t packet = burst_decoder_get_packet(&decoder);
37
+ nb::bytes packet_bytes(reinterpret_cast<const char *>(packet.data), packet.size);
38
+ result.append(packet_bytes);
39
+ }
40
+
41
+ if (fail_on_crc_error)
42
+ {
43
+ if (status == BURST_CRC_ERROR)
44
+ {
45
+ throw std::runtime_error("CRC error");
46
+ }
47
+ if (status == BURST_DECODE_ERROR)
48
+ {
49
+ throw std::runtime_error("Decode error");
50
+ }
51
+ if (status == BURST_OVERFLOW_ERROR)
52
+ {
53
+ throw std::runtime_error("Overflow error");
54
+ }
55
+ }
56
+ }
57
+ return result;
58
+ }
59
+
60
+ nb::bytes encode(nb::list data)
61
+ {
62
+ for (size_t i = 0; i < data.size(); i++)
63
+ {
64
+ nb::bytes data_bytes = data[i];
65
+ burst_encoder_add_packet(&encoder, (uint8_t *)data_bytes.data(), data_bytes.size());
66
+ }
67
+ // flush the encoder
68
+ burst_packet_t packet = burst_encoder_flush(&encoder);
69
+ return nb::bytes(reinterpret_cast<const char *>(packet.data), packet.size);
70
+ }
71
+ };
72
+
73
+ NB_MODULE(burst_interface_c, m)
74
+ {
75
+
76
+ nb::class_<BurstInterface>(m, "BurstInterfaceC")
77
+ .def(nb::init<>())
78
+ .def("decode", &BurstInterface::decode, "data"_a, "fail_on_crc_error"_a = false)
79
+ .def("encode", &BurstInterface::encode, "packets"_a);
80
+ }
@@ -0,0 +1,52 @@
1
+
2
+ from burst_link_protocol import BurstInterfacePy,BurstInterfaceC
3
+ import pytest
4
+ import numpy as np
5
+
6
+ def test_c_decoder_python():
7
+ python_interface = BurstInterfacePy()
8
+ c_interface = BurstInterfaceC()
9
+
10
+ packets = [b"Hello, world!", b"Goodbye, world!"]
11
+
12
+ data = python_interface.encode(packets)
13
+ decoded = c_interface.decode(data)
14
+ print(decoded)
15
+ assert packets == decoded
16
+
17
+ def test_python_crc_validation():
18
+ interface = BurstInterfacePy()
19
+ packets = [b"Hello, world!", b"Goodbye, world!"]
20
+
21
+ data = bytearray(interface.encode(packets))
22
+
23
+ for i in range( len(data)):
24
+ c_interface = BurstInterfaceC()
25
+
26
+ data_copy = data.copy()
27
+
28
+ # modify byte x
29
+ data_copy[i] = (data_copy[i] + 1) % 256
30
+ with pytest.raises(Exception):
31
+ decoded = c_interface.decode(bytes(data_copy),fail_on_crc_error=True)
32
+ print(decoded)
33
+ assert len(decoded) == len(packets), f"Expected {len(packets)} packets, got {len(decoded)}"
34
+
35
+ def test_max_size_error():
36
+ interface = BurstInterfacePy()
37
+ c_interface = BurstInterfaceC()
38
+ packets = np.random.bytes(1500)
39
+ with pytest.raises(Exception):
40
+ data = c_interface.decode(interface.encode([packets]),fail_on_crc_error=True)
41
+
42
+ def test_encoding():
43
+ interface = BurstInterfacePy()
44
+ c_interface = BurstInterfaceC()
45
+ packets = [b"Hello, world!", b"Goodbye, world!"]
46
+
47
+ assert interface.encode(packets) == c_interface.encode(packets)
48
+
49
+ # if __name__ == "__main__":
50
+ # # test_c_decoder_python()
51
+ # # test_python_crc_validation()
52
+ # # max_size_error()
@@ -0,0 +1,27 @@
1
+
2
+ from burst_link_protocol import BurstInterfacePy
3
+ import pytest
4
+
5
+ def test_python():
6
+ interface = BurstInterfacePy()
7
+ packets = [b"Hello, world!", b"Goodbye, world!"]
8
+
9
+ data = interface.encode(packets)
10
+ decoded = interface.decode(data)
11
+ assert packets == decoded
12
+
13
+ def test_python_crc_validation():
14
+ interface = BurstInterfacePy()
15
+ packets = [b"Hello, world!", b"Goodbye, world!"]
16
+
17
+ data = bytearray(interface.encode(packets))
18
+
19
+ for i in range( len(data)):
20
+ data_copy = data.copy()
21
+
22
+ # modify byte x
23
+ data_copy[i] = (data_copy[i] + 1) % 256
24
+ with pytest.raises(Exception):
25
+ decoded = interface.decode(bytes(data_copy))
26
+ print(decoded)
27
+ assert len(decoded) == len(packets), f"Expected {len(packets)} packets, got {len(decoded)}"
@@ -0,0 +1,20 @@
1
+
2
+ from burst_link_protocol import BurstInterfacePy,BurstInterfaceC
3
+ import pytest
4
+ import numpy as np
5
+
6
+ def prepare_packets(packet_size:int) -> bytes:
7
+ python_interface = BurstInterfacePy()
8
+ return python_interface.encode([np.zeros(packet_size).tobytes()])
9
+
10
+
11
+
12
+ @pytest.mark.parametrize("n", [1, 10, 100, 1000])
13
+ def test_performance_c(benchmark, n):
14
+ c_interface = BurstInterfaceC()
15
+ benchmark(c_interface.decode, prepare_packets(n))
16
+
17
+ @pytest.mark.parametrize("n", [1, 10, 100, 1000])
18
+ def test_performance_python(benchmark, n):
19
+ c_interface = BurstInterfacePy()
20
+ benchmark(c_interface.decode, prepare_packets(n))