diff --git a/.bumpversion.cfg b/.bumpversion.cfg index f7e15366..769d94b9 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.21.9 +current_version = 0.22.0 commit = True tag = False diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..32f9bb57 --- /dev/null +++ b/.clang-format @@ -0,0 +1,206 @@ +--- +Language: Cpp +AccessModifierOffset: -1 +AlignAfterOpenBracket: BlockIndent +AlignArrayOfStructures: Left +AlignConsecutiveMacros: true +# Enabled: true +# AcrossEmptyLines: true +# AcrossComments: true +AlignConsecutiveAssignments: true +# Enabled: true +# AcrossEmptyLines: true +# AcrossComments: true +AlignConsecutiveDeclarations: true +# Enabled: false +# AcrossEmptyLines: true +# AcrossComments: true +AlignConsecutiveBitFields: true +# Enabled: false +# AcrossEmptyLines: true +# AcrossComments: true +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +# Starting with clang-format 16: +# Kind: Always +# OverEmptyLines: 2 +# MaxEmptyLinesToKeep: 2 +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +# AttributeMacros: ['__capability', '__output', '__ununsed'] # useful for language extensions or static analyzer annotations. +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: After +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +# BracedInitializerIndentWidth: 4 +# BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: true +BreakBeforeBinaryOperators: None +# BreakBeforeConceptDeclarations: Allowed +BreakBeforeInheritanceComma: false +BreakInheritanceList: AfterComma +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +BreakStringLiterals: true +ColumnLimit: 132 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DeriveLineEnding: true +DerivePointerAlignment: true +DisableFormat: false +EmptyLineAfterAccessModifier: Always +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + - Regex: "^<.*" + Priority: 2 + SortPriority: 0 + - Regex: ".*" + Priority: 3 + SortPriority: 0 +IncludeIsMainRegex: "([-_](test|unittest))?$" +IncludeIsMainSourceRegex: "" +IndentCaseLabels: true +IndentCaseBlocks: true +IndentExternBlock: Indent +IndentGotoLabels: true +IndentPPDirectives: BeforeHash +# IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +# InsertBraces: true # be careful! +# InsertNewlineAtEOF: true # clang-format 16 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: Inner +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: NextLine +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PPIndentWidth: 4 +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - "c++" + - "C++" + CanonicalDelimiter: "" + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: "" + BasedOnStyle: google +ReferenceAlignment: Left +ReflowComments: true +SeparateDefinitionBlocks: Always +SortIncludes: true +SortUsingDeclarations: true + +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false + +SpaceAroundPointerQualifiers: Both + +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptControlMacros +# SpaceBeforeParensOptions -- TODO: +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpacesBeforeTrailingComments: 2 + +SpacesInAngles: Leave +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false + +Standard: Auto +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never diff --git a/.codeclimate.yml b/.codeclimate.yml index 059d2241..f159a802 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -7,7 +7,7 @@ engines: enabled: true ratings: paths: - - "**.py" + - "**.py" checks: argument-count: @@ -42,4 +42,4 @@ checks: threshold: 50 # language-specific defaults. an override will affect all languages. exclude_patterns: -- "pyxcp/tests/**" + - "pyxcp/tests/**" diff --git a/.darglint b/.darglint new file mode 100644 index 00000000..72ccc6c5 --- /dev/null +++ b/.darglint @@ -0,0 +1,2 @@ +[darglint] +strictness = long diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b86bc43f..de1986a3 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,7 @@ ## Types of changes + + - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) diff --git a/.github/workflows/pythonapp.yml b/.github/workflows/pythonapp.yml index 123da4be..65cd59ba 100644 --- a/.github/workflows/pythonapp.yml +++ b/.github/workflows/pythonapp.yml @@ -14,46 +14,42 @@ jobs: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: - fail-fast: false - matrix: - os: [ - macos-13, - windows-latest, - ubuntu-latest - ] - cibw_archs: ["auto"] - include: - - os: ubuntu-latest - cibw_archs: "aarch64" + fail-fast: false + matrix: + os: [macos-13, windows-latest, ubuntu-latest] + cibw_archs: ["auto"] + include: + - os: ubuntu-latest + cibw_archs: "aarch64" steps: - uses: actions/checkout@v2 - name: Install Rust uses: actions-rs/toolchain@v1 with: - toolchain: nightly - override: true - components: rustfmt, clippy + toolchain: nightly + override: true + components: rustfmt, clippy - name: Build wheel uses: pypa/cibuildwheel@v2.16 - uses: actions/upload-artifact@v2 with: - path: ./wheelhouse/*.whl + path: ./wheelhouse/*.whl build_sdist: name: Build source distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - - name: Build sdist - run: | - pip install -U build - python -m build --sdist + - name: Build sdist + run: | + pip install -U build + python -m build --sdist - - uses: actions/upload-artifact@v2 - with: - path: dist/*.tar.gz + - uses: actions/upload-artifact@v2 + with: + path: dist/*.tar.gz upload_pypi: needs: [build_wheels, build_sdist] runs-on: ubuntu-latest @@ -61,27 +57,27 @@ jobs: # alternatively, to publish when a GitHub Release is created, use the following rule: # if: github.event_name == 'release' && github.event.action == 'published' steps: - - uses: actions/download-artifact@v2 - with: - name: artifact - path: dist + - uses: actions/download-artifact@v2 + with: + name: artifact + path: dist - - uses: pypa/gh-action-pypi-publish@v1.4.2 - with: - user: __token__ - password: ${{ secrets.PYPI_PASSWORD }} + - uses: pypa/gh-action-pypi-publish@v1.4.2 + with: + user: __token__ + password: ${{ secrets.PYPI_PASSWORD }} # To test: repository_url: https://test.pypi.org/legacy/ # - # - name: Upload to PyPI - #uses: pypa/gh-action-pypi-publish@v1.4.2 - #with: - # user: ${{ secrets.PYPI_USER_NAME }} - # password: ${{ secrets.PYPI_PASSWORD }} - #- name: Build and publish - # env: - # TWINE_USERNAME: ${{ secrets.PYPI_USER_NAME }} - # TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - # if: env.TWINE_USERNAME != null - # run: | - # python setup.py bdist_wheel - # twine upload dist/* + # - name: Upload to PyPI + #uses: pypa/gh-action-pypi-publish@v1.4.2 + #with: + # user: ${{ secrets.PYPI_USER_NAME }} + # password: ${{ secrets.PYPI_PASSWORD }} + #- name: Build and publish + # env: + # TWINE_USERNAME: ${{ secrets.PYPI_USER_NAME }} + # TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + # if: env.TWINE_USERNAME != null + # run: | + # python setup.py bdist_wheel + # twine upload dist/* diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 6648c3f1..2bca4ca3 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -5,9 +5,9 @@ name: Run tests. on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: build: @@ -15,20 +15,20 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] # macos-latest - python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + os: [ubuntu-latest, windows-latest] # macos-latest + python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip setuptools - pip install -r requirements.txt - python setup.py install - - name: Test with pytest - run: | - pip install pytest - pytest + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools + pip install -r requirements.txt + python setup.py install + - name: Test with pytest + run: | + pip install pytest + pytest diff --git a/.pdm.toml b/.pdm.toml deleted file mode 100644 index 09a9763b..00000000 --- a/.pdm.toml +++ /dev/null @@ -1,25 +0,0 @@ -[python] -auto_global = false -build_isolation = false -check_update = false -parallel_install = true -project_max_depth = 5 -use_venv = false -path = "/usr/bin/python" - -[python.feature] -install_cache = true -install_cache_method = "symlink" - -[python.pypi] -json_api = true -url = "https://pypi.org/simple" -verify_ssl = false - -[python.python] -use_pyenv = false - -[python.strategy] -resolve_max_rounds = 10 -save = "minimum" -update = "reuse" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e663ef56..47efc495 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,107 +1,131 @@ repos: -- repo: local - hooks: - - id: check-yaml - name: check-yaml - entry: check-yaml - language: python - types: [yaml] - stages: [commit] - - id: check-json - name: check-json - entry: check-json - language: python - types: [json] - stages: [commit] - - id: check-toml - name: check-toml - entry: check-toml - language: python - types: [toml] - stages: [commit] - - id: check-ast - name: check-ast - entry: check-ast - language: python - types: [python] - stages: [commit] - #- id: autopep8 - #name: autopep8 - #entry: autopep8 - #language: python - #types: [python] - #args: [-a, -i] - #stages: [commit] - - id: check-builtin-literals - name: check-builtin-literals - entry: check-builtin-literals - language: python - types: [python] - stages: [commit] - - id: check-case-conflict - name: check-case-conflict - entry: check-case-conflict - language: python - types: [python] - stages: [commit] - - id: check-merge-conflict - name: check-merge-conflict - entry: check-merge-conflict - language: python - types: [text] - stages: [commit] - - id: fix-byte-order-marker - name: fix-byte-order-marker - entry: fix-byte-order-marker - language: python - types: [python] - stages: [commit] - - id: mixed-line-ending - name: mixed-line-ending - entry: mixed-line-ending - language: python - types_or: [c, c++, python] - stages: [commit] - - id: end-of-file-fixer - name: end-of-file-fixer - entry: end-of-file-fixer - language: python - types_or: [c, c++, python] - stages: [commit] - - id: trailing-whitespace-fixer - name: trailing-whitespace-fixer - entry: trailing-whitespace-fixer - language: python - types_or: [c, c++, python] - stages: [commit] - - id: reorder-python-imports - name: reorder-python-imports - entry: reorder-python-imports - language: python - types: [python] - stages: [commit] - - id: black - name: black - entry: black - language: python - types: [python] - stages: [commit] - - id: selective_tests - name: selective_tests - entry: ./selective_tests.py - language: python - types: [python] - stages: [commit] - - id: flake8 - name: flake8 - entry: flake8 - language: python - types: [python] - stages: [commit] - - id: pytest-check - name: pytest-check - entry: pytest - language: system - pass_filenames: false - always_run: true - stages: [push] + - repo: local + hooks: + - id: bandit + name: bandit + entry: bandit + language: system + types: [python] + require_serial: true + args: ["-c", "bandit.yml"] + - id: black + name: black + entry: black + language: system + types: [python] + require_serial: true + - id: ruff + name: ruff + entry: ruff + language: system + types: [python] + args: ["check"] + require_serial: true + - id: check-added-large-files + name: Check for added large files + entry: check-added-large-files + language: system + - id: check-toml + name: Check Toml + entry: check-toml + language: system + types: [toml] + - id: check-json + name: check-json + entry: check-json + language: python + types: [json] + - id: check-yaml + name: Check Yaml + entry: check-yaml + language: system + types: [yaml] + - id: check-ast + name: check-ast + entry: check-ast + language: python + types: [python] + stages: [commit] + - id: check-builtin-literals + name: check-builtin-literals + entry: check-builtin-literals + language: python + types: [python] + stages: [commit] + - id: check-case-conflict + name: check-case-conflict + entry: check-case-conflict + language: python + types: [python] + stages: [commit] + - id: check-merge-conflict + name: check-merge-conflict + entry: check-merge-conflict + language: python + types: [text] + stages: [commit] + - id: fix-byte-order-marker + name: fix-byte-order-marker + entry: fix-byte-order-marker + language: python + types: [python] + stages: [commit] + - id: mixed-line-ending + name: mixed-line-ending + entry: mixed-line-ending + language: python + types_or: [c, c++, python] + stages: [commit] + - id: end-of-file-fixer + name: end-of-file-fixer + entry: end-of-file-fixer + language: python + types_or: [python] + stages: [commit] + - id: darglint + name: darglint + entry: darglint + language: system + types: [python] + stages: [manual] + - id: end-of-file-fixer + name: Fix End of Files + entry: end-of-file-fixer + language: system + types: [text] + stages: [commit, push, manual] + - id: flake8 + name: flake8 + entry: flake8 + language: system + types: [python] + require_serial: true + args: [--darglint-ignore-regex, .*] + - id: isort + name: isort + entry: isort + require_serial: true + language: system + types_or: [cython, pyi, python] + args: ["--filter-files"] + - id: pyupgrade + name: pyupgrade + description: Automatically upgrade syntax for newer versions. + entry: pyupgrade + language: system + types: [python] + args: [--py38-plus] + - id: trailing-whitespace + name: Trim Trailing Whitespace + entry: trailing-whitespace-fixer + language: system + types: [text] + stages: [commit, push, manual] + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.6.0 + hooks: + - id: prettier + #- repo: https://github.com/necaris/pre-commit-pyright + #rev: '1.1.53' + #hooks: + #- id: pyright diff --git a/.readthedocs.yml b/.readthedocs.yml index ac448afd..5f125f5d 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,17 +1,15 @@ - version: 2 sphinx: - configuration: docs/conf.py + configuration: docs/conf.py formats: all python: - version: 3.7 - install: - - requirements: docs/requirements.txt - - method: pip - path: . - - method: setuptools - path: . - + version: 3.7 + install: + - requirements: docs/requirements.txt + - method: pip + path: . + - method: setuptools + path: . diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..7529d361 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,117 @@ + +cmake_minimum_required(VERSION 3.7...3.29) +project(pyxcp_extensions LANGUAGES C CXX) + +find_package(Python COMPONENTS Interpreter Development) +find_package(pybind11 CONFIG) + +SET(CMAKE_C_STANDARD 17) +set(CMAKE_CXX_STANDARD 23) + +message( STATUS "Found pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}: ${pybind11_INCLUDE_DIRS}") + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/dist") + + +SET(GCC_N_CLANG_BASE_OPTIONS "-std=c++23 -Wall -Wextra -Wpedantic -Warray-bounds -mtune=native -fexceptions") + +SET(MSVC_BASE_OPTIONS "/W3 /permissive- /EHsc /bigobj /Zc:__cplusplus /std:c++latest") + + + +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + if (MSVC) + SET(MSVC_BASE_OPTIONS "${MSVC_BASE_OPTIONS} /Od /fsanitize=address /Zi") + else() + SET(MSVC_BASE_OPTIONS "${MSVC_BASE_OPTIONS} -Og -g3 -ggdb --fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined -fsanitize=bounds") # -fsanitize=hwaddress + endif() +else () + if (MSVC) + SET(MSVC_BASE_OPTIONS "${MSVC_BASE_OPTIONS} /Ox") + else() + SET(MSVC_BASE_OPTIONS "${MSVC_BASE_OPTIONS} -O3 --fomit-frame-pointer") + endif() + endif () + + +if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(ENV{MACOSX_DEPLOYMENT_TARGET} "11.0") + SET(GCC_N_CLANG_EXTRA_OPTIONS "-stdlib=libc++") + message("Platform is Darwin") +elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") + message("Platform is WINDOWS") + SET(MSVC_EXTRA_OPTIONS "") +elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") + SET(GCC_N_CLANG_EXTRA_OPTIONS "-fvisibility=hidden -g0") # -fcoroutines + message("Platform is LINUX") +endif() + + +IF (CMAKE_C_COMPILER_ID STREQUAL "GNU") + +ELSEIF (CMAKE_C_COMPILER_ID MATCHES "Clang") + +ELSEIF (CMAKE_C_COMPILER_ID MATCHES "MSVC") + +ELSE () + +ENDIF () + +IF (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + +ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + +ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + +ELSE () + + +ENDIF () + +message("Compiling C with: " ${CMAKE_C_COMPILER_ID}) +message("Compiling Cpp with: " ${CMAKE_CXX_COMPILER_ID}) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(EXTENSION_INCS ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/cpp_ext) + +pybind11_add_module(rekorder pyxcp/recorder/wrap.cpp pyxcp/recorder/lz4.c pyxcp/recorder/lz4hc.c) +pybind11_add_module(cpp_ext pyxcp/cpp_ext/extension_wrapper.cpp) +pybind11_add_module(stim pyxcp/daq_stim/stim_wrapper.cpp pyxcp/daq_stim/stim.cpp pyxcp/daq_stim/scheduler.cpp) + +target_include_directories(rekorder PRIVATE ${EXTENSION_INCS} ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/recorder) +target_include_directories(cpp_ext PRIVATE ${EXTENSION_INCS}) +target_include_directories(stim PRIVATE ${EXTENSION_INCS} ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/daq_stim) + +target_compile_options(rekorder PUBLIC "-DEXTENSION_NAME=pyxcp.recorder.rekorder") +target_compile_options(cpp_ext PUBLIC "-DEXTENSION_NAME=pyxcp.cpp_ext.cpp_ext") +target_compile_options(stim PUBLIC "-DEXTENSION_NAME=pyxcp.daq_stim.stim") + +add_executable(asamkeydll ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/asamkeydll.c) +# set_target_properties(MyTarget PROPERTIES COMPILE_FLAGS "-m32" LINK_FLAGS "-m32") + +if (CMAKE_SIZEOF_VOID_P EQUAL 8) + # CMAKE_SYSTEM_NAME STREQUAL "Windows" +endif() + +IF (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_N_CLANG_BASE_OPTIONS} ${GCC_N_CLANG_EXTRA_OPTIONS}") + target_link_options(cpp_ext PUBLIC -flto=auto) + target_link_options(stim PUBLIC -flto=auto) + target_link_options(rekorder PUBLIC -flto=auto) +ELSEIF (CMAKE_C_COMPILER_ID MATCHES "MSVC") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MSVC_BASE_OPTIONS} ${MSVC_EXTRA_OPTIONS}") +ENDIF() + +IF (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" -fuse-ld=lld) +ENDIF() + +# target_include_directories(preprocessor PUBLIC $) +# target_link_libraries(preprocessor pybind11::headers) +# set_target_properties(preprocessor PROPERTIES INTERPROCEDURAL_OPTIMIZATION ON CXX_VISIBILITY_PRESET ON VISIBILITY_INLINES_HIDDEN ON) + +install(TARGETS rekorder LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/recorder) +install(TARGETS cpp_ext LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/cpp_ext) +install(TARGETS stim LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/daq_stim) +# install(TARGETS asamkeydll LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 56f27284..02604608 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f04252b6..58bc5546 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,6 +1,17 @@ +chrisoro <4160557+chrisoro@users.noreply.github.com> Christoph Schueler +Damien KAYSER Daniel Hrisca +danielhrisca +Devendra Giramkar +dkuschmierz <32884309+dkuschmierz@users.noreply.github.com> Felix Nieuwenhuizen +Jacob Schaer +Paul Gee +rui +Sedov Aleksandr Steffen Sanwald +The Codacy Badger +toebsen torgrimb waszil diff --git a/README.md b/README.md index 6a93fd6e..00654fef 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ - -pyXCP -===== +# pyXCP [![Codacy Badge](https://api.codacy.com/project/badge/Grade/85f774708b2542d98d02df55c743d24a)](https://app.codacy.com/app/christoph2/pyxcp?utm_source=github.com&utm_medium=referral&utm_content=christoph2/pyxcp&utm_campaign=Badge_Grade_Settings) [![Maintainability](https://api.codeclimate.com/v1/badges/4c639f3695f2725e392a/maintainability)](https://codeclimate.com/github/christoph2/pyxcp/maintainability) @@ -24,17 +22,20 @@ XCP also replaces the older CCP (CAN Calibration Protocol). pyXCP is hosted on Github, get the latest release: [https://github.com/christoph2/pyxcp](https://github.com/christoph2/pyxcp) You can install pyxcp from source: + ``` pip install -r requirements.txt python setup.py install ``` Alternatively, you can install pyxcp from source with pip: + ``` pip install git+https://github.com/christoph2/pyxcp.git ``` Alternatively, get pyxcp from [PyPI](https://pypi.org/project/pyxcp/): + ``` pip install pyxcp ``` diff --git a/appveyor.yml b/appveyor.yml index af542ea1..560feabb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,9 +1,7 @@ image: Visual Studio 2022 environment: - matrix: - # For Python versions available on Appveyor, see # https://www.appveyor.com/docs/windows-images-software/#python # The list here is complete (excluding Python 2.6, which @@ -63,11 +61,11 @@ on_finish: $wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\result.xml)) deploy: -- provider: GitHub - artifact: '*.*' - on: + - provider: GitHub + artifact: "*.*" + on: APPVEYOR_REPO_TAG: true - description: Test release -- do not use - tag: $(APPVEYOR_REPO_TAG_NAME) - draft: false - prerelease: true + description: Test release -- do not use + tag: $(APPVEYOR_REPO_TAG_NAME) + draft: false + prerelease: true diff --git a/bandit.yml b/bandit.yml new file mode 100644 index 00000000..9b2cfb0c --- /dev/null +++ b/bandit.yml @@ -0,0 +1,2 @@ +assert_used: + skips: ["*/test_*.py"] diff --git a/build_ext.py b/build_ext.py index 8495d8d4..5382c488 100644 --- a/build_ext.py +++ b/build_ext.py @@ -1,42 +1,64 @@ -import subprocess -from pathlib import Path -from typing import Any -from typing import Dict - -from pybind11.setup_helpers import build_ext -from pybind11.setup_helpers import naive_recompile -from pybind11.setup_helpers import ParallelCompile -from pybind11.setup_helpers import Pybind11Extension - -# from setuptools_cpp import CMakeExtension, ExtensionBuilder, Pybind11Extension -# ext_modules = [ -# CMakeExtension("pyxcp.recorder", sourcedir="pyxcp/recorder") -# ] - -print("Running 'build.py'...") - -PYB11_INCLUDE_DIRS = subprocess.check_output(["pybind11-config", "--includes"]) -EXT_NAMES = ["rekorder"] - -ParallelCompile("NPY_NUM_BUILD_JOBS", needs_recompile=naive_recompile).install() - -ext_modules = [ - Pybind11Extension( - EXT_NAMES[0], - include_dirs=[PYB11_INCLUDE_DIRS, "pyxcp/recorder"], - sources=["pyxcp/recorder/lz4.c", "pyxcp/recorder/wrap.cpp"], - define_macros=[("EXTENSION_NAME", EXT_NAMES[0]), ("NDEBUG", 1)], - optional=False, - cxx_std=20, # Extension will use C++20 generators/coroutines. - ), -] - - -def build(setup_kwargs: Dict[str, Any]) -> None: - setup_kwargs.update( - { - "ext_modules": ext_modules, - "cmd_class": dict(build_ext=Pybind11Extension), - "zip_safe": False, - } - ) +#!/usr/bin/env python + +import multiprocessing as mp +import os +import re +import subprocess # nosec +import sys +from pathlib import Path +from tempfile import TemporaryDirectory + + +TOP_DIR = Path(__file__).parent + + +def banner(msg: str) -> None: + print("=" * 80) + print(str.center(msg, 80)) + print("=" * 80) + + +def build_extension(debug: bool = False) -> None: + print("CMakeBuild::build_extension()") + + debug = bool(os.environ.get("DEBUG", 0)) or debug + cfg = "Debug" if debug else "Release" + print(f" BUILD-TYPE: {cfg!r}") + cmake_args = [ + f"-DPYTHON_EXECUTABLE={sys.executable}", + f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm + ] + build_args = ["--config Release", "--verbose"] + # cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 /path/to/src + + if sys.platform.startswith("darwin"): + # Cross-compile support for macOS - respect ARCHFLAGS if set + archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) + if archs: + cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] + + build_temp = Path(TemporaryDirectory(suffix=".build-temp").name) / "extension_it_in" + # build_temp = Path(".") / "build" + # print("cwd:", os.getcwd(), "build-dir:", build_temp, "top:", str(TOP_DIR)) + if not build_temp.exists(): + build_temp.mkdir(parents=True) + + banner("Step #1: Configure") + # cmake_args += ["--debug-output"] + subprocess.run(["cmake", str(TOP_DIR), *cmake_args], cwd=build_temp, check=True) # nosec + + cmake_args += [f"--parallel {mp.cpu_count()}"] + + banner("Step #2: Build") + # build_args += ["-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON"] + subprocess.run(["cmake", "--build", build_temp, *build_args], cwd=TOP_DIR, check=True) # nosec + + banner("Step #3: Install") + # subprocess.run(["cmake", "--install", "."], cwd=build_temp, check=True) # nosec + subprocess.run(["cmake", "--install", build_temp], cwd=TOP_DIR, check=True) # nosec + + +if __name__ == "__main__": + includes = subprocess.getoutput("pybind11-config --cmakedir") # nosec + os.environ["pybind11_DIR"] = includes + build_extension(False) diff --git a/docs/Makefile b/docs/Makefile index 44d90df2..13451fd7 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -17,4 +17,4 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py index cdbf1632..9547b921 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # @@ -13,6 +12,7 @@ import os import sys + sys.path.insert(0, os.path.abspath("../pyxcp")) diff --git a/docs/configuration.rst b/docs/configuration.rst index e0e37687..6e79ebdf 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1,158 +1,2 @@ Configuration ============= - -Parameters live in `JSON` or `TOML` :file:`pyxcp/examples` contains some example configurations. - -General pyXCP Parameters ------------------------- - -* `LOGLEVEL`: str, False, "WARN" -- "ERROR | "WARN" | "INFO" | "DEBUG" - Verbosity of logger. -* `DISABLE_ERROR_HANDLING`: bool, False, False -- Bypass error-handling for performance reasons (expert option!!!). - -* `CREATE_DAQ_TIMESTAMPS`: bool, False, False -- Generate DAQ records with timestamp. -* `TIMEOUT`: float, False, 2.0 -- General XCP timeout in seconds. -* `ALIGNMENT`: int, False, 1 -- 1 | 2 | 4, byte alignment. -* `DISCONNECT_RESPONSE_OPTIONAL`: bool, False, False -- Don't require response from DISCONNECT service. - - -eth -~~~ - -These parameters are rather self-explanatory. - -* `HOST`: str, "localhost" -* `PORT`: int, 5555 -* `PROTOCOL`: str, "TCP", "TCP" | "UDP" -* `IPV6`: bool, False -* `TCP_NODELAY`: bool, False - -sxi -~~~ - -Again, obvious parameters. - -`PORT`: str, "COM1" -`BITRATE`: int, 38400 -`BYTESIZE`: int, 8 -`PARITY`: str, "N" -`STOPBITS`: int, 1 - - -General CAN Parameters ----------------------- - -* `CAN_DRIVER`: str, REQUIRED -- "Canalystii" | "IsCAN" | "Ixxat" | "Kvaser" | "Neovi" | "NiCan" | - "PCan" | "Serial" | "SlCan" | "SocketCAN" | "Systec" | "Usb2Can" | "Vector" - (the driver names reflect the correspondending class names). -* `CHANNEL`: str, "" -- Highly driver specific value, see documentation. -* `MAX_DLC_REQUIRED`: bool, False -- if True, DLC is set to MAX_DLC, e.g. 8 on CAN Classic, unused bytes are set to zero. -* `CAN_USE_DEFAULT_LISTENER`: bool, True -- if True, the default listener thread is used. - If the canInterface implements a listener service, this parameter - can be set to False, and the default listener thread won't be started. -* `CAN_ID_MASTER`: int, REQUIRED -* `CAN_ID_SLAVE`: int, REQUIRED -* `CAN_ID_BROADCAST`: int, REQUIRED -* `BITRATE`: int, 250000 -* `RECEIVE_OWN_MESSAGES`: bool, False - - -Specific CAN Drivers --------------------- - -Every driver has some additional parameters, not further explained here, please refer to the -`python-can documentation. `_ - - -canalystii -~~~~~~~~~~ -* `BAUD`: int, None -- Uses `BAUD` instead of `BITRATE`. -* `TIMING0`: int, None -* `TIMING1`: int, None - -iscan -~~~~~ -* `POLL_INTERVAL`: float, 0.01 - -ixxat -~~~~~ - -* `UNIQUE_HARDWARE_ID`: str, None -* `RX_FIFO_SIZE`: int, 16 -* `TX_FIFO_SIZE`: int, 16 - -kvaser -~~~~~~ - -* `ACCEPT_VIRTUAL`: bool, True -* `DRIVER_MODE`: bool, True -* `NO_SAMP`: int, 1 -* `SJW`: int, 2 -* `TSEG1`: int, 5 -* `TSEG2`: int, 2 -* `SINGLE_HANDLÈ`: bool, True -* `FD`: bool, False -* `DATA_BITRATE`: int, None - -neovi -~~~~~ - -* `FD`: bool, False -* `DATA_BITRATE`: int, None -* `USE_SYSTEM_TIMESTAMP`: bool, False -* `SERIAL`: str, None -* `OVERRIDE_LIBRARY_NAME`: str, None - -nican -~~~~~ - -* `LOG_ERRORS`: bool, False - -pcan -~~~~ - -* `STATE`: str, "ACTIVE" - -serial -~~~~~~ - -* `BAUDRATE` int, 115200 -- Uses `BAUDRATE` instead of `BITRATE`. -* `TIMEOUT`: float, 0.1 -* `RTSCTS`: bool, False - -slcan -~~~~~ - -* `TTY_BAUDRATE`: int, 115200 -* `POLL_INTERVAL`: float, 0.01 -* `SLEEP_AFTER_OPEN`: float, 2.0 -* `RTSCTS`: bool, False - -socketcan -~~~~~~~~~ - -* `FD`: bool, False - -systec -~~~~~~ - -* `DEVICE_NUMBER`: int, 255 -* `RX_BUFFER_ENTRIES`: int, 4096 -* `TX_BUFFER_ENTRIES`: int, 4096 -* `STATE`: str, "ACTIVE" - -usb2can -~~~~~~~ - -`FLAGS`: int, 0 - -vector -~~~~~~ - -* `POLL_INTERVAL`: float, 0.01 -* `APP_NAME`: str, "" -* `SERIAL`: int, None -* `RX_QUEUE_SIZE`: int, 16384 -* `FD`: bool, False -* `DATA_BITRATE`: int, None - diff --git a/docs/howto_cli_tools.rst b/docs/howto_cli_tools.rst index b1f24a8c..5c68aed8 100644 --- a/docs/howto_cli_tools.rst +++ b/docs/howto_cli_tools.rst @@ -1,5 +1,3 @@ How-to write your own command-line tools ======================================== - - diff --git a/docs/installation.rst b/docs/installation.rst index 6d66f2bd..9724db32 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -10,4 +10,3 @@ Installation and Getting Started Prerequisites ------------- - diff --git a/docs/recorder.rst b/docs/recorder.rst index e69de29b..f55c3b3a 100644 --- a/docs/recorder.rst +++ b/docs/recorder.rst @@ -0,0 +1,5 @@ + + + +Recorder +======== diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 82dd4f8e..b23b9e52 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -1,4 +1,2 @@ Tutorial ======== - - diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..bb3cd41c --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2484 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""} + +[[package]] +name = "authlib" +version = "1.3.1" +description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Authlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:d35800b973099bbadc49b42b256ecb80041ad56b7fe1216a362c7943c088f377"}, + {file = "authlib-1.3.1.tar.gz", hash = "sha256:7ae843f03c06c5c0debd63c9db91f9fda64fa62a42a77419fa15fbb7e7a58917"}, +] + +[package.dependencies] +cryptography = "*" + +[[package]] +name = "babel" +version = "2.16.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"}, + {file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"}, +] + +[package.dependencies] +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "bandit" +version = "1.7.9" +description = "Security oriented static analyser for python code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"}, + {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} +PyYAML = ">=5.3.1" +rich = "*" +stevedore = ">=1.20.0" + +[package.extras] +baseline = ["GitPython (>=3.1.30)"] +sarif = ["jschema-to-python (>=1.2.3)", "sarif-om (>=1.0.4)"] +test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] +toml = ["tomli (>=1.1.0)"] +yaml = ["PyYAML"] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "black" +version = "24.8.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, + {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, + {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, + {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, + {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, + {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, + {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, + {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, + {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, + {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, + {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, + {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, + {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, + {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, + {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, + {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, + {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, + {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, + {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, + {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, + {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, + {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "cffi" +version = "1.17.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"}, + {file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"}, + {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"}, + {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"}, + {file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"}, + {file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"}, + {file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"}, + {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"}, + {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"}, + {file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"}, + {file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"}, + {file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"}, + {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"}, + {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"}, + {file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"}, + {file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"}, + {file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"}, + {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"}, + {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"}, + {file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"}, + {file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"}, + {file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"}, + {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"}, + {file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"}, + {file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"}, + {file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"}, + {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"}, + {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"}, + {file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"}, + {file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"}, + {file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "chardet" +version = "5.2.0" +description = "Universal encoding detector for Python 3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "construct" +version = "2.10.70" +description = "A powerful declarative symmetric parser/builder for binary data" +optional = false +python-versions = ">=3.6" +files = [ + {file = "construct-2.10.70-py3-none-any.whl", hash = "sha256:c80be81ef595a1a821ec69dc16099550ed22197615f4320b57cc9ce2a672cb30"}, + {file = "construct-2.10.70.tar.gz", hash = "sha256:4d2472f9684731e58cc9c56c463be63baa1447d674e0d66aeb5627b22f512c29"}, +] + +[package.extras] +extras = ["arrow", "cloudpickle", "cryptography", "lz4", "numpy", "ruamel.yaml"] + +[[package]] +name = "coverage" +version = "7.6.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "cryptography" +version = "43.0.0" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"}, + {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"}, + {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"}, + {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"}, + {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"}, + {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"}, + {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"}, + {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"}, + {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"}, + {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"}, + {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"}, + {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"}, + {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"}, + {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"}, + {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "darglint" +version = "1.8.1" +description = "A utility for ensuring Google-style docstrings stay up to date with the source code." +optional = false +python-versions = ">=3.6,<4.0" +files = [ + {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, + {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, +] + +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + +[[package]] +name = "dparse" +version = "0.6.4b0" +description = "A parser for Python dependency files" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dparse-0.6.4b0-py3-none-any.whl", hash = "sha256:592ff183348b8a5ea0a18442a7965e29445d3a26063654ec2c7e8ef42cd5753c"}, + {file = "dparse-0.6.4b0.tar.gz", hash = "sha256:f8d49b41a527f3d16a269f854e6665245b325e50e41d2c213810cb984553e5c8"}, +] + +[package.dependencies] +packaging = "*" +tomli = {version = "*", markers = "python_version < \"3.11\""} + +[package.extras] +all = ["dparse[conda]", "dparse[pipenv]", "dparse[poetry]"] +conda = ["pyyaml"] +pipenv = ["pipenv"] +poetry = ["poetry"] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "filelock" +version = "3.12.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, +] + +[package.extras] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] + +[[package]] +name = "flake8" +version = "7.1.1" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-7.1.1-py2.py3-none-any.whl", hash = "sha256:597477df7860daa5aa0fdd84bf5208a043ab96b8e96ab708770ae0364dd03213"}, + {file = "flake8-7.1.1.tar.gz", hash = "sha256:049d058491e228e03e67b390f311bbf88fce2dbaa8fa673e7aea87b7198b8d38"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.12.0,<2.13.0" +pyflakes = ">=3.2.0,<3.3.0" + +[[package]] +name = "flake8-docstrings" +version = "1.7.0" +description = "Extension for flake8 which uses pydocstyle to check docstrings" +optional = false +python-versions = ">=3.7" +files = [ + {file = "flake8_docstrings-1.7.0-py2.py3-none-any.whl", hash = "sha256:51f2344026da083fc084166a9353f5082b01f72901df422f74b4d953ae88ac75"}, + {file = "flake8_docstrings-1.7.0.tar.gz", hash = "sha256:4c8cc748dc16e6869728699e5d0d685da9a10b0ea718e090b1ba088e67a941af"}, +] + +[package.dependencies] +flake8 = ">=3" +pydocstyle = ">=2.1" + +[[package]] +name = "flake8-rst-docstrings" +version = "0.3.0" +description = "Python docstring reStructuredText (RST) validator for flake8" +optional = false +python-versions = ">=3.7" +files = [ + {file = "flake8-rst-docstrings-0.3.0.tar.gz", hash = "sha256:d1ce22b4bd37b73cd86b8d980e946ef198cfcc18ed82fedb674ceaa2f8d1afa4"}, + {file = "flake8_rst_docstrings-0.3.0-py3-none-any.whl", hash = "sha256:f8c3c6892ff402292651c31983a38da082480ad3ba253743de52989bdc84ca1c"}, +] + +[package.dependencies] +flake8 = ">=3" +pygments = "*" +restructuredtext-lint = "*" + +[package.extras] +develop = ["build", "twine"] + +[[package]] +name = "furo" +version = "2024.8.6" +description = "A clean customisable Sphinx documentation theme." +optional = false +python-versions = ">=3.8" +files = [ + {file = "furo-2024.8.6-py3-none-any.whl", hash = "sha256:6cd97c58b47813d3619e63e9081169880fbe331f0ca883c871ff1f3f11814f5c"}, + {file = "furo-2024.8.6.tar.gz", hash = "sha256:b63e4cee8abfc3136d3bc03a3d45a76a850bada4d6374d24c1716b0e01394a01"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +pygments = ">=2.7" +sphinx = ">=6.0,<9.0" +sphinx-basic-ng = ">=1.0.0.beta2" + +[[package]] +name = "identify" +version = "2.6.0" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"}, + {file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "8.4.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, + {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "line-profiler" +version = "4.1.3" +description = "Line-by-line profiler" +optional = false +python-versions = ">=3.6" +files = [ + {file = "line_profiler-4.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b26cccca30c0f859c585cd4a6c75ffde4dca80ba98a858d3d04b44a6b560c65"}, + {file = "line_profiler-4.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e8a1ed7bf88049cb8d069a2dac96c91b25b5a77cb712c207b7f484ab86f8b134"}, + {file = "line_profiler-4.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c320a8ccb2b9d0df85b8f19000242407d0cb1ea5804b4967fe6f755824c81a87"}, + {file = "line_profiler-4.1.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5751939d9dd95b1ec74e0aee428fe17d037fcb346fd23a7bf928b71c2dca2d19"}, + {file = "line_profiler-4.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b45f405d63730e5284403c1ff293f1e7f8ac7a39486db4c55a858712cec333d"}, + {file = "line_profiler-4.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9e24d61810ad153ab6a795d68f735812de4131f282128b799467f7fa56cac94f"}, + {file = "line_profiler-4.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f961465381e5bdc9fa7e5597af6714ada700d3e6ca61cca56763477f1047ff23"}, + {file = "line_profiler-4.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:6112436cb48ab635bc64e3dbfd80f67b56967e72aa7853e5084a64e11be5fe65"}, + {file = "line_profiler-4.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:16c8d2830e9daf0bcd49422e9367db5c825b02b88c383b9228c281ce14a5ad80"}, + {file = "line_profiler-4.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0e3ed5dd55bda1b0f65893ff377b6aedae69490f7be4fd5d818dd5bcc75553bf"}, + {file = "line_profiler-4.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f0ad37589b270e59f65ec6704435f02ece6d4246af112c0413095a5d3b13285b"}, + {file = "line_profiler-4.1.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c29ef65e3e0085f20ffedcddfa8d02f6f6eaa0dacec29129cd74d206f9f6c"}, + {file = "line_profiler-4.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ef054e1b6fd2443341911a2ddad0f8b6ed24903fa6a7e5e8201cd4272132e3a"}, + {file = "line_profiler-4.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:02bc0650ef8f87a489d6fbafcc0040ca76144d2a4c40e4044babccfe769b5525"}, + {file = "line_profiler-4.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f032c0973f0c1150440dce5f9b91509fce474c11b10c2c93a2109e1e0dab8a45"}, + {file = "line_profiler-4.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec8a34285338aadc6a74e91b022b6d8ea19ac5deaaa0c9b880a1ab7b4ed45c43"}, + {file = "line_profiler-4.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8ae10578f1325772ccfa2833288d826e4bc781214d74b87331a6b7e5793252ca"}, + {file = "line_profiler-4.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b7c89c68379879d3a11c5e76499f0f7a08683436762af6bf51db126d3cb9cdd9"}, + {file = "line_profiler-4.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9f4abf9ecb8b508d96420dde44d54a8484e73468132229bbba2229283a7e9fb"}, + {file = "line_profiler-4.1.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d12bf40ed654ad1d5c132be172054b9ec5ae3ba138ca2099002075fb14396a64"}, + {file = "line_profiler-4.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56d17f3bf22b9c7d72b3cb2d283d71152f4cc98e8ba88e720c743b2e3d9be6ad"}, + {file = "line_profiler-4.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9d7c7593ae86215d99d1d32e4b92ed6ace2ac8388aab781b74bf97d44e72ff1f"}, + {file = "line_profiler-4.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:248f16ba356ac1e19be834b0bdaf29c95c1c9229beaa63e0e3aad9aa3edfc012"}, + {file = "line_profiler-4.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:b85468d30ed16e362e8a044df0f331796c6ec5a76a55e88aae57078a2eec6afa"}, + {file = "line_profiler-4.1.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:82d5333d1ffac08b34828213bd674165e50876610061faa97660928b346a620d"}, + {file = "line_profiler-4.1.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f56985a885e2936eab6303fc82f1a20e5e0bb6d4d8f44f8a3825179d261053e"}, + {file = "line_profiler-4.1.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:713d43be1382f47c2f04d5d25ba3c65978292249849f85746a8476d6a8863717"}, + {file = "line_profiler-4.1.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6a3dd7ba3a17da254338313ec1d4ce4bdd723812e5cb58f4d05b78c1c5dbe4"}, + {file = "line_profiler-4.1.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:481bbace88b2e15fb63a16e578a48faa28eba7399afe7da6ce1bde569780c346"}, + {file = "line_profiler-4.1.3-cp36-cp36m-win_amd64.whl", hash = "sha256:654b16f9e82b0ce7f7657ef859bf2324275e9cd70c8169414922c9cb37d5589f"}, + {file = "line_profiler-4.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:39332137af7a562c44524cef7c37de9860428ce2cde8b9c51047ccad9fd5eca4"}, + {file = "line_profiler-4.1.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dad96626acd5804c818c374d34ce1debea07b1e100b160499f4dfbcf5fc1cbe6"}, + {file = "line_profiler-4.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7125846d636959907e307c1f0bbf6f05fe5b7ca195b929f7b676fd20cf0763f2"}, + {file = "line_profiler-4.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a89de2a09363dd1a62a0a49e82a7157854b6e92b1893627b14e952412357db60"}, + {file = "line_profiler-4.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9e11f5831a251d3a3551372b523b3bc0da1e912ab2ade2c4d9d8e0b225eed6ab"}, + {file = "line_profiler-4.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:66d856975284dc62ac6f5a97757e160c1eb9898078014385cf74b829d8d806b7"}, + {file = "line_profiler-4.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3fb0f43900d36d7ccd8b30b8506498440d5ec610f2f1d40de3de11c3e304fb90"}, + {file = "line_profiler-4.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7394227bfb5bf15002d3695e674916fe82c38957cd2f56fccd43b71dc3447d1e"}, + {file = "line_profiler-4.1.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8e19a0ca3198b173a5b7caa304be3b39d122f89b0cfc2a134c5cbb4105ee2fd6"}, + {file = "line_profiler-4.1.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ad57e3c80fb0aee0c86a25d738e3556063eb3d57d0a43217de13f134417915d"}, + {file = "line_profiler-4.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cca919a8199236326f14f3719e992f30dd43a272b0e8fcb98e436a66e4a96fc"}, + {file = "line_profiler-4.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d6753834e1ea03ea19015d0553f0ce0d61bbf2269b85fc0f42833d616369488b"}, + {file = "line_profiler-4.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a32559afd550852f2054a441d33afe16e8b68b167ffb15373ec2b521c6fdc51f"}, + {file = "line_profiler-4.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:e526f9dfad5e8e21cd5345d5213757cfc26af33f072042f3ccff36b10c46a23c"}, + {file = "line_profiler-4.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5aec873bea3a1357c1a21f788b44d29e288df2a579b4433c8a85fc2b0a8c229d"}, + {file = "line_profiler-4.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6059a8960487fc1e7b333178d39c53d3de5fd3c7da04477019e70d13c4c8520c"}, + {file = "line_profiler-4.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2ac815ba3cdc8603de6b0ea57a725f4aea1e0a2b7d8c99fabb43f6f2b1670dc0"}, + {file = "line_profiler-4.1.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ebd58a953fa86384150b79638331133ef0c22d8d68f046e00fe97e62053edae"}, + {file = "line_profiler-4.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c91e4cb038496e771220daccb512dab5311619392fec59ea916e9316630e9825"}, + {file = "line_profiler-4.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b4e4a49a42d4d9e1dce122dd0a5a427f9a337c22cf8a82712f006cae038870bf"}, + {file = "line_profiler-4.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:209d41401238eb0da340f92dfaf60dd84500be475b2b6738cf0ef28579b4df9a"}, + {file = "line_profiler-4.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:68684974e81344344174caf723bb4ab6659bc186d05c8f7e2453002e6bf74cff"}, + {file = "line_profiler-4.1.3.tar.gz", hash = "sha256:e5f1123c3672c3218ba063c23bd64a51159e44649fed6780b993c781fb5ed318"}, +] + +[package.extras] +all = ["Cython (>=3.0.3)", "IPython (>=7.14.0)", "IPython (>=7.18.0)", "IPython (>=8.12.2)", "IPython (>=8.14.0)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.11.2)", "cibuildwheel (>=2.8.1)", "cmake (>=3.21.2)", "coverage[toml] (>=6.1.1)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=7.3.0)", "ninja (>=1.10.2)", "pytest (>=6.2.5)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest-cov (>=3.0.0)", "rich (>=12.3.0)", "scikit-build (>=0.11.1)", "setuptools (>=41.0.1)", "setuptools (>=68.2.2)", "ubelt (>=1.3.4)", "xdoctest (>=1.1.3)"] +all-strict = ["Cython (==3.0.3)", "IPython (==7.14.0)", "IPython (==7.18.0)", "IPython (==8.12.2)", "IPython (==8.14.0)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.11.2)", "cibuildwheel (==2.8.1)", "cmake (==3.21.2)", "coverage[toml] (==6.1.1)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==7.3.0)", "ninja (==1.10.2)", "pytest (==6.2.5)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest-cov (==3.0.0)", "rich (==12.3.0)", "scikit-build (==0.11.1)", "setuptools (==41.0.1)", "setuptools (==68.2.2)", "ubelt (==1.3.4)", "xdoctest (==1.1.3)"] +ipython = ["IPython (>=7.14.0)", "IPython (>=7.18.0)", "IPython (>=8.12.2)", "IPython (>=8.14.0)"] +ipython-strict = ["IPython (==7.14.0)", "IPython (==7.18.0)", "IPython (==8.12.2)", "IPython (==8.14.0)"] +optional = ["IPython (>=7.14.0)", "IPython (>=7.18.0)", "IPython (>=8.12.2)", "IPython (>=8.14.0)", "rich (>=12.3.0)"] +optional-strict = ["IPython (==7.14.0)", "IPython (==7.18.0)", "IPython (==8.12.2)", "IPython (==8.14.0)", "rich (==12.3.0)"] +tests = ["coverage[toml] (>=6.1.1)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=6.5.0)", "coverage[toml] (>=7.3.0)", "pytest (>=6.2.5)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest (>=7.4.4)", "pytest-cov (>=3.0.0)", "ubelt (>=1.3.4)", "xdoctest (>=1.1.3)"] +tests-strict = ["coverage[toml] (==6.1.1)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==6.5.0)", "coverage[toml] (==7.3.0)", "pytest (==6.2.5)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest (==7.4.4)", "pytest-cov (==3.0.0)", "ubelt (==1.3.4)", "xdoctest (==1.1.3)"] + +[[package]] +name = "line-profiler-pycharm" +version = "1.1.0" +description = "PyCharm Line Profiler helper package with which one can visualize profiles from the 'line-profiler' into PyCharm" +optional = false +python-versions = ">=3" +files = [ + {file = "line-profiler-pycharm-1.1.0.tar.gz", hash = "sha256:e419de1db1b0ed49213d0b9ee33f99f24f6a2d431c7a76d39361f2c2a031af30"}, + {file = "line_profiler_pycharm-1.1.0-py3-none-any.whl", hash = "sha256:3b9d80571d44685a7c5a01e3985965cd76fe523c22db6250cefb4a75ebd73fc7"}, +] + +[package.dependencies] +line-profiler = "*" + +[[package]] +name = "livereload" +version = "2.7.0" +description = "Python LiveReload is an awesome tool for web developers" +optional = false +python-versions = ">=3.7" +files = [ + {file = "livereload-2.7.0-py3-none-any.whl", hash = "sha256:19bee55aff51d5ade6ede0dc709189a0f904d3b906d3ea71641ed548acff3246"}, + {file = "livereload-2.7.0.tar.gz", hash = "sha256:f4ba199ef93248902841e298670eebfe1aa9e148e19b343bc57dbf1b74de0513"}, +] + +[package.dependencies] +tornado = "*" + +[[package]] +name = "mako" +version = "1.3.5" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, + {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "marshmallow" +version = "3.22.0" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.22.0-py3-none-any.whl", hash = "sha256:71a2dce49ef901c3f97ed296ae5051135fd3febd2bf43afe0ae9a82143a494d9"}, + {file = "marshmallow-3.22.0.tar.gz", hash = "sha256:4972f529104a220bb8637d595aa4c9762afbe7f7a77d82dc58c1615d70c5823e"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["marshmallow[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +docs = ["alabaster (==1.0.0)", "autodocsumm (==0.2.13)", "sphinx (==8.0.2)", "sphinx-issues (==4.1.0)", "sphinx-version-warning (==1.1.2)"] +tests = ["pytest", "pytz", "simplejson"] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.1" +description = "Collection of plugins for markdown-it-py" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mdit_py_plugins-0.4.1-py3-none-any.whl", hash = "sha256:1020dfe4e6bfc2c79fb49ae4e3f5b297f5ccd20f010187acc52af2921e27dc6a"}, + {file = "mdit_py_plugins-0.4.1.tar.gz", hash = "sha256:834b8ac23d1cd60cec703646ffd22ae97b7955a6d596eb1d304be1e251ae499c"}, +] + +[package.dependencies] +markdown-it-py = ">=1.0.0,<4.0.0" + +[package.extras] +code-style = ["pre-commit"] +rtd = ["myst-parser", "sphinx-book-theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "msgpack" +version = "1.0.8" +description = "MessagePack serializer" +optional = false +python-versions = ">=3.8" +files = [ + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, + {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, + {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, + {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, + {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, + {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, + {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, + {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, + {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, + {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, + {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, +] + +[[package]] +name = "mypy" +version = "1.11.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a32fc80b63de4b5b3e65f4be82b4cfa362a46702672aa6a0f443b4689af7008c"}, + {file = "mypy-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1952f5ea8a5a959b05ed5f16452fddadbaae48b5d39235ab4c3fc444d5fd411"}, + {file = "mypy-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1e30dc3bfa4e157e53c1d17a0dad20f89dc433393e7702b813c10e200843b03"}, + {file = "mypy-1.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c63350af88f43a66d3dfeeeb8d77af34a4f07d760b9eb3a8697f0386c7590b4"}, + {file = "mypy-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:a831671bad47186603872a3abc19634f3011d7f83b083762c942442d51c58d58"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b6343d338390bb946d449677726edf60102a1c96079b4f002dedff375953fc5"}, + {file = "mypy-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fe9f4e5e521b458d8feb52547f4bade7ef8c93238dfb5bbc790d9ff2d770ca"}, + {file = "mypy-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:886c9dbecc87b9516eff294541bf7f3655722bf22bb898ee06985cd7269898de"}, + {file = "mypy-1.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca4a60e1dd9fd0193ae0067eaeeb962f2d79e0d9f0f66223a0682f26ffcc809"}, + {file = "mypy-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0bd53faf56de9643336aeea1c925012837432b5faf1701ccca7fde70166ccf72"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f39918a50f74dc5969807dcfaecafa804fa7f90c9d60506835036cc1bc891dc8"}, + {file = "mypy-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0bc71d1fb27a428139dd78621953effe0d208aed9857cb08d002280b0422003a"}, + {file = "mypy-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b868d3bcff720dd7217c383474008ddabaf048fad8d78ed948bb4b624870a417"}, + {file = "mypy-1.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a707ec1527ffcdd1c784d0924bf5cb15cd7f22683b919668a04d2b9c34549d2e"}, + {file = "mypy-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:64f4a90e3ea07f590c5bcf9029035cf0efeae5ba8be511a8caada1a4893f5525"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:749fd3213916f1751fff995fccf20c6195cae941dc968f3aaadf9bb4e430e5a2"}, + {file = "mypy-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b639dce63a0b19085213ec5fdd8cffd1d81988f47a2dec7100e93564f3e8fb3b"}, + {file = "mypy-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c956b49c5d865394d62941b109728c5c596a415e9c5b2be663dd26a1ff07bc0"}, + {file = "mypy-1.11.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45df906e8b6804ef4b666af29a87ad9f5921aad091c79cc38e12198e220beabd"}, + {file = "mypy-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:d44be7551689d9d47b7abc27c71257adfdb53f03880841a5db15ddb22dc63edb"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2684d3f693073ab89d76da8e3921883019ea8a3ec20fa5d8ecca6a2db4c54bbe"}, + {file = "mypy-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79c07eb282cb457473add5052b63925e5cc97dfab9812ee65a7c7ab5e3cb551c"}, + {file = "mypy-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11965c2f571ded6239977b14deebd3f4c3abd9a92398712d6da3a772974fad69"}, + {file = "mypy-1.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a2b43895a0f8154df6519706d9bca8280cda52d3d9d1514b2d9c3e26792a0b74"}, + {file = "mypy-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:1a81cf05975fd61aec5ae16501a091cfb9f605dc3e3c878c0da32f250b74760b"}, + {file = "mypy-1.11.1-py3-none-any.whl", hash = "sha256:0624bdb940255d2dd24e829d99a13cfeb72e4e9031f9492148f410ed30bcab54"}, + {file = "mypy-1.11.1.tar.gz", hash = "sha256:f404a0b069709f18bbdb702eb3dcfe51910602995de00bd39cea3050b5772d08"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.6.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "myst-parser" +version = "3.0.1" +description = "An extended [CommonMark](https://spec.commonmark.org/) compliant parser," +optional = false +python-versions = ">=3.8" +files = [ + {file = "myst_parser-3.0.1-py3-none-any.whl", hash = "sha256:6457aaa33a5d474aca678b8ead9b3dc298e89c68e67012e73146ea6fd54babf1"}, + {file = "myst_parser-3.0.1.tar.gz", hash = "sha256:88f0cb406cb363b077d176b51c476f62d60604d68a8dcdf4832e080441301a87"}, +] + +[package.dependencies] +docutils = ">=0.18,<0.22" +jinja2 = "*" +markdown-it-py = ">=3.0,<4.0" +mdit-py-plugins = ">=0.4,<1.0" +pyyaml = "*" +sphinx = ">=6,<8" + +[package.extras] +code-style = ["pre-commit (>=3.0,<4.0)"] +linkify = ["linkify-it-py (>=2.0,<3.0)"] +rtd = ["ipython", "sphinx (>=7)", "sphinx-autodoc2 (>=0.5.0,<0.6.0)", "sphinx-book-theme (>=1.1,<2.0)", "sphinx-copybutton", "sphinx-design", "sphinx-pyscript", "sphinx-tippy (>=0.4.3)", "sphinx-togglebutton", "sphinxext-opengraph (>=0.9.0,<0.10.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] +testing = ["beautifulsoup4", "coverage[toml]", "defusedxml", "pytest (>=8,<9)", "pytest-cov", "pytest-param-files (>=0.6.0,<0.7.0)", "pytest-regressions", "sphinx-pytest"] +testing-docutils = ["pygments", "pytest (>=8,<9)", "pytest-param-files (>=0.6.0,<0.7.0)"] + +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pbr" +version = "6.0.0" +description = "Python Build Reasonableness" +optional = false +python-versions = ">=2.6" +files = [ + {file = "pbr-6.0.0-py2.py3-none-any.whl", hash = "sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda"}, + {file = "pbr-6.0.0.tar.gz", hash = "sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9"}, +] + +[[package]] +name = "pep8-naming" +version = "0.14.1" +description = "Check PEP-8 naming conventions, plugin for flake8" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pep8-naming-0.14.1.tar.gz", hash = "sha256:1ef228ae80875557eb6c1549deafed4dabbf3261cfcafa12f773fe0db9be8a36"}, + {file = "pep8_naming-0.14.1-py3-none-any.whl", hash = "sha256:63f514fc777d715f935faf185dedd679ab99526a7f2f503abb61587877f7b1c5"}, +] + +[package.dependencies] +flake8 = ">=5.0.0" + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pre-commit" +version = "3.5.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "pre-commit-hooks" +version = "4.6.0" +description = "Some out-of-the-box hooks for pre-commit." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pre_commit_hooks-4.6.0-py2.py3-none-any.whl", hash = "sha256:a69199e6a2d45ec59c1020a81ca1549abddc2afb798276d9a0d951752d6abbfe"}, + {file = "pre_commit_hooks-4.6.0.tar.gz", hash = "sha256:eb1f43ee67869cd41b4c59017fad4a0f9d4d61201d163f2135535aaf65035a2b"}, +] + +[package.dependencies] +"ruamel.yaml" = ">=0.15" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "psutil" +version = "6.0.0" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, + {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, + {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, + {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, + {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, + {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, + {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, + {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, + {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, + {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "pycodestyle" +version = "2.12.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.12.1-py2.py3-none-any.whl", hash = "sha256:46f0fb92069a7c28ab7bb558f05bfc0110dac69a0cd23c61ea0040283a9d78b3"}, + {file = "pycodestyle-2.12.1.tar.gz", hash = "sha256:6838eae08bbce4f6accd5d5572075c63626a15ee3e6f842df996bf62f6d73521"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pydantic" +version = "2.8.2" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"}, + {file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"}, +] + +[package.dependencies] +annotated-types = ">=0.4.0" +pydantic-core = "2.20.1" +typing-extensions = [ + {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, + {version = ">=4.6.1", markers = "python_version < \"3.13\""}, +] + +[package.extras] +email = ["email-validator (>=2.0.0)"] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"}, + {file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"}, + {file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"}, + {file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"}, + {file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"}, + {file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"}, + {file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"}, + {file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"}, + {file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"}, + {file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"}, + {file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"}, + {file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"}, + {file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"}, + {file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"}, + {file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"}, + {file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"}, + {file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"}, + {file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"}, + {file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"}, + {file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"}, + {file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"}, + {file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"}, + {file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"}, + {file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"}, + {file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"}, + {file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"}, + {file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"}, + {file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"}, + {file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"}, + {file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"}, + {file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"}, + {file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"}, + {file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pydocstyle" +version = "6.3.0" +description = "Python docstring style checker" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pydocstyle-6.3.0-py3-none-any.whl", hash = "sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019"}, + {file = "pydocstyle-6.3.0.tar.gz", hash = "sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"}, +] + +[package.dependencies] +snowballstemmer = ">=2.2.0" + +[package.extras] +toml = ["tomli (>=1.2.3)"] + +[[package]] +name = "pyflakes" +version = "3.2.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyserial" +version = "3.5" +description = "Python Serial Port Extension" +optional = false +python-versions = "*" +files = [ + {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"}, + {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"}, +] + +[package.extras] +cp2110 = ["hidapi"] + +[[package]] +name = "pytest" +version = "8.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-can" +version = "4.4.2" +description = "Controller Area Network interface module for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python_can-4.4.2-py3-none-any.whl", hash = "sha256:e956d781b45563c244c1f3c8fe001e292d1857519cff663d7c184673a68879f9"}, + {file = "python_can-4.4.2.tar.gz", hash = "sha256:1c46c0935f39f7a9c3e76b03249af0580689ebf7a1844195e92f87257f009df5"}, +] + +[package.dependencies] +msgpack = {version = ">=1.0.0,<1.1.0", markers = "platform_system != \"Windows\""} +packaging = ">=23.1" +pywin32 = {version = ">=305", markers = "platform_system == \"Windows\" and platform_python_implementation == \"CPython\""} +typing-extensions = ">=3.10.0.0" +wrapt = ">=1.10,<2.0" + +[package.extras] +canalystii = ["canalystii (>=0.1.0)"] +canine = ["python-can-canine (>=0.2.2)"] +cantact = ["cantact (>=0.0.7)"] +cvector = ["python-can-cvector"] +gs-usb = ["gs-usb (>=0.2.1)"] +lint = ["black (==24.4.*)", "mypy (==1.10.*)", "pylint (==3.2.*)", "ruff (==0.4.8)"] +mf4 = ["asammdf (>=6.0.0)"] +neovi = ["filelock", "python-ics (>=2.12)"] +nixnet = ["nixnet (>=0.3.2)"] +pcan = ["uptime (>=3.0.1,<3.1.0)"] +remote = ["python-can-remote"] +seeedstudio = ["pyserial (>=3.0)"] +serial = ["pyserial (>=3.0,<4.0)"] +sontheim = ["python-can-sontheim (>=0.1.2)"] +viewer = ["windows-curses"] + +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + +[[package]] +name = "pyupgrade" +version = "3.8.0" +description = "A tool to automatically upgrade syntax for newer versions." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyupgrade-3.8.0-py2.py3-none-any.whl", hash = "sha256:08d0e6129f5e9da7e7a581bdbea689e0d49c3c93eeaf156a07ae2fd794f52660"}, + {file = "pyupgrade-3.8.0.tar.gz", hash = "sha256:1facb0b8407cca468dfcc1d13717e3a85aa37b9e6e7338664ad5bfe5ef50c867"}, +] + +[package.dependencies] +tokenize-rt = ">=3.2.0" + +[[package]] +name = "pyusb" +version = "1.2.1" +description = "Python USB access module" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "pyusb-1.2.1-py3-none-any.whl", hash = "sha256:2b4c7cb86dbadf044dfb9d3a4ff69fd217013dbe78a792177a3feb172449ea36"}, + {file = "pyusb-1.2.1.tar.gz", hash = "sha256:a4cc7404a203144754164b8b40994e2849fde1cfff06b08492f12fff9d9de7b9"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "restructuredtext-lint" +version = "1.4.0" +description = "reStructuredText linter" +optional = false +python-versions = "*" +files = [ + {file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"}, +] + +[package.dependencies] +docutils = ">=0.11,<1.0" + +[[package]] +name = "rich" +version = "13.7.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.1-py3-none-any.whl", hash = "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222"}, + {file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "ruamel-yaml" +version = "0.18.6" +description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636"}, + {file = "ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b"}, +] + +[package.dependencies] +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} + +[package.extras] +docs = ["mercurial (>5.7)", "ryd"] +jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.8" +description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" +optional = false +python-versions = ">=3.6" +files = [ + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"}, + {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"}, + {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"}, + {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"}, + {file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"}, + {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"}, + {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"}, + {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"}, + {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, +] + +[[package]] +name = "ruff" +version = "0.1.15" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, + {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, + {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, + {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, + {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, +] + +[[package]] +name = "safety" +version = "3.2.6" +description = "Checks installed dependencies for known vulnerabilities and licenses." +optional = false +python-versions = ">=3.7" +files = [ + {file = "safety-3.2.6-py3-none-any.whl", hash = "sha256:fcd87f8c1882a72c26aef77b1e2b0f373800fb76df1b5f44ebeba9bff4e37b5e"}, + {file = "safety-3.2.6.tar.gz", hash = "sha256:6a361e362d582f1ac52a179f1ae0fd1b5acbe05d37b1144fdeae792573201f9f"}, +] + +[package.dependencies] +Authlib = ">=1.2.0" +Click = ">=8.0.2" +dparse = ">=0.6.4b0" +filelock = ">=3.12.2,<3.13.0" +jinja2 = ">=3.1.0" +marshmallow = ">=3.15.0" +packaging = ">=21.0" +psutil = ">=6.0.0,<6.1.0" +pydantic = ">=1.10.12" +requests = "*" +rich = "*" +"ruamel.yaml" = ">=0.17.21" +safety-schemas = ">=0.0.4" +setuptools = ">=65.5.1" +typer = "*" +typing-extensions = ">=4.7.1" +urllib3 = ">=1.26.5" + +[package.extras] +github = ["pygithub (>=1.43.3)"] +gitlab = ["python-gitlab (>=1.3.0)"] +spdx = ["spdx-tools (>=0.8.2)"] + +[[package]] +name = "safety-schemas" +version = "0.0.4" +description = "Schemas for Safety tools" +optional = false +python-versions = ">=3.7" +files = [ + {file = "safety_schemas-0.0.4-py3-none-any.whl", hash = "sha256:b8b93e447bbffe62e4bd4364877f8ac0dc9688056911b2618d6f48773f9c9011"}, + {file = "safety_schemas-0.0.4.tar.gz", hash = "sha256:5ec6a8e2a80620a829a9d236165cce9d9e864b0345345d1fc983397eb5d2ac65"}, +] + +[package.dependencies] +dparse = ">=0.6.4b0" +packaging = ">=21.0" +pydantic = "*" +ruamel-yaml = ">=0.17.21" +typing-extensions = ">=4.7.1" + +[[package]] +name = "setuptools" +version = "73.0.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-73.0.1-py3-none-any.whl", hash = "sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e"}, + {file = "setuptools-73.0.1.tar.gz", hash = "sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193"}, +] + +[package.extras] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.6" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, + {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, +] + +[[package]] +name = "sphinx" +version = "7.1.2" +description = "Python documentation generator" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, + {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.21" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.13" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] + +[[package]] +name = "sphinx-autobuild" +version = "2021.3.14" +description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." +optional = false +python-versions = ">=3.6" +files = [ + {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, + {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, +] + +[package.dependencies] +colorama = "*" +livereload = "*" +sphinx = "*" + +[package.extras] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "sphinx-basic-ng" +version = "1.0.0b2" +description = "A modern skeleton for Sphinx themes." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sphinx_basic_ng-1.0.0b2-py3-none-any.whl", hash = "sha256:eb09aedbabfb650607e9b4b68c9d240b90b1e1be221d6ad71d61c52e29f7932b"}, + {file = "sphinx_basic_ng-1.0.0b2.tar.gz", hash = "sha256:9ec55a47c90c8c002b5960c57492ec3021f5193cb26cebc2dc4ea226848651c9"}, +] + +[package.dependencies] +sphinx = ">=4.0" + +[package.extras] +docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] + +[[package]] +name = "sphinx-click" +version = "6.0.0" +description = "Sphinx extension that automatically documents click applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinx_click-6.0.0-py3-none-any.whl", hash = "sha256:1e0a3c83bcb7c55497751b19d07ebe56b5d7b85eb76dd399cf9061b497adc317"}, + {file = "sphinx_click-6.0.0.tar.gz", hash = "sha256:f5d664321dc0c6622ff019f1e1c84e58ce0cecfddeb510e004cf60c2a3ab465b"}, +] + +[package.dependencies] +click = ">=8.0" +docutils = "*" +sphinx = ">=4.0" + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.4" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, + {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.1" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, + {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "stevedore" +version = "5.2.0" +description = "Manage dynamic plugins for Python applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "stevedore-5.2.0-py3-none-any.whl", hash = "sha256:1c15d95766ca0569cad14cb6272d4d31dae66b011a929d7c18219c176ea1b5c9"}, + {file = "stevedore-5.2.0.tar.gz", hash = "sha256:46b93ca40e1114cea93d738a6c1e365396981bb6bb78c27045b7587c9473544d"}, +] + +[package.dependencies] +pbr = ">=2.0.0,<2.1.0 || >2.1.0" + +[[package]] +name = "tokenize-rt" +version = "6.0.0" +description = "A wrapper around the stdlib `tokenize` which roundtrips." +optional = false +python-versions = ">=3.8" +files = [ + {file = "tokenize_rt-6.0.0-py2.py3-none-any.whl", hash = "sha256:d4ff7ded2873512938b4f8cbb98c9b07118f01d30ac585a30d7a88353ca36d22"}, + {file = "tokenize_rt-6.0.0.tar.gz", hash = "sha256:b9711bdfc51210211137499b5e355d3de5ec88a85d2025c520cbb921b5194367"}, +] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.12.5" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.5-py3-none-any.whl", hash = "sha256:af914f5a9c59ed9d0762c7b64d3b5d5df007448eb9cd2edc8a46b1eafead172f"}, + {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, +] + +[[package]] +name = "tornado" +version = "6.4.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.8" +files = [ + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, +] + +[[package]] +name = "traitlets" +version = "5.11.2" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.11.2-py3-none-any.whl", hash = "sha256:98277f247f18b2c5cabaf4af369187754f4fb0e85911d473f72329db8a7f4fae"}, + {file = "traitlets-5.11.2.tar.gz", hash = "sha256:7564b5bf8d38c40fa45498072bf4dc5e8346eb087bbf1e2ae2d8774f6a0f078e"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.5.1)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typeguard" +version = "4.3.0" +description = "Run-time type checker for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typeguard-4.3.0-py3-none-any.whl", hash = "sha256:4d24c5b39a117f8a895b9da7a9b3114f04eb63bade45a4492de49b175b6f7dfa"}, + {file = "typeguard-4.3.0.tar.gz", hash = "sha256:92ee6a0aec9135181eae6067ebd617fd9de8d75d714fb548728a4933b1dea651"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} +typing-extensions = ">=4.10.0" + +[package.extras] +doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)"] +test = ["coverage[toml] (>=7)", "mypy (>=1.2.0)", "pytest (>=7)"] + +[[package]] +name = "typer" +version = "0.12.4" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.7" +files = [ + {file = "typer-0.12.4-py3-none-any.whl", hash = "sha256:819aa03699f438397e876aa12b0d63766864ecba1b579092cc9fe35d886e34b6"}, + {file = "typer-0.12.4.tar.gz", hash = "sha256:c9c1613ed6a166162705b3347b8d10b661ccc5d95692654d0fb628118f2c34e6"}, +] + +[package.dependencies] +click = ">=8.0.0" +rich = ">=10.11.0" +shellingham = ">=1.3.0" +typing-extensions = ">=3.7.4.3" + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "uptime" +version = "3.0.1" +description = "Cross-platform uptime library" +optional = false +python-versions = "*" +files = [ + {file = "uptime-3.0.1.tar.gz", hash = "sha256:7c300254775b807ce46e3dcbcda30aa3b9a204b9c57a7ac1e79ee6dbe3942973"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.26.3" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"}, + {file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[[package]] +name = "xdoctest" +version = "1.2.0" +description = "A rewrite of the builtin doctest module" +optional = false +python-versions = ">=3.8" +files = [ + {file = "xdoctest-1.2.0-py3-none-any.whl", hash = "sha256:0f1ecf5939a687bd1fc8deefbff1743c65419cce26dff908f8b84c93fbe486bc"}, + {file = "xdoctest-1.2.0.tar.gz", hash = "sha256:d8cfca6d8991e488d33f756e600d35b9fdf5efd5c3a249d644efcbbbd2ed5863"}, +] + +[package.dependencies] +colorama = {version = ">=0.4.1", optional = true, markers = "platform_system == \"Windows\" and extra == \"colors\""} +Pygments = {version = ">=2.4.1", optional = true, markers = "python_version >= \"3.5.0\" and extra == \"colors\""} + +[package.extras] +all = ["IPython (>=7.23.1)", "Pygments (>=2.0.0)", "Pygments (>=2.4.1)", "attrs (>=19.2.0)", "colorama (>=0.4.1)", "debugpy (>=1.0.0)", "debugpy (>=1.3.0)", "debugpy (>=1.6.0)", "ipykernel (>=6.0.0)", "ipykernel (>=6.11.0)", "ipython-genutils (>=0.2.0)", "jedi (>=0.16)", "jinja2 (>=3.0.0)", "jupyter-client (>=7.0.0)", "jupyter-core (>=4.7.0)", "nbconvert (>=6.0.0)", "nbconvert (>=6.1.0)", "pyflakes (>=2.2.0)", "pytest (>=4.6.0)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "tomli (>=0.2.0)"] +all-strict = ["IPython (==7.23.1)", "Pygments (==2.0.0)", "Pygments (==2.4.1)", "attrs (==19.2.0)", "colorama (==0.4.1)", "debugpy (==1.0.0)", "debugpy (==1.3.0)", "debugpy (==1.6.0)", "ipykernel (==6.0.0)", "ipykernel (==6.11.0)", "ipython-genutils (==0.2.0)", "jedi (==0.16)", "jinja2 (==3.0.0)", "jupyter-client (==7.0.0)", "jupyter-core (==4.7.0)", "nbconvert (==6.0.0)", "nbconvert (==6.1.0)", "pyflakes (==2.2.0)", "pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)", "tomli (==0.2.0)"] +colors = ["Pygments (>=2.0.0)", "Pygments (>=2.4.1)", "colorama (>=0.4.1)"] +colors-strict = ["Pygments (==2.0.0)", "Pygments (==2.4.1)", "colorama (==0.4.1)"] +docs = ["Pygments (>=2.9.0)", "myst-parser (>=0.18.0)", "sphinx (>=5.0.1)", "sphinx-autoapi (>=1.8.4)", "sphinx-autobuild (>=2021.3.14)", "sphinx-reredirects (>=0.0.1)", "sphinx-rtd-theme (>=1.0.0)", "sphinxcontrib-napoleon (>=0.7)"] +docs-strict = ["Pygments (==2.9.0)", "myst-parser (==0.18.0)", "sphinx (==5.0.1)", "sphinx-autoapi (==1.8.4)", "sphinx-autobuild (==2021.3.14)", "sphinx-reredirects (==0.0.1)", "sphinx-rtd-theme (==1.0.0)", "sphinxcontrib-napoleon (==0.7)"] +jupyter = ["IPython (>=7.23.1)", "attrs (>=19.2.0)", "debugpy (>=1.0.0)", "debugpy (>=1.3.0)", "debugpy (>=1.6.0)", "ipykernel (>=6.0.0)", "ipykernel (>=6.11.0)", "ipython-genutils (>=0.2.0)", "jedi (>=0.16)", "jinja2 (>=3.0.0)", "jupyter-client (>=7.0.0)", "jupyter-core (>=4.7.0)", "nbconvert (>=6.0.0)", "nbconvert (>=6.1.0)"] +jupyter-strict = ["IPython (==7.23.1)", "attrs (==19.2.0)", "debugpy (==1.0.0)", "debugpy (==1.3.0)", "debugpy (==1.6.0)", "ipykernel (==6.0.0)", "ipykernel (==6.11.0)", "ipython-genutils (==0.2.0)", "jedi (==0.16)", "jinja2 (==3.0.0)", "jupyter-client (==7.0.0)", "jupyter-core (==4.7.0)", "nbconvert (==6.0.0)", "nbconvert (==6.1.0)"] +optional = ["IPython (>=7.23.1)", "Pygments (>=2.0.0)", "Pygments (>=2.4.1)", "attrs (>=19.2.0)", "colorama (>=0.4.1)", "debugpy (>=1.0.0)", "debugpy (>=1.3.0)", "debugpy (>=1.6.0)", "ipykernel (>=6.0.0)", "ipykernel (>=6.11.0)", "ipython-genutils (>=0.2.0)", "jedi (>=0.16)", "jinja2 (>=3.0.0)", "jupyter-client (>=7.0.0)", "jupyter-core (>=4.7.0)", "nbconvert (>=6.0.0)", "nbconvert (>=6.1.0)", "pyflakes (>=2.2.0)", "tomli (>=0.2.0)"] +optional-strict = ["IPython (==7.23.1)", "Pygments (==2.0.0)", "Pygments (==2.4.1)", "attrs (==19.2.0)", "colorama (==0.4.1)", "debugpy (==1.0.0)", "debugpy (==1.3.0)", "debugpy (==1.6.0)", "ipykernel (==6.0.0)", "ipykernel (==6.11.0)", "ipython-genutils (==0.2.0)", "jedi (==0.16)", "jinja2 (==3.0.0)", "jupyter-client (==7.0.0)", "jupyter-core (==4.7.0)", "nbconvert (==6.0.0)", "nbconvert (==6.1.0)", "pyflakes (==2.2.0)", "tomli (==0.2.0)"] +tests = ["pytest (>=4.6.0)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)"] +tests-binary = ["cmake (>=3.21.2)", "cmake (>=3.25.0)", "ninja (>=1.10.2)", "ninja (>=1.11.1)", "pybind11 (>=2.10.3)", "pybind11 (>=2.7.1)", "scikit-build (>=0.11.1)", "scikit-build (>=0.16.1)"] +tests-binary-strict = ["cmake (==3.21.2)", "cmake (==3.25.0)", "ninja (==1.10.2)", "ninja (==1.11.1)", "pybind11 (==2.10.3)", "pybind11 (==2.7.1)", "scikit-build (==0.11.1)", "scikit-build (==0.16.1)"] +tests-strict = ["pytest (==4.6.0)", "pytest (==6.2.5)", "pytest-cov (==3.0.0)"] + +[[package]] +name = "zipp" +version = "3.20.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"}, + {file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"}, +] + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8.1" +content-hash = "1cac889cdead9ab2b624cded84f8eab7bccafca05a0d931f02be32cc4390fe93" diff --git a/pyproject.toml b/pyproject.toml index fa598ccd..5a053cee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,44 +1,50 @@ + [build-system] -requires = ["pdm", "pdm-pep517", "pybind11<3.0.0,>=2.9.0", "wheel", "build", "twine", "setuptools"] -build-backend = "pdm.pep517.api" +requires = ["poetry-core>=1.0.0", "setuptools>=68.0.0", "pybind11>=2.12.0", "pybind11[global]>=2.12.0"] +build-backend = "poetry.core.masonry.api" -[tool.pdm] -includes = [] -build = "build_ext.py" -license-expression = "LGPL-3.0-or-later" -license-files.paths = ["LICENSE"] -[tool.pdm.dev-dependencies] -dev = [ - "pytest<7.0.0,>=6.2.5", - "pytest-runner<6.0.0,>=5.3.1", - "pytest-cov<4.0.0,>=3.0.0", +[tool.poetry.dev-dependencies] +Pygments = ">=2.10.0" +bandit = ">=1.7.4" +black = ">=21.10b0" +coverage = {extras = ["toml"], version = ">=6.2"} +darglint = ">=1.8.1" +flake8 = ">=4.0.1" +flake8-bugbear = ">=21.9.2" +flake8-docstrings = ">=1.6.0" +flake8-rst-docstrings = ">=0.2.5" +furo = ">=2021.11.12" +isort = ">=5.10.1" +mypy = ">=0.930" +pep8-naming = ">=0.12.1" +pre-commit = ">=2.16.0" +pre-commit-hooks = ">=4.1.0" +pytest = ">=6.2.5" +pyupgrade = ">=2.29.1" +safety = ">=1.10.3" +sphinx = ">=4.3.2" +sphinx-autobuild = ">=2021.3.14" +sphinx-click = ">=3.0.2" +typeguard = ">=2.13.3" +xdoctest = {extras = ["colors"], version = ">=0.15.10"} +myst-parser = {version = ">=0.16.1"} -] [project] -authors = [ - {name = "Christoph Schueler"}, - {email = "cpu12.gems@googlemail.com"} -] -requires-python = ">=3.7" -dependencies = [ - "setuptools-cpp<1.0.0,>=0.1.0", - "Mako<2.0.0,>=1.1.6", - "construct<3.0.0,>=2.10.67", - "pyserial<4.0,>=3.5", - "pyusb<2.0.0,>=1.2.1", - "toml<1.0.0,>=0.10.2", - "python-can>=4.0.0", - "uptime>=3.0.1", - "chardet>=5.2.0", - "traitlets>=5.9.0", -] name = "pyxcp" -version = "0.21.9" +version = "0.22.0" +dynamic = ["license", "readme", "authors", "requires-python", "description", "classifiers", "scripts", "dependencies", "optional-dependencies"] + + +[tool.poetry] +authors = ["Christoph Schueler "] +name = "pyxcp" +version = "0.21.6" readme = "README.md" description = "Universal Calibration Protocol for Python" keywords = ["automotive", "ecu", "xcp", "asam", "autosar"] -license = {file = "LICENSE"} +homepage = "https://github.com/christoph2/pyxcp" +license = "LGPLv3" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Topic :: Software Development", @@ -49,31 +55,68 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12" + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13" +] +build = "build_ext.py" +include = [ + { path = "pyxcp/cpp_ext/*.so", format = "wheel" }, + { path = "pyxcp/cpp_ext/*.pyd", format = "wheel" }, + { path = "pyxcp/daq_stim/*.so", format = "wheel" }, + { path = "pyxcp/daq_stim/*.pyd", format = "wheel" }, + { path = "pyxcp/recorder/*.so", format = "wheel" }, + { path = "pyxcp/recorder/*.pyd", format = "wheel" }, + { path = "pyxcp/*.exe", format = "wheel" }, + { path = "CMakeLists.txt", format = "wheel" }, ] -dynamic = ["entry-points"] -[project.urls] -homepage = "https://github.com/christoph2/pyxcp" +[tool.poetry.dependencies] +python = "^3.8.1" +construct = "^2.10.68" +mako = "^1.2.4" +pyserial = "^3.5" +pyusb = "^1.2.1" +python-can = "^4.2.2" +uptime = "^3.0.1" +rich = "^13.6.0" +chardet = "^5.2.0" +traitlets = "<=5.11.2" +line-profiler-pycharm = "^1.1.0" -[project.optional-dependencies] -doc = [ - "sphinx", - "sphinxcontrib-napoleon" -] +toml = "^0.10.2" +bandit = "^1.7.8" +tomlkit = "^0.12.5" +pytz = "^2024.1" +[tool.poetry.group.dev.dependencies] +ruff = "^0.1.0" -[project.scripts] +pre-commit-hooks = "^4.6.0" +darglint = "^1.8.1" +[tool.poetry.scripts] pyxcp-probe-can-drivers = "pyxcp.scripts.pyxcp_probe_can_drivers:main" xcp-id-scanner = "pyxcp.scripts.xcp_id_scanner:main" xcp-fetch-a2l = "pyxcp.scripts.xcp_fetch_a2l:main" xcp-info = "pyxcp.scripts.xcp_info:main" +xcp-profile = "pyxcp.scripts.xcp_profile:main" [tool.pytest] addopts = "--verbose --tb=short --junitxml=result.xml -o junit_family=xunit2" testpaths = "pyxcp/tests" +[tool.isort] +profile = "black" +force_single_line = false +lines_after_imports = 2 + +[tool.mypy] +strict = false +warn_unreachable = true +pretty = true +show_column_numbers = true +show_error_context = true + [tool.flake8] -ignore = ["D203", "E203", "E266", "E501", "W503", "F403", "F401", "BLK100"] +ignore = ["F403", "D203", "E203", "E266", "E501", "W503", "E501", "F401", "BLK100"] exclude = ''' /( \.git @@ -96,6 +139,11 @@ statistics = true show-source = true max-line-length = 132 select = ["B","C","E","F","W","T4","B9"] +extend-select = "B950" +extend-ignore = ["E203", "E501", "E701"] + +[tool.ruff] +line-length = 132 [tool.black] line-length=132 @@ -120,7 +168,24 @@ exclude = ''' build-verbosity = 3 #test-command = "pytest {package}/tests" #test-command = "pytest -svv pyxcp/tests" -build = "cp3{7,8,9,10,11,12}-*" +build = "cp3{7,8,9,10,11,12,13}-*" skip = ["*-manylinux_i686", "*-musllinux_x86_64", "*-musllinux_i686"] # Skip Linux 32bit and MUSL builds. build-frontend = "build" +[tool.pyright] +include = ["pyxcp", "build_ext.py"] +ignore = ["pyxcp/conf_test.py", "pyxcp/config_new.py", "pyxcp/config_tr.py", "pyxcp/examples/memxfer.py", + "pyxcp/examples/ts_tester.py", "pyxcp/recorder/converter/**", "pyxcp/transport/candriver/**", "pyxcp/recorder/simdjson/**", + "pyxcp/recorder/mio/**", "pyxcp/recorder/lz4/**"] +#defineConstant = { DEBUG = true } +#stubPath = "src/stubs" + +reportMissingImports = true +reportMissingTypeStubs = false + +#executionEnvironments = [ +# { root = "src/web", pythonVersion = "3.5", pythonPlatform = "Windows", extraPaths = [ "src/service_libs" ] }, +# { root = "src/sdk", pythonVersion = "3.0", extraPaths = [ "src/backend" ] }, +# { root = "src/tests", extraPaths = ["src/tests/e2e", "src/sdk" ]}, +# { root = "src" } +#] diff --git a/pyxcp/__init__.py b/pyxcp/__init__.py index e6a9f573..27494c9e 100644 --- a/pyxcp/__init__.py +++ b/pyxcp/__init__.py @@ -1,28 +1,20 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -"""Universal Calibration Protocol for Python""" -import sys +"""Universal Calibration Protocol for Python.""" -if sys.platform == "win32" and sys.version_info[:2] < (3, 11): - # patch the time module with the high resolution alternatives - try: - from win_precise_time import sleep as wpsleep - from win_precise_time import time as wptime +from rich import pretty +from rich.console import Console +from rich.traceback import install as tb_install - import time - time.sleep = wpsleep - time.time = wptime +pretty.install() - except ImportError: - pass +from .master import Master # noqa: F401, E402 +from .transport import Can, Eth, SxI, Usb # noqa: F401, E402 -from .master import Master -from .transport import Can -from .transport import Eth -from .transport import SxI -from .transport import Usb +console = Console() +tb_install(show_locals=True, max_frames=3) # Install custom exception handler. -# if you update this manually, do not forget to update .bumpversion.cfg and pyproject.toml -__version__ = "0.21.9" +# if you update this manually, do not forget to update +# .bumpversion.cfg and pyproject.toml. +__version__ = "0.22.0" diff --git a/pyxcp/aml/EtasCANMonitoring.a2l b/pyxcp/aml/EtasCANMonitoring.a2l index 4f337874..6852a207 100644 --- a/pyxcp/aml/EtasCANMonitoring.a2l +++ b/pyxcp/aml/EtasCANMonitoring.a2l @@ -1,83 +1,82 @@ -ASAP2_VERSION 1 30 -/begin PROJECT - aProjectName - "description of project" - - /begin HEADER - "project" - VERSION "1.0" - PROJECT_NO "1.0" - /end HEADER - - /begin MODULE - aModuleName - "description of module" - - /begin MOD_PAR - "" - /end MOD_PAR - - /begin IF_DATA CAN_MONITORING - /begin TP_BLOB - 500 - /end TP_BLOB - /end IF_DATA - - /begin MEASUREMENT - aMeasurementName - "description of measurement" - ULONG - aConversionName - 0 - 0.0 - 0 - 1000 - /begin IF_DATA CAN_MONITORING - /begin KP_BLOB - 0x0 32 - /end KP_BLOB - /end IF_DATA - FORMAT "" - BYTE_ORDER MSB_LAST - BIT_MASK 0xFFFFFFFF - /end MEASUREMENT - - /begin COMPU_METHOD - aConversionName - "description of conversion" - RAT_FUNC - "%f5.2" - "" - COEFFS 0 1.0 0.0 0 0 1 - /end COMPU_METHOD - - - - - - - - - /begin FRAME - aFrameName - "description of frame" - 0 - 0 - /begin IF_DATA CAN_MONITORING - QP_BLOB 0x0200 0 8 - /end IF_DATA - FRAME_MEASUREMENT aMeasurementName - /end FRAME - - /begin FUNCTION - aFunctionName - "description of function" - /begin OUT_MEASUREMENT - aMeasurementName - /end OUT_MEASUREMENT - /end FUNCTION - - /end MODULE - -/end PROJECT - +ASAP2_VERSION 1 30 +/begin PROJECT + aProjectName + "description of project" + + /begin HEADER + "project" + VERSION "1.0" + PROJECT_NO "1.0" + /end HEADER + + /begin MODULE + aModuleName + "description of module" + + /begin MOD_PAR + "" + /end MOD_PAR + + /begin IF_DATA CAN_MONITORING + /begin TP_BLOB + 500 + /end TP_BLOB + /end IF_DATA + + /begin MEASUREMENT + aMeasurementName + "description of measurement" + ULONG + aConversionName + 0 + 0.0 + 0 + 1000 + /begin IF_DATA CAN_MONITORING + /begin KP_BLOB + 0x0 32 + /end KP_BLOB + /end IF_DATA + FORMAT "" + BYTE_ORDER MSB_LAST + BIT_MASK 0xFFFFFFFF + /end MEASUREMENT + + /begin COMPU_METHOD + aConversionName + "description of conversion" + RAT_FUNC + "%f5.2" + "" + COEFFS 0 1.0 0.0 0 0 1 + /end COMPU_METHOD + + + + + + + + + /begin FRAME + aFrameName + "description of frame" + 0 + 0 + /begin IF_DATA CAN_MONITORING + QP_BLOB 0x0200 0 8 + /end IF_DATA + FRAME_MEASUREMENT aMeasurementName + /end FRAME + + /begin FUNCTION + aFunctionName + "description of function" + /begin OUT_MEASUREMENT + aMeasurementName + /end OUT_MEASUREMENT + /end FUNCTION + + /end MODULE + +/end PROJECT diff --git a/pyxcp/aml/XCP_Common.aml b/pyxcp/aml/XCP_Common.aml index 01eac0e2..879198ad 100644 --- a/pyxcp/aml/XCP_Common.aml +++ b/pyxcp/aml/XCP_Common.aml @@ -406,4 +406,3 @@ taggedstruct Common_Parameters { block "PGM" struct Pgm; block "DAQ_EVENT" taggedunion Daq_Event; }; /******************** end of Common Parameters *****************************/ - diff --git a/pyxcp/aml/XCPonUSB.aml b/pyxcp/aml/XCPonUSB.aml index cdcf6ec7..e5470ce2 100644 --- a/pyxcp/aml/XCPonUSB.aml +++ b/pyxcp/aml/XCPonUSB.aml @@ -103,4 +103,4 @@ struct USB_Parameters { }; /* end of optional */ }; /************************* end of USB ***********************************/ -/end A2ML \ No newline at end of file +/end A2ML diff --git a/pyxcp/aml/ifdata_CAN.a2l b/pyxcp/aml/ifdata_CAN.a2l index fd0fd27e..55553cb8 100644 --- a/pyxcp/aml/ifdata_CAN.a2l +++ b/pyxcp/aml/ifdata_CAN.a2l @@ -18,4 +18,3 @@ begin XCP_ON_CAN FIXED 0x330 /end DAQ_LIST_CAN_ID /end XCP_ON_CAN - diff --git a/pyxcp/aml/ifdata_Eth.a2l b/pyxcp/aml/ifdata_Eth.a2l index d9a04153..98e3219e 100644 --- a/pyxcp/aml/ifdata_Eth.a2l +++ b/pyxcp/aml/ifdata_Eth.a2l @@ -9,4 +9,3 @@ 0x5555 /* PORT */ "127.0.0.1" /* ADDRESS */ /end XCP_ON_UDP_IP - diff --git a/pyxcp/aml/ifdata_Flx.a2l b/pyxcp/aml/ifdata_Flx.a2l index a809e1a5..a702ec46 100644 --- a/pyxcp/aml/ifdata_Flx.a2l +++ b/pyxcp/aml/ifdata_Flx.a2l @@ -92,4 +92,3 @@ STIM VARIABLE /end XCP_PACKET /end POOL_BUFFER /end XCP_ON_FLX - diff --git a/pyxcp/aml/ifdata_SxI.a2l b/pyxcp/aml/ifdata_SxI.a2l index 329dfdaf..80a11459 100644 --- a/pyxcp/aml/ifdata_SxI.a2l +++ b/pyxcp/aml/ifdata_SxI.a2l @@ -11,4 +11,3 @@ HEADER_LEN_CTR_WORD NO_CHECKSUM /end XCP_ON_SxI - diff --git a/pyxcp/aml/ifdata_USB.a2l b/pyxcp/aml/ifdata_USB.a2l index fa0eb253..2d5b5f00 100644 --- a/pyxcp/aml/ifdata_USB.a2l +++ b/pyxcp/aml/ifdata_USB.a2l @@ -79,4 +79,3 @@ FIXED_OUT 0x02 /* uses Endpoint 2 OUT. */ /end DAQ_LIST_USB_ENDPOINT /end XCP_ON_USB - diff --git a/pyxcp/asam/types.py b/pyxcp/asam/types.py index 6e240229..5584536f 100644 --- a/pyxcp/asam/types.py +++ b/pyxcp/asam/types.py @@ -1,7 +1,7 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import struct + INTEL = "<" MOTOROLA = ">" @@ -15,7 +15,7 @@ """ -class AsamBaseType(object): +class AsamBaseType: """Base class for ASAM codecs. Note @@ -51,7 +51,7 @@ def encode(self, value): bytes Encoded value. """ - return struct.pack("{}{}".format(self.byteorder, self.FMT), value) + return struct.pack(f"{self.byteorder}{self.FMT}", value) def decode(self, value): """Decode a value. @@ -68,7 +68,7 @@ def decode(self, value): data-type data-type is determined by derived class. """ - return struct.unpack("{}{}".format(self.byteorder, self.FMT), bytes(value))[0] + return struct.unpack(f"{self.byteorder}{self.FMT}", bytes(value))[0] class A_Uint8(AsamBaseType): diff --git a/pyxcp/asamkeydll.c b/pyxcp/asamkeydll.c index 3530c44b..eed22b2a 100644 --- a/pyxcp/asamkeydll.c +++ b/pyxcp/asamkeydll.c @@ -110,7 +110,6 @@ int main(int argc, char ** argv) } res = GetKey((char *)&dllname, privilege, seedlen, (BYTE *)&seedBuffer, &keylen, (BYTE *)&keyBuffer); - printf("%lu\n", res); if (res == 0) { hexlify(keyBuffer, keylen); } diff --git a/pyxcp/checksum.py b/pyxcp/checksum.py index a1d4b728..63a758c5 100644 --- a/pyxcp/checksum.py +++ b/pyxcp/checksum.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Checksum calculation for memory ranges .. [1] XCP Specification, BUILD_CHECKSUM service. diff --git a/pyxcp/cmdline.py b/pyxcp/cmdline.py index e31bf993..f8d85b34 100644 --- a/pyxcp/cmdline.py +++ b/pyxcp/cmdline.py @@ -1,68 +1,50 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ Parse (transport-layer specific) command line parameters and create a XCP master instance. """ -import argparse -from pyxcp.config import readConfiguration +import warnings +from dataclasses import dataclass +from typing import List + +from pyxcp.config import create_application from pyxcp.master import Master -from pyxcp.transport.can import registered_drivers -from pyxcp.transport.can import try_to_install_system_supplied_drivers -try_to_install_system_supplied_drivers() -CAN_DRIVERS = registered_drivers() +warnings.simplefilter("always") -class ArgumentParser: - """ +@dataclass +class Option: + short_opt: str + long_opt: str = "" + dest: str = "" + help: str = "" + type: str = "" + default: str = "" - Parameter - --------- - callout: callable - Process user-supplied arguments. - """ - def __init__(self, callout=None, *args, **kws): - self.callout = callout - kws.update(formatter_class=argparse.RawDescriptionHelpFormatter, add_help=True) - self._parser = argparse.ArgumentParser(*args, **kws) - self._parser.add_argument( - "-c", - "--config-file", - type=argparse.FileType("r"), - dest="conf", - help="File to read (extended) parameters from.", - ) - self._parser.add_argument( - "-l", - "--loglevel", - choices=["ERROR", "WARN", "INFO", "DEBUG"], - default="INFO", - ) - self._parser.epilog = "To get specific help on transport layers\nuse -h, e.g. {} eth -h".format(self._parser.prog) - self._args = [] +class FakeParser: - @property - def args(self): - return self._args + options: List[Option] = [] - def run(self, policy=None): - """""" - self._args = self.parser.parse_args() - args = self.args - if args.conf is None: - raise RuntimeError("Configuration file must be specified! (option: -c )") - config = readConfiguration(args.conf) - config["LOGLEVEL"] = args.loglevel - if "TRANSPORT" not in config: - raise AttributeError("TRANSPORT must be specified in config!") - transport = config["TRANSPORT"].lower() - master = Master(transport, config=config, policy=policy) - if self.callout: - self.callout(master, args) + def add_argument(self, short_opt: str, long_opt: str = "", dest: str = "", help: str = "", type: str = "", default: str = ""): + warnings.warn("Argument parser extension is currently not supported.", DeprecationWarning, 2) + self.options.append(Option(short_opt, long_opt, dest, help, type, default)) + + +class ArgumentParser: + def __init__(self, callout=None, *args, **kws): + self._parser = FakeParser() + if callout is not None: + warnings.warn("callout argument is currently not supported.", DeprecationWarning, 2) + + def run(self, policy=None, transport_layer_interface=None): + application = create_application(self.parser.options) + master = Master( + application.transport.layer, config=application, policy=policy, transport_layer_interface=transport_layer_interface + ) return master @property diff --git a/pyxcp/config.py b/pyxcp/config.py deleted file mode 100644 index 32d856ef..00000000 --- a/pyxcp/config.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import json -import pathlib - -try: - import toml -except ImportError: - HAS_TOML = False -else: - HAS_TOML = True - - -def readConfiguration(conf): - """Read a configuration file either in JSON or TOML format.""" - if conf: - if isinstance(conf, dict): - return dict(conf) - pth = pathlib.Path(conf.name) - suffix = pth.suffix.lower() - if suffix == ".json": - reader = json - elif suffix == ".toml" and HAS_TOML: - reader = toml - else: - reader = None - if reader: - return reader.loads(conf.read()) - else: - return {} - else: - return {} - - -class Configuration: - """""" - - def __init__(self, parameters, config): - self.parameters = parameters - self.config = config - for key, (tp, required, default) in self.parameters.items(): - if key in self.config: - if not isinstance(self.config[key], tp): - raise TypeError(f"Parameter {key} requires {tp}") - else: - if required: - raise AttributeError(f"{key} must be specified in config!") - else: - self.config[key] = default - - def get(self, key): - return self.config.get(key) - - def __repr__(self): - return f"{self.config:s}" - - __str__ = __repr__ diff --git a/pyxcp/config/__init__.py b/pyxcp/config/__init__.py new file mode 100644 index 00000000..c7b6dc2d --- /dev/null +++ b/pyxcp/config/__init__.py @@ -0,0 +1,1089 @@ +#!/usr/bin/env python +import io +import json +import logging +import sys +import typing +from pathlib import Path + +import can +import toml +from rich.logging import RichHandler +from rich.prompt import Confirm +from traitlets import ( + Any, + Bool, + Callable, + Dict, + Enum, + Float, + Integer, + List, + TraitError, + Unicode, + Union, +) +from traitlets.config import Application, Instance, SingletonConfigurable, default + +from pyxcp.config import legacy + + +class CanBase: + has_fd = False + has_bitrate = True + has_data_bitrate = False + has_poll_interval = False + has_receive_own_messages = False + has_timing = False + + OPTIONAL_BASE_PARAMS = ( + "has_fd", + "has_bitrate", + "has_data_bitrate", + "has_poll_interval", + "has_receive_own_messages", + "has_timing", + ) + + CAN_PARAM_MAP = { + "sjw_abr": None, + "tseg1_abr": None, + "tseg2_abr": None, + "sjw_dbr": None, + "tseg1_dbr": None, + "tseg2_dbr": None, + } + + +class CanAlystii(SingletonConfigurable, CanBase): + """CANalyst-II is a USB to CAN Analyzer device produced by Chuangxin Technology.""" + + interface_name = "canalystii" + + has_timing = True + + device = Integer(default_value=None, allow_none=True, help="""Optional USB device number.""").tag(config=True) + rx_queue_size = Integer( + default_value=None, + allow_none=True, + help="""If set, software received message queue can only grow to this many +messages (for all channels) before older messages are dropped """, + ).tag(config=True) + + +class CanTact(SingletonConfigurable, CanBase): + """Interface for CANtact devices from Linklayer Labs""" + + interface_name = "cantact" + + has_poll_interval = True + has_timing = True + + monitor = Bool(default_value=False, allow_none=True, help="""If true, operate in listen-only monitoring mode""").tag( + config=True + ) + + +class Etas(SingletonConfigurable, CanBase): + """ETAS""" + + interface_name = "etas" + + has_fd = True + has_data_bitrate = True + has_receive_own_messages = True + + +class Gs_Usb(SingletonConfigurable, CanBase): + """Geschwister Schneider USB/CAN devices and candleLight USB CAN interfaces.""" + + interface_name = "gs_usb" + + index = Integer( + default_value=None, + allow_none=True, + help="""device number if using automatic scan, starting from 0. +If specified, bus/address shall not be provided.""", + ).tag(config=True) + bus = Integer(default_value=None, allow_none=True, help="""number of the bus that the device is connected to""").tag( + config=True + ) + address = Integer(default_value=None, allow_none=True, help="""address of the device on the bus it is connected to""").tag( + config=True + ) + + +class Neovi(SingletonConfigurable, CanBase): + """Intrepid Control Systems (ICS) neoVI interfaces.""" + + interface_name = "neovi" + + has_fd = True + has_data_bitrate = True + has_receive_own_messages = True + + use_system_timestamp = Bool( + default_value=None, allow_none=True, help="Use system timestamp for can messages instead of the hardware timestamp" + ).tag(config=True) + serial = Unicode( + default_value=None, allow_none=True, help="Serial to connect (optional, will use the first found if not supplied)" + ).tag(config=True) + override_library_name = Unicode( + default_value=None, allow_none=True, help="Absolute path or relative path to the library including filename." + ).tag(config=True) + + +class IsCan(SingletonConfigurable, CanBase): + """Interface for isCAN from Thorsis Technologies GmbH, former ifak system GmbH.""" + + interface_name = "iscan" + + has_poll_interval = True + + +class Ixxat(SingletonConfigurable, CanBase): + """IXXAT Virtual Communication Interface""" + + interface_name = "ixxat" + + has_fd = True + has_data_bitrate = True + has_receive_own_messages = True + + unique_hardware_id = Integer( + default_value=None, + allow_none=True, + help="""UniqueHardwareId to connect (optional, will use the first found if not supplied)""", + ).tag(config=True) + extended = Bool(default_value=None, allow_none=True, help="""Enables the capability to use extended IDs.""").tag(config=True) + rx_fifo_size = Integer(default_value=None, allow_none=True, help="""Receive fifo size""").tag(config=True) + tx_fifo_size = Integer(default_value=None, allow_none=True, help="""Transmit fifo size""").tag(config=True) + ssp_dbr = Integer( + default_value=None, + allow_none=True, + help="Secondary sample point (data). Only takes effect with fd and bitrate switch enabled.", + ).tag(config=True) + + CAN_PARAM_MAP = { + "sjw_abr": "sjw_abr", + "tseg1_abr": "tseg1_abr", + "tseg2_abr": "tseg2_abr", + "sjw_dbr": "sjw_dbr", + "tseg1_dbr": "tseg1_dbr", + "tseg2_dbr": "tseg2_dbr", + } + + +class Kvaser(SingletonConfigurable, CanBase): + """Kvaser's CANLib""" + + interface_name = "kvaser" + + has_fd = True + has_data_bitrate = True + has_receive_own_messages = True + + CAN_PARAM_MAP = { + "sjw_abr": "sjw", + "tseg1_abr": "tseg1", + "tseg2_abr": "tseg2", + } + + accept_virtual = Bool(default_value=None, allow_none=True, help="If virtual channels should be accepted.").tag(config=True) + no_samp = Enum( + values=[1, 3], + default_value=None, + allow_none=True, + help="""Either 1 or 3. Some CAN controllers can also sample each bit three times. +In this case, the bit will be sampled three quanta in a row, +with the last sample being taken in the edge between TSEG1 and TSEG2. +Three samples should only be used for relatively slow baudrates""", + ).tag(config=True) + driver_mode = Bool(default_value=None, allow_none=True, help="Silent or normal.").tag(config=True) + single_handle = Bool( + default_value=None, + allow_none=True, + help="""Use one Kvaser CANLIB bus handle for both reading and writing. +This can be set if reading and/or writing is done from one thread. """, + ).tag(config=True) + + +class NeouSys(SingletonConfigurable, CanBase): + """Neousys CAN Interface""" + + interface_name = "neousys" + + device = Integer(default_value=None, allow_none=True, help="Device number").tag(config=True) + + +class NiCan(SingletonConfigurable, CanBase): + """National Instruments NI-CAN""" + + interface_name = "nican" + + log_errors = Bool( + default_value=None, + allow_none=True, + help="""If True, communication errors will appear as CAN messages with +``is_error_frame`` set to True and ``arbitration_id`` will identify +the error. """, + ).tag(config=True) + + +class NixNet(SingletonConfigurable, CanBase): + """National Instruments NI-XNET""" + + interface_name = "nixnet" + + has_poll_interval = True + has_receive_own_messages = True + has_timing = True + has_fd = True + + CAN_PARAM_MAP = { + "data_bitrate": "fd_bitrate", + } + + can_termination = Bool(default_value=None, allow_none=True, help="Enable bus termination.") + + +class PCan(SingletonConfigurable, CanBase): + """PCAN Basic API""" + + interface_name = "pcan" + + has_fd = True + has_timing = True + + CAN_PARAM_MAP = { + "sjw_abr": "nom_sjw", + "tseg1_abr": "nom_tseg1", + "tseg2_abr": "nom_tseg2", + "sjw_dbr": "data_sjw", + "tseg1_dbr": "data_tseg1", + "tseg2_dbr": "data_tseg2", + } + + device_id = Integer( + default_value=None, + allow_none=True, + help="""Select the PCAN interface based on its ID. The device ID is a 8/32bit +value that can be configured for each PCAN device. If you set the +device_id parameter, it takes precedence over the channel parameter. +The constructor searches all connected interfaces and initializes the +first one that matches the parameter value. If no device is found, +an exception is raised.""", + ).tag(config=True) + state = Instance(klass=can.BusState, default_value=None, allow_none=True, help="BusState of the channel.").tag(config=True) + + f_clock = Enum( + values=[20000000, 24000000, 30000000, 40000000, 60000000, 80000000], + default_value=None, + allow_none=True, + help="""Ignored if not using CAN-FD. +Pass either f_clock or f_clock_mhz.""", + ).tag(config=True) + f_clock_mhz = Enum( + values=[20, 24, 30, 40, 60, 80], + default_value=None, + allow_none=True, + help="""Ignored if not using CAN-FD. +Pass either f_clock or f_clock_mhz. """, + ).tag(config=True) + + nom_brp = Integer( + min=1, + max=1024, + default_value=None, + allow_none=True, + help="""Clock prescaler for nominal time quantum. +Ignored if not using CAN-FD.""", + ).tag(config=True) + data_brp = Integer( + min=1, + max=1024, + default_value=None, + allow_none=True, + help="""Clock prescaler for fast data time quantum. +Ignored if not using CAN-FD.""", + ).tag(config=True) + + auto_reset = Bool( + default_value=None, + allow_none=True, + help="""Enable automatic recovery in bus off scenario. +Resetting the driver takes ~500ms during which +it will not be responsive.""", + ).tag(config=True) + + +class Robotell(SingletonConfigurable, CanBase): + """Interface for Chinese Robotell compatible interfaces""" + + interface_name = "robotell" + + ttyBaudrate = Integer( + default_value=None, + allow_none=True, + help="""baudrate of underlying serial or usb device +(Ignored if set via the `channel` parameter, e.g. COM7@11500).""", + ).tag(config=True) + rtscts = Bool(default_value=None, allow_none=True, help="turn hardware handshake (RTS/CTS) on and off.").tag(config=True) + + +class SeeedStudio(SingletonConfigurable, CanBase): + """Seeed USB-Can analyzer interface.""" + + interface_name = "seeedstudio" + + timeout = Float(default_value=None, allow_none=True, help="Timeout for the serial device in seconds.").tag(config=True) + baudrate = Integer(default_value=None, allow_none=True, help="Baud rate of the serial device in bit/s.").tag(config=True) + frame_type = Enum( + values=["STD", "EXT"], default_value=None, allow_none=True, help="To select standard or extended messages." + ).tag(config=True) + operation_mode = Enum( + values=["normal", "loopback", "silent", "loopback_and_silent"], default_value=None, allow_none=True, help=""" """ + ).tag(config=True) + + +class Serial(SingletonConfigurable, CanBase): + """A text based interface.""" + + interface_name = "serial" + + has_bitrate = False + + rtscts = Bool(default_value=None, allow_none=True, help="turn hardware handshake (RTS/CTS) on and off.").tag(config=True) + timeout = Float(default_value=None, allow_none=True, help="Timeout for the serial device in seconds.").tag(config=True) + baudrate = Integer(default_value=None, allow_none=True, help="Baud rate of the serial device in bit/s.").tag(config=True) + + +class SlCan(SingletonConfigurable, CanBase): + """CAN over Serial / SLCAN.""" + + interface_name = "slcan" + + has_poll_interval = True + + ttyBaudrate = Integer(default_value=None, allow_none=True, help="Baud rate of the serial device in bit/s.").tag(config=True) + rtscts = Bool(default_value=None, allow_none=True, help="turn hardware handshake (RTS/CTS) on and off.").tag(config=True) + timeout = Float(default_value=None, allow_none=True, help="Timeout for the serial device in seconds.").tag(config=True) + btr = Integer(default_value=None, allow_none=True, help="BTR register value to set custom can speed.").tag(config=True) + sleep_after_open = Float( + default_value=None, allow_none=True, help="Time to wait in seconds after opening serial connection." + ).tag(config=True) + + +class SocketCan(SingletonConfigurable, CanBase): + """Linux SocketCAN.""" + + interface_name = "socketcan" + + has_fd = True + has_bitrate = False + has_receive_own_messages = True + + local_loopback = Bool( + default_value=None, + allow_none=True, + help="""If local loopback should be enabled on this bus. +Please note that local loopback does not mean that messages sent +on a socket will be readable on the same socket, they will only +be readable on other open sockets on the same machine. More info +can be read on the socketcan documentation: +See https://www.kernel.org/doc/html/latest/networking/can.html#socketcan-local-loopback1""", + ).tag(config=True) + + +class SocketCanD(SingletonConfigurable, CanBase): + """Network-to-CAN bridge as a Linux damon.""" + + interface_name = "socketcand" + + has_bitrate = False + + host = Unicode(default_value=None, allow_none=True, help=""" """).tag(config=True) + port = Integer(default_value=None, allow_none=True, help=""" """).tag(config=True) + + +class Systec(SingletonConfigurable, CanBase): + """SYSTEC interface""" + + interface_name = "systec" + + has_receive_own_messages = True + + state = Instance(klass=can.BusState, default_value=None, allow_none=True, help="BusState of the channel.").tag(config=True) + device_number = Integer(min=0, max=254, default_value=None, allow_none=True, help="The device number of the USB-CAN.").tag( + config=True + ) + rx_buffer_entries = Integer( + default_value=None, allow_none=True, help="The maximum number of entries in the receive buffer." + ).tag(config=True) + tx_buffer_entries = Integer( + default_value=None, allow_none=True, help="The maximum number of entries in the transmit buffer." + ).tag(config=True) + + +class Udp_Multicast(SingletonConfigurable, CanBase): + """A virtual interface for CAN communications between multiple processes using UDP over Multicast IP.""" + + interface_name = "udp_multicast" + + has_fd = True + has_bitrate = False + has_receive_own_messages = True + + port = Integer(default_value=None, allow_none=True, help="The IP port to read from and write to.").tag(config=True) + hop_limit = Integer(default_value=None, allow_none=True, help="The hop limit in IPv6 or in IPv4 the time to live (TTL).").tag( + config=True + ) + + +class Usb2Can(SingletonConfigurable, CanBase): + """Interface to a USB2CAN Bus.""" + + interface_name = "usb2can" + + flags = Integer( + default_value=None, allow_none=True, help="Flags to directly pass to open function of the usb2can abstraction layer." + ).tag(config=True) + dll = Unicode(default_value=None, allow_none=True, help="Path to the DLL with the CANAL API to load.").tag(config=True) + serial = Unicode(default_value=None, allow_none=True, help="Alias for `channel` that is provided for legacy reasons.").tag( + config=True + ) + + +class Vector(SingletonConfigurable, CanBase): + """Vector Informatik CAN interfaces.""" + + interface_name = "vector" + + has_fd = True + has_data_bitrate = True + has_poll_interval = True + has_receive_own_messages = True + has_timing = True + + CAN_PARAM_MAP = { + "sjw_abr": "sjw_abr", + "tseg1_abr": "tseg1_abr", + "tseg2_abr": "tseg2_abr", + "sjw_dbr": "sjw_dbr", + "tseg1_dbr": "tseg1_dbr", + "tseg2_dbr": "tseg2_dbr", + } + + serial = Integer( + default_value=None, + allow_none=True, + help="""Serial number of the hardware to be used. +If set, the channel parameter refers to the channels ONLY on the specified hardware. +If set, the `app_name` does not have to be previously defined in +*Vector Hardware Config*.""", + ).tag(config=True) + rx_queue_size = Integer( + min=16, max=32768, default_value=None, allow_none=True, help="Number of messages in receive queue (power of 2)." + ).tag(config=True) + app_name = Unicode(default_value=None, allow_none=True, help="Name of application in *Vector Hardware Config*.").tag( + config=True + ) + + +class Virtual(SingletonConfigurable, CanBase): + """ """ + + interface_name = "virtual" + + has_bitrate = False + has_receive_own_messages = True + + rx_queue_size = Integer( + default_value=None, + allow_none=True, + help="""The size of the reception queue. The reception +queue stores messages until they are read. If the queue reaches +its capacity, it will start dropping the oldest messages to make +room for new ones. If set to 0, the queue has an infinite capacity. +Be aware that this can cause memory leaks if messages are read +with a lower frequency than they arrive on the bus. """, + ).tag(config=True) + preserve_timestamps = Bool( + default_value=None, + allow_none=True, + help="""If set to True, messages transmitted via +will keep the timestamp set in the +:class:`~can.Message` instance. Otherwise, the timestamp value +will be replaced with the current system time.""", + ).tag(config=True) + + +CAN_INTERFACE_MAP = { + "canalystii": CanAlystii, + "cantact": CanTact, + "etas": Etas, + "gs_usb": Gs_Usb, + "iscan": IsCan, + "ixxat": Ixxat, + "kvaser": Kvaser, + "neousys": NeouSys, + "neovi": Neovi, + "nican": NiCan, + "nixnet": NixNet, + "pcan": PCan, + "robotell": Robotell, + "seeedstudio": SeeedStudio, + "serial": Serial, + "slcan": SlCan, + "socketcan": SocketCan, + "socketcand": SocketCanD, + "systec": Systec, + "udp_multicast": Udp_Multicast, + "usb2can": Usb2Can, + "vector": Vector, + "virtual": Virtual, +} + + +class Can(SingletonConfigurable): + VALID_INTERFACES = can.interfaces.VALID_INTERFACES + + interface = Enum( + values=VALID_INTERFACES, default_value=None, allow_none=True, help="CAN interface supported by python-can" + ).tag(config=True) + channel = Any( + default_value=None, allow_none=True, help="Channel identification. Expected type and value is backend dependent." + ).tag(config=True) + max_dlc_required = Bool(False, help="Master to slave frames always to have DLC = MAX_DLC = 8").tag(config=True) + # max_can_fd_dlc = Integer(64, help="").tag(config=True) + padding_value = Integer(0, help="Fill value, if max_dlc_required == True and DLC < MAX_DLC").tag(config=True) + use_default_listener = Bool(True, help="").tag(config=True) + can_id_master = Integer(allow_none=False, help="CAN-ID master -> slave (Bit31= 1: extended identifier)").tag( + config=True + ) # CMD and STIM packets + can_id_slave = Integer(allow_none=True, help="CAN-ID slave -> master (Bit31= 1: extended identifier)").tag( + config=True + ) # RES, ERR, EV, SERV and DAQ packets. + can_id_broadcast = Integer( + default_value=None, allow_none=True, help="Auto detection CAN-ID (Bit31= 1: extended identifier)" + ).tag(config=True) + daq_identifier = List(trait=Integer(), default_value=[], allow_none=True, help="One CAN identifier per DAQ-list.").tag( + config=True + ) + bitrate = Integer(250000, help="CAN bitrate in bits/s (arbitration phase, if CAN FD).").tag(config=True) + receive_own_messages = Bool(False, help="Enable self-reception of sent messages.").tag(config=True) + poll_interval = Float(default_value=None, allow_none=True, help="Poll interval in seconds when reading messages.").tag( + config=True + ) + fd = Bool(False, help="If CAN-FD frames should be supported.").tag(config=True) + data_bitrate = Integer(default_value=None, allow_none=True, help="Which bitrate to use for data phase in CAN FD.").tag( + config=True + ) + sjw_abr = Integer( + default_value=None, allow_none=True, help="Bus timing value sample jump width (arbitration, SJW if CAN classic)." + ).tag(config=True) + tseg1_abr = Integer( + default_value=None, allow_none=True, help="Bus timing value tseg1 (arbitration, TSEG1 if CAN classic)." + ).tag(config=True) + tseg2_abr = Integer( + default_value=None, allow_none=True, help="Bus timing value tseg2 (arbitration, TSEG2, if CAN classic)" + ).tag(config=True) + sjw_dbr = Integer(default_value=None, allow_none=True, help="Bus timing value sample jump width (data).").tag(config=True) + tseg1_dbr = Integer(default_value=None, allow_none=True, help="Bus timing value tseg1 (data).").tag(config=True) + tseg2_dbr = Integer(default_value=None, allow_none=True, help="Bus timing value tseg2 (data).").tag(config=True) + timing = Union( + trait_types=[Instance(klass=can.BitTiming), Instance(klass=can.BitTimingFd)], + default_value=None, + allow_none=True, + help="""Custom bit timing settings. +(.s https://github.com/hardbyte/python-can/blob/develop/can/bit_timing.py) +If this parameter is provided, it takes precedence over all other +timing-related parameters. + """, + ).tag(config=True) + + classes = List( + [ + CanAlystii, + CanTact, + Etas, + Gs_Usb, + Neovi, + IsCan, + Ixxat, + Kvaser, + NeouSys, + NiCan, + NixNet, + PCan, + Robotell, + SeeedStudio, + Serial, + SlCan, + SocketCan, + SocketCanD, + Systec, + Udp_Multicast, + Usb2Can, + Vector, + Virtual, + ] + ) + + def __init__(self, **kws): + super().__init__(**kws) + + if self.parent.layer == "CAN": + if self.interface is None or self.interface not in self.VALID_INTERFACES: + raise TraitError( + f"CAN interface must be one of {sorted(list(self.VALID_INTERFACES))} not the" + " {type(self.interface).__name__} {self.interface}." + ) + self.canalystii = CanAlystii.instance(config=self.config, parent=self) + self.cantact = CanTact.instance(config=self.config, parent=self) + self.etas = Etas.instance(config=self.config, parent=self) + self.gs_usb = Gs_Usb.instance(config=self.config, parent=self) + self.neovi = Neovi.instance(config=self.config, parent=self) + self.iscan = IsCan.instance(config=self.config, parent=self) + self.ixxat = Ixxat.instance(config=self.config, parent=self) + self.kvaser = Kvaser.instance(config=self.config, parent=self) + self.neousys = NeouSys.instance(config=self.config, parent=self) + self.nican = NiCan.instance(config=self.config, parent=self) + self.nixnet = NixNet.instance(config=self.config, parent=self) + self.pcan = PCan.instance(config=self.config, parent=self) + self.robotell = Robotell.instance(config=self.config, parent=self) + self.seeedstudio = SeeedStudio.instance(config=self.config, parent=self) + self.serial = Serial.instance(config=self.config, parent=self) + self.slcan = SlCan.instance(config=self.config, parent=self) + self.socketcan = SocketCan.instance(config=self.config, parent=self) + self.socketcand = SocketCanD.instance(config=self.config, parent=self) + self.systec = Systec.instance(config=self.config, parent=self) + self.udp_multicast = Udp_Multicast.instance(config=self.config, parent=self) + self.usb2can = Usb2Can.instance(config=self.config, parent=self) + self.vector = Vector.instance(config=self.config, parent=self) + self.virtual = Virtual.instance(config=self.config, parent=self) + + +class Eth(SingletonConfigurable): + """Ethernet.""" + + host = Unicode("localhost", help="Hostname or IP address of XCP slave.").tag(config=True) + port = Integer(5555, help="TCP/UDP port to connect.").tag(config=True) + protocol = Enum(values=["TCP", "UDP"], default_value="UDP", help="").tag(config=True) + ipv6 = Bool(False, help="Use IPv6 if `True` else IPv4.").tag(config=True) + tcp_nodelay = Bool(False, help="*** Expert option *** -- Disable Nagle's algorithm if `True`.").tag(config=True) + bind_to_address = Unicode(default_value=None, allow_none=True, help="Bind to specific local address.").tag(config=True) + bind_to_port = Integer(default_value=None, allow_none=True, help="Bind to specific local port.").tag(config=True) + + +class SxI(SingletonConfigurable): + """SCI and SPI connections.""" + + port = Unicode("COM1", help="Name of communication interface.").tag(config=True) + bitrate = Integer(38400, help="Connection bitrate").tag(config=True) + bytesize = Enum(values=[5, 6, 7, 8], default_value=8, help="Size of byte.").tag(config=True) + parity = Enum(values=["N", "E", "O", "M", "S"], default_value="N", help="Paritybit calculation.").tag(config=True) + stopbits = Enum(values=[1, 1.5, 2], default_value=1, help="Number of stopbits.").tag(config=True) + mode = Enum( + values=[ + "ASYNCH_FULL_DUPLEX_MODE", + "SYNCH_FULL_DUPLEX_MODE_BYTE", + "SYNCH_FULL_DUPLEX_MODE_WORD", + "SYNCH_FULL_DUPLEX_MODE_DWORD", + "SYNCH_MASTER_SLAVE_MODE_BYTE", + "SYNCH_MASTER_SLAVE_MODE_WORD", + "SYNCH_MASTER_SLAVE_MODE_DWORD", + ], + default_value="ASYNCH_FULL_DUPLEX_MODE", + help="Asynchronous (SCI) or synchronous (SPI) communication mode.", + ).tag(config=True) + header_format = Enum( + values=[ + "HEADER_LEN_BYTE", + "HEADER_LEN_CTR_BYTE", + "HEADER_LEN_FILL_BYTE", + "HEADER_LEN_WORD", + "HEADER_LEN_CTR_WORD", + "HEADER_LEN_FILL_WORD", + ], + default_value="HEADER_LEN_CTR_WORD", + help="""XCPonSxI header format. +Number of bytes: + + LEN CTR FILL +______________________________________________________________ +HEADER_LEN_BYTE | 1 X X +HEADER_LEN_CTR_BYTE | 1 1 X +HEADER_LEN_FILL_BYTE | 1 X 1 +HEADER_LEN_WORD | 2 X X +HEADER_LEN_CTR_WORD | 2 2 X +HEADER_LEN_FILL_WORD | 2 X 2 +""", + ).tag(config=True) + tail_format = Enum( + values=["NO_CHECKSUM", "CHECKSUM_BYTE", "CHECKSUM_WORD"], default_value="NO_CHECKSUM", help="XCPonSxI tail format." + ).tag(config=True) + framing = Bool(False, help="Enable SCI framing mechanism (ESC chars).").tag(config=True) + esc_sync = Integer(0x01, min=0, max=255, help="SCI framing protocol character SYNC.").tag(config=True) + esc_esc = Integer(0x00, min=0, max=255, help="SCI framing protocol character ESC.").tag(config=True) + + +class Usb(SingletonConfigurable): + """Universal Serial Bus connections.""" + + serial_number = Unicode("", help="Device serial number.").tag(config=True) + configuration_number = Integer(1, help="USB configuration number.").tag(config=True) + interface_number = Integer(2, help="USB interface number.").tag(config=True) + vendor_id = Integer(0, help="USB vendor ID.").tag(config=True) + product_id = Integer(0, help="USB product ID.").tag(config=True) + library = Unicode("", help="Absolute path to USB shared library.").tag(config=True) + header_format = Enum( + values=[ + "HEADER_LEN_BYTE", + "HEADER_LEN_CTR_BYTE", + "HEADER_LEN_FILL_BYTE", + "HEADER_LEN_WORD", + "HEADER_LEN_CTR_WORD", + "HEADER_LEN_FILL_WORD", + ], + default_value="HEADER_LEN_CTR_WORD", + help="", + ).tag(config=True) + in_ep_number = Integer(1, help="Ingoing USB reply endpoint number (IN-EP for RES/ERR, DAQ, and EV/SERV).").tag(config=True) + in_ep_transfer_type = Enum( + values=["BULK_TRANSFER", "INTERRUPT_TRANSFER"], default_value="BULK_TRANSFER", help="Ingoing: Supported USB transfer types." + ).tag(config=True) + in_ep_max_packet_size = Integer(512, help="Ingoing: Maximum packet size of endpoint in bytes.").tag(config=True) + in_ep_polling_interval = Integer(0, help="Ingoing: Polling interval of endpoint.").tag(config=True) + in_ep_message_packing = Enum( + values=["MESSAGE_PACKING_SINGLE", "MESSAGE_PACKING_MULTIPLE", "MESSAGE_PACKING_STREAMING"], + default_value="MESSAGE_PACKING_SINGLE", + help="Ingoing: Packing of XCP Messages.", + ).tag(config=True) + in_ep_alignment = Enum( + values=["ALIGNMENT_8_BIT", "ALIGNMENT_16_BIT", "ALIGNMENT_32_BIT", "ALIGNMENT_64_BIT"], + default_value="ALIGNMENT_8_BIT", + help="Ingoing: Alignment border.", + ).tag(config=True) + in_ep_recommended_host_bufsize = Integer(0, help="Ingoing: Recommended host buffer size.").tag(config=True) + out_ep_number = Integer(0, help="Outgoing USB command endpoint number (OUT-EP for CMD and STIM).").tag(config=True) + out_ep_transfer_type = Enum( + values=["BULK_TRANSFER", "INTERRUPT_TRANSFER"], + default_value="BULK_TRANSFER", + help="Outgoing: Supported USB transfer types.", + ).tag(config=True) + out_ep_max_packet_size = Integer(512, help="Outgoing: Maximum packet size of endpoint in bytes.").tag(config=True) + out_ep_polling_interval = Integer(0, help="Outgoing: Polling interval of endpoint.").tag(config=True) + out_ep_message_packing = Enum( + values=["MESSAGE_PACKING_SINGLE", "MESSAGE_PACKING_MULTIPLE", "MESSAGE_PACKING_STREAMING"], + default_value="MESSAGE_PACKING_SINGLE", + help="Outgoing: Packing of XCP Messages.", + ).tag(config=True) + out_ep_alignment = Enum( + values=["ALIGNMENT_8_BIT", "ALIGNMENT_16_BIT", "ALIGNMENT_32_BIT", "ALIGNMENT_64_BIT"], + default_value="ALIGNMENT_8_BIT", + help="Outgoing: Alignment border.", + ).tag(config=True) + out_ep_recommended_host_bufsize = Integer(0, help="Outgoing: Recommended host buffer size.").tag(config=True) + + +class Transport(SingletonConfigurable): + """ """ + + classes = List([Can, Eth, SxI, Usb]) + + layer = Enum( + values=["CAN", "ETH", "SXI", "USB"], + default_value=None, + allow_none=True, + help="Choose one of the supported XCP transport layers.", + ).tag(config=True) + create_daq_timestamps = Bool(False, help="Record time of frame reception or set timestamp to 0.").tag(config=True) + timeout = Float( + 2.0, + help="""raise `XcpTimeoutError` after `timeout` seconds +if there is no response to a command.""", + ).tag(config=True) + alignment = Enum(values=[1, 2, 4, 8], default_value=1).tag(config=True) + + can = Instance(Can).tag(config=True) + eth = Instance(Eth).tag(config=True) + sxi = Instance(SxI).tag(config=True) + usb = Instance(Usb).tag(config=True) + + def __init__(self, **kws): + super().__init__(**kws) + self.can = Can.instance(config=self.config, parent=self) + self.eth = Eth.instance(config=self.config, parent=self) + self.sxi = SxI.instance(config=self.config, parent=self) + self.usb = Usb.instance(config=self.config, parent=self) + + +class General(SingletonConfigurable): + """ """ + + # loglevel = Unicode("INFO", help="Set the log level by value or name.").tag(config=True) + disable_error_handling = Bool(False, help="Disable XCP error-handler for performance reasons.").tag(config=True) + disconnect_response_optional = Bool(False, help="Ignore missing response on DISCONNECT request.").tag(config=True) + seed_n_key_dll = Unicode("", allow_none=False, help="Dynamic library used for slave resource unlocking.").tag(config=True) + seed_n_key_dll_same_bit_width = Bool(False, help="").tag(config=True) + seed_n_key_function = Callable( + default_value=None, + allow_none=True, + help="""Python function used for slave resource unlocking. +Could be used if seed-and-key algorithm is known instead of `seed_n_key_dll`.""", + ).tag(config=True) + stim_support = Bool(False, help="").tag(config=True) + + +class ProfileCreate(Application): + description = "\nCreate a new profile" + + dest_file = Unicode(default_value=None, allow_none=True, help="destination file name").tag(config=True) + aliases = Dict( # type:ignore[assignment] + dict( + d="ProfileCreate.dest_file", + o="ProfileCreate.dest_file", + ) + ) + + def start(self): + pyxcp = self.parent.parent + if self.dest_file: + dest = Path(self.dest_file) + if dest.exists(): + if not Confirm.ask(f"Destination file [green]{dest.name!r}[/green] already exists. Do you want to overwrite it?"): + print("Aborting...") + self.exit(1) + with dest.open("w", encoding="latin1") as out_file: + pyxcp.generate_config_file(out_file) + else: + pyxcp.generate_config_file(sys.stdout) + + +class ProfileConvert(Application): + description = "\nConvert legacy configuration file (.json/.toml) to new Python based format." + + config_file = Unicode(help="Name of legacy config file (.json/.toml).", default_value=None, allow_none=False).tag( + config=True + ) # default_value="pyxcp_conf.py", + + dest_file = Unicode(default_value=None, allow_none=True, help="destination file name").tag(config=True) + + aliases = Dict( # type:ignore[assignment] + dict( + c="ProfileConvert.config_file", + d="ProfileConvert.dest_file", + o="ProfileConvert.dest_file", + ) + ) + + def start(self): + pyxcp = self.parent.parent + pyxcp._read_configuration(self.config_file, emit_warning=False) + if self.dest_file: + dest = Path(self.dest_file) + if dest.exists(): + if not Confirm.ask(f"Destination file [green]{dest.name!r}[/green] already exists. Do you want to overwrite it?"): + print("Aborting...") + self.exit(1) + with dest.open("w", encoding="latin1") as out_file: + pyxcp.generate_config_file(out_file) + else: + pyxcp.generate_config_file(sys.stdout) + + +class ProfileApp(Application): + subcommands = Dict( + dict( + create=(ProfileCreate, ProfileCreate.description.splitlines()[0]), + convert=(ProfileConvert, ProfileConvert.description.splitlines()[0]), + ) + ) + + def start(self): + if self.subapp is None: + print(f"No subcommand specified. Must specify one of: {self.subcommands.keys()}") + print() + self.print_description() + self.print_subcommands() + self.exit(1) + else: + self.subapp.start() + + +class PyXCP(Application): + description = "pyXCP application" + config_file = Unicode(default_value="pyxcp_conf.py", help="base name of config file").tag(config=True) + + classes = List([General, Transport]) + + subcommands = dict( + profile=( + ProfileApp, + """ + Profile stuff + """.strip(), + ) + ) + + def start(self): + if self.subapp: + self.subapp.start() + exit(2) + else: + self._read_configuration(self.config_file) + self._setup_logger() + + def _setup_logger(self): + from pyxcp.types import Command + + # Remove any handlers installed by `traitlets`. + for hdl in self.log.handlers: + self.log.removeHandler(hdl) + + # formatter = logging.Formatter(fmt=self.log_format, datefmt=self.log_datefmt) + + keywords = list(Command.__members__.keys()) + ["ARGS", "KWS"] # Syntax highlight XCP commands and other stuff. + rich_handler = RichHandler( + rich_tracebacks=True, + tracebacks_show_locals=True, + log_time_format=self.log_datefmt, + level=self.log_level, + keywords=keywords, + ) + # rich_handler.setFormatter(formatter) + self.log.addHandler(rich_handler) + + def initialize(self, argv=None): + from pyxcp import __version__ as pyxcp_version + + PyXCP.version = pyxcp_version + PyXCP.name = Path(sys.argv[0]).name + self.parse_command_line(argv[1:]) + self.log.debug(f"pyxcp version: {self.version}") + + def _read_configuration(self, file_name: str, emit_warning: bool = True) -> None: + self.read_configuration_file(file_name, emit_warning) + self.general = General.instance(config=self.config, parent=self) + self.transport = Transport.instance(parent=self) + + def read_configuration_file(self, file_name: str, emit_warning: bool = True): + self.legacy_config: bool = False + + pth = Path(file_name) + if not pth.exists(): + raise FileNotFoundError(f"Configuration file {file_name!r} does not exist.") + suffix = pth.suffix.lower() + if suffix == ".py": + self.load_config_file(pth) + else: + self.legacy_config = True + if suffix == ".json": + reader = json + elif suffix == ".toml": + reader = toml + else: + raise ValueError(f"Unknown file type for config: {suffix}") + with pth.open("r") as f: + if emit_warning: + self.log.warning(f"Legacy configuration file format ({suffix}), please use Python based configuration.") + cfg = reader.loads(f.read()) + if cfg: + cfg = legacy.convert_config(cfg, self.log) + self.config = cfg + return cfg + + flags = Dict( # type:ignore[assignment] + dict( + debug=({"PyXCP": {"log_level": 10}}, "Set loglevel to DEBUG"), + ) + ) + + @default("log_level") + def _default_value(self): + return logging.INFO # traitlets default is logging.WARN + + aliases = Dict( # type:ignore[assignment] + dict( + c="PyXCP.config_file", # Application + log_level="PyXCP.log_level", + l="PyXCP.log_level", + ) + ) + + def _iterate_config_class(self, klass, class_names: typing.List[str], config, out_file: io.IOBase = sys.stdout) -> None: + sub_classes = [] + class_path = ".".join(class_names) + print( + f"""\n# ------------------------------------------------------------------------------ +# {class_path} configuration +# ------------------------------------------------------------------------------""", + end="\n\n", + file=out_file, + ) + if hasattr(klass, "classes"): + kkk = klass.classes + if hasattr(kkk, "default"): + if class_names[-1] not in ("PyXCP"): + sub_classes.extend(kkk.default()) + for name, tr in klass.class_own_traits().items(): + md = tr.metadata + if md.get("config"): + help = md.get("help", "").lstrip() + commented_lines = "\n".join([f"# {line}" for line in help.split("\n")]) + print(f"#{commented_lines}", file=out_file) + value = tr.default() + if isinstance(tr, Instance) and tr.__class__.__name__ not in ("Dict", "List"): + continue + if isinstance(tr, Enum): + print(f"# Choices: {tr.info()}", file=out_file) + else: + print(f"# Type: {tr.info()}", file=out_file) + print(f"# Default: {value!r}", file=out_file) + if name in config: + cfg_value = config[name] + print(f"c.{class_path!s}.{name!s} = {cfg_value!r}", end="\n\n", file=out_file) + else: + print(f"# c.{class_path!s}.{name!s} = {value!r}", end="\n\n", file=out_file) + if class_names is None: + class_names = [] + for sub_klass in sub_classes: + self._iterate_config_class( + sub_klass, class_names + [sub_klass.__name__], config=config.get(sub_klass.__name__, {}), out_file=out_file + ) + + def generate_config_file(self, file_like: io.IOBase, config=None) -> None: + print("#", file=file_like) + print("# Configuration file for pyXCP.", file=file_like) + print("#", file=file_like) + print("c = get_config() # noqa", end="\n\n", file=file_like) + + for klass in self._classes_with_config_traits(): + self._iterate_config_class( + klass, [klass.__name__], config=self.config.get(klass.__name__, {}) if config is None else {}, out_file=file_like + ) + + +application: typing.Optional[PyXCP] = None + + +def create_application(options: typing.Optional[typing.List[typing.Any]] = None) -> PyXCP: + global application + if options is None: + options = [] + if application is not None: + return application + application = PyXCP() + application.initialize(sys.argv) + application.start() + return application + + +def get_application(options: typing.Optional[typing.List[typing.Any]] = None) -> PyXCP: + if options is None: + options = [] + global application + if application is None: + application = create_application(options) + return application diff --git a/pyxcp/config/legacy.py b/pyxcp/config/legacy.py new file mode 100644 index 00000000..e3eb2a8d --- /dev/null +++ b/pyxcp/config/legacy.py @@ -0,0 +1,120 @@ +from collections import defaultdict + +from traitlets.config import LoggingConfigurable +from traitlets.config.loader import Config + + +LEGACY_KEYWORDS = { + # General + "LOGLEVEL": "General.loglevel", + "DISABLE_ERROR_HANDLING": "General.disable_error_handling", + "SEED_N_KEY_DLL": "General.seed_n_key_dll", + "SEED_N_KEY_DLL_SAME_BIT_WIDTH": "General.seed_n_key_dll_same_bit_width", + "DISCONNECT_RESPONSE_OPTIONAL": "General.disconnect_response_optional", + # Transport + "TRANSPORT": "Transport.layer", + "CREATE_DAQ_TIMESTAMPS": "Transport.create_daq_timestamps", + "TIMEOUT": "Transport.timeout", + "ALIGNMENT": "Transport.alignment", + # Eth + "HOST": "Transport.Eth.host", + "PORT": "Transport.Eth.port", + "PROTOCOL": "Transport.Eth.protocol", + "IPV6": "Transport.Eth.ipv6", + "TCP_NODELAY": "Transport.Eth.tcp_nodelay", + # Usb + "SERIAL_NUMBER": "Transport.Usb.serial_number", + "CONFIGURATION_NUMBER": "Transport.Usb.configuration_number", + "INTERFACE_NUMBER": "Transport.Usb.interface_number", + "COMMAND_ENDPOINT_NUMBER": "Transport.Usb.out_ep_number", + "REPLY_ENDPOINT_NUMBER": "Transport.Usb.in_ep_number", + "VENDOR_ID": "Transport.Usb.vendor_id", + "PRODUCT_ID": "Transport.Usb.product_id", + "LIBRARY": "Transport.Usb.library", + # Can + "CAN_DRIVER": "Transport.Can.interface", + "CHANNEL": "Transport.Can.channel", + "MAX_DLC_REQUIRED": "Transport.Can.max_dlc_required", + # "MAX_CAN_FD_DLC": "Transport.Can.max_can_fd_dlc", + "PADDING_VALUE": "Transport.Can.padding_value", + "CAN_USE_DEFAULT_LISTENER": "Transport.Can.use_default_listener", + # Swap master and slave IDs. (s. https://github.com/christoph2/pyxcp/issues/130) + "CAN_ID_SLAVE": "Transport.Can.can_id_master", + "CAN_ID_MASTER": "Transport.Can.can_id_slave", + "CAN_ID_BROADCAST": "Transport.Can.can_id_broadcast", + "BITRATE": "Transport.Can.bitrate", + "RECEIVE_OWN_MESSAGES": "Transport.Can.receive_own_messages", + "POLL_INTERVAL": "Transport.Can.poll_interval", + "FD": "Transport.Can.fd", + "DATA_BITRATE": "Transport.Can.data_bitrate", + "ACCEPT_VIRTUAL": "Transport.Can.Kvaser.accept_virtual", + "SJW": "Transport.Can.sjw_abr", + "TSEG1": "Transport.Can.tseg1_abr", + "TSEG2": "Transport.Can.tseg2_abr", + "TTY_BAUDRATE": "Transport.Can.SlCan.ttyBaudrate", + "UNIQUE_HARDWARE_ID": "Transport.Can.Ixxat.unique_hardware_id", + "RX_FIFO_SIZE": "Transport.Can.Ixxat.rx_fifo_size", + "TX_FIFO_SIZE": "Transport.Can.Ixxat.tx_fifo_size", + "DRIVER_MODE": "Transport.Can.Kvaser.driver_mode", + "NO_SAMP": "Transport.Can.Kvaser.no_samp", + "SINGLE_HANDLE": "Transport.Can.Kvaser.single_handle", + "USE_SYSTEM_TIMESTAMP": "Transport.Can.Neovi.use_system_timestamp", + "OVERRIDE_LIBRARY_NAME": "Transport.Can.Neovi.override_library_name", + "BAUDRATE": "Transport.Can.Serial.baudrate", + "SLEEP_AFTER_OPEN": "Transport.Can.SlCan.sleep_after_open", + "DEVICE_NUMBER": "Transport.Can.Systec.device_number", + "RX_BUFFER_ENTRIES": "Transport.Can.Systec.rx_buffer_entries", + "TX_BUFFER_ENTRIES": "Transport.Can.Systec.tx_buffer_entries", + "FLAGS": "Transport.Can.Usb2Can.flags", + "APP_NAME": "Transport.Can.Vector.app_name", + "RX_QUEUE_SIZE": "Transport.Can.Vector.rx_queue_size", +} + + +def nested_dict_update(d: dict, key: str, value) -> None: + root, *path, key = key.split(".") + sub_dict = d[root] + for part in path: + if part not in sub_dict: + sub_dict[part] = defaultdict(dict) + sub_dict = sub_dict[part] + sub_dict[key] = value + + +def convert_config(legacy_config: dict, logger: LoggingConfigurable) -> Config: + interface_name = None + resolv = [] + d = defaultdict(dict) + for key, value in legacy_config.items(): + key = key.upper() + item = LEGACY_KEYWORDS.get(key) + if item is None: + logger.warning(f"Unknown keyword {key!r} in config file") + continue + if key == "CAN_DRIVER": + value = value.lower() + interface_name = value + if key in ("SERIAL", "LOG_ERRORS", "STATE", "RTSCTS"): + resolv.append((key, value)) + else: + nested_dict_update(d=d, key=item, value=value) + for key, value in resolv: + if key == "SERIAL": + if interface_name == "neovi": + d["Transport.Can.Neovi.serial"] = value + elif interface_name == "vector": + d["Transport.Can.Vector.serial"] = value + elif key == "LOG_ERRORS": + if interface_name == "nican": + d["Transport.Can.NiCan.log_errors"] = value + elif key == "STATE": + if interface_name == "pcan": + d["Transport.Can.PCan.state"] = value + elif interface_name == "systec": + d["Transport.Can.Systec.state"] = value + elif key == "RTSCTS": + if interface_name == "serial": + d["Transport.Can.Serial.rtscts"] = value + elif interface_name == "slcan": + d["Transport.Can.SlCan.rtscts"] = value + return Config(d) diff --git a/pyxcp/constants.py b/pyxcp/constants.py index 8f3d8e5b..c78dad52 100644 --- a/pyxcp/constants.py +++ b/pyxcp/constants.py @@ -1,48 +1,47 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import struct -from typing import Callable -from typing import NewType +from typing import Any, Callable -PackerType = NewType("PackerType", Callable[[int], bytes]) -UnpackerType = NewType("UnpackerType", Callable[[bytes], int]) + +PackerType = Callable[[int], bytes] +UnpackerType = Callable[[bytes], tuple[Any, ...]] def makeBytePacker(byteorder: str = "@") -> PackerType: """""" - return struct.Struct("{}B".format(byteorder)).pack + return struct.Struct(f"{byteorder}B").pack def makeByteUnpacker(byteorder: str = "@") -> UnpackerType: """""" - return struct.Struct("{}B".format(byteorder)).unpack + return struct.Struct(f"{byteorder}B").unpack def makeWordPacker(byteorder: str = "@") -> PackerType: """""" - return struct.Struct("{}H".format(byteorder)).pack + return struct.Struct(f"{byteorder}H").pack def makeWordUnpacker(byteorder: str = "@") -> UnpackerType: """""" - return struct.Struct("{}H".format(byteorder)).unpack + return struct.Struct(f"{byteorder}H").unpack def makeDWordPacker(byteorder: str = "@") -> PackerType: """""" - return struct.Struct("{}I".format(byteorder)).pack + return struct.Struct(f"{byteorder}I").pack def makeDWordUnpacker(byteorder: str = "@") -> UnpackerType: """""" - return struct.Struct("{}I".format(byteorder)).unpack + return struct.Struct(f"{byteorder}I").unpack def makeDLongPacker(byteorder: str = "@") -> PackerType: """""" - return struct.Struct("{}Q".format(byteorder)).pack + return struct.Struct(f"{byteorder}Q").pack def makeDLongUnpacker(byteorder: str = "@") -> UnpackerType: """""" - return struct.Struct("{}Q".format(byteorder)).unpack + return struct.Struct(f"{byteorder}Q").unpack diff --git a/pyxcp/cpp_ext/__init__.py b/pyxcp/cpp_ext/__init__.py new file mode 100644 index 00000000..4a07cae5 --- /dev/null +++ b/pyxcp/cpp_ext/__init__.py @@ -0,0 +1,8 @@ +from .cpp_ext import ( # noqa: F401 + Bin, + DaqList, + McObject, + Timestamp, + TimestampInfo, + TimestampType, +) diff --git a/pyxcp/cpp_ext/bin.hpp b/pyxcp/cpp_ext/bin.hpp new file mode 100644 index 00000000..b6745d44 --- /dev/null +++ b/pyxcp/cpp_ext/bin.hpp @@ -0,0 +1,104 @@ + +#if !defined(__BIN_HPP) + #define __BIN_HPP + + #include + #include + #include + #include + #include + #include + + #include "mcobject.hpp" + +class Bin { + public: + + Bin(std::uint16_t size) : m_size(size), m_residual_capacity(size) { + } + + Bin(std::uint16_t size, uint16_t residual_capacity, const std::vector& entries) : + m_size(size), m_residual_capacity(residual_capacity), m_entries(entries) { + } + + void append(const McObject& bin) { + m_entries.emplace_back(bin); + } + + void set_entries(std::vector&& entries) { + m_entries = std::move(entries); + } + + std::uint16_t get_size() const { + return m_size; + } + + void set_size(const std::uint16_t size) { + m_size = size; + } + + std::uint16_t get_residual_capacity() const { + return m_residual_capacity; + } + + void set_residual_capacity(const std::uint16_t residual_capacity) { + m_residual_capacity = residual_capacity; + } + + const std::vector& get_entries() const { + return m_entries; + } + + bool operator==(const Bin& other) const { + return (m_size == other.m_size) && (m_residual_capacity == other.m_residual_capacity) && (m_entries == other.m_entries); + } + + std::string dumps() const { + std::stringstream ss; + + ss << to_binary(m_size); + ss << to_binary(m_residual_capacity); + + std::size_t entries_size = m_entries.size(); + ss << to_binary(entries_size); + for (const auto& entry : m_entries) { + ss << entry.dumps(); + } + + return ss.str(); + } + + private: + + std::uint16_t m_size; + std::uint16_t m_residual_capacity; + std::vector m_entries{}; +}; + +std::string bin_entries_to_string(const std::vector& entries); + +std::string to_string(const Bin& obj) { + std::stringstream ss; + + ss << "Bin(residual_capacity=" << obj.get_residual_capacity() << ", entries=[" << bin_entries_to_string(obj.get_entries()) + << "])"; + return ss.str(); +} + +std::string bin_entries_to_string(const std::vector& entries) { + std::stringstream ss; + + for (const auto& entry : entries) { + ss << to_string(entry) << ",\n "; + } + return ss.str(); +} + + #if 0 + + @property + def __len__(self) -> int: + return len(self.entries) + #endif + +#endif // __BIN_HPP diff --git a/pyxcp/cpp_ext/blockmem.hpp b/pyxcp/cpp_ext/blockmem.hpp new file mode 100644 index 00000000..8204c124 --- /dev/null +++ b/pyxcp/cpp_ext/blockmem.hpp @@ -0,0 +1,58 @@ + +#ifndef __BLOCKMEM_HPP +#define __BLOCKMEM_HPP + +#include +#include +#include + +/* + * + * Super simplicistic block memory manager. + * + */ +template +class BlockMemory { + public: + + using mem_block_t = std::array; + + constexpr explicit BlockMemory() noexcept : m_memory{ nullptr }, m_allocation_count{ 0 } { + m_memory = new T[_IS * _NB]; + } + + ~BlockMemory() noexcept { + if (m_memory) { + delete[] m_memory; + } + } + + BlockMemory(const BlockMemory&) = delete; + + constexpr T* acquire() noexcept { + const std::scoped_lock lock(m_mtx); + + if (m_allocation_count >= _NB) { + return nullptr; + } + T* ptr = reinterpret_cast(m_memory + (m_allocation_count * _IS)); + m_allocation_count++; + return ptr; + } + + constexpr void release() noexcept { + const std::scoped_lock lock(m_mtx); + if (m_allocation_count == 0) { + return; + } + m_allocation_count--; + } + + private: + + T* m_memory; + std::uint32_t m_allocation_count; + std::mutex m_mtx; +}; + +#endif // __BLOCKMEM_HPP diff --git a/pyxcp/cpp_ext/daqlist.hpp b/pyxcp/cpp_ext/daqlist.hpp new file mode 100644 index 00000000..b8361056 --- /dev/null +++ b/pyxcp/cpp_ext/daqlist.hpp @@ -0,0 +1,197 @@ + +#if !defined(__DAQ_LIST_HPP) + #define __DAQ_LIST_HPP + + #include "bin.hpp" + #include "helper.hpp" + #include "mcobject.hpp" + +using flatten_odts_t = std::vector>>; + +class DaqList { + public: + + using daq_list_initialzer_t = std::tuple; + + DaqList( + std::string_view meas_name, std::uint16_t event_num, bool stim, bool enable_timestamps, + const std::vector& measurements + ) : + m_name(meas_name), m_event_num(event_num), m_stim(stim), m_enable_timestamps(enable_timestamps) { + // std::cout << "DAQ-List: " << meas_name << " " << event_num << " " << stim << " " << enable_timestamps << std::endl; + for (const auto& measurement : measurements) { + auto const& [name, address, ext, dt_name] = measurement; + m_measurements.emplace_back(McObject(name, address, static_cast(ext), 0, dt_name)); + } + } + + bool get_enable_timestamps() const { + return m_enable_timestamps; + } + + const std::string& get_name() const { + return m_name; + } + + std::uint16_t get_event_num() const { + return m_event_num; + } + + bool get_stim() const { + return m_stim; + } + + const std::vector& get_measurements() const { + return m_measurements; + } + + const std::vector& get_measurements_opt() const { + return m_measurements_opt; + } + + const std::vector& get_header_names() const { + return m_header_names; + } + + const std::vector>& get_headers() const noexcept { + return m_headers; + } + + std::uint16_t get_odt_count() const { + return m_odt_count; + } + + std::uint16_t get_total_entries() const { + return m_total_entries; + } + + std::uint16_t get_total_length() const { + return m_total_length; + } + + const flatten_odts_t& get_flatten_odts() const { + return m_flatten_odts; + } + + void set_measurements_opt(const std::vector& measurements_opt) { + m_measurements_opt = measurements_opt; + + auto odt_count = 0u; + auto total_entries = 0u; + auto total_length = 0u; + for (const auto& bin : measurements_opt) { + odt_count++; + std::vector> flatten_odt{}; + for (const auto& mc_obj : bin.get_entries()) { + for (const auto& component : mc_obj.get_components()) { + m_header_names.emplace_back(component.get_name()); + flatten_odt.emplace_back( + component.get_name(), component.get_address(), component.get_ext(), component.get_length(), + component.get_type_index() + ); + m_headers.emplace_back(component.get_name(), TYPE_MAP_REV.at(component.get_type_index())); + total_entries++; + total_length += component.get_length(); + } + } + m_flatten_odts.emplace_back(flatten_odt); + } + m_odt_count = odt_count; + m_total_entries = total_entries; + m_total_length = total_length; + } + + std::string dumps() const { + std::stringstream ss; + + ss << to_binary(m_name); + ss << to_binary(m_event_num); + ss << to_binary(m_stim); + ss << to_binary(m_enable_timestamps); + + ss << to_binary(m_odt_count); + ss << to_binary(m_total_entries); + ss << to_binary(m_total_length); + + std::size_t meas_size = m_measurements.size(); + ss << to_binary(meas_size); + for (const auto& mc_obj : m_measurements) { + ss << mc_obj.dumps(); + } + std::size_t meas_opt_size = m_measurements_opt.size(); + ss << to_binary(meas_opt_size); + for (const auto& mc_obj : m_measurements_opt) { + ss << mc_obj.dumps(); + } + std::size_t hname_size = m_header_names.size(); + ss << to_binary(hname_size); + for (const auto& hdr_obj : m_header_names) { + ss << to_binary(hdr_obj); + } + ///// + std::size_t odt_size = m_flatten_odts.size(); + ss << to_binary(odt_size); + for (const auto& odt : m_flatten_odts) { + ss << to_binary(odt.size()); + for (const auto& odt_entry : odt) { + const auto& [name, address, ext, size, type_index] = odt_entry; + ss << to_binary(name); + ss << to_binary(address); + ss << to_binary(ext); + ss << to_binary(size); + ss << to_binary(type_index); + } + } + return ss.str(); + } + + std::string to_string() const { + std::stringstream ss; + + ss << "DaqList("; + ss << "name=\"" << m_name << "\", "; + ss << "event_num=" << static_cast(m_event_num) << ", "; + ss << "stim=" << bool_to_string(m_stim) << ", "; + ss << "enable_timestamps" << bool_to_string(m_enable_timestamps) << ", "; + ss << "measurements=[\n"; + for (const auto& meas : m_measurements) { + ss << ::to_string(meas) << ",\n"; + } + ss << "],\n"; + ss << "measurements_opt=[\n"; + for (const auto& meas : m_measurements_opt) { + ss << ::to_string(meas) << ",\n"; + } + ss << "],\n"; + ss << "header_names=[\n"; + for (const auto& header : m_header_names) { + ss << "\"" << header << "\","; + } + ss << "\n]"; + + // using flatten_odts_t = std::vector>>; + ss << ")"; + return ss.str(); + } + + static void loads(std::string_view buffer) { + } + + private: + + std::string m_name; + std::uint16_t m_event_num; + bool m_stim; + bool m_enable_timestamps; + std::vector m_measurements; + std::vector m_measurements_opt; + std::vector m_header_names; + std::vector> m_headers; + std::uint16_t m_odt_count; + std::uint16_t m_total_entries; + std::uint16_t m_total_length; + flatten_odts_t m_flatten_odts; +}; + +#endif // __DAQ_LIST_HPP diff --git a/pyxcp/cpp_ext/event.hpp b/pyxcp/cpp_ext/event.hpp new file mode 100644 index 00000000..b4e8fa5f --- /dev/null +++ b/pyxcp/cpp_ext/event.hpp @@ -0,0 +1,67 @@ + +#ifndef __EVENT_HPP +#define __EVENT_HPP + +#include +#include +#include + +class Event { + public: + + Event(const Event& other) noexcept { + std::scoped_lock lock(other.m_mtx); + m_flag = other.m_flag; + } + + ~Event() = default; + Event() = default; + + void signal() noexcept { + std::scoped_lock lock(m_mtx); + m_flag = true; + m_cond.notify_one(); + } + + void wait() noexcept { + std::unique_lock lock(m_mtx); + m_cond.wait(lock, [this] { return m_flag; }); + m_flag = false; + } + + bool state() const noexcept { + std::scoped_lock lock(m_mtx); + return m_flag; + } + + private: + + mutable std::mutex m_mtx{}; + bool m_flag{ false }; + std::condition_variable m_cond{}; +}; + +#if 0 +class Spinlock { + public: + + Spinlock() : m_flag(ATOMIC_FLAG_INIT) { + } + + ~Spinlock() = default; + + void lock() { + while (m_flag.test_and_set()) { + } + } + + void unlock() { + m_flag.clear(); + } + +private: + std::atomic_flag m_flag; +}; +#endif + +#endif // __EVENT_HPP diff --git a/pyxcp/cpp_ext/extension_wrapper.cpp b/pyxcp/cpp_ext/extension_wrapper.cpp new file mode 100644 index 00000000..5f928079 --- /dev/null +++ b/pyxcp/cpp_ext/extension_wrapper.cpp @@ -0,0 +1,94 @@ + +#include +#include +#include +#include +#include + +#include + +#include "bin.hpp" +#include "daqlist.hpp" +#include "mcobject.hpp" + +namespace py = pybind11; +using namespace pybind11::literals; + +class PyTimestampInfo : public TimestampInfo { + public: + + using TimestampInfo::TimestampInfo; +}; + +PYBIND11_MODULE(cpp_ext, m) { + m.doc() = "C++ extensions for pyXCP."; + + //m.def("sleep_ms", &sleep_ms, "milliseconds"_a); + //m.def("sleep_ns", &sleep_ns, "nanoseconds"_a); + + py::class_(m, "McObject") + .def( + py::init< + std::string_view, std::uint32_t, std::uint8_t, std::uint16_t, const std::string&, const std::vector&>(), + "name"_a, "address"_a, "ext"_a, "length"_a, "data_type"_a = "", "components"_a = std::vector() + ) + .def_property("name", &McObject::get_name, &McObject::set_name) + .def_property("address", &McObject::get_address, &McObject::set_address) + .def_property("ext", &McObject::get_ext, &McObject::set_ext) + .def_property("length", &McObject::get_length, &McObject::set_length) + .def_property("data_type", &McObject::get_data_type, &McObject::set_data_type) + .def_property_readonly("components", &McObject::get_components) + + .def("add_component", &McObject::add_component, "component"_a) + .def("__repr__", [](const McObject& self) { return to_string(self); }); + + py::class_(m, "Bin") + .def(py::init(), "size"_a) + .def_property("size", &Bin::get_size, &Bin::set_size) + .def_property("residual_capacity", &Bin::get_residual_capacity, &Bin::set_residual_capacity) + .def_property("entries", &Bin::get_entries, nullptr) + .def("append", &Bin::append) + + .def("__repr__", [](const Bin& self) { return to_string(self); }) + + .def("__eq__", [](const Bin& self, const Bin& other) { return self == other; }) + + .def("__len__", [](const Bin& self) { return std::size(self.get_entries()); }); + + py::class_(m, "DaqList") + .def( + py::init&>(), "name"_a, + "event_num"_a, "stim"_a, "enable_timestamps"_a, "measurements"_a + ) + .def("__repr__", [](const DaqList& self) { return self.to_string(); }) + .def_property("name", &DaqList::get_name, nullptr) + .def_property("event_num", &DaqList::get_event_num, nullptr) + .def_property("stim", &DaqList::get_stim, nullptr) + .def_property("enable_timestamps", &DaqList::get_enable_timestamps, nullptr) + .def_property("measurements", &DaqList::get_measurements, nullptr) + .def_property("measurements_opt", &DaqList::get_measurements_opt, &DaqList::set_measurements_opt) + .def_property("headers", &DaqList::get_headers, nullptr) + .def_property("odt_count", &DaqList::get_odt_count, nullptr) + .def_property("total_entries", &DaqList::get_total_entries, nullptr) + .def_property("total_length", &DaqList::get_total_length, nullptr); + + py::enum_(m, "TimestampType") + .value("ABSOLUTE_TS", TimestampType::ABSOLUTE_TS) + .value("RELATIVE_TS", TimestampType::RELATIVE_TS); + + py::class_(m, "Timestamp") + .def(py::init(), "ts_type"_a) + .def_property_readonly("absolute", &Timestamp::absolute) + .def_property_readonly("relative", &Timestamp::relative) + .def_property_readonly("value", &Timestamp::get_value) + .def_property_readonly("initial_value", &Timestamp::get_initial_value); + + py::class_(m, "TimestampInfo", py::dynamic_attr()) + .def(py::init()) + .def(py::init()) + + .def_property_readonly("timestamp_ns", &TimestampInfo::get_timestamp_ns) + .def_property("utc_offset", &TimestampInfo::get_utc_offset, &TimestampInfo::set_utc_offset) + .def_property("dst_offset", &TimestampInfo::get_dst_offset, &TimestampInfo::set_dst_offset) + .def_property("timezone", &TimestampInfo::get_timezone, &TimestampInfo::set_timezone); +} diff --git a/pyxcp/cpp_ext/helper.hpp b/pyxcp/cpp_ext/helper.hpp new file mode 100644 index 00000000..a8f5004e --- /dev/null +++ b/pyxcp/cpp_ext/helper.hpp @@ -0,0 +1,264 @@ + +#if !defined(__HELPER_HPP) + #define __HELPER_HPP + + #if defined(_WIN32) || defined(_WIN64) + + #else + #include + #include + #endif + + #include + #include + #include + #include + #include + #include + + #if __has_include() + #include // Needed for feature testing. + #endif + + #ifdef __has_include + #if __has_include() + #include + #endif + #if defined(__STDCPP_BFLOAT16_T__) + #define HAS_BFLOAT16 (1) + #else + #define HAS_BFLOAT16 (0) + #endif + + #if defined(__STDCPP_FLOAT16_T__) + #define HAS_FLOAT16 (1) + #else + #define HAS_FLOAT16 (0) + #endif + #else + #define HAS_FLOAT16 (0) + #define HAS_BFLOAT16 (0) + #endif + +constexpr std::endian target_byteorder() { + return std::endian::native; +} + +template +constexpr void DBG_PRINTN(Args &&...args) noexcept { + ((std::cout << std::forward(args) << " "), ...); +} + +// NOTE: C++23 has std::byteswap() +constexpr auto _bswap(std::uint64_t v) noexcept { + return ((v & UINT64_C(0x0000'0000'0000'00FF)) << 56) | ((v & UINT64_C(0x0000'0000'0000'FF00)) << 40) | + ((v & UINT64_C(0x0000'0000'00FF'0000)) << 24) | ((v & UINT64_C(0x0000'0000'FF00'0000)) << 8) | + ((v & UINT64_C(0x0000'00FF'0000'0000)) >> 8) | ((v & UINT64_C(0x0000'FF00'0000'0000)) >> 24) | + ((v & UINT64_C(0x00FF'0000'0000'0000)) >> 40) | ((v & UINT64_C(0xFF00'0000'0000'0000)) >> 56); +} + +constexpr auto _bswap(std::uint32_t v) noexcept { + return ((v & UINT32_C(0x0000'00FF)) << 24) | ((v & UINT32_C(0x0000'FF00)) << 8) | ((v & UINT32_C(0x00FF'0000)) >> 8) | + ((v & UINT32_C(0xFF00'0000)) >> 24); +} + +constexpr auto _bswap(std::uint16_t v) noexcept { + return ((v & UINT16_C(0x00FF)) << 8) | ((v & UINT16_C(0xFF00)) >> 8); +} + +template +inline std::string to_binary(const T &value) { + std::string result; + + auto ptr = reinterpret_cast(&value); + for (std::size_t idx = 0; idx < sizeof(T); ++idx) { + auto ch = ptr[idx]; + result.push_back(ch); + } + return result; +} + +template<> +inline std::string to_binary(const std::string &value) { + std::string result; + + auto ptr = reinterpret_cast(value.c_str()); + const std::size_t length = std::size(value); + + // We are using Pascal strings as serialization format. + auto len_bin = to_binary(length); + std::copy(len_bin.begin(), len_bin.end(), std::back_inserter(result)); + for (std::size_t idx = 0; idx < length; ++idx) { + auto ch = ptr[idx]; + result.push_back(ch); + } + return result; +} + +inline auto bool_to_string(bool value) { + return (value == true) ? "True" : "False"; +} + +inline auto byte_order_to_string(int value) { + switch (value) { + case 0: + return "INTEL"; + case 1: + return "MOTOROLA"; + default: + return ""; + } + return ""; +} + +template +static std::map reverse_map(const std::map &m) { + std::map result; + for (const auto &[k, v] : m) { + result[v] = k; + } + return result; +} + +enum class TimestampType : std::uint8_t { + ABSOLUTE_TS, + RELATIVE_TS +}; + +class TimestampInfo { + public: + + TimestampInfo(const TimestampInfo &) = default; + TimestampInfo(TimestampInfo &&) = default; + TimestampInfo &operator=(const TimestampInfo &) = default; + TimestampInfo &operator=(TimestampInfo &&) = default; + virtual ~TimestampInfo() {} + + TimestampInfo() : m_timestamp_ns(0), m_timezone{}, m_utc_offset(0), m_dst_offset(0) { + } + + TimestampInfo(std::uint64_t timestamp_ns, const std::string &timezone, std::int16_t utc_offset, std::int16_t dst_offset) : + m_timestamp_ns(timestamp_ns), m_timezone(timezone), m_utc_offset(utc_offset), m_dst_offset(dst_offset) { + } + + explicit TimestampInfo(std::uint64_t timestamp_ns) : m_timestamp_ns(timestamp_ns) { + #if defined(_WIN32) || defined(_WIN64) + m_timezone = std::chrono::current_zone()->name(); + #else + tzset(); + m_timezone = tzname[0]; + + #endif // _WIN32 || _WIN64 + } + + std::string get_timezone() const noexcept { + return m_timezone; + } + + void set_timezone(const std::string &value) noexcept { + m_timezone = value; + } + + std::uint64_t get_timestamp_ns() const noexcept { + return m_timestamp_ns; + } + + void set_utc_offset(std::int16_t value) noexcept { + m_utc_offset = value; + } + + std::int16_t get_utc_offset() const noexcept { + return m_utc_offset; + } + + void set_dst_offset(std::int16_t value) noexcept { + m_dst_offset = value; + } + + std::int16_t get_dst_offset() const noexcept { + return m_dst_offset; + } + + std::string to_string() const noexcept { + std::stringstream ss; + ss << "TimestamInfo(\n"; + ss << "\ttimestamp_ns=" << m_timestamp_ns << ",\n"; + ss << "\ttimezone=\"" << m_timezone << "\",\n"; + ss << "\tutc_offset=" << m_utc_offset << ",\n"; + ss << "\tdst_offset=" << m_dst_offset << "\n"; + ss << ");"; + return ss.str(); + } + + virtual void dummy() const noexcept {}; + + private: + + std::uint64_t m_timestamp_ns; + std::string m_timezone{}; + std::int16_t m_utc_offset{ 0 }; + std::int16_t m_dst_offset{ 0 }; +}; + +class Timestamp { + public: + + explicit Timestamp(TimestampType ts_type) : m_type(ts_type) { + m_initial = absolute(); + } + + Timestamp(const Timestamp &) = default; + Timestamp(Timestamp &&) = default; + + std::uint64_t get_value() const noexcept { + if (m_type == TimestampType::ABSOLUTE_TS) { + return absolute(); + } else if (m_type == TimestampType::RELATIVE_TS) { + return relative(); + } + } + + std::uint64_t get_initial_value() const noexcept { + return m_initial; + } + + std::uint64_t absolute() const noexcept { + std::uint64_t current; + + #if defined(_WIN32) || defined(_WIN64) + current = std::chrono::duration_cast(m_clk.now().time_since_epoch()).count(); + #else + // On MacOS `clock_gettime_nsec_np` could be used. + timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + current = static_cast(ts.tv_sec) * 1'000'000'000 + ts.tv_nsec; + #endif // _WIN32 || _WIN64 + return current; + } + + std::uint64_t relative() const noexcept { + return absolute() - m_initial; + } + + private: + + TimestampType m_type; + #if defined(_WIN32) || defined(_WIN64) + std::chrono::utc_clock m_clk; + #else + + #endif // _WIN32 || _WIN64 + std::uint64_t m_initial; +}; + +#if 0 +inline void sleep_ms(std::uint64_t milliseconds) { + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); +} + +inline void sleep_ns(std::uint64_t nanoseconds) { + std::this_thread::sleep_for(std::chrono::nanoseconds(nanoseconds)); +} +#endif + +#endif // __HELPER_HPP diff --git a/pyxcp/cpp_ext/mcobject.hpp b/pyxcp/cpp_ext/mcobject.hpp new file mode 100644 index 00000000..ab48ef41 --- /dev/null +++ b/pyxcp/cpp_ext/mcobject.hpp @@ -0,0 +1,241 @@ + +#if !defined(__MC_OBJECT_HPP) + #define __MC_OBJECT_HPP + + #include + #include + #include + #include + #include + #include + + #include "helper.hpp" + +const std::map> TYPE_MAP = { + { "U8", { 0, 1 } }, + { "I8", { 1, 1 } }, + { "U16", { 2, 2 } }, + { "I16", { 3, 2 } }, + { "U32", { 4, 4 } }, + { "I32", { 5, 4 } }, + { "U64", { 6, 8 } }, + { "I64", { 7, 8 } }, + { "F32", { 8, 4 } }, + { "F64", { 9, 8 } }, + #if HAS_FLOAT16 + { "F16", { 10, 2 } }, + #endif + #if HAS_BFLOAT16 + { "BF16", { 11, 2 } }, + #endif +}; + +enum class TypeCode : std::uint8_t { + U8, + I8, + U16, + I16, + U32, + I32, + U64, + I64, + F32, + F64, + F16, + BF16, +}; + +const std::map TYPE_TO_TYPE_CODE_MAP = { + { "U8", TypeCode::U8 }, + { "I8", TypeCode::I8 }, + { "U16", TypeCode::U16 }, + { "I16", TypeCode::I16 }, + { "U32", TypeCode::U32 }, + { "I32", TypeCode::I32 }, + { "U64", TypeCode::U64 }, + { "I64", TypeCode::I64 }, + { "F32", TypeCode::F32 }, + { "F64", TypeCode::F64 }, + #if HAS_FLOAT16 + { "F16", TypeCode::F16 }, + #endif + #if HAS_BFLOAT16 + { "BF16", TypeCode::BF16 }, + #endif +}; + +const std::map TYPE_MAP_REV = { + { 0, "U8" }, + { 1, "I8" }, + { 2, "U16" }, + { 3, "I16" }, + { 4, "U32" }, + { 5, "I32" }, + { 6, "U64" }, + { 7, "I64" }, + { 8, "F32" }, + { 9, "F64" }, + #if HAS_FLOAT16 + { 10, "F16" }, + #endif + #if HAS_BFLOAT16 + { 11, "BF16" }, + #endif +}; + +inline std::vector get_data_types() { + std::vector result; + + for (const auto& [k, v] : TYPE_MAP) { + result.emplace_back(k); + } + + return result; +} + +class McObject { + public: + + explicit McObject( + std::string_view name, std::uint32_t address, std::uint8_t ext, std::uint16_t length, const std::string& data_type, + const std::vector& components = std::vector() + ) : + m_name(name), + m_address(address), + m_ext(ext), + m_length(length), + m_data_type(data_type), + m_type_index(-1), + m_components(components) { + if (data_type != "") { + std::string dt_toupper; + + dt_toupper.resize(data_type.size()); + + std::transform(data_type.begin(), data_type.end(), dt_toupper.begin(), [](unsigned char c) -> unsigned char { + return std::toupper(c); + }); + + if (!TYPE_MAP.contains(dt_toupper)) { + throw std::runtime_error("Invalid data type: " + data_type); + } + + const auto [ti, len] = TYPE_MAP.at(dt_toupper); + m_type_index = ti; + m_length = len; + } + } + + McObject(const McObject& obj) = default; + McObject(McObject&& obj) = default; + McObject& operator=(const McObject&) = default; + McObject& operator=(McObject&&) = default; + + const std::string& get_name() const { + return m_name; + } + + void set_name(std::string_view name) { + m_name = name; + } + + std::uint32_t get_address() const { + return m_address; + } + + void set_address(std::uint32_t address) { + m_address = address; + } + + std::uint8_t get_ext() const { + return m_ext; + } + + void set_ext(std::uint8_t ext) { + m_ext = ext; + } + + const std::string& get_data_type() const { + return m_data_type; + } + + void set_data_type(const std::string& value) { + m_data_type = value; + } + + std::uint16_t get_length() const { + return m_length; + } + + void set_length(std::uint16_t length) { + m_length = length; + } + + std::int32_t get_type_index() const { + return m_type_index; + } + + const std::vector& get_components() const { + return m_components; + } + + void add_component(const McObject& obj) { + m_components.emplace_back(obj); + } + + bool operator==(const McObject& other) const { + return (m_name == other.m_name) && (m_address == other.m_address) && (m_ext == other.m_ext) && + (m_length == other.m_length) && (m_data_type == other.m_data_type) && + (std::equal(m_components.begin(), m_components.end(), other.m_components.begin(), other.m_components.end())); + } + + std::string dumps() const { + std::stringstream ss; + + ss << to_binary(m_name); + ss << to_binary(m_address); + ss << to_binary(m_ext); + ss << to_binary(m_length); + ss << to_binary(m_data_type); + ss << to_binary(m_type_index); + + std::size_t ccount = m_components.size(); + ss << to_binary(ccount); + for (const auto& obj : m_components) { + ss << obj.dumps(); + } + return ss.str(); + } + + private: + + std::string m_name; + std::uint32_t m_address; + std::uint8_t m_ext; + std::uint16_t m_length; + std::string m_data_type; + std::int16_t m_type_index; + std::vector m_components{}; +}; + +std::string mc_components_to_string(const std::vector& components); + +std::string to_string(const McObject& obj) { + std::stringstream ss; + + ss << "McObject(name='" << obj.get_name() << "', address=" << obj.get_address() + << ", ext=" << static_cast(obj.get_ext()) << ", data_type='" << obj.get_data_type() + << "', length=" << obj.get_length() << ", components=[" << mc_components_to_string(obj.get_components()) << "])"; + return ss.str(); +} + +std::string mc_components_to_string(const std::vector& components) { + std::stringstream ss; + + for (const auto& obj : components) { + ss << to_string(obj) << ",\n "; + } + return ss.str(); +} + +#endif // __MC_OBJECT_HPP diff --git a/pyxcp/cpp_ext/tsqueue.hpp b/pyxcp/cpp_ext/tsqueue.hpp new file mode 100644 index 00000000..d53440fd --- /dev/null +++ b/pyxcp/cpp_ext/tsqueue.hpp @@ -0,0 +1,46 @@ + +#ifndef __TSQUEUE_HPP +#define __TSQUEUE_HPP + +#include +#include +#include + +template +class TsQueue { + public: + + TsQueue() = default; + + TsQueue(const TsQueue& other) noexcept { + std::scoped_lock lock(other.m_mtx); + m_queue = other.m_queue; + } + + void put(T value) noexcept { + std::scoped_lock lock(m_mtx); + m_queue.push(value); + m_cond.notify_one(); + } + + std::shared_ptr get() noexcept { + std::unique_lock lock(m_mtx); + m_cond.wait(lock, [this] { return !m_queue.empty(); }); + std::shared_ptr result(std::make_shared(m_queue.front())); + m_queue.pop(); + return result; + } + + bool empty() const noexcept { + std::scoped_lock lock(m_mtx); + return m_queue.empty(); + } + + private: + + mutable std::mutex m_mtx; + std::queue m_queue; + std::condition_variable m_cond; +}; + +#endif // __TSQUEUE_HPP diff --git a/pyxcp/cxx/asynchiofactory.hpp b/pyxcp/cxx/asynchiofactory.hpp deleted file mode 100644 index 9e94816b..00000000 --- a/pyxcp/cxx/asynchiofactory.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#if !defined(__ASYNCHIOFACTORY_HPP) -#define __ASYNCHIOFACTORY_HPP - -#include - -#include "iasyncioservice.hpp" - -#if defined(_WIN32) - #include "iocp.hpp" -#else - #include "epoll.hpp" -#endif - - -inline std::unique_ptr createAsyncIoService() -{ -#if defined(_WIN32) - return std::make_unique(); -#else - return std::make_unique(); -#endif -} - -#endif // __ASYNCHIOFACTORY_HPP diff --git a/pyxcp/cxx/blocking_client.cpp b/pyxcp/cxx/blocking_client.cpp deleted file mode 100644 index f4e1a7df..00000000 --- a/pyxcp/cxx/blocking_client.cpp +++ /dev/null @@ -1,44 +0,0 @@ - - -#include "blocking_socket.hpp" - -#include -#include - -using std::cout; -using std::endl; -using std::setw; -using std::internal; -using std::fixed; -using std::setfill; - -using namespace std; - -std::array hellomsg {"hello world!!!"}; - -int main(void) -{ - CAddress address; - auto sock = Socket {PF_INET, SOCK_STREAM, IPPROTO_TCP}; - - - //sock.getaddrinfo(PF_INET, SOCK_STREAM, IPPROTO_TCP, "localhost", 50007, address, 0); - sock.getaddrinfo(PF_INET, SOCK_STREAM, IPPROTO_TCP, "192.168.168.100", 50007, address, 0); - sock.connect(address); - - //auto opt_val = sock.get_option(SO_REUSEADDR, SOL_SOCKET); - auto opt_val = sock.get_option(SO_RCVBUF, SOL_SOCKET); - printf("before: %u\n", opt_val); - sock.set_option(SO_SNDBUF, SOL_SOCKET, 64 * 1024); - sock.set_option(SO_RCVBUF, SOL_SOCKET, 64 * 1024); - opt_val = sock.get_option(SO_RCVBUF, SOL_SOCKET); - printf("after: %u\n", opt_val); - - - sock.startReceiverThread(); - //sock.getaddrinfo(PF_INET, SOCK_STREAM, IPPROTO_TCP, "google.de", 80, address, 0); - //printf("addr: %x", address.address); -// sock.shutdownReceiverThread(); - sock.write(hellomsg); - Sleep(2000); -} diff --git a/pyxcp/cxx/blocking_socket.cpp b/pyxcp/cxx/blocking_socket.cpp deleted file mode 100644 index 99d159a1..00000000 --- a/pyxcp/cxx/blocking_socket.cpp +++ /dev/null @@ -1,43 +0,0 @@ - -#include "blocking_socket.hpp" - - -void * blockingReceiverThread(void * param) { - Socket * const socket = reinterpret_cast(param); - std::array buffer; - int nbytes; - - printf("Starting thread... [%d]\n", socket->getSocket()); - - nbytes = socket->read(buffer, 128); - - printf("[%d] bytes received.\n", nbytes); - if (nbytes) { - printf("data: [%s]\n", buffer.data()); - } - - printf("Exiting thread...\n"); - - return NULL; -} - -#include "blocking_socket.hpp" - - -[[noreturn]] void blockingReceiverThread(Socket * socket) { - //Socket * const socket = reinterpret_cast(param); - std::array buffer; - int nbytes; - - printf("Starting thread... [%d]\n", socket->getSocket()); - - while (true) { - nbytes = socket->read(buffer, 128); - - printf("[%d] bytes received.\n", nbytes); - if (nbytes) { - printf("data: [%s]\n", buffer.data()); - } - } - printf("Exiting thread...\n"); -} diff --git a/pyxcp/cxx/blocking_socket.hpp b/pyxcp/cxx/blocking_socket.hpp deleted file mode 100644 index cefa3511..00000000 --- a/pyxcp/cxx/blocking_socket.hpp +++ /dev/null @@ -1,558 +0,0 @@ - -#include - -#include "utils.hpp" - -#if !defined(__BLOCKING_SOCKET_HPP) -#define __BLOCKING_SOCKET_HPP - -#if defined(_WIN32) - #include - #include - #include - #include - #include -#elif defined(__unix__) - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - - #define INVALID_SOCKET (-1) - #define SOCKET_ERROR (-1) - #define ADDRINFO addrinfo - #define SOCKADDR struct sockaddr - #define SOCKADDR_STORAGE sockaddr_storage - - typedef int SOCKET; -#endif - -#include - -#define ADDR_LEN sizeof(SOCKADDR_STORAGE) - -template using buffer_t = std::array; - -void * blockingReceiverThread(void * param); - -struct CAddress { - int length; - struct sockaddr address; -}; - -class Socket { - public: - - explicit Socket(int family = PF_INET, int socktype = SOCK_STREAM, int protocol = IPPROTO_TCP) : m_family(family), m_socktype(socktype), - m_protocol(protocol), m_connected(false), m_addr(nullptr), m_thread(0) { - m_socket = ::socket(m_family, m_socktype, m_protocol); - m_connected_socket = 0; - if (m_socket == INVALID_SOCKET) { - SocketErrorExit("Socket::Socket()"); - } - blocking(true); - ZeroOut(&m_peerAddress, sizeof(SOCKADDR_STORAGE)); - } - - ~Socket() { -#if defined(__unix__) - ::close(m_socket); -#elif #defined(_WIN32) - ::closesocket(m_socket); -#endif - } - - void blocking(bool enabled) { - int flags = ::fcntl(m_socket, F_GETFL); - - if (flags == -1) { - SocketErrorExit("blocking::fcntl()"); - } - flags = enabled ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); - if (::fcntl(m_socket, F_SETFL, flags) == -1) { - SocketErrorExit("blocking::fcntl()"); - } - } - - void set_option(int optname, int level, int value) { - - if (::setsockopt(m_socket, level, optname, (const char*) &value, sizeof(value)) == SOCKET_ERROR) { - SocketErrorExit("Socket::set_option()"); - } - } - - int get_option(int optname, int level) { - int value; - socklen_t len; - - len = sizeof(value); - if (::getsockopt(m_socket, level, optname, (char*) &value, &len) == SOCKET_ERROR) { - SocketErrorExit("Socket::get_option()"); - } - return value; - } - - bool get_reuse_addr() { - return static_cast(get_option(SO_REUSEADDR, SOL_SOCKET)); - } - - void set_reuse_addr(bool on) { - set_option(SO_REUSEADDR, SOL_SOCKET, static_cast(on)); - } - - int get_send_buffer_size() { - return get_option(SO_SNDBUF, SOL_SOCKET); - } - - void set_send_buffer_size(int size) { - set_option(SO_SNDBUF, SOL_SOCKET, size); - } - - int get_rcv_buffer_size() { - return get_option(SO_RCVBUF, SOL_SOCKET); - } - - void set_rcv_buffer_size(int size) { - set_option(SO_RCVBUF, SOL_SOCKET, size); - } - - bool getaddrinfo(int family, int socktype, int protocol, const char * hostname, int port, CAddress & address, int flags = AI_PASSIVE) { - int err; - ADDRINFO hints; - ADDRINFO * t_addr; - char port_str[16] = {0}; - - ZeroOut(&hints, sizeof(hints)); - hints.ai_family = family; - hints.ai_socktype = socktype; - hints.ai_protocol = protocol; - hints.ai_flags = flags; - ::sprintf(port_str, "%d", port); - err = ::getaddrinfo(hostname, port_str, &hints, &t_addr); - if (err != 0) { - printf("%s\n", gai_strerror(err)); - ::freeaddrinfo(t_addr); - SocketErrorExit("getaddrinfo()"); - return false; - } - address.length = t_addr->ai_addrlen; - CopyMemory(&address.address, t_addr->ai_addr, sizeof(SOCKADDR)); - ::freeaddrinfo(t_addr); - return true; - } - - void connect(CAddress & address) { - if (::connect(m_socket, &address.address, address.length) == SOCKET_ERROR) { - SocketErrorExit("Socket::connect()"); - } - m_connected_socket = m_socket; - printf("Sock-conn: %d\n", m_connected_socket); - } - - void bind(CAddress & address) { - if (::bind(m_socket, &address.address, address.length) == SOCKET_ERROR) { - SocketErrorExit("Socket::bind()"); - } - } - - void listen(int backlog = 1) { - if (::listen(m_socket, backlog) == SOCKET_ERROR) { - SocketErrorExit("Socket::listen()"); - } - } - - void accept(CAddress & peerAddress) { - - peerAddress.length = sizeof peerAddress.address; - m_connected_socket = ::accept(m_socket, (SOCKADDR *)&peerAddress.address, (socklen_t*)&peerAddress.length); - - if (m_connected_socket == INVALID_SOCKET) { - SocketErrorExit("Socket::accept()"); - } - } - - void startReceiverThread() { - int res = 0; - - res = ::pthread_create(&m_thread, NULL, blockingReceiverThread, this); - if (res == -1) { - OsErrorExit("startReceiverThread::pthread_create"); - } - } - - void shutdownReceiverThread() { - int res = 0; - - res = ::pthread_kill(m_thread, SIGINT); - if (res == -1) { - OsErrorExit("shutdownReceiverThread::pthread_kill"); - } - res = pthread_join(m_thread, NULL); - if (res == -1) { - OsErrorExit("shutdownReceiverThread::pthread_join"); - } - } - - template - int read(std::array& arr, size_t len) { - int nbytes; - - nbytes = ::recv(m_connected_socket, (char*)arr.data(), len, 0); - if (nbytes == -1) { - OsErrorExit("read::recv"); - } - - return nbytes; - } - - template - void write(std::array& arr) { - if (m_socktype == SOCK_DGRAM) { -#if 0 - if (sendto(m_socket, (char const *)arr.data(), arr.size(), 0, - (SOCKADDR * )(SOCKADDR_STORAGE const *)&XcpTl_Connection.connectionAddress, ADDR_LEN) == SOCKET_ERROR) { - SocketErrorExit("send::sendto()"); - } -#endif - } else if (m_socktype == SOCK_STREAM) { - if (::send(m_connected_socket, (char const *)arr.data(), arr.size(), 0) == SOCKET_ERROR) { - SocketErrorExit("send::send()"); -#if defined(_WIN32) - closesocket(m_connected_socket); -#elif defined(__unix__) - close(m_connected_socket); -#endif - } - } - } - - SOCKET getSocket() const { - return m_socket; - } - -protected: - - -private: - int m_family; - int m_socktype; - int m_protocol; - bool m_connected; - addrinfo * m_addr; - pthread_t m_thread; - //TimeoutTimer m_timeout {150}; - - SOCKET m_socket; - SOCKET m_connected_socket; - //CAddress ourAddress; - SOCKADDR_STORAGE m_peerAddress; -}; - - -#endif // __BLOCKING_SOCKET_HPP - -/* - * pyXCP - * - * (C) 2021 by Christoph Schueler - * - * All Rights Reserved - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * - * s. FLOSS-EXCEPTION.txt - */ - - -#include -#include -#include - -#include "utils.hpp" - -#if !defined(__BLOCKING_SOCKET_HPP) -#define __BLOCKING_SOCKET_HPP - -#if defined(_WIN32) - #include - #include - #include - #include - #include -#elif defined(__unix__) - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - - #define INVALID_SOCKET (-1) - #define SOCKET_ERROR (-1) - #define ADDRINFO addrinfo - #define SOCKADDR struct sockaddr - #define SOCKADDR_STORAGE sockaddr_storage - - typedef int SOCKET; -#endif - -#include - -#define ADDR_LEN sizeof(SOCKADDR_STORAGE) - -template using buffer_t = std::array; - -class Socket; - -[[noreturn]] void blockingReceiverThread(Socket * socket); - -struct CAddress { - int length; - struct sockaddr address; -}; - -class Socket { -public: - - using listen_thread_t = std::function; - - explicit Socket(int family = PF_INET, int socktype = SOCK_STREAM, int protocol = IPPROTO_TCP) : m_family(family), m_socktype(socktype), - m_protocol(protocol), m_connected(false), m_addr(nullptr) { - m_socket = ::socket(m_family, m_socktype, m_protocol); - m_connected_socket = 0; - if (m_socket == INVALID_SOCKET) { - SocketErrorExit("Socket::Socket()"); - } - blocking(true); - ZeroOut(&m_peerAddress, sizeof(SOCKADDR_STORAGE)); - } - - ~Socket() { -#if defined(__unix__) - ::close(m_socket); -#elif #defined(_WIN32) - ::closesocket(m_socket); -#endif - } - - void blocking(bool enabled) { - int flags = ::fcntl(m_socket, F_GETFL); - - if (flags == -1) { - SocketErrorExit("blocking::fcntl()"); - } - flags = enabled ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); - if (::fcntl(m_socket, F_SETFL, flags) == -1) { - SocketErrorExit("blocking::fcntl()"); - } - } - - void set_option(int optname, int level, int value) { - - if (::setsockopt(m_socket, level, optname, (const char*) &value, sizeof(value)) == SOCKET_ERROR) { - SocketErrorExit("Socket::set_option()"); - } - } - - int get_option(int optname, int level) { - int value; - socklen_t len; - - len = sizeof(value); - if (::getsockopt(m_socket, level, optname, (char*) &value, &len) == SOCKET_ERROR) { - SocketErrorExit("Socket::get_option()"); - } - return value; - } - - bool get_reuse_addr() { - return static_cast(get_option(SO_REUSEADDR, SOL_SOCKET)); - } - - void set_reuse_addr(bool on) { - set_option(SO_REUSEADDR, SOL_SOCKET, static_cast(on)); - } - - int get_send_buffer_size() { - return get_option(SO_SNDBUF, SOL_SOCKET); - } - - void set_send_buffer_size(int size) { - set_option(SO_SNDBUF, SOL_SOCKET, size); - } - - int get_rcv_buffer_size() { - return get_option(SO_RCVBUF, SOL_SOCKET); - } - - void set_rcv_buffer_size(int size) { - set_option(SO_RCVBUF, SOL_SOCKET, size); - } - - bool getaddrinfo(int family, int socktype, int protocol, const char * hostname, int port, CAddress & address, int flags = AI_PASSIVE) { - int err; - ADDRINFO hints; - ADDRINFO * t_addr; - char port_str[16] = {0}; - - ZeroOut(&hints, sizeof(hints)); - hints.ai_family = family; - hints.ai_socktype = socktype; - hints.ai_protocol = protocol; - hints.ai_flags = flags; - ::sprintf(port_str, "%d", port); - err = ::getaddrinfo(hostname, port_str, &hints, &t_addr); - if (err != 0) { - printf("%s\n", gai_strerror(err)); - ::freeaddrinfo(t_addr); - SocketErrorExit("getaddrinfo()"); - return false; - } - address.length = t_addr->ai_addrlen; - CopyMemory(&address.address, t_addr->ai_addr, sizeof(SOCKADDR)); - ::freeaddrinfo(t_addr); - return true; - } - - void connect(CAddress & address) { - if (::connect(m_socket, &address.address, address.length) == SOCKET_ERROR) { - SocketErrorExit("Socket::connect()"); - } - m_connected_socket = m_socket; - printf("Sock-conn: %d\n", m_connected_socket); - } - - void bind(CAddress & address) { - if (::bind(m_socket, &address.address, address.length) == SOCKET_ERROR) { - SocketErrorExit("Socket::bind()"); - } - } - - void listen(int backlog = 1) { - if (::listen(m_socket, backlog) == SOCKET_ERROR) { - SocketErrorExit("Socket::listen()"); - } - } - - void accept(CAddress & peerAddress) { - - peerAddress.length = sizeof peerAddress.address; - m_connected_socket = ::accept(m_socket, (SOCKADDR *)&peerAddress.address, (socklen_t*)&peerAddress.length); - - if (m_connected_socket == INVALID_SOCKET) { - SocketErrorExit("Socket::accept()"); - } - } - - void startReceiverThread() { - int res = 0; - - m_thread = new std::thread(blockingReceiverThread, this); - } - - void shutdownReceiverThread() { - int res = 0; - - //res = ::pthread_kill(m_thread->native_handle(), SIGINT); - res = ::pthread_cancel(m_thread->native_handle()); - if (res == -1) { - OsErrorExit("shutdownReceiverThread::pthread_kill"); - } - m_thread->join(); - delete m_thread; - } - - template - int read(std::array& arr, size_t len) { - int nbytes; - - nbytes = ::recv(m_connected_socket, (char*)arr.data(), len, 0); - if (nbytes == -1) { - OsErrorExit("read::recv"); - } - - return nbytes; - } - - template - void write(std::array& arr) { - if (m_socktype == SOCK_DGRAM) { -#if 0 - if (sendto(m_socket, (char const *)arr.data(), arr.size(), 0, - (SOCKADDR * )(SOCKADDR_STORAGE const *)&XcpTl_Connection.connectionAddress, ADDR_LEN) == SOCKET_ERROR) { - SocketErrorExit("send::sendto()"); - } -#endif - } else if (m_socktype == SOCK_STREAM) { - if (::send(m_connected_socket, (char const *)arr.data(), arr.size(), 0) == SOCKET_ERROR) { - SocketErrorExit("send::send()"); -#if defined(_WIN32) - closesocket(m_connected_socket); -#elif defined(__unix__) - close(m_connected_socket); -#endif - } - } - } - - SOCKET getSocket() const { - return m_socket; - } - -protected: - - -private: - int m_family; - int m_socktype; - int m_protocol; - bool m_connected; - addrinfo * m_addr; - std::thread * m_thread = nullptr; - //TimeoutTimer m_timeout {150}; - - SOCKET m_socket; - SOCKET m_connected_socket; - //CAddress ourAddress; - SOCKADDR_STORAGE m_peerAddress; -}; - - -#endif // __BLOCKING_SOCKET_HPP diff --git a/pyxcp/cxx/concurrent_queue.hpp b/pyxcp/cxx/concurrent_queue.hpp deleted file mode 100644 index 6fb087a8..00000000 --- a/pyxcp/cxx/concurrent_queue.hpp +++ /dev/null @@ -1,60 +0,0 @@ - -#if !defined(__CONCURRENT_QUEUE) -#define __CONCURRENT_QUEUE - -#include -#include -#include -#include -#include - -template class ConcurrentQueue { -public: - - explicit ConcurrentQueue<_Ty>() = default; - - ConcurrentQueue<_Ty>(const ConcurrentQueue<_Ty>& other) noexcept : - m_elements(other.m_elements), m_mtx(), m_cond() - {} - - bool empty() const { - std::unique_lock lock(m_mtx); - - return m_elements.empty(); - } - - void enqueue(const _Ty& item) { - std::lock_guard lock(m_mtx); - bool const empty = m_elements.empty(); - - m_elements.emplace_back(std::move(item)); - m_mtx.unlock(); - - if (empty) { - m_cond.notify_one(); - } - - } - - bool dequeue(_Ty& item, uint32_t timeout = 50) { - std::unique_lock lock(m_mtx); - - while (m_elements.empty()) { - if (m_cond.wait_for(lock, std::chrono::milliseconds(timeout)) == std::cv_status::timeout) { - return false; // Wait timed out. - } - } - - item = std::move(m_elements.front()); - m_elements.pop_front(); - return true; - } - -private: - //std::queue<_Ty> m_elements {}; - std::deque<_Ty> m_elements {}; - mutable std::mutex m_mtx {}; - std::condition_variable m_cond {}; -}; - -#endif // __CONCURRENT_QUEUE diff --git a/pyxcp/cxx/eth.hpp b/pyxcp/cxx/eth.hpp deleted file mode 100644 index 6f9b0eae..00000000 --- a/pyxcp/cxx/eth.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#if !defined(__ETH_HPP) -#define __ETH_HPP - -#if defined(_WIN32) - #include -#else - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include -#endif - -#include "config.h" -#include "utils.hpp" - -#include - -#if defined(_WIN32) - -struct Eth { - - Eth() { - WSAData data; - if (::WSAStartup(MAKEWORD(2, 2), &data) != 0) { - OsErrorExit("Eth::Eth() -- WSAStartup"); - } - } - - ~Eth() { - ::WSACleanup(); - } -}; - -#else - -struct Eth { - - Eth() = default; - ~Eth() = default; -}; - -#endif - -#endif // __ETH_HPP diff --git a/pyxcp/cxx/exceptions.hpp b/pyxcp/cxx/exceptions.hpp deleted file mode 100644 index 934d906f..00000000 --- a/pyxcp/cxx/exceptions.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#if !defined(__EXCEPTIONS_HPP) -#define __EXCEPTIONS_HPP - -#include - - -struct OSException : public std::exception { - const char * what () const throw() { - return "OS Exception"; - } -}; - -struct TimeoutException : public std::exception { - const char * what () const throw() { - return "Timeout Exception"; - } -}; - -struct CapacityExhaustedException : public std::exception { - const char * what () const throw() { - return "Capacity Exhausted Exception"; - } -}; - -struct InvalidObjectException : public std::exception { - const char * what () const throw() { - return "Invalid Object Exception"; - } -}; -#endif // __EXCEPTIONS_HPP diff --git a/pyxcp/cxx/iasyncioservice.hpp b/pyxcp/cxx/iasyncioservice.hpp deleted file mode 100644 index 396d9892..00000000 --- a/pyxcp/cxx/iasyncioservice.hpp +++ /dev/null @@ -1,31 +0,0 @@ -/* - * - * Interface for asynchronous I/O services (IOCP, epoll, kqueue...). - * - * - */ - -#if !defined(__IASYNCHIOSERVICE_HPP) -#define __IASYNCHIOSERVICE_HPP - -#include -#include - -#include "socket.hpp" - -enum class MessageCode : uint64_t { - QUIT, - TIMEOUT -}; - -class IAsyncIoService { -public: - virtual ~IAsyncIoService() = default; - virtual void registerSocket(Socket& socket) = 0; - virtual void postUserMessage(MessageCode messageCode, void * data = nullptr) const = 0; - virtual void postQuitMessage() const = 0; - virtual HANDLE getHandle() const = 0; - -}; - -#endif // __IASYNCHIOSERVICE_HPP diff --git a/pyxcp/cxx/iresource.hpp b/pyxcp/cxx/iresource.hpp deleted file mode 100644 index 273ae137..00000000 --- a/pyxcp/cxx/iresource.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#if !defined(__IRESOURCE_HPP) -#define __IRESOURCE_HPP - -/* - * - * Interface for pool-able resources. - * - */ -class IResource { -public: - - virtual ~IResource() = default; - virtual void reset() = 0; - -}; - -#endif // __IRESOURCE_HPP diff --git a/pyxcp/cxx/isocket.hpp b/pyxcp/cxx/isocket.hpp deleted file mode 100644 index 8d79b00f..00000000 --- a/pyxcp/cxx/isocket.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#if !defined(__ISOCKET_HPP) -#define __ISOCKET_HPP - -struct CAddress { - int length; - struct sockaddr address; -}; - - -class ISocket { -public: - ~ISocket() = default; - - virtual void connect(CAddress & address) = 0; - virtual void bind(CAddress & address) = 0; - virtual void listen(int backlog = 10) = 0; - virtual void accept(CAddress & peerAddress) = 0; - virtual void option(int optname, int level, int * value) = 0; - virtual bool getaddrinfo(int family, int socktype, int protocol, const char * hostname, int port, CAddress & address, int flags = AI_PASSIVE) = 0; -}; - -#endif // __ISOCKET_HPP diff --git a/pyxcp/cxx/linux/epoll.cpp b/pyxcp/cxx/linux/epoll.cpp deleted file mode 100644 index 31888bfc..00000000 --- a/pyxcp/cxx/linux/epoll.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "epoll.hpp" - -constexpr size_t MAX_EVENTS = 8; - -static struct epoll_event events[MAX_EVENTS]; - -void * WorkerThread(void * param) -{ - Epoll const * const epoll = reinterpret_cast(param); - Socket const * socket; - TimeoutTimer const * timeout_timer; - EventRecord * event_record; - int nfds; - int idx; - char buffer[128]; - int evt_mask; - uint64_t timeout_value; - - printf("Entering worker thread...\n"); - - for (;;) { - nfds = epoll_wait(epoll->getHandle() ,events, MAX_EVENTS, 500); - for (idx = 0; idx < nfds; ++idx) { - evt_mask = events[idx].events; - event_record = reinterpret_cast(events[idx].data.ptr); - printf("Evt#%d: %x %d\n", idx, evt_mask, event_record->event_type); - if (event_record->event_type == EventType::SOCKET) { - socket = event_record->obj.socket; - printf("Socket-Handle: %d\n", socket->getHandle()); - if (evt_mask & EPOLLIN) { - read(socket->getHandle(), buffer, 128); - printf("R: %s\n", buffer); - } else if (evt_mask & EPOLLHUP) { - printf("HANG-UP\n"); - //SocketErrorExit("HANG-UP"); - } else if (evt_mask & EPOLLERR) { - SocketErrorExit("WorkerThread::epoll_wait()"); - } - } else if (event_record->event_type == EventType::TIMEOUT) { - timeout_timer = event_record->obj.timeout_timer; - printf("Timeout-Handle: %d\n", timeout_timer->getHandle()); - read(timeout_timer->getHandle(), &timeout_value, sizeof(uint64_t)); - printf("Timeout\n"); - } else { - printf("Invalid event type.\n"); - } - } - } - - return nullptr; -} diff --git a/pyxcp/cxx/linux/epoll.hpp b/pyxcp/cxx/linux/epoll.hpp deleted file mode 100644 index 3fbb242d..00000000 --- a/pyxcp/cxx/linux/epoll.hpp +++ /dev/null @@ -1,87 +0,0 @@ -#if !defined(__EPOLL_HPP) -#define __EPOLL_HPP - -#include -#include - -#include -#include -#include - -#include "socket.hpp" -#include "iasyncioservice.hpp" - -void * WorkerThread(void * param); - -enum class EventType { - SOCKET, - TIMEOUT -}; - -struct EventRecord { - EventType event_type; - union { - Socket const * socket; - TimeoutTimer const * timeout_timer; - } obj; -}; - -class Epoll : public IAsyncIoService { -public: - Epoll(size_t numProcessors = 1, size_t multiplier = 1) { - int ret; - - m_epoll_fd = ::epoll_create(42); - ret = pthread_create(&m_worker_thread, nullptr, &WorkerThread, reinterpret_cast(this)); - if (ret != 0) { - OsErrorExit("Epoll:Epoll() -- Create worker thread"); - } - } - - ~Epoll() { - ::close(m_epoll_fd); - } - - void registerSocket(Socket& socket) { - - registerHandle(socket.getHandle(), reinterpret_cast(&socket), EventType::SOCKET); - registerHandle(socket.getTimeout().getHandle(), reinterpret_cast(&socket.getTimeout()), EventType::TIMEOUT); - printf("S: %d T: %d\n", socket.getHandle(), socket.getTimeout().getHandle()); - } - - void postUserMessage(MessageCode messageCode, void * data = nullptr) const {} - void postQuitMessage() const {} - - HANDLE getHandle() const { - return m_epoll_fd; - } - -protected: - - void registerHandle(HANDLE handle, void const * data_ptr, EventType event_type) { - - struct epoll_event event; - auto event_record = std::make_shared(); - m_events.emplace_back(event_record); - - event_record->event_type = event_type; - if (event_type == EventType::SOCKET) { - event_record->obj.socket = reinterpret_cast(data_ptr); - } else if (event_type == EventType::TIMEOUT) { - event_record->obj.timeout_timer = static_cast(data_ptr); - } - event.data.ptr = event_record.get(); - event.events = EPOLLIN; - if (::epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, handle, &event) == -1) { - OsErrorExit("Epoll::registerHandle()"); - } - } - -private: - int m_epoll_fd; - pthread_t m_worker_thread; - std::vector> m_events; -}; - - -#endif // __EPOLL_HPP diff --git a/pyxcp/cxx/linux/lit_tester.cpp b/pyxcp/cxx/linux/lit_tester.cpp deleted file mode 100644 index d5a88a77..00000000 --- a/pyxcp/cxx/linux/lit_tester.cpp +++ /dev/null @@ -1,19 +0,0 @@ - -#include -#include - -#include - -using namespace std::literals; -using namespace std; - - -int main() -{ -// cout << static_cast(23ms) << endl; - auto d1 = 250ns; - - std::chrono::nanoseconds d2 = 1us; - std::cout << "250ns = " << d1.count() << " nanoseconds\n" << "1us = " << d2.count() << " nanoseconds\n"; - -} diff --git a/pyxcp/cxx/linux/socket.hpp b/pyxcp/cxx/linux/socket.hpp deleted file mode 100644 index e63cf43f..00000000 --- a/pyxcp/cxx/linux/socket.hpp +++ /dev/null @@ -1,234 +0,0 @@ -#if !defined(__SOCKET_HPP) -#define __SOCKET_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "isocket.hpp" -#include "utils.hpp" -#include "timeout.hpp" - -#define SOCKET_ERROR (-1) -#define INVALID_SOCKET (-1) - -using HANDLE = int; -using SOCKET = int; - -class Socket : public ISocket { -public: - - Socket(int family = PF_INET, int socktype = SOCK_STREAM, int protocol = IPPROTO_TCP) : - m_family(family), m_socktype(socktype), m_protocol(protocol), m_connected(false), - m_addr(nullptr), m_timeout(150) { - m_socket = ::socket(m_family, m_socktype, m_protocol); - if (m_socket == INVALID_SOCKET) { - SocketErrorExit("Socket::Socket()"); - } - blocking(false); - ZeroOut(&m_peerAddress, sizeof(sockaddr_storage)); - } - - ~Socket() { - ::close(m_socket); - } - - void blocking(bool enabled) { - int flags = fcntl(m_socket, F_GETFL); - - if (flags == -1) { - SocketErrorExit("Socket::blocking()"); - } - flags = enabled ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); - if (fcntl(m_socket, F_SETFL, flags) == -1) { - SocketErrorExit("Socket::blocking()"); - } - } - - void option(int optname, int level, int * value) { - socklen_t len; - - len = sizeof(*value); - if (*value == 0) { - ::getsockopt(m_socket, level, optname, (char*) value, &len); - } else { - ::setsockopt(m_socket, level, optname, (const char*) value, len); - } - } - - bool getaddrinfo(int family, int socktype, int protocol, const char * hostname, int port, CAddress & address, int flags = AI_PASSIVE) { - int err; - addrinfo hints; - addrinfo * t_addr; - char port_str[16] = {0}; - - ZeroOut(&hints, sizeof(hints)); - hints.ai_family = family; - hints.ai_socktype = socktype; - hints.ai_protocol = protocol; - hints.ai_flags = flags; - - ::sprintf(port_str, "%d", port); - err = ::getaddrinfo(hostname, port_str, &hints, &t_addr); - if (err != 0) { - printf("%s\n", gai_strerror(err)); - ::freeaddrinfo(t_addr); - SocketErrorExit("getaddrinfo()"); - return false; - } - - address.length = t_addr->ai_addrlen; - ::memcpy(&address.address, t_addr->ai_addr, sizeof(struct sockaddr)); - - ::freeaddrinfo(t_addr); - return true; - } - - void connect(CAddress & address) { - blocking(true); - if (::connect(m_socket, &address.address, address.length) == SOCKET_ERROR) { - if (errno != EINPROGRESS) { - SocketErrorExit("Socket::connect()"); - } - } - blocking(false); - } - - void bind(CAddress & address) { - if (::bind(m_socket, &address.address, address.length) == SOCKET_ERROR) { - SocketErrorExit("Socket::bind()"); - } - } - - void listen(int backlog = 5) { - if (::listen(m_socket, backlog) == SOCKET_ERROR) { - SocketErrorExit("Socket::listen()"); - } - } - - void accept(CAddress & peerAddress) { - int sock; - - peerAddress.length = sizeof peerAddress.address; - sock = ::accept(m_socket, (sockaddr *)&peerAddress.address, (socklen_t*)&peerAddress.length); - - if (sock == INVALID_SOCKET) { - SocketErrorExit("Socket::accept()"); - } - } - - template - void write(std::array& arr, bool alloc = true) { - size_t bytesWritten = 0; - int addrLen; - - m_timeout.arm(); - - if (m_socktype == SOCK_DGRAM) { -#if 0 - if (sendto(m_socket, (char const *)arr.data(), arr.size(), 0, (struct sockaddr const *)&XcpTl_Connection.connectionAddress, addrSize) == -1) { - SocketErrorExit("Socket::write() -- sendto()"); - } -#endif - } else if (m_socktype == SOCK_STREAM) { - if (send(m_socket, (char const *)arr.data(), arr.size(), 0) == -1) { - SocketErrorExit("Socket::write() -- send()"); - } - } -#if 0 - //PerIoData * iod = new PerIoData(128); - PerIoData * iod; - - if (alloc == true) { - iod = m_pool_mgr.get_iod().acquire(); - //iod = m_iod_pool.acquire(); - } - iod->reset(); - iod->set_buffer(arr); - iod->set_opcode(IoType::IO_WRITE); - iod->set_transfer_length(arr.size()); - if (m_socktype == SOCK_DGRAM) { - addrLen = sizeof(SOCKADDR_STORAGE); - if (::WSASendTo(m_socket, - iod->get_buffer(), - 1, - &bytesWritten, - 0, - (LPSOCKADDR)&m_peerAddress, - addrLen, - (LPWSAOVERLAPPED)iod, - nullptr - ) == SOCKET_ERROR) { - // WSA_IO_PENDING - SocketErrorExit("Socket::send()"); - } - } else if (m_socktype == SOCK_STREAM) { - if (::WSASend( - m_socket, - iod->get_buffer(), - 1, - &bytesWritten, - 0, - (LPWSAOVERLAPPED)iod, - nullptr) == SOCKET_ERROR) { - SocketErrorExit("Socket::send()"); - closesocket(m_socket); - } - } -#endif - printf("Status: %d bytes_written: %d\n", errno, bytesWritten); - } -#if 0 - void read(size_t count) { - if ( (n = read(sockfd, line, MAXLINE)) < 0) { - if (errno == ECONNRESET) { - close(sockfd); - events[i].data.fd = -1; - } else printf("readline error\n"); - } else if (n == 0) { - close(sockfd); - events[i].data.fd = -1; - } - - } -#endif - - void triggerRead(unsigned int len); - - HANDLE getHandle() const { - return m_socket; - } - - const TimeoutTimer& getTimeout() const { - return m_timeout; - } - -private: - int m_family; - int m_socktype; - int m_protocol; - bool m_connected; -// PoolManager m_pool_mgr; - addrinfo * m_addr; - TimeoutTimer m_timeout {150}; - int m_socket; - //CAddress ourAddress; - sockaddr_storage m_peerAddress; -}; - -#endif // __SOCKET_HPP diff --git a/pyxcp/cxx/linux/timeout.hpp b/pyxcp/cxx/linux/timeout.hpp deleted file mode 100644 index 4dfc2be1..00000000 --- a/pyxcp/cxx/linux/timeout.hpp +++ /dev/null @@ -1,81 +0,0 @@ - -#if !defined(__TIMEOUT_HPP) -#define __TIMEOUT_HPP - -#include -#include -#include -#include -#include -#include - -#include "utils.hpp" - -#include - -using namespace std::literals; - -/* - * - * Implements a file descriptor based time-out. - * - * Resolution is milli-seconds. - * - * Could be used together with poll(), epoll(), or select(). - * - */ - -class TimeoutTimer { -public: - - explicit TimeoutTimer(uint64_t value) : m_millis(value), m_timer_fd(-1) { - m_timer_fd = ::timerfd_create(CLOCK_MONOTONIC, 0); - if (m_timer_fd == -1) - OsErrorExit("TimeoutTimer::TimeoutTimer() -- timerfd_create"); - } - - ~TimeoutTimer() { - ::close(m_timer_fd); - } - - void arm() { - struct itimerspec new_value {0}; - - new_value.it_interval = {0}; - new_value.it_value.tv_sec = m_millis / 1000; - new_value.it_value.tv_nsec = (m_millis % 1000) * (1000 * 1000); - - settime(new_value); - } - - void disarm() { - struct itimerspec new_value {0}; - - settime(new_value); - } - - int getHandle() const { - return m_timer_fd; - } - - uint64_t getValue() const { - return m_millis; - } - - void setValue(uint64_t new_millis) { - m_millis = new_millis; - } - -private: - - void settime(const itimerspec& new_value) { - if (::timerfd_settime(m_timer_fd, 0, &new_value, nullptr) == -1) { - OsErrorExit("TimeoutTimer::disarm() -- timerfd_settime"); - } - } - - uint64_t m_millis; - int m_timer_fd; -}; - -#endif // __TIMEOUT_HPP diff --git a/pyxcp/cxx/memoryblock.hpp b/pyxcp/cxx/memoryblock.hpp deleted file mode 100644 index 0fc36c3d..00000000 --- a/pyxcp/cxx/memoryblock.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#if !defined(__MEMORYBLOCK_HPP) -#define __MEMORYBLOCK_HPP - -#include "iresource.hpp" - -/* - * - * Fixed size memory block. - * - */ -template class MemoryBlock : IResource { - -public: - - explicit MemoryBlock() : m_memory(nullptr) { - m_memory = new T[N]; - //printf("MemBlock-ctor: %p\n", m_memory); - } - - ~MemoryBlock() { - //printf("MemoryBlock-dtor: %p\n", m_memory); - if (m_memory) { - delete[] m_memory; - } - } - - T * data() { - return m_memory; - } - - void reset() { - #if !defined(NDEBUG) - - #endif - } - -private: - T * m_memory; - -}; - -#endif // __MEMORYBLOCK_HPP diff --git a/pyxcp/cxx/pool.hpp b/pyxcp/cxx/pool.hpp deleted file mode 100644 index 40a0e147..00000000 --- a/pyxcp/cxx/pool.hpp +++ /dev/null @@ -1,81 +0,0 @@ -#if !defined(__POOL_H) -#define __POOL_H - -#include -#include -#include -#include -#include - -#include "exceptions.hpp" - - -/* - * - * Fixed-size generic resource pool. - * - */ - -template void dump(std::deque& list) { - - for (auto elem: list) { - printf("%p ", elem); - } - printf("\n"); -} - -template class Pool { -public: - - explicit Pool() : m_mtx(), m_high_water_mark(N), m_allocation_count(0) { - for (size_t i = 0; i < N; ++i) { - m_free_objs.push_back(new Obj()); - } - } - - ~Pool() noexcept { - for (auto elem: m_used_objs) { - delete elem; - } - for (auto elem: m_free_objs) { - delete elem; - } - } - - Obj * acquire() { - const std::lock_guard lock(m_mtx); - if (m_free_objs.empty()) { - throw CapacityExhaustedException(); - } - auto obj = m_free_objs.front(); - m_free_objs.pop_front(); - m_used_objs.push_back(obj); - //printf("ACQ %p\n", obj); - return obj; - } - - void release(Obj * obj) - { - const std::lock_guard lock(m_mtx); - //printf("REL: %p\n", obj); - auto iter = std::find(std::begin(m_used_objs), std::end(m_used_objs), obj); - auto found = iter != std::end(m_used_objs); - if (found) { - obj->reset(); - m_free_objs.push_front(obj); - m_used_objs.erase(iter); - } else { - throw InvalidObjectException(); - } - } - -private: - - std::mutex m_mtx; - size_t m_high_water_mark; - size_t m_allocation_count; - std::deque m_used_objs; - std::deque m_free_objs; -}; - -#endif // __POOL_H diff --git a/pyxcp/cxx/poolmgr.cpp b/pyxcp/cxx/poolmgr.cpp deleted file mode 100644 index ca5467b1..00000000 --- a/pyxcp/cxx/poolmgr.cpp +++ /dev/null @@ -1,6 +0,0 @@ - -#include "poolmgr.hpp" - - - -PoolManager::IodPool_t PoolManager::m_iod_pool; // Initialization of static member. diff --git a/pyxcp/cxx/poolmgr.hpp b/pyxcp/cxx/poolmgr.hpp deleted file mode 100644 index a8c9373c..00000000 --- a/pyxcp/cxx/poolmgr.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#if !defined(__POOLMGR_H) -#define __POOLMGR_H - -#include "pool.hpp" -#include "periodata.hpp" - -/* - * - * PoolManager holds various resource pools. - * - * - */ - -class PoolManager { -public: - using IodPool_t = Pool; - - PoolManager() = default; - ~PoolManager() = default; - - IodPool_t& get_iod() const { - return m_iod_pool; - } - -private: - - static IodPool_t m_iod_pool; -}; - - -#endif // __POOLMGR_H diff --git a/pyxcp/cxx/test_queue.cpp b/pyxcp/cxx/test_queue.cpp deleted file mode 100644 index 687b1d15..00000000 --- a/pyxcp/cxx/test_queue.cpp +++ /dev/null @@ -1,69 +0,0 @@ - -#include - -#include - -#include -#include -#include - -#include "concurrent_queue.hpp" - -namespace py = pybind11; - -auto queue = ConcurrentQueue {}; - -using tuple_t = std::tuple; - -auto frame_queue = ConcurrentQueue {}; - -void worker(int num) -{ - printf("Entering #%u\n", num); - for (int i = 0; i < 10; ++i) { - queue.enqueue(num + i); - } -} - - -int main(int ac, char const * av[]) -{ - - auto value = 0; - auto frame = std::make_tuple(20, 1, 1.0045, "hello world!!!"); - uint16_t length, counter; - double timestamp; - py::bytes payload {}; - - frame_queue.enqueue(frame); - - std::thread t0(worker, 10); - std::thread t1(worker, 20); - std::thread t2(worker, 30); - std::thread t3(worker, 40); - std::thread t4(worker, 50); - - for (auto i = 0; i < 100; ++i) { - if (queue.dequeue(value, 1000)) { - printf("%02u\n", value); - } else { - printf("TIME-OUT!!!\n"); - break; - } - } - - t4.join(); - t3.join(); - t2.join(); - t1.join(); - t0.join(); - - tuple_t flonz; - frame_queue.dequeue(flonz); - //printf("%u %u %g\n", std::get<0>(flonz), std::get<1>(flonz), std::get<2>(flonz)); - - std::tie(length, counter, timestamp, payload) = flonz; - printf("%u %u %g\n", length, counter, timestamp); - - return 0; -} diff --git a/pyxcp/cxx/timestamp.hpp b/pyxcp/cxx/timestamp.hpp deleted file mode 100644 index 45495989..00000000 --- a/pyxcp/cxx/timestamp.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#if !defined(__TIMESTAMP_HPP) -#define __TIMESTAMP_HPP - -#include - -#if defined(_WIN32) - #include -#else - #include -#endif - -class Timestamp { -public: - -#if defined(_WIN32) - Timestamp() { - LARGE_INTEGER tps; - - ::QueryPerformanceFrequency(&tps); - m_ticks_per_second = tps.QuadPart; - m_starting_time = static_cast(get_raw_value()); - } - - double get() const { - return get_raw_value() - m_starting_time; - } -#else - Timestamp() { - struct timespec resolution = {0}; - - if (::clock_getres(CLOCK_MONOTONIC_RAW, &resolution) == -1) { - } - m_starting_time = get_raw_value(); - } - - double get() const { - struct timespec dt = {0}; - - dt = diff(m_starting_time, get_raw_value()); - return static_cast(dt.tv_sec) + (static_cast(dt.tv_nsec) / (1000.0 * 1000.0 * 1000.0)); - } -#endif - -private: - -#if defined(_WIN32) - double get_raw_value() const { - LARGE_INTEGER now; - - ::QueryPerformanceCounter(&now); - - return static_cast(now.QuadPart) / static_cast(m_ticks_per_second); - } - - double m_starting_time; - uint64_t m_ticks_per_second; -#else - struct timespec get_raw_value() const { - struct timespec now; - - if (::clock_gettime(CLOCK_MONOTONIC_RAW, &now) == -1) { - } - return now; - } - - struct timespec diff(const struct timespec& start, const struct timespec& end) const { - struct timespec temp; - - if ((end.tv_nsec-start.tv_nsec) < 0) { - temp.tv_sec = end.tv_sec-start.tv_sec - 1; - temp.tv_nsec = 1000000000L + end.tv_nsec - start.tv_nsec; - } else { - temp.tv_sec = end.tv_sec-start.tv_sec; - temp.tv_nsec = end.tv_nsec-start.tv_nsec; - } - return temp; - } - - struct timespec m_starting_time; - -#endif -}; - -#endif // __TIMESTAMP_HPP diff --git a/pyxcp/cxx/utils.cpp b/pyxcp/cxx/utils.cpp deleted file mode 100644 index 91a3e801..00000000 --- a/pyxcp/cxx/utils.cpp +++ /dev/null @@ -1,38 +0,0 @@ - -#include - -#include "utils.hpp" - -#if defined(_WIN32) - #include -#else - -#endif - -void SocketErrorExit(const char * method) -{ - fprintf(stderr, "%s failed with: %d\n", method, GET_LAST_SOCKET_ERROR()); - exit(1); -} - -void OsErrorExit(const char * method) -{ - fprintf(stderr, "%s failed with: %d\n", method, GET_LAST_ERROR()); - exit(1); -} - -#if !defined(_WIN32) -/* - * - * Window-ish Sleep function for Linux. - * - */ -void Sleep(unsigned ms) -{ - struct timespec value = {0}, rem = {0}; - - value.tv_sec = ms / 1000; - value.tv_nsec = (ms % 1000) * 1000 * 1000; - nanosleep(&value, &rem); -} -#endif diff --git a/pyxcp/cxx/utils.hpp b/pyxcp/cxx/utils.hpp deleted file mode 100644 index 584a3c42..00000000 --- a/pyxcp/cxx/utils.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#if !defined(__UTILS_HPP) -#define __UTILS_HPP - -#if defined(_WIN32) - #define ZeroOut(p, s) ::SecureZeroMemory((p), (s)) - #define GET_LAST_SOCKET_ERROR() WSAGetLastError() - #define GET_LAST_ERROR() GetLastError() -#else - #include - #include - #include - - #define ZeroOut(p, s) ::memset((p), 0, (s)) - #define CopyMemory(d, s, l) ::memcpy((d), (s), (l)) - #define GET_LAST_SOCKET_ERROR() errno - #define GET_LAST_ERROR() errno - void Sleep(unsigned ms); -#endif - -#if defined(NDEBUG) - #define DBG_PRINT(...) -#else - #define DBG_PRINT(...) printf(VA_ARGS) -#endif - -void SocketErrorExit(const char * method); -void OsErrorExit(const char * method); - -#endif // __UTILS_HPP diff --git a/pyxcp/cxx/win/iocp.cpp b/pyxcp/cxx/win/iocp.cpp deleted file mode 100644 index a9856b71..00000000 --- a/pyxcp/cxx/win/iocp.cpp +++ /dev/null @@ -1,242 +0,0 @@ - -#include "iocp.hpp" -#include "socket.hpp" -#include "exceptions.hpp" -#include "timeout.hpp" - -#include -#include -#include - - -/* - * - * - * - */ - - -static DWORD WINAPI WorkerThread(LPVOID lpParameter); - -IOCP::IOCP(size_t numProcessors, size_t multiplier) -{ - m_numWorkerThreads = numProcessors * multiplier; - m_port.handle = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, static_cast(0), m_numWorkerThreads); - if (m_port.handle == nullptr) { - OsErrorExit("IOCP::IOCP()"); - } - - m_threads.reserve(m_numWorkerThreads); - - HANDLE hThread; - - for (DWORD idx = 0; idx < m_numWorkerThreads; ++idx) { - hThread = ::CreateThread(nullptr, 0, WorkerThread, reinterpret_cast(this), 0, nullptr); - ::SetThreadPriority(hThread, THREAD_PRIORITY_ABOVE_NORMAL); - m_threads.push_back(hThread); - } -} - -IOCP::~IOCP() -{ - DWORD numThreads = static_cast(m_threads.size()); - std::ldiv_t divres = std::ldiv(numThreads, MAXIMUM_WAIT_OBJECTS); - DWORD rounds = static_cast(divres.quot); - DWORD remaining = static_cast(divres.rem); - HANDLE * thrArray = nullptr; - DWORD offset = 0; - DWORD idx = 0; - - postQuitMessage(); - - thrArray = new HANDLE[MAXIMUM_WAIT_OBJECTS]; - for (DWORD r = 0; r < rounds; ++r) { - for (idx = 0; idx < MAXIMUM_WAIT_OBJECTS; ++idx) { - thrArray[idx] = m_threads.at(idx + offset); - } - ::WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, thrArray, TRUE, INFINITE); - for (idx = 0; idx < MAXIMUM_WAIT_OBJECTS; ++idx) { - ::CloseHandle(thrArray[idx]); - } - offset += MAXIMUM_WAIT_OBJECTS; - } - - if (remaining > 0) { - for (idx = 0; idx < remaining; ++idx) { - thrArray[idx] = m_threads.at(idx + offset); - } - ::WaitForMultipleObjects(remaining, thrArray, TRUE, INFINITE); - for (idx = 0; idx < remaining; ++idx) { - ::CloseHandle(thrArray[idx]); - } - } - delete[] thrArray; - ::CloseHandle(m_port.handle); -} - -void IOCP::registerHandle(const PerHandleData& object) -{ - HANDLE handle; - bool ok; - - handle = ::CreateIoCompletionPort(object.m_handle, m_port.handle, reinterpret_cast(&object), 0); - ok = (handle == m_port.handle); - if ((handle == nullptr) || (!ok)) { - OsErrorExit("IOCP::registerHandle()"); - } -} - - -void IOCP::registerSocket(Socket& socket) -{ - auto handleData = PerHandleData(HandleType::HANDLE_SOCKET, socket.getHandle()); - - socket.setIOCP(this); - registerHandle(handleData); - -} - -void IOCP::postUserMessage(MessageCode messageCode, void * data) const -{ - if (!::PostQueuedCompletionStatus(m_port.handle, 0, static_cast(messageCode), (OVERLAPPED*)data)) { - OsErrorExit("IOCP::postUserMessage()"); - } -} - -void IOCP::postQuitMessage() const -{ - postUserMessage(MessageCode::QUIT, nullptr); -} - -HANDLE IOCP::getHandle() const -{ - return m_port.handle; -} - -static DWORD WINAPI WorkerThread(LPVOID lpParameter) -{ - IOCP const * const iocp = reinterpret_cast(lpParameter); - DWORD bytesTransfered = 0; - ULONG_PTR completionKey; - PerIoData * iod = nullptr; - PerHandleData * phd = nullptr; - OVERLAPPED * olap = nullptr; - bool exitLoop = false; - MessageCode messageCode; - DWORD error; - - printf("Entering worker thread %d.\n", ::GetCurrentThreadId()); - while (!exitLoop) { - if (::GetQueuedCompletionStatus(iocp->getHandle(), &bytesTransfered, &completionKey, (LPOVERLAPPED*)&olap, INFINITE)) { - if (bytesTransfered == 0) { - messageCode = static_cast(completionKey); - if (messageCode == MessageCode::TIMEOUT) { - // TODO: Timeout handling. - } else if (messageCode == MessageCode::QUIT) { - iocp->postQuitMessage(); // "Broadcast" - exitLoop = true; - } - - } else { - phd = reinterpret_cast(completionKey); - iod = reinterpret_cast(olap); - printf("\tOPCODE: %d bytes: %d\n", iod->get_opcode(), bytesTransfered); - switch (iod->get_opcode()) { - case IoType::IO_WRITE: - iod->decr_bytes_to_xfer(bytesTransfered); -// phd->m_socket->triggerRead(1024); - if (iod->xfer_finished()) { - delete iod; - } else { - //iod->m_wsabuf.buf = iod->m_wsabuf.buf + (iod->get_bytes_to_xfer() - iod->m_bytesRemaining); - //iod->m_wsabuf.len = iod->m_bytesRemaining; - iod->reset(); - } - break; - case IoType::IO_READ: - printf("IO_READ() numBytes: %d\n", bytesTransfered); - break; - case IoType::IO_ACCEPT: - break; - } - } - } else { - error = ::GetLastError(); - if (olap == nullptr) { - - } else { - // Failed I/O operation. - // The function stores information in the variables pointed to by lpNumberOfBytes, lpCompletionKey. - } - //Win_ErrorMsg("IOWorkerThread::GetQueuedCompletionStatus()", error); - } - } - printf("Exiting worker thread %d\n", ::GetCurrentThreadId()); - ::ExitThread(0); -} - -void CALLBACK Timeout_CB(void * lpParam, unsigned char TimerOrWaitFired) -{ - IOCP const * const iocp = reinterpret_cast(lpParam); - - //printf("TIMEOUT\n"); - iocp->postUserMessage(MessageCode::TIMEOUT); -} - - -#if 0 -void Socket::triggerRead(unsigned int len) -{ - DWORD numReceived = (DWORD)0; - DWORD flags = (DWORD)0; - DWORD err = 0; - int addrLen; - static char buf[1024]; - - PerIoData * iod = new PerIoData(128); - - iod->m_wsabuf.buf = buf; - iod->m_wsabuf.len = len; - iod->m_opcode = IoType::IO_READ; - iod->m_bytesRemaining = iod->m_bytesToXfer = len; - iod->reset(); - - if (m_socktype == SOCK_STREAM) { - if (WSARecv(m_socket, - &iod->m_wsabuf, - 1, - &numReceived, - &flags, - (LPWSAOVERLAPPED)&iod->m_overlapped, - (LPWSAOVERLAPPED_COMPLETION_ROUTINE)nullptr) == SOCKET_ERROR) { - err = WSAGetLastError(); - if (err != WSA_IO_PENDING) { - - } - } - } else if (m_socktype == SOCK_DGRAM) { - addrLen = sizeof(SOCKADDR_STORAGE); - if (WSARecvFrom(m_socket, - &iod->m_wsabuf, - 1, - &numReceived, - &flags, - (LPSOCKADDR)&numReceived, - &addrLen, - (LPWSAOVERLAPPED)&iod->m_overlapped, - (LPWSAOVERLAPPED_COMPLETION_ROUTINE)nullptr)) { - err = WSAGetLastError(); - if (err != WSA_IO_PENDING) { - - } - } - } -} - -typedef std::function CompleteHandler_t; - -class AsyncIoServiceFactory { - -}; - -#endif diff --git a/pyxcp/cxx/win/iocp.hpp b/pyxcp/cxx/win/iocp.hpp deleted file mode 100644 index 2c500263..00000000 --- a/pyxcp/cxx/win/iocp.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#if !defined(__IOCP_HPP) -#define __IOCP_HPP - -#include "iasyncioservice.hpp" -#include "socket.hpp" -#include "perhandledata.hpp" -#include "periodata.hpp" -#include "poolmgr.hpp" -#include -#include -#include - -#if !defined(__GNUC__) -#pragma comment(lib,"ws2_32.lib") // MSVC only. -#endif - - -struct PerPortData { - HANDLE handle; -}; - - -class IOCP : public IAsyncIoService { -public: - IOCP(size_t numProcessors = 1, size_t multiplier = 1); - ~IOCP(); - void registerSocket(Socket& socket); - void postUserMessage(MessageCode messageCode, void * data = nullptr) const; - void postQuitMessage() const; - HANDLE getHandle() const; - -protected: - void registerHandle(const PerHandleData& object); - -private: - PerPortData m_port; - DWORD m_numWorkerThreads; - std::vector m_threads; - PoolManager m_pool_mgr; -}; - -#endif // __IOCP_HPP diff --git a/pyxcp/cxx/win/perhandledata.hpp b/pyxcp/cxx/win/perhandledata.hpp deleted file mode 100644 index 0655bfde..00000000 --- a/pyxcp/cxx/win/perhandledata.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#if !defined(__PERHANDLEDATA_HPP) -#define __PERHANDLEDATA_HPP - -#include - -enum class HandleType { - HANDLE_SOCKET, - HANDLE_FILE, - HANDLE_NAMED_PIPE, - HANDLE_USER, -}; - - -struct PerHandleData { - HandleType m_handleType; - HANDLE m_handle; - DWORD m_seqNoSend; - DWORD m_seqNoRecv; - - PerHandleData(HandleType handleType, const HANDLE& handle) : m_handleType(handleType), m_handle(handle), m_seqNoSend(0), m_seqNoRecv(0) {} -}; - - -#endif // __PERHANDLEDATA_HPP diff --git a/pyxcp/cxx/win/periodata.hpp b/pyxcp/cxx/win/periodata.hpp deleted file mode 100644 index 5c9cbb00..00000000 --- a/pyxcp/cxx/win/periodata.hpp +++ /dev/null @@ -1,97 +0,0 @@ -#if !defined(__PERIODATA_HPP) -#define __PERIODATA_HPP - -#include -#include -#include "utils.hpp" -#include - -enum class IoType { - IO_ACCEPT, - IO_CONNECT, - IO_READ, - IO_WRITE -}; - -class PerIoData { - -public: - - explicit PerIoData(size_t bufferSize = 128) { - m_xferBuffer = nullptr; - m_xferBuffer = new char[bufferSize]; - m_wsabuf.buf = m_xferBuffer; - - m_wsabuf.len = bufferSize; - m_bytesRemaining = 0; - m_bytes_to_xfer = 0; - } - - ~PerIoData() { - if (m_xferBuffer) { - delete[] m_xferBuffer; - } - } - - void setup_write_request() { - - } - - void set_opcode(IoType opcode) { - m_opcode = opcode; - } - - template void set_buffer(std::array& arr) { - - m_wsabuf.buf = arr.data(); - m_wsabuf.len = arr.size(); - } - - WSABUF * get_buffer() { - return &m_wsabuf; - } - - IoType get_opcode() const { - return m_opcode; - } - - void set_transfer_length(size_t length) { - m_bytesRemaining = m_bytes_to_xfer = length; - } - - size_t get_bytes_to_xfer() const { - return m_bytes_to_xfer; - } - - void decr_bytes_to_xfer(size_t amount) { - printf("remaining: %d amount: %d\n",m_bytesRemaining, amount); - assert((static_cast(m_bytesRemaining) - static_cast(amount)) >= 0); - - m_bytesRemaining -= amount; - } - - bool xfer_finished() const { - return m_bytesRemaining == 0; - } - - OVERLAPPED * get_overlapped() { - return &m_overlapped; - } - - void reset() { - ZeroOut(&m_overlapped, sizeof(OVERLAPPED)); - m_wsabuf.len = 0; - m_bytesRemaining = 0; - m_bytes_to_xfer = 0; - } - -private: - OVERLAPPED m_overlapped; - IoType m_opcode; - WSABUF m_wsabuf; - char * m_xferBuffer; - size_t m_bytes_to_xfer; - size_t m_bytesRemaining; -}; - -#endif // __PERIODATA_HPP diff --git a/pyxcp/cxx/win/socket.hpp b/pyxcp/cxx/win/socket.hpp deleted file mode 100644 index 6e00ca83..00000000 --- a/pyxcp/cxx/win/socket.hpp +++ /dev/null @@ -1,185 +0,0 @@ - -#if !defined(__SOCKET_HPP) -#define __SOCKET_HPP - -#include - -#include -#include -#include -#include -#include - -#include "isocket.hpp" -#include "timeout.hpp" -#include "periodata.hpp" -#include "perhandledata.hpp" -#include "pool.hpp" -#include "poolmgr.hpp" - -class IOCP; - -class Socket : public ISocket { -public: - - using Pool_t = Pool; - - Socket(int family = PF_INET, int socktype = SOCK_STREAM, int protocol = IPPROTO_TCP) : - m_family(family), m_socktype(socktype), m_protocol(protocol), m_connected(false), - m_pool_mgr(PoolManager()), m_addr(nullptr) { - m_socket = ::WSASocket(m_family, m_socktype, m_protocol, NULL, 0, WSA_FLAG_OVERLAPPED); - if (m_socket == INVALID_SOCKET) { - SocketErrorExit("Socket::Socket()"); - } - ZeroOut(&m_peerAddress, sizeof(SOCKADDR_STORAGE)); - } - - ~Socket() { - ::closesocket(m_socket); - } - - void option(int optname, int level, int * value) { - int len; - - len = sizeof(*value); - if (*value == 0) { - ::getsockopt(m_socket, level, optname, (char*) value, &len); - } else { - ::setsockopt(m_socket, level, optname, (const char*) value, len); - } - } - - bool getaddrinfo(int family, int socktype, int protocol, const char * hostname, int port, CAddress & address, int flags = AI_PASSIVE) { - int err; - ADDRINFO hints; - ADDRINFO * t_addr; - char port_str[16] = {0}; - - ZeroOut(&hints, sizeof(hints)); - hints.ai_family = family; - hints.ai_socktype = socktype; - hints.ai_protocol = protocol; - hints.ai_flags = flags; - - ::sprintf(port_str, "%d", port); - err = ::getaddrinfo(hostname, port_str, &hints, &t_addr); - if (err != 0) { - printf("%s\n", gai_strerror(err)); - ::freeaddrinfo(t_addr); - SocketErrorExit("getaddrinfo()"); - return false; - } - - address.length = t_addr->ai_addrlen; - ::CopyMemory(&address.address, t_addr->ai_addr, sizeof(struct sockaddr)); - - ::freeaddrinfo(t_addr); - return true; - } - - void connect(CAddress & address) { - if (::connect(m_socket, &address.address, address.length) == SOCKET_ERROR) { - SocketErrorExit("Socket::connect()"); - } - PerHandleData handleData(HandleType::HANDLE_SOCKET, getHandle()); - } - - void bind(CAddress & address) { - if (::bind(m_socket, &address.address, address.length) == SOCKET_ERROR) { - SocketErrorExit("Socket::bind()"); - } - } - - void listen(int backlog = 5) { - if (::listen(m_socket, backlog) == SOCKET_ERROR) { - SocketErrorExit("Socket::listen()"); - } - } - - void accept(CAddress & peerAddress) { - SOCKET sock; - - peerAddress.length = sizeof peerAddress.address; - sock = ::accept(m_socket, (sockaddr *)&peerAddress.address, &peerAddress.length); - - if (sock == INVALID_SOCKET) { - SocketErrorExit("Socket::accept()"); - } - } - - template - void write(std::array& arr, bool alloc = true) { - DWORD bytesWritten = 0; - int addrLen; - //PerIoData * iod = new PerIoData(128); - PerIoData * iod; - - if (alloc == true) { - iod = m_pool_mgr.get_iod().acquire(); - //iod = m_iod_pool.acquire(); - } - m_timeout.arm(); - iod->reset(); - iod->set_buffer(arr); - iod->set_opcode(IoType::IO_WRITE); - iod->set_transfer_length(arr.size()); - if (m_socktype == SOCK_DGRAM) { - addrLen = sizeof(SOCKADDR_STORAGE); - if (::WSASendTo(m_socket, - iod->get_buffer(), - 1, - &bytesWritten, - 0, - (LPSOCKADDR)&m_peerAddress, - addrLen, - (LPWSAOVERLAPPED)iod, - nullptr - ) == SOCKET_ERROR) { - // WSA_IO_PENDING - SocketErrorExit("Socket::send()"); - } - } else if (m_socktype == SOCK_STREAM) { - if (::WSASend( - m_socket, - iod->get_buffer(), - 1, - &bytesWritten, - 0, - (LPWSAOVERLAPPED)iod, - nullptr) == SOCKET_ERROR) { - SocketErrorExit("Socket::send()"); - closesocket(m_socket); - } - } - printf("Status: %d bytes_written: %d\n", WSAGetLastError(), bytesWritten); - } - void triggerRead(unsigned int len); - - HANDLE getHandle() const { - return reinterpret_cast(m_socket); - } - - const TimeoutTimer& getTimeoutTimer() const { - return m_timeout; - } - - void setIOCP(IOCP * iocp) { - m_iocp = iocp; - m_timeout.setIOCP(iocp); - } - -private: - int m_family; - int m_socktype; - int m_protocol; - bool m_connected; - PoolManager m_pool_mgr; - ADDRINFO * m_addr; - SOCKET m_socket; - //CAddress ourAddress; - SOCKADDR_STORAGE m_peerAddress; - TimeoutTimer m_timeout {150}; - IOCP * m_iocp = nullptr; -}; - -#endif // __SOCKET_HPP diff --git a/pyxcp/cxx/win/timeout.hpp b/pyxcp/cxx/win/timeout.hpp deleted file mode 100644 index 5c40816c..00000000 --- a/pyxcp/cxx/win/timeout.hpp +++ /dev/null @@ -1,83 +0,0 @@ - -#if !defined(__TIMEOUT_HPP) -#define __TIMEOUT_HPP - -#include - -#include "utils.hpp" - - -/* - * - * Implements a timer queue based time-out. - * - * Resolution is milli-seconds. - * - * - */ - -class IOCP; - -void CALLBACK Timeout_CB(void * lpParam, unsigned char TimerOrWaitFired); - - -class TimeoutTimer { -public: - - explicit TimeoutTimer(uint64_t value) : m_millis(value) { - m_timer_queue = ::CreateTimerQueue(); - if (m_timer_queue == nullptr) - OsErrorExit("TimeoutTimer::TimeoutTimer() -- CreateTimerQueue"); - } - - TimeoutTimer(const TimeoutTimer&) = default; - TimeoutTimer(const TimeoutTimer&&) = delete; - - ~TimeoutTimer() { - if (!::DeleteTimerQueue(m_timer_queue)) { - OsErrorExit("TimeoutTimer::~TimeoutTimer() -- DeleteTimerQueueEx"); - } - } - - void arm() { - if (m_iocp != nullptr) { - if (!::CreateTimerQueueTimer(&m_timer, m_timer_queue, Timeout_CB, reinterpret_cast(m_iocp), m_millis, 0, 0)) { - OsErrorExit("TimeoutTimer::arm() -- CreateTimerQueueTimer"); - } - } - } - - void disarm() { - if (m_timer != INVALID_HANDLE_VALUE) { - if (!::DeleteTimerQueueTimer(m_timer_queue, m_timer, nullptr)) { - OsErrorExit("TimeoutTimer::disarm() -- DeleteTimerQueueTimer"); - } - m_timer = INVALID_HANDLE_VALUE; - } - } - - HANDLE getHandle() const { - return m_timer_queue; - } - - uint64_t getValue() const { - return m_millis; - } - - void setValue(uint64_t new_millis) { - m_millis = new_millis; - } - - void setIOCP(IOCP * iocp) { - m_iocp = iocp; - } - -private: - - uint64_t m_millis; - HANDLE m_timer_queue {INVALID_HANDLE_VALUE}; - HANDLE m_timer {INVALID_HANDLE_VALUE}; - IOCP * m_iocp = nullptr; -}; - -#endif // __TIMEOUT_HPP diff --git a/pyxcp/daq_stim/__init__.py b/pyxcp/daq_stim/__init__.py new file mode 100644 index 00000000..0c78dc4a --- /dev/null +++ b/pyxcp/daq_stim/__init__.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python + +# from pprint import pprint +from time import time_ns +from typing import Dict, List, TextIO + +from pyxcp import types +from pyxcp.config import get_application +from pyxcp.cpp_ext import DaqList +from pyxcp.daq_stim.optimize import make_continuous_blocks +from pyxcp.daq_stim.optimize.binpacking import first_fit_decreasing +from pyxcp.recorder import DaqOnlinePolicy as _DaqOnlinePolicy +from pyxcp.recorder import DaqRecorderPolicy as _DaqRecorderPolicy +from pyxcp.recorder import MeasurementParameters +from pyxcp.utils import CurrentDatetime + + +DAQ_ID_FIELD_SIZE = { + "IDF_ABS_ODT_NUMBER": 1, + "IDF_REL_ODT_NUMBER_ABS_DAQ_LIST_NUMBER_BYTE": 2, + "IDF_REL_ODT_NUMBER_ABS_DAQ_LIST_NUMBER_WORD": 3, + "IDF_REL_ODT_NUMBER_ABS_DAQ_LIST_NUMBER_WORD_ALIGNED": 4, +} + +DAQ_TIMESTAMP_SIZE = { + "S1": 1, + "S2": 2, + "S4": 4, +} + + +class DaqProcessor: + def __init__(self, daq_lists: List[DaqList]): + # super().__init__() + self.daq_lists = daq_lists + self.log = get_application().log + + def setup(self, start_datetime: CurrentDatetime | None = None, write_multiple: bool = True): + self.daq_info = self.xcp_master.getDaqInfo() + if start_datetime is None: + start_datetime = CurrentDatetime(time_ns()) + self.start_datetime = start_datetime + # print(self.start_datetime) + try: + processor = self.daq_info.get("processor") + properties = processor.get("properties") + resolution = self.daq_info.get("resolution") + if properties["configType"] == "STATIC": + raise TypeError("DAQ configuration is static, cannot proceed.") + self.supports_timestampes = properties["timestampSupported"] + self.supports_prescaler = properties["prescalerSupported"] + self.supports_pid_off = properties["pidOffSupported"] + if self.supports_timestampes: + mode = resolution.get("timestampMode") + self.ts_fixed = mode.get("fixed") + self.ts_size = DAQ_TIMESTAMP_SIZE[mode.get("size")] + ts_factor = types.DAQ_TIMESTAMP_UNIT_TO_NS[mode.get("unit")] + ts_ticks = resolution.get("timestampTicks") + self.ts_scale_factor = ts_factor * ts_ticks + else: + self.ts_size = 0 + self.ts_fixed = False + self.ts_scale_factor = 0.0 + key_byte = processor.get("keyByte") + header_len = DAQ_ID_FIELD_SIZE[key_byte["identificationField"]] + max_dto = self.xcp_master.slaveProperties.maxDto + self.min_daq = processor.get("minDaq") + max_odt_entry_size = resolution.get("maxOdtEntrySizeDaq") + max_payload_size = min(max_odt_entry_size, max_dto - header_len) + # First ODT may contain timestamp. + self.selectable_timestamps = False + if not self.supports_timestampes: + max_payload_size_first = max_payload_size + # print("NO TIMESTAMP SUPPORT") + else: + if self.ts_fixed: + # print("Fixed timestamp") + max_payload_size_first = max_payload_size - self.ts_size + else: + # print("timestamp variable.") + self.selectable_timestamps = True + + except Exception as e: + raise TypeError(f"DAQ_INFO corrupted: {e}") from e + + # DAQ optimization. + for daq_list in self.daq_lists: + if self.selectable_timestamps: + if daq_list.enable_timestamps: + max_payload_size_first = max_payload_size - self.ts_size + else: + max_payload_size_first = max_payload_size + ttt = make_continuous_blocks(daq_list.measurements, max_payload_size, max_payload_size_first) + daq_list.measurements_opt = first_fit_decreasing(ttt, max_payload_size, max_payload_size_first) + byte_order = 0 if self.xcp_master.slaveProperties.byteOrder == "INTEL" else 1 + self._first_pids = [] + daq_count = len(self.daq_lists) + self.xcp_master.freeDaq() + + # Allocate + self.xcp_master.allocDaq(daq_count) + measurement_list = [] + for i, daq_list in enumerate(self.daq_lists, self.min_daq): + measurements = daq_list.measurements_opt + measurement_list.append((i, measurements)) + odt_count = len(measurements) + self.xcp_master.allocOdt(i, odt_count) + # Iterate again over ODT entries -- we need to respect sequencing requirements. + for i, measurements in measurement_list: + for j, measurement in enumerate(measurements): + entry_count = len(measurement.entries) + self.xcp_master.allocOdtEntry(i, j, entry_count) + # Write DAQs + for i, daq_list in enumerate(self.daq_lists, self.min_daq): + measurements = daq_list.measurements_opt + for j, measurement in enumerate(measurements): + if len(measurement.entries) == 0: + continue # CAN special case: No room for data in first ODT. + self.xcp_master.setDaqPtr(i, j, 0) + for entry in measurement.entries: + self.xcp_master.writeDaq(0xFF, entry.length, entry.ext, entry.address) + + # arm DAQ lists -- this is technically a function on its own. + for i, daq_list in enumerate(self.daq_lists, self.min_daq): + # print(daq_list.name, daq_list.event_num, daq_list.stim) + mode = 0x00 + if self.supports_timestampes and (self.ts_fixed or (self.selectable_timestamps and daq_list.enable_timestamps)): + mode = 0x10 + if daq_list.stim: + mode |= 0x02 + ### + ## mode |= 0x20 + ### + self.xcp_master.setDaqListMode( + daqListNumber=i, mode=mode, eventChannelNumber=daq_list.event_num, prescaler=1, priority=0xFF + ) + res = self.xcp_master.startStopDaqList(0x02, i) + self._first_pids.append(res.firstPid) + if start_datetime: + pass + self.measurement_params = MeasurementParameters( + byte_order, + header_len, + self.supports_timestampes, + self.ts_fixed, + self.supports_prescaler, + self.selectable_timestamps, + self.ts_scale_factor, + self.ts_size, + self.min_daq, + self.start_datetime, + self.daq_lists, + self._first_pids, + ) + self.set_parameters(self.measurement_params) + + def start(self): + self.xcp_master.startStopSynch(0x01) + + def stop(self): + self.xcp_master.startStopSynch(0x00) + + def first_pids(self): + return self._first_pids + + +class DaqRecorder(DaqProcessor, _DaqRecorderPolicy): + + def __init__(self, daq_lists: List[DaqList], file_name: str, prealloc: int = 200, chunk_size: int = 1): + DaqProcessor.__init__(self, daq_lists) + _DaqRecorderPolicy.__init__(self) + self.file_name = file_name + self.prealloc = prealloc + self.chunk_size = chunk_size + + def initialize(self): + metadata = self.measurement_params.dumps() + _DaqRecorderPolicy.create_writer(self, self.file_name, self.prealloc, self.chunk_size, metadata) + _DaqRecorderPolicy.initialize(self) + + def finalize(self): + _DaqRecorderPolicy.finalize(self) + + def start(self): + DaqProcessor.start(self) + + +class DaqOnlinePolicy(DaqProcessor, _DaqOnlinePolicy): + """Base class for on-line measurements. + Handles multiple inheritence. + """ + + def __init__(self, daq_lists: List[DaqList]): + DaqProcessor.__init__(self, daq_lists) + _DaqOnlinePolicy.__init__(self) + + def start(self): + DaqProcessor.start(self) + + +class DaqToCsv(DaqOnlinePolicy): + """Save a measurement as CSV files (one per DAQ-list).""" + + def initialize(self): + self.log.debug("DaqCsv::Initialize()") + self.files: Dict[int, TextIO] = {} + for num, daq_list in enumerate(self.daq_lists): + if daq_list.stim: + continue + out_file = open(f"{daq_list.name}.csv", "w") + self.files[num] = out_file + hdr = ",".join(["timestamp0", "timestamp1"] + [h[0] for h in daq_list.headers]) + out_file.write(f"{hdr}\n") + + def on_daq_list(self, daq_list: int, ts0: int, ts1: int, payload: list): + self.files[daq_list].write(f"{ts0},{ts1},{', '.join([str(x) for x in payload])}\n") + + def finalize(self): + self.log.debug("DaqCsv::finalize()") + ## + ## NOTE: `finalize` is guaranteed to be called, but `Initialize` may fail for reasons. + ## So if you allocate resources in `Initialize` check if this really happened. + ## + if hasattr(self, "files"): + for f in self.files.values(): + f.close() diff --git a/pyxcp/daq_stim/optimize/__init__.py b/pyxcp/daq_stim/optimize/__init__.py new file mode 100644 index 00000000..e0ebf401 --- /dev/null +++ b/pyxcp/daq_stim/optimize/__init__.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +"""Optimize data-structures like memory sections.""" + +from itertools import groupby +from operator import attrgetter +from typing import List + +from pyxcp.cpp_ext import McObject + + +def make_continuous_blocks(chunks: List[McObject], upper_bound=None, upper_bound_initial=None) -> List[McObject]: + """Try to make continous blocks from a list of small, unordered `chunks`. + + Parameters + ---------- + chunks: list of `McObject` + + Returns + ------- + sorted list of `McObject` + """ + + def key_func(x): + return (x.ext, x.address) + + values = [] + # 1. Groupy by address. + for _key, value in groupby(sorted(chunks, key=key_func), key=key_func): + # 2. Pick the largest one. + values.append(max(value, key=attrgetter("length"))) + result_sections = [] + last_section = None + last_ext = None + first_section = True + if upper_bound_initial is None: + upper_bound_initial = upper_bound + while values: + section = values.pop(0) + if (last_section and section.address <= last_section.address + last_section.length) and not (section.ext != last_ext): + last_end = last_section.address + last_section.length - 1 + current_end = section.address + section.length - 1 + if last_end > section.address: + pass + else: + offset = current_end - last_end + if upper_bound: + if first_section: + upper_bound = upper_bound_initial + first_section = False + if last_section.length + offset <= upper_bound: + last_section.length += offset + last_section.add_component(section) + else: + result_sections.append( + McObject(name="", address=section.address, ext=section.ext, length=section.length, components=[section]) + ) + else: + last_section.length += offset + last_section.add_component(section) + else: + # Create a new section. + result_sections.append( + McObject(name="", address=section.address, ext=section.ext, length=section.length, components=[section]) + ) + last_section = result_sections[-1] + last_ext = last_section.ext + return result_sections diff --git a/pyxcp/daq_stim/optimize/binpacking.py b/pyxcp/daq_stim/optimize/binpacking.py new file mode 100644 index 00000000..98c44767 --- /dev/null +++ b/pyxcp/daq_stim/optimize/binpacking.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +"""Bin-packing algorithms. +""" +from typing import List, Optional + +from pyxcp.cpp_ext import Bin + + +def first_fit_decreasing(items, bin_size: int, initial_bin_size: Optional[int] = None) -> List[Bin]: + """bin-packing with first-fit-decreasing algorithm. + + Parameters + ---------- + items: list + items that need to be stored/allocated. + + bin_size: int + + Returns + ------- + list + Resulting bins + """ + if initial_bin_size is None: + initial_bin_size = bin_size + # bin_size = max(bin_size, initial_bin_size) + bins = [Bin(size=initial_bin_size)] # Initial bin + for item in sorted(items, key=lambda x: x.length, reverse=True): + if item.length > bin_size: + raise ValueError(f"Item {item!r} is too large to fit in a {bin_size} byte sized bin.") + for bin in bins: + if bin.residual_capacity >= item.length: + bin.append(item) + bin.residual_capacity -= item.length + break + else: + new_bin = Bin(size=bin_size) + bins.append(new_bin) + new_bin.append(item) + new_bin.residual_capacity -= item.length + return bins diff --git a/pyxcp/daq_stim/scheduler.cpp b/pyxcp/daq_stim/scheduler.cpp new file mode 100644 index 00000000..befe218a --- /dev/null +++ b/pyxcp/daq_stim/scheduler.cpp @@ -0,0 +1,28 @@ + +#include "scheduler.hpp" + +#if defined(_WIN32) + +VOID CALLBACK TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired) { + if (lpParam == NULL) { + printf("TimerRoutine lpParam is NULL\n"); + } else { + // lpParam points to the argument; in this case it is an int + + printf("Timer routine called. Parameter is %d.\n", *(int*)lpParam); + if (TimerOrWaitFired) { + printf("The wait timed out.\n"); + } else { + printf("The wait event was signaled.\n"); + } + } +} + + #include + +void mul4_vectorized(float* ptr) { + __m128 f = _mm_loadu_ps(ptr); + f = _mm_mul_ps(f, f); + _mm_storeu_ps(ptr, f); +} +#endif diff --git a/pyxcp/daq_stim/scheduler.hpp b/pyxcp/daq_stim/scheduler.hpp new file mode 100644 index 00000000..5a4fc009 --- /dev/null +++ b/pyxcp/daq_stim/scheduler.hpp @@ -0,0 +1,75 @@ + + +#ifndef STIM_SCHEDULER_HPP +#define STIM_SCHEDULER_HPP + +#if !defined(_CRT_SECURE_NO_WARNINGS) + #define _CRT_SECURE_NO_WARNINGS (1) +#endif + +#include + +#if defined(_WIN32) + #include + + #include + +VOID CALLBACK TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired); + +struct Scheduler { + Scheduler() = default; + ~Scheduler() = default; + + bool start_thread() noexcept { + if (timer_thread.joinable()) { + return false; + } + + m_TimerQueue = CreateTimerQueue(); + if (NULL == m_TimerQueue) { + printf("CreateTimerQueue failed (%d)\n", GetLastError()); + return false; + } + + // Set a timer to call the timer routine in 10 seconds. + if (!CreateTimerQueueTimer(&m_timer, m_TimerQueue, (WAITORTIMERCALLBACK)TimerRoutine, nullptr, 1, 500, 0)) { + printf("CreateTimerQueueTimer failed (%d)\n", GetLastError()); + return false; + } + + stop_timer_thread_flag = false; + timer_thread = std::jthread([this]() { + while (!stop_timer_thread_flag) { + printf("ENTER SLEEP loop!!!\n"); + SleepEx(INFINITE, TRUE); + stop_timer_thread_flag = TRUE; + } + }); + return true; + } + + bool stop_thread() noexcept { + if (!timer_thread.joinable()) { + return false; + } + stop_timer_thread_flag = true; + // my_queue.put(std::nullopt); + timer_thread.join(); + return true; + } + + std::jthread timer_thread{}; + bool stop_timer_thread_flag{}; + HANDLE m_timer{}; + HANDLE m_TimerQueue; +}; +#else + +struct Scheduler { + Scheduler() = default; + ~Scheduler() = default; +}; + +#endif + +#endif // STIM_SCHEDULER_HPP diff --git a/pyxcp/daq_stim/stim.cpp b/pyxcp/daq_stim/stim.cpp new file mode 100644 index 00000000..ca6e8624 --- /dev/null +++ b/pyxcp/daq_stim/stim.cpp @@ -0,0 +1,13 @@ + +#if defined(_MSC_VER) + #pragma comment(lib, "Winmm.lib") + #pragma comment(lib, "Avrt.lib") +#endif + +#include "stim.hpp" + +void make_dto() { +} + +void init() { +} diff --git a/pyxcp/daq_stim/stim.hpp b/pyxcp/daq_stim/stim.hpp new file mode 100644 index 00000000..1bfe0155 --- /dev/null +++ b/pyxcp/daq_stim/stim.hpp @@ -0,0 +1,604 @@ + +#if !defined(__STIM_HPP) + #define __STIM_HPP + + #include + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include "helper.hpp" + #include "scheduler.hpp" + + #if defined(_MSC_VER) && (defined(_WIN32) || defined(_WIN64)) + #include + #include + #endif + +namespace py = pybind11; + +constexpr double TMR_RESOLUTION = 1.0 / 1000.0; // Timer resolution is one millisecond. + +constexpr std::uint8_t MIN_STIM_PID = 0x00; +constexpr std::uint8_t MAX_STIM_PID = 0xBF; + +struct DAQListType {}; + +///// From BlueParrot XCP. + +using XcpDaq_ODTEntryIntegerType = std::uint16_t; +using XcpDaq_ODTIntegerType = std::uint16_t; + +typedef enum tagXcpDaq_DirectionType { + XCP_DIRECTION_NONE, + XCP_DIRECTION_DAQ, + XCP_DIRECTION_STIM, + XCP_DIRECTION_DAQ_STIM +} XcpDaq_DirectionType; + +//////////////// C++ style //////////////// + +struct OdtEntryType { + void clear() { + address = 0; + address_extension = 0; + bitOffset = 0; + entry_size = 0; + } + + std::uint32_t address; + std::uint16_t address_extension; + std::uint16_t bitOffset; + std::uint32_t entry_size; +}; + +struct OdtType { + XcpDaq_ODTEntryIntegerType numOdtEntries; + std::uint16_t firstOdtEntry; + + void clear() { + m_entries.resize(0); + } + + void resize(std::uint16_t n) { + m_entries.resize(n); + } + + std::vector m_entries; +}; + +struct DynamicListType { + void clear() { + numOdts = 0; + firstOdt = 0; + mode = 0; + prescaler = 0; + counter = 0; + event_channel_number = 0; + priority = 0; + m_odts.resize(0); + } + + void resize(std::size_t n) { + m_odts.resize(n); + } + + XcpDaq_ODTIntegerType numOdts; + std::uint16_t firstOdt; + std::uint16_t mode; + + std::uint16_t prescaler; + std::uint16_t event_channel_number; + std::uint16_t counter; + std::uint16_t priority; + + std::vector m_odts{}; +}; + +//////////////// C++ style //////////////// + +typedef struct tagXcpDaq_ListConfigurationType { + XcpDaq_ODTIntegerType numOdts; + std::uint16_t firstOdt; +} XcpDaq_ListConfigurationType; + +typedef struct tagXcpDaq_ListStateType { + std::uint16_t mode; + #if XCP_DAQ_ENABLE_PRESCALER == XCP_ON + std::uint16_t prescaler; + std::uint16_t counter; + #endif /* XCP_DAQ_ENABLE_PRESCALER */ +} XcpDaq_ListStateType; + +typedef enum tagXcpDaq_EntityKindType { + XCP_ENTITY_UNUSED, + XCP_ENTITY_DAQ_LIST, + XCP_ENTITY_ODT, + XCP_ENTITY_ODT_ENTRY +} XcpDaq_EntityKindType; + +typedef struct tagXcpDaq_EventType { + std::uint8_t const * const name; + std::uint8_t nameLen; + std::uint8_t properties; + std::uint8_t timeunit; + std::uint8_t cycle; + /* unit8_t priority; */ +} XcpDaq_EventType; + +typedef struct tagXcpDaq_MessageType { + std::uint8_t dlc; + std::uint8_t const * data; +} XcpDaq_MessageType; + +///// + +class FakeEnum { + public: + + FakeEnum(std::uint8_t value) : m_value(value) { + } + + const std::string get_name() const { + return std::string("STIM"); + } + + const std::uint8_t get_value() const { + return m_value; + } + + operator int() const { + return m_value; + } + + int bit_length() const { + return 8; + } + + py::bytes to_bytes(std::uint8_t length, std::string_view encoding) const { + std::stringstream ss; + + ss << m_value; + return py::bytes(ss.str()); + } + + private: + + std::uint8_t m_value; +}; + +struct StimParameters { + std::byte max_dto; +}; + +struct DaqEventInfo { + explicit DaqEventInfo( + /*std::string_view*/ const std::string& name, std::int8_t unit_exp, std::size_t cycle, std::size_t maxDaqList, + std::size_t priority, std::string_view consistency, bool daq, bool stim, bool packed + ) : + m_name(name), + m_unit_exp(unit_exp), + m_cycle(cycle), + m_maxDaqList(maxDaqList), + m_priority(priority), + m_consistency(consistency), + m_daq(daq), + m_stim(stim), + m_packed(packed) { + if (cycle == 0) { + m_periodic = false; + m_cycle_time = 0; + } else { + m_periodic = true; + auto cycle_time = cycle * std::pow(10, unit_exp); + if (cycle_time < TMR_RESOLUTION) { + cycle_time = TMR_RESOLUTION; + } + m_cycle_time = static_cast(cycle_time * 1000.0); + } + } + + public: + + std::string m_name; + std::int8_t m_unit_exp; + std::size_t m_cycle; + std::size_t m_maxDaqList; + std::size_t m_priority; + std::string m_consistency{}; + bool m_daq; + bool m_stim; + bool m_packed; + bool m_periodic; + std::size_t m_cycle_time; + std::set m_daq_lists{}; +}; + +enum class EventChannelTimeUnit : std::uint8_t { + EVENT_CHANNEL_TIME_UNIT_1NS = 0, + EVENT_CHANNEL_TIME_UNIT_10NS = 1, + EVENT_CHANNEL_TIME_UNIT_100NS = 2, + EVENT_CHANNEL_TIME_UNIT_1US = 3, + EVENT_CHANNEL_TIME_UNIT_10US = 4, + EVENT_CHANNEL_TIME_UNIT_100US = 5, + EVENT_CHANNEL_TIME_UNIT_1MS = 6, + EVENT_CHANNEL_TIME_UNIT_10MS = 7, + EVENT_CHANNEL_TIME_UNIT_100MS = 8, + EVENT_CHANNEL_TIME_UNIT_1S = 9, + EVENT_CHANNEL_TIME_UNIT_1PS = 10, + EVENT_CHANNEL_TIME_UNIT_10PS = 11, + EVENT_CHANNEL_TIME_UNIT_100PS = 12, +}; + +enum class DAQ_TIMESTAMP_UNIT_TO_EXP : std::int8_t { + DAQ_TIMESTAMP_UNIT_1PS = -12, + DAQ_TIMESTAMP_UNIT_10PS = -11, + DAQ_TIMESTAMP_UNIT_100PS = -10, + DAQ_TIMESTAMP_UNIT_1NS = -9, + DAQ_TIMESTAMP_UNIT_10NS = -8, + DAQ_TIMESTAMP_UNIT_100NS = -7, + DAQ_TIMESTAMP_UNIT_1US = -6, + DAQ_TIMESTAMP_UNIT_10US = -5, + DAQ_TIMESTAMP_UNIT_100US = -4, + DAQ_TIMESTAMP_UNIT_1MS = -3, + DAQ_TIMESTAMP_UNIT_10MS = -2, + DAQ_TIMESTAMP_UNIT_100MS = -1, + DAQ_TIMESTAMP_UNIT_1S = 0, +}; + +void sched_init(); // TODO: Incl. + +class Stim { + public: + + const std::uint8_t RESUME = 0x80; + const std::uint8_t RUNNING = 0x40; + const std::uint8_t PID_OFF = 0x20; + const std::uint8_t TIMESTAMP = 0x10; + const std::uint8_t DIRECTION = 0x02; + const std::uint8_t SELECTED = 0x01; + + const std::uint8_t DIRECTION_STIM = 0x02; + + using feed_function_t = std::function)>; + using send_function_t = std::function)>; + + explicit Stim(bool activate = true) : m_activate(activate) { + #if defined(_MSC_VER) && (defined(_WIN32) || defined(_WIN64)) + if (timeBeginPeriod(100) == TIMERR_NOERROR) { + // std::cout << "timeBeginPeriod() OK!!!" << std::endl; + } else { + // std::cout << "timeBeginPeriod() failed!!!" << std::endl; + } + + DWORD task_index = 0UL; + + auto xxx = AvSetMmThreadCharacteristics("Pro Audio", &task_index); + + auto start = timeGetTime(); + #endif + // m_scheduler.start_thread(); + } + + void setParameters(const StimParameters& params) { + m_params = params; + } + + void setDaqEventInfo(const std::vector& daq_event_info) { + std::uint16_t idx = 0; + + // DBG_PRINTN("SET_DAQ_EVENT_INFO\n"); + if (!m_activate) { + return; + } + + for (const auto& event : daq_event_info) { + m_daq_event_info.try_emplace(idx, event); + if (event.m_stim) { + // std::cout << "\tSTIM: " << event.m_name << ":" << idx << std::endl; + } + idx++; + } + } + + void setDaqPtr(std::uint16_t daqListNumber, std::uint16_t odtNumber, std::uint16_t odtEntryNumber) { + if (!m_activate) { + return; + } + + if (!validateEntryNumber(daqListNumber, odtNumber, odtEntryNumber)) { + return; + } + m_daq_ptr = { daqListNumber, odtNumber, odtEntryNumber }; + // DBG_PRINTN("SET_DAQ_PTR -- daq:", daqListNumber, " odt:", odtNumber, " entry:", odtEntryNumber, std::endl); + } + + void clearDaqList(std::uint16_t daqListNumber) { + if (!m_activate) { + return; + } + + if (!validateEntryNumber(daqListNumber)) { + return; + } + auto entry = m_daq_lists[daqListNumber]; + // DBG_PRINTN("CLEAR_DAQ_LIST -- daq:", daqListNumber, std::endl); + entry.clear(); + } + + void writeDaq(std::uint16_t bitOffset, std::uint16_t entrySize, std::uint16_t addressExt, std::uint32_t address) { + if (!m_activate) { + return; + } + + auto [d, o, e] = m_daq_ptr; + auto& entry = m_daq_lists[d].m_odts[o].m_entries[e]; + + // DBG_PRINTN("WRITE_DAQ -- bit-offset:", bitOffset, " size:", entrySize, " addr-ext:", addressExt," addr:", address, + // std::endl); + + entry.bitOffset = bitOffset; + entry.address = address; + entry.address_extension = addressExt; + entry.entry_size = entrySize; + #if 0 + std::cout << "\tBO: " << entry.bitOffset << std::endl; + std::cout << "\tES: " << entry.entry_size << std::endl; + std::cout << "\tAD: " << entry.address << std::endl; + std::cout << "\tAE: " << entry.address_extension << std::endl; + #endif + } + + void setDaqListMode( + std::uint16_t mode, std::uint16_t daqListNumber, std::uint16_t eventChannelNumber, std::uint16_t prescaler, + std::uint16_t priority + ) { + if (!m_activate) { + return; + } + if (!validateEntryNumber(daqListNumber)) { + throw std::runtime_error("Invalid DAQ list number: " + std::to_string(eventChannelNumber) + "\n"); + } + // std::cout << "SET_DAQ_LIST_MODE: " << mode << ":" << daqListNumber << ":" << eventChannelNumber << ":" << prescaler << + // ":" + // << priority << std::endl; + + auto& entry = m_daq_lists.at(daqListNumber); + + entry.mode = mode; + entry.prescaler = prescaler; + // The use of a prescaler is only used for DAQ lists with DIRECTION = DAQ. + + entry.priority = priority; + entry.event_channel_number = eventChannelNumber; + + if (!m_daq_event_info.contains(eventChannelNumber)) { + throw std::runtime_error("Invalid Event channel number: " + std::to_string(eventChannelNumber) + "\n"); + } + auto& event = m_daq_event_info.at(eventChannelNumber); + + event.m_daq_lists.emplace(daqListNumber); + + if ((mode & DIRECTION_STIM) == DIRECTION_STIM) { + // std::cout << "\tSTIM-MODE!!!\n"; + + m_stim_lists.emplace(daqListNumber); + + // std::cout << "\t\tEvent: " << event.m_name << " ==> " << event.m_cycle_time << " - " << event.m_periodic << + // std::endl; + calculate_scheduler_period(event.m_cycle_time); + } + } + + void startStopDaqList(std::uint16_t mode, std::uint16_t daqListNumber) { + if (!m_activate) { + return; + } + if (!validateEntryNumber(daqListNumber)) { + return; + } + auto& entry = m_daq_lists.at(daqListNumber); + /* + * 00 = stop + * 01 = start + * 02 = select + */ + if (mode == 0) { + entry.mode &= ~(SELECTED | RUNNING); + } else if (mode == 1) { + entry.mode |= (RUNNING); + } else if (mode == 2) { + entry.mode |= (SELECTED); + } + + // std::cout << "START_STOP_DAQ_LIST " << mode << ":" << daqListNumber << std::endl; + } + + void startStopSynch(std::uint16_t mode) { + if (!m_activate) { + return; + } + // std::cout << "START_STOP_SYNCH " << mode << ":" << std::endl; + + // send(0x00, { 0x04, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0xa0, 0xb0 }); + + for (auto dl : m_stim_lists) { + // std::cout << "\tRunning list: " << dl << std::endl; + } + auto idx = 0; + for (auto dl : m_daq_lists) { + // std::cout << "DAQ-L: " << idx << " " << dl.mode << " - " << dl.event_channel_number << ":" << dl.prescaler << + // std::endl; + idx++; + } + } + + void writeDaqMultiple(/* const std::vector&*/ std::uint16_t daqElements) { + if (!m_activate) { + return; + } + } + + void dtoCtrProperties( + std::uint16_t modifier, std::uint16_t eventChannel, std::uint16_t relatedEventChannel, std::uint16_t mode + ) { + if (!m_activate) { + return; + } + } + + void setDaqPackedMode( + std::uint16_t, std::uint16_t daqListNumber, std::uint16_t daqPackedMode, std::uint16_t dpmTimestampMode = 0, + std::uint16_t dpmSampleCount = 0 + ) { + if (!m_activate) { + return; + } + if (!validateEntryNumber(daqListNumber)) { + return; + } + // PACKED_MODE + } + + void clear() { + if (!m_activate) { + return; + } + m_daq_lists.clear(); + m_stim_lists.clear(); + m_first_pids.clear(); + } + + void freeDaq() { + if (!m_activate) { + return; + } + clear(); + } + + void allocDaq(std::uint16_t daqCount) { + if (!m_activate) { + return; + } + m_daq_lists.resize(daqCount); + std::for_each(m_daq_lists.cbegin(), m_daq_lists.cend(), [](auto elem) { elem.clear(); }); + } + + void allocOdt(std::uint16_t daqListNumber, std::uint16_t odtCount) { + if (!m_activate) { + return; + } + if (!validateEntryNumber(daqListNumber)) { + return; + } + m_daq_lists[daqListNumber].resize(odtCount); + } + + void allocOdtEntry(std::uint16_t daqListNumber, std::uint16_t odtNumber, std::uint16_t odtEntriesCount) { + if (!m_activate) { + return; + } + if (!validateEntryNumber(daqListNumber, odtNumber)) { + return; + } + m_daq_lists[daqListNumber].m_odts[odtNumber].resize(odtEntriesCount); + } + + void set_first_pid(std::uint16_t daqListNumber, std::uint16_t firstPid) { + if (!m_activate) { + return; + } + m_first_pids[daqListNumber] = firstPid; + } + + void set_policy_feeder(const feed_function_t& fun) { + m_feed_function = fun; + } + + void set_frame_sender(const send_function_t& fun) { + m_send_function = fun; + } + + void send(std::uint8_t stim_list_num, const std::vector frame) { + if (!m_activate) { + return; + } + if (m_send_function) { + m_send_function.value()(FakeEnum(stim_list_num), frame); + } + } + + protected: + + void calculate_scheduler_period(std::size_t value) { + if (!m_activate) { + return; + } + if (!m_scheduler_period) { + m_scheduler_period = value; + } + if (!m_scheduler_max_value) { + m_scheduler_max_value = value; + } + + // std::cout << "\tSCHED_Value: " << value << " - BEFORE: " << *m_scheduler_period << ":" << *m_scheduler_max_value << + // std::endl; + m_scheduler_period = std::gcd(*m_scheduler_period, value); + m_scheduler_max_value = std::lcm(*m_scheduler_max_value, value); + // std::cout << "\tSCHED_Period: " << *m_scheduler_period << " max: " << *m_scheduler_max_value << std::endl; + } + + bool validateEntryNumber( + std::uint16_t daqListNumber, std::optional odtNumber = std::nullopt, + std::optional odtEntryNumber = std::nullopt + ) const { + if (!m_activate) { + return false; + } + if (daqListNumber >= std::size(m_daq_lists)) { + return false; + } + if (odtNumber) { + auto entry = m_daq_lists[daqListNumber]; + if (*odtNumber >= std::size(entry.m_odts)) { + return false; + } + } + if (odtEntryNumber) { + auto entry = m_daq_lists[daqListNumber].m_odts[*odtNumber]; + if (*odtEntryNumber >= std::size(entry.m_entries)) { + return false; + } + } + return true; + } + + private: + + bool m_activate; + StimParameters m_params{}; + std::vector m_daq_lists{}; + std::tuple m_daq_ptr; + std::optional m_scheduler_period{ std::nullopt }; + std::optional m_scheduler_max_value{ std::nullopt }; + std::map m_daq_event_info; + std::map m_first_pids{}; + std::set m_stim_lists{}; + std::optional m_feed_function{ std::nullopt }; + std::optional m_send_function{ std::nullopt }; + // Scheduler m_scheduler{}; + // bool m_daq_running{ false }; +}; + +#endif // __STIM_HPP diff --git a/pyxcp/daq_stim/stim_wrapper.cpp b/pyxcp/daq_stim/stim_wrapper.cpp new file mode 100644 index 00000000..6938ab99 --- /dev/null +++ b/pyxcp/daq_stim/stim_wrapper.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +#include + +namespace py = pybind11; +using namespace py::literals; + +#if defined(_MSC_VER) + #pragma warning(disable: 4251 4273) +#endif + +#include "stim.hpp" + +PYBIND11_MODULE(stim, m) { + py::class_(m, "DaqEventInfo") + .def(py::init() + ); + + py::class_(m, "Stim") + .def(py::init()) + .def("setDaqEventInfo", &Stim::setDaqEventInfo) + .def("clear", &Stim::clear) + .def("freeDaq", &Stim::freeDaq) + .def("allocDaq", &Stim::allocDaq) + .def("allocOdt", &Stim::allocOdt) + .def("allocOdtEntry", &Stim::allocOdtEntry) + .def("setDaqPtr", &Stim::setDaqPtr) + .def("clearDaqList", &Stim::clearDaqList) + .def("writeDaq", &Stim::writeDaq) + .def("setDaqListMode", &Stim::setDaqListMode) + .def("startStopDaqList", &Stim::startStopDaqList) + .def("startStopSynch", &Stim::startStopSynch) + + .def("set_first_pid", &Stim::set_first_pid) + .def("set_policy_feeder", [](Stim& self, const py::function& callback) { self.set_policy_feeder(callback); }) + .def("set_frame_sender", [](Stim& self, const py::function& callback) { self.set_frame_sender(callback); }); + + py::class_(m, "FakeEnum") + .def(py::init()) + .def_property_readonly("name", &FakeEnum::get_name) + .def_property_readonly("value", &FakeEnum::get_value) + .def("bit_length", &FakeEnum::bit_length) + .def("to_bytes", &FakeEnum::to_bytes) + .def("__int__", [](const FakeEnum& self) { return self.get_value(); }); + ; +} diff --git a/pyxcp/dllif.py b/pyxcp/dllif.py index 92584e70..afc17660 100644 --- a/pyxcp/dllif.py +++ b/pyxcp/dllif.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import binascii import ctypes import enum import platform import re -import subprocess +import subprocess # nosec import sys from pathlib import Path @@ -24,31 +23,30 @@ class SeedNKeyError(Exception): """""" -LOADER = Path(sys.modules["pyxcp"].__file__).parent / "asamkeydll" # Absolute path to DLL loader. +LOADER = Path(str(sys.modules["pyxcp"].__file__)).parent / "asamkeydll" # Absolute path to DLL loader. bwidth, _ = platform.architecture() -if sys.platform in ("win32", "linux"): +if sys.platform in ("win32", "linux", "darwin"): if bwidth == "64bit": use_ctypes = False elif bwidth == "32bit": use_ctypes = True else: - raise RuntimeError("Platform '{}' currently not supported.".format(sys.platform)) + raise RuntimeError(f"Platform {sys.platform!r} currently not supported.") -def getKey(logger, dllName: str, privilege: int, seed: str, assume_same_bit_width: bool): - +def getKey(logger, dllName: str, privilege: int, seed: bytes, assume_same_bit_width: bool): dllName = str(Path(dllName).absolute()) # Fix loader issues. - use_ctypes = False + use_ctypes: bool = False if assume_same_bit_width: use_ctypes = True if use_ctypes: try: - lib = ctypes.cdll.LoadLibrary(dllName) + lib: ctypes.CDLL = ctypes.cdll.LoadLibrary(dllName) except OSError: - logger.error(f"Could not load DLL '{dllName}' -- Probably an 64bit vs 32bit issue?") + logger.error(f"Could not load DLL {dllName!r} -- Probably an 64bit vs 32bit issue?") return (SeedNKeyResult.ERR_COULD_NOT_LOAD_DLL, None) func = lib.XCP_ComputeKeyFromSeed func.restype = ctypes.c_uint32 @@ -59,9 +57,9 @@ def getKey(logger, dllName: str, privilege: int, seed: str, assume_same_bit_widt ctypes.POINTER(ctypes.c_uint8), ctypes.c_char_p, ] - key_buffer = ctypes.create_string_buffer(b"\000" * 128) - key_length = ctypes.c_uint8(128) - ret_code = func( + key_buffer: ctypes.Array[ctypes.c_char] = ctypes.create_string_buffer(b"\000" * 128) + key_length: ctypes.c_uint8 = ctypes.c_uint8(128) + ret_code: int = func( privilege, len(seed), ctypes.c_char_p(seed), @@ -75,16 +73,21 @@ def getKey(logger, dllName: str, privilege: int, seed: str, assume_same_bit_widt [LOADER, dllName, str(privilege), binascii.hexlify(seed).decode("ascii")], stdout=subprocess.PIPE, shell=False, - ) + ) # nosec except FileNotFoundError as exc: - logger.error(f"Could not find executable '{LOADER}' -- {exc}") + logger.error(f"Could not find executable {LOADER!r} -- {exc}") return (SeedNKeyResult.ERR_COULD_NOT_LOAD_DLL, None) except OSError as exc: - logger.error(f"Cannot execute {LOADER} -- {exc}") + logger.error(f"Cannot execute {LOADER!r} -- {exc}") return (SeedNKeyResult.ERR_COULD_NOT_LOAD_DLL, None) - key = p0.stdout.read() + key: bytes = b"" + if p0.stdout: + key = p0.stdout.read() + p0.stdout.close() + p0.kill() + p0.wait() if not key: - logger.error(f"Something went wrong while calling seed-and-key-DLL '{dllName}'") + logger.error(f"Something went wrong while calling seed-and-key-DLL {dllName!r}") return (SeedNKeyResult.ERR_COULD_NOT_LOAD_DLL, None) res = re.split(b"\r?\n", key) returnCode = int(res[0]) diff --git a/pyxcp/errormatrix.py b/pyxcp/errormatrix.py index c95dcf8c..1c8f2ba2 100644 --- a/pyxcp/errormatrix.py +++ b/pyxcp/errormatrix.py @@ -1,11 +1,11 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Types and structures to support error-handling as specified by XCP. """ import enum from collections import namedtuple -from pyxcp.types import Command -from pyxcp.types import XcpError + +from pyxcp.types import Command, XcpError + Handler = namedtuple("Handler", "preAction action") @@ -257,6 +257,7 @@ class Severity(enum.IntEnum): ), }, Command.TRANSPORT_LAYER_CMD: { + XcpError.ERR_CMD_UNKNOWN: ((PreAction.NONE), Action.DISPLAY_ERROR), XcpError.ERR_TIMEOUT: ((PreAction.SYNCH,), Action.REPEAT_2_TIMES), XcpError.ERR_CMD_BUSY: ((PreAction.WAIT_T7), Action.REPEAT_INF_TIMES), XcpError.ERR_PGM_ACTIVE: ((PreAction.WAIT_T7), Action.REPEAT_INF_TIMES), @@ -268,6 +269,7 @@ class Severity(enum.IntEnum): ), }, Command.USER_CMD: { + XcpError.ERR_CMD_UNKNOWN: ((PreAction.NONE), Action.DISPLAY_ERROR), XcpError.ERR_TIMEOUT: ((PreAction.SYNCH,), Action.REPEAT_2_TIMES), XcpError.ERR_CMD_BUSY: ((PreAction.WAIT_T7), Action.REPEAT_INF_TIMES), XcpError.ERR_PGM_ACTIVE: ((PreAction.WAIT_T7), Action.REPEAT_INF_TIMES), diff --git a/pyxcp/examples/conf_can.json b/pyxcp/examples/conf_can.json deleted file mode 100644 index 2b587987..00000000 --- a/pyxcp/examples/conf_can.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "TRANSPORT": "CAN", - "CAN_DRIVER": "Kvaser", - "CAN_USE_DEFAULT_LISTENER": true, - "CHANNEL": 0, - "ACCEPT_VIRTUAL": true, - "BAUDRATE_PRESET": true, - "CAN_ID_MASTER": 257, - "CAN_ID_SLAVE": 258, - "CAN_ID_BROADCAST": 256, - "MAX_DLC_REQUIRED": false, - "BITRATE": 250000, - "BTL_CYCLES": 16, - "SAMPLE_RATE": 1, - "SAMPLE_POINT": 87.5, - "SJW": 2, - "TSEG1": 5, - "TSEG2": 2, - "CREATE_DAQ_TIMESTAMPS": false -} diff --git a/pyxcp/examples/conf_can.toml b/pyxcp/examples/conf_can.toml index 1172d147..c8640c8f 100644 --- a/pyxcp/examples/conf_can.toml +++ b/pyxcp/examples/conf_can.toml @@ -1,5 +1,5 @@ TRANSPORT = "CAN" -CAN_DRIVER = "Kvaser" +CAN_DRIVER = "KVaser" CAN_USE_DEFAULT_LISTENER = true CHANNEL = "0" ACCEPT_VIRTUAL = true @@ -7,7 +7,9 @@ CAN_ID_MASTER = 257 CAN_ID_SLAVE = 258 CAN_ID_BROADCAST = 256 MAX_DLC_REQUIRED = false -BITRATE = 250000 +BITRATE = 50000 +DATA_BITRATE = 50000 +FD=false BTL_CYCLES = 16 SAMPLE_RATE = 1 SAMPLE_POINT = 87.5 diff --git a/pyxcp/examples/conf_can_vector.json b/pyxcp/examples/conf_can_vector.json index 6c19a8bb..4eda4766 100644 --- a/pyxcp/examples/conf_can_vector.json +++ b/pyxcp/examples/conf_can_vector.json @@ -1,11 +1,11 @@ { - "TRANSPORT": "CAN", - "CAN_DRIVER": "Vector", - "CAN_USE_DEFAULT_LISTENER": true, - "CHANNEL": "1", - "CAN_ID_MASTER": 2, - "CAN_ID_SLAVE": 1, - "CAN_ID_BROADCAST": 256, - "MAX_DLC_REQUIRED": false, - "CREATE_DAQ_TIMESTAMPS": false + "TRANSPORT": "CAN", + "CAN_DRIVER": "Vector", + "CAN_USE_DEFAULT_LISTENER": true, + "CHANNEL": "1", + "CAN_ID_MASTER": 2, + "CAN_ID_SLAVE": 1, + "CAN_ID_BROADCAST": 256, + "MAX_DLC_REQUIRED": false, + "CREATE_DAQ_TIMESTAMPS": false } diff --git a/pyxcp/examples/conf_can_vector.toml b/pyxcp/examples/conf_can_vector.toml index 53dc1f12..59aeebc7 100644 --- a/pyxcp/examples/conf_can_vector.toml +++ b/pyxcp/examples/conf_can_vector.toml @@ -1,9 +1,11 @@ TRANSPORT = "CAN" CAN_DRIVER = "Vector" CAN_USE_DEFAULT_LISTENER = true -CHANNEL = "1" +CHANNEL = "0" CAN_ID_MASTER = 2 CAN_ID_SLAVE = 1 CAN_ID_BROADCAST = 256 -MAX_DLC_REQUIRED = false +# MAX_DLC_REQUIRED = true CREATE_DAQ_TIMESTAMPS = false +BITRATE=1000000 +SEED_N_KEY_DLL = "SeedNKeyXcp.dll" diff --git a/pyxcp/examples/conf_eth.json b/pyxcp/examples/conf_eth.json deleted file mode 100644 index 8a600255..00000000 --- a/pyxcp/examples/conf_eth.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "TRANSPORT": "ETH", - "HOST": "localhost", - "PORT": 5555, - "PROTOCOL": "TCP", - "IPV6": false, - "CREATE_DAQ_TIMESTAMPS": false -} diff --git a/pyxcp/examples/conf_eth.toml b/pyxcp/examples/conf_eth.toml index 6c16717c..bfd9cafc 100644 --- a/pyxcp/examples/conf_eth.toml +++ b/pyxcp/examples/conf_eth.toml @@ -1,6 +1,9 @@ TRANSPORT = "ETH" -HOST = "localhost" +#HOST = "192.168.197.40" +#HOST = "192.168.198.175" +HOST="localhost" PORT = 5555 PROTOCOL = "UDP" IPV6 = false -CREATE_DAQ_TIMESTAMPS = false +CREATE_DAQ_TIMESTAMPS = true +SEED_N_KEY_DLL="SeedNKeyXcp.dll" diff --git a/pyxcp/examples/conf_nixnet.json b/pyxcp/examples/conf_nixnet.json index 7223d61d..a4fbed45 100644 --- a/pyxcp/examples/conf_nixnet.json +++ b/pyxcp/examples/conf_nixnet.json @@ -1,20 +1,20 @@ { - "TRANSPORT": "CAN", - "CAN_DRIVER": "NiXnet", - "CAN_USE_DEFAULT_LISTENER": true, - "CHANNEL": "CAN4", - "ACCEPT_VIRTUAL": true, - "BAUDRATE_PRESET": true, - "CAN_ID_MASTER": 1911, - "CAN_ID_SLAVE": 819, - "CAN_ID_BROADCAST": 256, - "MAX_DLC_REQUIRED": false, - "BITRATE": 500000, - "BTL_CYCLES": 16, - "SAMPLE_RATE": 1, - "SAMPLE_POINT": 87.5, - "SJW": 2, - "TSEG1": 5, - "TSEG2": 2, - "CREATE_DAQ_TIMESTAMPS": false + "TRANSPORT": "CAN", + "CAN_DRIVER": "NiXnet", + "CAN_USE_DEFAULT_LISTENER": true, + "CHANNEL": "CAN4", + "ACCEPT_VIRTUAL": true, + "BAUDRATE_PRESET": true, + "CAN_ID_MASTER": 1911, + "CAN_ID_SLAVE": 819, + "CAN_ID_BROADCAST": 256, + "MAX_DLC_REQUIRED": false, + "BITRATE": 500000, + "BTL_CYCLES": 16, + "SAMPLE_RATE": 1, + "SAMPLE_POINT": 87.5, + "SJW": 2, + "TSEG1": 5, + "TSEG2": 2, + "CREATE_DAQ_TIMESTAMPS": false } diff --git a/pyxcp/examples/conf_sxi.json b/pyxcp/examples/conf_sxi.json index 2c94135b..29776af4 100644 --- a/pyxcp/examples/conf_sxi.json +++ b/pyxcp/examples/conf_sxi.json @@ -1,9 +1,9 @@ { - "TRANSPORT": "SXI", - "PORT": "COM10", - "BITRATE": 38400, - "BYTESIZE": 8, - "PARITY": "N", - "STOPBITS": 1, - "CREATE_DAQ_TIMESTAMPS": false + "TRANSPORT": "SXI", + "PORT": "COM10", + "BITRATE": 38400, + "BYTESIZE": 8, + "PARITY": "N", + "STOPBITS": 1, + "CREATE_DAQ_TIMESTAMPS": false } diff --git a/pyxcp/examples/ex_arrow.py b/pyxcp/examples/ex_arrow.py new file mode 100644 index 00000000..04b30518 --- /dev/null +++ b/pyxcp/examples/ex_arrow.py @@ -0,0 +1,109 @@ +import argparse +import logging +from array import array +from dataclasses import dataclass, field +from typing import Any, List + +import pyarrow as pa +import pyarrow.parquet as pq + +from pyxcp.recorder import XcpLogFileDecoder + + +MAP_TO_ARROW = { + "U8": pa.uint8(), + "I8": pa.int8(), + "U16": pa.uint16(), + "I16": pa.int16(), + "U32": pa.uint32(), + "I32": pa.int32(), + "U64": pa.uint64(), + "I64": pa.int64(), + "F32": pa.float32(), + "F64": pa.float64(), + "F16": pa.float16(), + "BF16": pa.float16(), +} + +MAP_TO_ARRAY = { + "U8": "B", + "I8": "b", + "U16": "H", + "I16": "h", + "U32": "L", + "I32": "l", + "U64": "Q", + "I64": "q", + "F32": "f", + "F64": "d", + "F16": "f", + "BF16": "f", +} + +logger = logging.getLogger("PyXCP") + +parser = argparse.ArgumentParser(description="Use .xmraw files in an Apache Arrow application.") +parser.add_argument("xmraw_file", help=".xmraw file") +args = parser.parse_args() + + +@dataclass +class Storage: + name: str + arrow_type: Any + arr: array + + +@dataclass +class StorageContainer: + name: str + arr: List[Storage] = field(default_factory=[]) + ts0: List[int] = field(default_factory=lambda: array("Q")) + ts1: List[int] = field(default_factory=lambda: array("Q")) + + +class Decoder(XcpLogFileDecoder): + + def initialize(self) -> None: + self.arrow_tables = [] + for dl in self.daq_lists: + result = [] + for name, type_str in dl.headers: + array_txpe = MAP_TO_ARRAY[type_str] + arrow_type = MAP_TO_ARROW[type_str] + sd = Storage(name, arrow_type, array(array_txpe)) + print(f"\t{name!r} {array_txpe} {arrow_type}", sd) + result.append(sd) + sc = StorageContainer(dl.name, result) + self.arrow_tables.append(sc) + + def finalize(self) -> Any: + result = [] + for arr in self.arrow_tables: + timestamp0 = arr.ts0 + timestamp1 = arr.ts1 + names = ["timestamp0", "timestamp1"] + data = [timestamp0, timestamp1] + for sd in arr.arr: + adt = pa.array(sd.arr, type=sd.arrow_type) + names.append(sd.name) + data.append(adt) + table = pa.Table.from_arrays(data, names=names) + fname = f"{arr.name}.parquet" + print("Writing table", fname) + pq.write_table(table, fname) + print("done.", table.shape) + result.append(table) + return result + + def on_daq_list(self, daq_list_num: int, timestamp0: int, timestamp1: int, measurements: list) -> None: + sc = self.arrow_tables[daq_list_num] + sc.ts0.append(timestamp0) + sc.ts1.append(timestamp1) + for idx, elem in enumerate(measurements): + sto = sc.arr[idx] + sto.arr.append(elem) + + +decoder = Decoder(args.xmraw_file) +res = decoder.run() diff --git a/pyxcp/examples/ex_mdf.py b/pyxcp/examples/ex_mdf.py new file mode 100644 index 00000000..0129806c --- /dev/null +++ b/pyxcp/examples/ex_mdf.py @@ -0,0 +1,124 @@ +import argparse +import logging +from array import array +from dataclasses import dataclass, field +from pathlib import Path +from typing import Any, List + +import numpy as np +from asammdf import MDF, Signal +from asammdf.blocks.v4_blocks import HeaderBlock # ChannelGroup +from asammdf.blocks.v4_constants import FLAG_HD_TIME_OFFSET_VALID # FLAG_HD_LOCAL_TIME, + +from pyxcp.recorder import XcpLogFileDecoder + + +MAP_TO_NP = { + "U8": np.uint8, + "I8": np.int8, + "U16": np.uint16, + "I16": np.int16, + "U32": np.uint32, + "I32": np.int32, + "U64": np.uint64, + "I64": np.int64, + "F32": np.float32, + "F64": np.float64, + "F16": np.float16, + "BF16": np.float16, +} + +MAP_TO_ARRAY = { + "U8": "B", + "I8": "b", + "U16": "H", + "I16": "h", + "U32": "L", + "I32": "l", + "U64": "Q", + "I64": "q", + "F32": "f", + "F64": "d", + "F16": "f", + # "BF16" +} + +logger = logging.getLogger("PyXCP") + +parser = argparse.ArgumentParser(description="Use .xmraw files in an Apache Arrow application.") +parser.add_argument("xmraw_file", help=".xmraw file") +args = parser.parse_args() + + +@dataclass +class Storage: + name: str + arrow_type: Any + arr: array + + +@dataclass +class StorageContainer: + name: str + arr: list[Storage] = field(default_factory=[]) + ts0: List[int] = field(default_factory=lambda: array("Q")) + ts1: List[int] = field(default_factory=lambda: array("Q")) + + +class Decoder(XcpLogFileDecoder): + + def __init__(self, recording_file_name: str): + super().__init__(recording_file_name) + self.mdf_file_name = Path(recording_file_name).with_suffix(".mf4") + + def initialize(self) -> None: + self.tables = [] + for dl in self.daq_lists: + result = [] + for name, type_str in dl.headers: + array_txpe = MAP_TO_ARRAY[type_str] + arrow_type = MAP_TO_NP[type_str] + sd = Storage(name, arrow_type, array(array_txpe)) + result.append(sd) + sc = StorageContainer(dl.name, result) + self.tables.append(sc) + print("Extracting DAQ lists...") + + def finalize(self) -> None: + print("Creating MDF result...") + timestamp_info = self.parameters.timestamp_info + hdr = HeaderBlock( + abs_time=timestamp_info.timestamp_ns, + tz_offset=timestamp_info.utc_offset, + daylight_save_time=timestamp_info.dst_offset, + time_flags=FLAG_HD_TIME_OFFSET_VALID, + ) + hdr.comment = f"""Timezone: {timestamp_info.timezone}""" # Test-Comment. + mdf4 = MDF(version="4.10") + mdf4.header = hdr + # result = [] + for idx, arr in enumerate(self.tables): + signals = [] + timestamps = arr.ts0 + for sd in arr.arr: + + signal = Signal(samples=sd.arr, name=sd.name, timestamps=timestamps) + signals.append(signal) + print(f"Appending data-group {arr.name!r}") + mdf4.append(signals, acq_name=arr.name, comment="Created by pyXCP recorder") + print(f"Writing '{self.mdf_file_name!s}'") + mdf4.save(self.mdf_file_name, compression=2, overwrite=True) + print("Done.") + return mdf4 + + def on_daq_list(self, daq_list_num: int, timestamp0: int, timestamp1: int, measurements: list) -> None: + sc = self.tables[daq_list_num] + sc.ts0.append(timestamp0) + sc.ts1.append(timestamp1) + for idx, elem in enumerate(measurements): + sto = sc.arr[idx] + sto.arr.append(elem) + + +decoder = Decoder(args.xmraw_file) +res = decoder.run() diff --git a/pyxcp/examples/ex_sqlite.py b/pyxcp/examples/ex_sqlite.py new file mode 100644 index 00000000..24c78ec9 --- /dev/null +++ b/pyxcp/examples/ex_sqlite.py @@ -0,0 +1,128 @@ +import argparse +import logging +import os +import sqlite3 +from array import array +from dataclasses import dataclass, field +from mmap import PAGESIZE +from pathlib import Path +from typing import Any, List + +from pyxcp.recorder import XcpLogFileDecoder +from pyxcp.recorder.converter import MAP_TO_ARRAY + + +MAP_TO_SQL = { + "U8": "INTEGER", + "I8": "INTEGER", + "U16": "INTEGER", + "I16": "INTEGER", + "U32": "INTEGER", + "I32": "INTEGER", + "U64": "INTEGER", + "I64": "INTEGER", + "F32": "FLOAT", + "F64": "FLOAT", + "F16": "FLOAT", + "BF16": "FLOAT", +} + +logger = logging.getLogger("PyXCP") + +parser = argparse.ArgumentParser(description="Use .xmraw files in an Apache Arrow application.") +parser.add_argument("xmraw_file", help=".xmraw file") +args = parser.parse_args() + + +@dataclass +class Storage: + name: str + arrow_type: Any + arr: array + + +@dataclass +class StorageContainer: + name: str + arr: List[Storage] = field(default_factory=[]) + ts0: List[int] = field(default_factory=lambda: array("Q")) + ts1: List[int] = field(default_factory=lambda: array("Q")) + + +class Decoder(XcpLogFileDecoder): + + def __init__(self, recording_file_name: str): + super().__init__(recording_file_name) + self.sq3_file_name = Path(recording_file_name).with_suffix(".sq3") + try: + os.unlink(self.sq3_file_name) + except Exception as e: + print(e) + + def initialize(self) -> None: + self.create_database(self.sq3_file_name) + self.arrow_tables = [] + self.insert_stmt = {} + for dl in self.daq_lists: + result = [] + for name, type_str in dl.headers: + array_txpe = MAP_TO_ARRAY[type_str] + sql_type = MAP_TO_SQL[type_str] + sd = Storage(name, sql_type, array(array_txpe)) + result.append(sd) + sc = StorageContainer(dl.name, result) + print(f"Creating table {sc.name!r}.") + self.create_table(sc) + self.insert_stmt[sc.name] = ( + f"""INSERT INTO {sc.name}({', '.join(['ts0', 'ts1'] + [r.name for r in sc.arr])}) VALUES({', '.join(["?" for _ in range(len(sc.arr) + 2)])})""" + ) + self.arrow_tables.append(sc) + print("\nInserting data...") + + def create_database(self, db_name: str) -> None: + self.conn = sqlite3.Connection(db_name) + self.cursor = self.conn.cursor() + self.execute("PRAGMA FOREIGN_KEYS=ON") + self.execute(f"PRAGMA PAGE_SIZE={PAGESIZE}") + self.execute("PRAGMA SYNCHRONOUS=OFF") + self.execute("PRAGMA LOCKING_MODE=EXCLUSIVE") + self.execute("PRAGMA TEMP_STORE=MEMORY") + + timestamp_info = self.parameters.timestamp_info + self.execute( + "CREATE TABLE timestamp_info(timestamp_ns INTEGER, utc_offset INTEGER, dst_offset INTEGER, timezone VARCHAR(255))" + ) + self.execute("CREATE TABLE table_names(name VARCHAR(255))") + self.execute( + "INSERT INTO timestamp_info VALUES(?, ?, ?, ?)", + [timestamp_info.timestamp_ns, timestamp_info.utc_offset, timestamp_info.dst_offset, timestamp_info.timezone], + ) + + def create_table(self, sc: StorageContainer) -> None: + columns = ["ts0 INTEGER", "ts1 INTEGER"] + for elem in sc.arr: + columns.append(f"{elem.name} {elem.arrow_type}") + ddl = f"CREATE TABLE {sc.name}({', '.join(columns)})" + self.execute(ddl) + self.execute("INSERT INTO table_names VALUES(?)", [sc.name]) + + def execute(self, *args: List[str]) -> None: + try: + self.cursor.execute(*args) + except Exception as e: + print(e) + + def finalize(self) -> None: + self.conn.commit() + self.conn.close() + print("Done.") + + def on_daq_list(self, daq_list_num: int, timestamp0: int, timestamp1: int, measurements: list) -> None: + sc = self.arrow_tables[daq_list_num] + insert_stmt = self.insert_stmt[sc.name] + data = [timestamp0, timestamp1, *measurements] + self.execute(insert_stmt, data) + + +decoder = Decoder(args.xmraw_file) +decoder.run() diff --git a/pyxcp/examples/run_daq.py b/pyxcp/examples/run_daq.py new file mode 100644 index 00000000..52f3bda4 --- /dev/null +++ b/pyxcp/examples/run_daq.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python + +import time + +from pyxcp.cmdline import ArgumentParser +from pyxcp.daq_stim import DaqList, DaqRecorder, DaqToCsv # noqa: F401 + + +ap = ArgumentParser(description="DAQ test") + +XCP_LITE = True + +# +# NOTE: UPDATE TO CORRECT ADDRESSES BEFORE RUNNING!!! +# +if XCP_LITE: + # Vectorgrp XCPlite. + DAQ_LISTS = [ + DaqList( + "part_1", + 0, + False, + False, + [ + ("byteCounter", 0x203EA, 0, "U8"), + ("wordCounter", 0x203EC, 0, "U16"), + ("dwordCounter", 0x20410, 0, "U32"), + ("sbyteCounter", 0x203EB, 0, "I8"), + ], + ), + DaqList( + "part_2", + 0, + False, + False, + [ + ("swordCounter", 0x20414, 0, "I16"), + ("sdwordCounter", 0x20418, 0, "I32"), + ("channel1", 0x203F8, 0, "F64"), + ("channel2", 0x20400, 0, "F64"), + ("channel3", 0x20408, 0, "F64"), + ], + ), + ] +else: + # XCPsim from CANape. + DAQ_LISTS = [ + DaqList( + "pwm_stuff", + 2, + False, + True, + [ + ("channel1", 0x1BD004, 0, "F32"), + ("period", 0x001C0028, 0, "F32"), + ("channel2", 0x1BD008, 0, "F32"), + ("PWMFiltered", 0x1BDDE2, 0, "U8"), + ("PWM", 0x1BDDDF, 0, "U8"), + ("Triangle", 0x1BDDDE, 0, "I8"), + ], + ), + DaqList( + "bytes", + 1, + False, + True, + [ + ("TestByte_000", 0x1BE11C, 0, "U8"), + ("TestByte_015", 0x1BE158, 0, "U8"), + ("TestByte_016", 0x1BE15C, 0, "U8"), + ("TestByte_023", 0x1BE178, 0, "U8"), + ("TestByte_024", 0x1BE17C, 0, "U8"), + ("TestByte_034", 0x1BE1A4, 0, "U8"), + ("TestByte_059", 0x1BE208, 0, "U8"), + ("TestByte_061", 0x1BE210, 0, "U8"), + ("TestByte_063", 0x1BE218, 0, "U8"), + ("TestByte_064", 0x1BE21C, 0, "U8"), + ("TestByte_097", 0x1BE2A0, 0, "U8"), + ("TestByte_107", 0x1BE2C8, 0, "U8"), + ("TestByte_131", 0x1BE328, 0, "U8"), + ("TestByte_156", 0x1BE38C, 0, "U8"), + ("TestByte_159", 0x1BE398, 0, "U8"), + ("TestByte_182", 0x1BE3F4, 0, "U8"), + ("TestByte_183", 0x1BE3F8, 0, "U8"), + ("TestByte_189", 0x1BE410, 0, "U8"), + ("TestByte_195", 0x1BE428, 0, "U8"), + ("TestByte_216", 0x1BE47C, 0, "U8"), + ("TestByte_218", 0x1BE484, 0, "U8"), + ("TestByte_221", 0x1BE490, 0, "U8"), + ("TestByte_251", 0x1BE508, 0, "U8"), + ("TestByte_263", 0x1BE538, 0, "U8"), + ("TestByte_276", 0x1BE56C, 0, "U8"), + ("TestByte_277", 0x1BE570, 0, "U8"), + ("TestByte_297", 0x1BE5C0, 0, "U8"), + ("TestByte_302", 0x1BE5D4, 0, "U8"), + ("TestByte_324", 0x1BE62C, 0, "U8"), + ("TestByte_344", 0x1BE67C, 0, "U8"), + ("TestByte_346", 0x1BE684, 0, "U8"), + ], + ), + DaqList( + "words", + 3, + False, + True, + [ + ("TestWord_001", 0x1BE120, 0, "U16"), + ("TestWord_003", 0x1BE128, 0, "U16"), + ("TestWord_004", 0x1BE12C, 0, "U16"), + ("TestWord_005", 0x1BE134, 0, "U16"), + ("TestWord_006", 0x1BE134, 0, "U16"), + ("TestWord_007", 0x1BE138, 0, "U16"), + ("TestWord_008", 0x1BE13C, 0, "U16"), + ("TestWord_009", 0x1BE140, 0, "U16"), + ("TestWord_011", 0x1BE148, 0, "U16"), + ], + ), + ] + +# daq_parser = DaqToCsv(DAQ_LISTS) # Record to CSV file(s). +daq_parser = DaqRecorder(DAQ_LISTS, "run_daq", 2) # Record to ".xmraw" file. + +with ap.run(policy=daq_parser) as x: + x.connect() + if x.slaveProperties.optionalCommMode: + x.getCommModeInfo() + + x.cond_unlock("DAQ") # DAQ resource is locked in many cases. + + print("setup DAQ lists.") + daq_parser.setup() # Execute setup procedures. + print("start DAQ lists.") + daq_parser.start() # Start DAQ lists. + + time.sleep(15.0 * 60.0) # Run for 15 minutes. + + print("Stop DAQ....") + daq_parser.stop() # Stop DAQ lists. + print("finalize DAQ lists.\n") + x.disconnect() + +if hasattr(daq_parser, "files"): # `files` attribute is specific to `DaqToCsv`. + print("Data written to:") + print("================") + for fl in daq_parser.files.values(): + print(fl.name) diff --git a/pyxcp/examples/xcp_policy.py b/pyxcp/examples/xcp_policy.py index f1ac96ea..4c939f6f 100644 --- a/pyxcp/examples/xcp_policy.py +++ b/pyxcp/examples/xcp_policy.py @@ -1,18 +1,17 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -"""Demostrates how to use frame recording policies. +"""Demostrates how to use frame recording policies including recorder extension. """ from pprint import pprint from pyxcp.cmdline import ArgumentParser -from pyxcp.transport.base import FrameRecorderAcquisitionPolicy -from pyxcp.transport.base import StdoutPolicy +from pyxcp.transport.base import FrameRecorderPolicy, StdoutPolicy # noqa: F401 + ap = ArgumentParser(description="pyXCP frame recording policy example.") LOG_FILE = "pyxcp" -policy = FrameRecorderAcquisitionPolicy(LOG_FILE) +policy = FrameRecorderPolicy(LOG_FILE) use_recorder = True # policy = StdoutPolicy() # You may also try this one. @@ -25,7 +24,7 @@ identifier = x.identifier(0x01) print("\nSlave Properties:") print("=================") - print(f"ID: '{identifier}'") + print(f"ID: {identifier!r}") pprint(x.slaveProperties) x.disconnect() @@ -34,7 +33,7 @@ from pyxcp.utils import hexDump try: - import pandas + import pandas # noqa: F401 except ImportError: has_pandas = False else: diff --git a/pyxcp/examples/xcp_read_benchmark.py b/pyxcp/examples/xcp_read_benchmark.py index 6ed04aa2..0eb71502 100644 --- a/pyxcp/examples/xcp_read_benchmark.py +++ b/pyxcp/examples/xcp_read_benchmark.py @@ -1,14 +1,15 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Very basic hello-world example. """ import time -from pyxcp.cmdline import ArgumentParser import matplotlib.pyplot as plt -import numpy as np import seaborn as sns +from pyxcp.cmdline import ArgumentParser +from pyxcp.transport import FrameRecorderPolicy + + sns.set() @@ -16,13 +17,14 @@ LENGTH = 0x1000 ITERATIONS = 100 -ap = ArgumentParser(description="pyXCP hello world.") +recorder_policy = FrameRecorderPolicy() # Create frame recorder. +ap = ArgumentParser(description="pyXCP hello world.", policy=recorder_policy) with ap.run() as x: xs = [] ys = [] x.connect() for ctoSize in range(8, 64 + 4, 4): - print("CTO-Size: {}".format(ctoSize)) + print(f"CTO-Size: {ctoSize}") xs.append(ctoSize) start = time.perf_counter() for _ in range(ITERATIONS): @@ -30,7 +32,7 @@ data = x.fetch(LENGTH, ctoSize) et = time.perf_counter() - start ys.append(et) - print("CTO size: {:-3} -- elapsed time {:-3.04}".format(ctoSize, et)) + print(f"CTO size: {ctoSize:-3} -- elapsed time {et:-3.04}") x.disconnect() plt.plot(xs, ys) plt.show() diff --git a/pyxcp/examples/xcp_skel.py b/pyxcp/examples/xcp_skel.py index 10f9cf45..82978894 100644 --- a/pyxcp/examples/xcp_skel.py +++ b/pyxcp/examples/xcp_skel.py @@ -1,8 +1,6 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Use this as a copy-and-paste template for your own scripts. """ -from pprint import pprint from pyxcp.cmdline import ArgumentParser diff --git a/pyxcp/examples/xcp_unlock.py b/pyxcp/examples/xcp_unlock.py index 080d82b9..c6032654 100644 --- a/pyxcp/examples/xcp_unlock.py +++ b/pyxcp/examples/xcp_unlock.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Very basic hello-world example. """ from pyxcp.cmdline import ArgumentParser + """ """ diff --git a/pyxcp/examples/xcp_user_supplied_driver.py b/pyxcp/examples/xcp_user_supplied_driver.py index b8a65212..c497bcda 100644 --- a/pyxcp/examples/xcp_user_supplied_driver.py +++ b/pyxcp/examples/xcp_user_supplied_driver.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """User supplied CAN driver. Run as: @@ -31,7 +30,7 @@ def connect(self): def close(self): pass - def getTimestampResolution(self): + def get_timestamp_resolution(self): pass def read(self): diff --git a/pyxcp/examples/xcphello.py b/pyxcp/examples/xcphello.py index e370fe33..932a7697 100644 --- a/pyxcp/examples/xcphello.py +++ b/pyxcp/examples/xcphello.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Very basic hello-world example. """ from pprint import pprint @@ -7,6 +6,7 @@ from pyxcp.cmdline import ArgumentParser from pyxcp.utils import decode_bytes + daq_info = False @@ -33,13 +33,16 @@ def callout(master, args): identifier = x.identifier(0x01) print("\nSlave Properties:") print("=================") - print(f"ID: '{identifier}'") + print(f"ID: {identifier!r}") pprint(x.slaveProperties) + x.cond_unlock() cps = x.getCurrentProtectionStatus() print("\nProtection Status") print("=================") for k, v in cps.items(): print(f" {k:6s}: {v}") + daq = x.getDaqInfo() + pprint(daq) if daq_info: dqp = x.getDaqProcessorInfo() print("\nDAQ Processor Info:") @@ -54,7 +57,7 @@ def callout(master, args): dq = "DAQ" if evt.daqEventProperties.daq else "" st = "STIM" if evt.daqEventProperties.stim else "" dq_st = dq + " " + st - print(f' [{idx:04}] "{name:s}"') + print(f" [{idx:04}] {name:r}") print(f" dir: {dq_st}") print(f" packed: {evt.daqEventProperties.packed}") PFX_CONS = "CONSISTENCY_" diff --git a/pyxcp/examples/xcphello_recorder.py b/pyxcp/examples/xcphello_recorder.py index 2bc17748..9449536b 100644 --- a/pyxcp/examples/xcphello_recorder.py +++ b/pyxcp/examples/xcphello_recorder.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Very basic hello-world example. """ from pprint import pprint @@ -9,6 +8,7 @@ from pyxcp.transport import FrameRecorderPolicy from pyxcp.utils import decode_bytes + daq_info = False @@ -39,7 +39,7 @@ def callout(master, args): identifier = x.identifier(0x01) print("\nSlave Properties:") print("=================") - print(f"ID: '{identifier}'") + print(f"ID: {identifier!r}") pprint(x.slaveProperties) cps = x.getCurrentProtectionStatus() print("\nProtection Status") @@ -60,7 +60,7 @@ def callout(master, args): dq = "DAQ" if evt.daqEventProperties.daq else "" st = "STIM" if evt.daqEventProperties.stim else "" dq_st = dq + " " + st - print(f' [{idx:04}] "{name:s}"') + print(f" [{idx:04}] {name:r}") print(f" dir: {dq_st}") print(f" packed: {evt.daqEventProperties.packed}") PFX_CONS = "CONSISTENCY_" @@ -80,7 +80,7 @@ def callout(master, args): print("=================") print(f"{x.getDaqListInfo(idx)}") x.disconnect() - +print("After recording...") ## ## Now read and dump recorded frames. ## diff --git a/pyxcp/logger.py b/pyxcp/logger.py deleted file mode 100644 index 4ccccc9a..00000000 --- a/pyxcp/logger.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import logging - -logging.basicConfig() - - -class Logger(object): - - LOGGER_BASE_NAME = "pyxcp" - FORMAT = "[%(levelname)s (%(name)s)]: %(message)s" - - def __init__(self, name, level=logging.WARN): - self.logger = logging.getLogger("{0}.{1}".format(self.LOGGER_BASE_NAME, name)) - self.logger.setLevel(level) - handler = logging.StreamHandler() - handler.setLevel(level) - formatter = logging.Formatter(self.FORMAT) - handler.setFormatter(formatter) - self.logger.addHandler(handler) - #self.logger.propagate = False - self.lastMessage = None - self.lastSeverity = None - - def getLastError(self): - result = (self.lastSeverity, self.lastMessage) - self.lastSeverity = self.lastMessage = None - return result - - def log(self, message, level): - self.lastSeverity = level - self.lastMessage = message - self.logger.log(level, "{0}".format(message)) - # print("{0}{1}".format(level, message)) - - def info(self, message): - self.log(message, logging.INFO) - - def warn(self, message): - self.log(message, logging.WARN) - - def debug(self, message): - self.log(message, logging.DEBUG) - - def error(self, message): - self.log(message, logging.ERROR) - - def critical(self, message): - self.log(message, logging.CRITICAL) - - def verbose(self): - self.logger.setLevel(logging.DEBUG) - - def silent(self): - self.logger.setLevel(logging.CRITICAL) - - def setLevel(self, level): - LEVEL_MAP = { - "INFO": logging.INFO, - "WARN": logging.WARN, - "DEBUG": logging.DEBUG, - "ERROR": logging.ERROR, - "CRITICAL": logging.CRITICAL, - } - if isinstance(level, str): - level = LEVEL_MAP.get(level.upper(), logging.WARN) - self.logger.setLevel(level) diff --git a/pyxcp/master/__init__.py b/pyxcp/master/__init__.py index b9ffe221..eee88b47 100644 --- a/pyxcp/master/__init__.py +++ b/pyxcp/master/__init__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Lowlevel API reflecting available XCP services .. note:: For technical reasons the API is split into two parts; @@ -7,4 +6,4 @@ .. [1] XCP Specification, Part 2 - Protocol Layer Specification """ -from .master import Master +from .master import Master # noqa: F401 diff --git a/pyxcp/master/errorhandler.py b/pyxcp/master/errorhandler.py index 57475fee..a118372a 100644 --- a/pyxcp/master/errorhandler.py +++ b/pyxcp/master/errorhandler.py @@ -1,28 +1,24 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Implements error-handling according to XCP spec. """ import functools import logging -import os import threading import time import types from collections import namedtuple -from pyxcp.errormatrix import Action -from pyxcp.errormatrix import ERROR_MATRIX -from pyxcp.errormatrix import PreAction -from pyxcp.types import COMMAND_CATEGORIES -from pyxcp.types import XcpError -from pyxcp.types import XcpResponseError -from pyxcp.types import XcpTimeoutError +from typing import Generic, List, Optional, TypeVar + +import can + +from pyxcp.errormatrix import ERROR_MATRIX, Action, PreAction +from pyxcp.types import COMMAND_CATEGORIES, XcpError, XcpResponseError, XcpTimeoutError handle_errors = True # enable/disable XCP error-handling. -logger = logging.getLogger("pyxcp.errorhandler") -class SingletonBase(object): +class SingletonBase: _lock = threading.Lock() def __new__(cls, *args, **kws): @@ -31,7 +27,7 @@ def __new__(cls, *args, **kws): try: cls._lock.acquire() if not hasattr(cls, "_instance"): - cls._instance = super(SingletonBase, cls).__new__(cls) + cls._instance = super().__new__(cls) finally: cls._lock.release() return cls._instance @@ -44,9 +40,19 @@ class InternalError(Exception): """Indicates an internal error, like invalid service.""" -class UnhandledError(Exception): +class SystemExit(Exception): """""" + def __init__(self, msg: str, error_code: int = None, *args, **kws): + super().__init__(*args, **kws) + self.error_code = error_code + self.msg = msg + + def __str__(self): + return f"SystemExit(error_code={self.error_code}, message={self.msg!r})" + + __repr__ = __str__ + class UnrecoverableError(Exception): """""" @@ -78,10 +84,10 @@ def getActions(service, error_code): eh = getErrorHandler(service) if eh is None: raise InternalError(f"Invalid Service 0x{service:02x}") - print(f"Try to handle error -- Service: {service.name} Error-Code: {error_code}") + # print(f"Try to handle error -- Service: {service.name} Error-Code: {error_code}") handler = eh.get(error_str) if handler is None: - raise UnhandledError(f"Service '{service.name}' has no handler for '{error_code}'.") + raise SystemExit(f"Service {service.name!r} has no handler for {error_code}.", error_code=error_code) preActions, actions = handler return preActions, actions @@ -89,8 +95,7 @@ def getActions(service, error_code): def actionIter(actions): """Iterate over action from :file:`errormatrix.py`""" if isinstance(actions, (tuple, list)): - for item in actions: - yield item + yield from actions else: yield actions @@ -116,12 +121,14 @@ def __init__(self, args=None, kwargs=None): self.args = tuple(args) self.kwargs = kwargs or {} - def __str__(self): + def __str__(self) -> str: res = f"{self.__class__.__name__}(ARGS = {self.args}, KWS = {self.kwargs})" return res - def __eq__(self, other): - return (self.args == other.args if other is not None else ()) and (self.kwargs == other.kwargs if other is not None else {}) + def __eq__(self, other) -> bool: + return (self.args == other.args if other is not None else False) and ( + self.kwargs == other.kwargs if other is not None else False + ) __repr__ = __str__ @@ -144,7 +151,7 @@ class Repeater: def __init__(self, initial_value: int): self._counter = initial_value - #print("\tREPEATER ctor", hex(id(self))) + # print("\tREPEATER ctor", hex(id(self))) def repeat(self): """Check if repetition is required. @@ -153,7 +160,7 @@ def repeat(self): ------- bool """ - #print("\t\tCOUNTER:", hex(id(self)), self._counter) + # print("\t\tCOUNTER:", hex(id(self)), self._counter) if self._counter == Repeater.INFINITE: return True elif self._counter > 0: @@ -174,8 +181,6 @@ def display_error(): class Handler: """""" - logger = logger - def __init__(self, instance, func, arguments, error_code=None): self.instance = instance if hasattr(func, "__closure__") and func.__closure__: @@ -185,29 +190,40 @@ def __init__(self, instance, func, arguments, error_code=None): self.func = func self.arguments = arguments self.service = self.instance.service - self.error_code = error_code + self._error_code: int = 0 + if error_code is not None: + self._error_code = error_code self._repeater = None + self.logger = logging.getLogger("PyXCP") def __str__(self): - return f"Handler(func = {func_name(self.func)} arguments = {self.arguments} service = {self.service} error_code = {self.error_code})" + return f"Handler(func = {func_name(self.func)} -- {self.arguments} service = {self.service} error_code = {self.error_code})" def __eq__(self, other): if other is None: return False return (self.instance == other.instance) and (self.func == other.func) and (self.arguments == other.arguments) + @property + def error_code(self) -> int: + return self._error_code + + @error_code.setter + def error_code(self, value: int) -> None: + self._error_code = value + @property def repeater(self): - #print("\tGet repeater", hex(id(self._repeater)), self._repeater is None) + # print("\tGet repeater", hex(id(self._repeater)), self._repeater is None) return self._repeater @repeater.setter def repeater(self, value): - #print("\tSet repeater", hex(id(value))) + # print("\tSet repeater", hex(id(value))) self._repeater = value def execute(self): - self.logger.debug(f"EXECUTE func = {func_name(self.func)} arguments = {self.arguments})") + self.logger.debug(f"Execute({func_name(self.func)} -- {self.arguments})") if isinstance(self.func, types.MethodType): return self.func(*self.arguments.args, **self.arguments.kwargs) else: @@ -227,40 +243,42 @@ def actions(self, preActions, actions): fn = Function(self.instance.synch, Arguments()) result_pre_actions.append(fn) elif item == PreAction.GET_SEED_UNLOCK: - raise NotImplementedError("GET_SEED_UNLOCK") + raise NotImplementedError("Pre-action GET_SEED_UNLOCK") elif item == PreAction.SET_MTA: fn = Function(self.instance.setMta, Arguments(self.instance.mta)) result_pre_actions.append(fn) elif item == PreAction.SET_DAQ_PTR: fn = Function(self.instance.setDaqPtr, Arguments(self.instance.currentDaqPtr)) elif item == PreAction.START_STOP_X: - raise NotImplementedError("START_STOP_X") + raise NotImplementedError("Pre-action START_STOP_X") elif item == PreAction.REINIT_DAQ: - raise NotImplementedError("REINIT_DAQ") + raise NotImplementedError("Pre-action REINIT_DAQ") elif item == PreAction.DISPLAY_ERROR: pass elif item == PreAction.DOWNLOAD: - raise NotImplementedError("DOWNLOAD") + raise NotImplementedError("Pre-action DOWNLOAD") elif item == PreAction.PROGRAM: - raise NotImplementedError("PROGRAM") + raise NotImplementedError("Pre-action PROGRAM") elif item == PreAction.UPLOAD: - raise NotImplementedError("UPLOAD") + raise NotImplementedError("Pre-action UPLOAD") elif item == PreAction.UNLOCK_SLAVE: resource = COMMAND_CATEGORIES.get(self.instance.service) # noqa: F841 - raise NotImplementedError("UNLOCK_SLAVE") + raise NotImplementedError("Pre-action UNLOCK_SLAVE") for item in actionIter(actions): if item == Action.NONE: pass elif item == Action.DISPLAY_ERROR: - raise UnhandledError("Could not proceed due to unhandled error.") + raise SystemExit("Could not proceed due to unhandled error (DISPLAY_ERROR).", self.error_code) elif item == Action.RETRY_SYNTAX: - raise UnhandledError("Could not proceed due to unhandled error.") + raise SystemExit("Could not proceed due to unhandled error (RETRY_SYNTAX).", self.error_code) elif item == Action.RETRY_PARAM: - raise UnhandledError("Could not proceed due to unhandled error.") + raise SystemExit("Could not proceed due to unhandled error (RETRY_PARAM).", self.error_code) elif item == Action.USE_A2L: - raise UnhandledError("Could not proceed due to unhandled error.") + raise SystemExit("Could not proceed due to unhandled error (USE_A2L).", self.error_code) elif item == Action.USE_ALTERATIVE: - raise UnhandledError("Could not proceed due to unhandled error.") # TODO: check alternatives. + raise SystemExit( + "Could not proceed due to unhandled error (USE_ALTERATIVE).", self.error_code + ) # TODO: check alternatives. elif item == Action.REPEAT: repetitionCount = Repeater.REPEAT elif item == Action.REPEAT_2_TIMES: @@ -268,49 +286,53 @@ def actions(self, preActions, actions): elif item == Action.REPEAT_INF_TIMES: repetitionCount = Repeater.INFINITE elif item == Action.RESTART_SESSION: - raise UnhandledError("Could not proceed due to unhandled error.") + raise SystemExit("Could not proceed due to unhandled error (RESTART_SESSION).", self.error_code) elif item == Action.TERMINATE_SESSION: - raise UnhandledError("Could not proceed due to unhandled error.") + raise SystemExit("Could not proceed due to unhandled error (TERMINATE_SESSION).", self.error_code) elif item == Action.SKIP: pass elif item == Action.NEW_FLASH_WARE: - raise UnhandledError("Could not proceed due to unhandled error") + raise SystemExit("Could not proceed due to unhandled error (NEW_FLASH_WARE)", self.error_code) return result_pre_actions, result_actions, Repeater(repetitionCount) -class HandlerStack: +T = TypeVar("T") + + +class HandlerStack(Generic[T]): """""" - def __init__(self): - self._stack = [] + def __init__(self) -> None: + self._stack: List[T] = [] - def push(self, handler): - if handler != self.tos(): - self._stack.append(handler) + def push(self, value: T): + if value != self.tos(): + self._stack.append(value) - def pop(self): + def pop(self) -> None: if len(self) > 0: self._stack.pop() - def tos(self): + def tos(self) -> Optional[T]: if len(self) > 0: return self._stack[-1] else: return None + # raise ValueError("empty stack.") - def empty(self): + def empty(self) -> bool: return self._stack == [] - def __len__(self): + def __len__(self) -> int: return len(self._stack) - def __repr__(self): + def __repr__(self) -> str: result = [] for idx in range(len(self)): result.append(str(self[idx])) return "\n".join(result) - def __getitem__(self, ndx): + def __getitem__(self, ndx: int) -> T: return self._stack[ndx] __str__ = __repr__ @@ -319,44 +341,53 @@ def __getitem__(self, ndx): class Executor(SingletonBase): """""" - handlerStack = HandlerStack() - repeater = None - logger = logger - previous_error_code = None - error_code = None - func = None - arguments = None + def __init__(self): + self.handlerStack = HandlerStack() + self.repeater = None + self.logger = logging.getLogger("PyXCP") + self.previous_error_code = None + self.error_code = None + self.func = None + self.arguments = None def __call__(self, inst, func, arguments): - self.logger.debug(f"__call__({func.__qualname__})") self.inst = inst self.func = func self.arguments = arguments handler = Handler(inst, func, arguments) self.handlerStack.push(handler) - #print("\tENTER handler:", hex(id(handler))) try: while True: try: handler = self.handlerStack.tos() - #print("\t\tEXEC", hex(id(handler))) res = handler.execute() except XcpResponseError as e: - self.logger.error(f"XcpResponseError [{str(e)}]") + # self.logger.critical(f"XcpResponseError [{e.get_error_code()}]") self.error_code = e.get_error_code() - except XcpTimeoutError as e: - self.logger.error(f"XcpTimeoutError [{str(e)}]") + handler.error_code = self.error_code + except XcpTimeoutError: + # self.logger.error(f"XcpTimeoutError [{str(e)}]") self.error_code = XcpError.ERR_TIMEOUT - except Exception as e: - raise UnrecoverableError(f"Don't know how to handle exception '{repr(e)}'") from e + handler.error_code = self.error_code + except TimeoutError: + raise + except can.CanError: + # self.logger.critical(f"Exception raised by Python CAN [{str(e)}]") + raise + except Exception: + # self.logger.critical(f"Exception [{str(e)}]") + raise else: self.error_code = None - # print("\t\t\t*** SUCCESS ***") self.handlerStack.pop() if self.handlerStack.empty(): - # print("OK, all handlers passed: '{}'.".format(res)) return res + if self.error_code == XcpError.ERR_CMD_SYNCH: + # Don't care about SYNCH for now... + self.inst.logger.info("SYNCH received.") + continue + if self.error_code is not None: preActions, actions, repeater = handler.actions(*getActions(inst.service, self.error_code)) if handler.repeater is None: @@ -368,7 +399,9 @@ def __call__(self, inst, func, arguments): if handler.repeater.repeat(): continue else: - raise UnrecoverableError(f"Max. repetition count reached while trying to execute service '{handler.func.__name__}'.") + raise UnrecoverableError( + f"Max. repetition count reached while trying to execute service {handler.func.__name__!r}." + ) finally: # cleanup of class variables self.previous_error_code = None diff --git a/pyxcp/master/master.py b/pyxcp/master/master.py index e625e401..5dcc0eed 100644 --- a/pyxcp/master/master.py +++ b/pyxcp/master/master.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Lowlevel API reflecting available XCP services. .. note:: For technical reasons the API is split into two parts; @@ -8,37 +7,26 @@ .. [1] XCP Specification, Part 2 - Protocol Layer Specification """ import functools -import logging import struct import traceback import warnings -from time import sleep -from typing import Callable -from typing import Collection -from typing import Dict -from typing import List -from typing import Optional -from typing import Union - -from pyxcp import checksum -from pyxcp import types -from pyxcp.config import Configuration -from pyxcp.constants import makeBytePacker -from pyxcp.constants import makeByteUnpacker -from pyxcp.constants import makeDLongPacker -from pyxcp.constants import makeDLongUnpacker -from pyxcp.constants import makeDWordPacker -from pyxcp.constants import makeDWordUnpacker -from pyxcp.constants import makeWordPacker -from pyxcp.constants import makeWordUnpacker -from pyxcp.constants import PackerType -from pyxcp.constants import UnpackerType -from pyxcp.master.errorhandler import disable_error_handling -from pyxcp.master.errorhandler import wrapped -from pyxcp.transport.base import createTransport -from pyxcp.utils import decode_bytes -from pyxcp.utils import delay -from pyxcp.utils import SHORT_SLEEP +from typing import Any, Callable, Collection, Dict, List, Optional, Tuple + +from pyxcp import checksum, types +from pyxcp.constants import ( + makeBytePacker, + makeByteUnpacker, + makeDLongPacker, + makeDLongUnpacker, + makeDWordPacker, + makeDWordUnpacker, + makeWordPacker, + makeWordUnpacker, +) +from pyxcp.daq_stim.stim import DaqEventInfo, Stim +from pyxcp.master.errorhandler import SystemExit, disable_error_handling, wrapped +from pyxcp.transport.base import create_transport +from pyxcp.utils import decode_bytes, delay, short_sleep def broadcasted(func: Callable): @@ -50,7 +38,7 @@ class SlaveProperties(dict): """Container class for fixed parameters, like byte-order, maxCTO, ...""" def __init__(self, *args, **kws): - super(SlaveProperties, self).__init__(*args, **kws) + super().__init__(*args, **kws) def __getattr__(self, name): return self[name] @@ -70,39 +58,36 @@ class Master: Parameters ---------- - transportName : str + transport_name : str XCP transport layer name ['can', 'eth', 'sxi'] config: dict """ - PARAMETER_MAP = { - # Type Req'd Default - "LOGLEVEL": (str, False, "WARN"), - "DISABLE_ERROR_HANDLING": ( - bool, - False, - False, - ), # Bypass error-handling for performance reasons. - "SEED_N_KEY_DLL": (str, False, ""), - "SEED_N_KEY_DLL_SAME_BIT_WIDTH": (bool, False, False), - "DISCONNECT_RESPONSE_OPTIONAL": (bool, False, False), - } - - def __init__(self, transportName, config=None, policy=None): + def __init__(self, transport_name: Optional[str], config, policy=None, transport_layer_interface=None): + if transport_name is None: + raise ValueError("No transport-layer selected") # Never reached -- to keep type-checkers happy. self.ctr = 0 self.succeeded = True - self.config = Configuration(self.PARAMETER_MAP or {}, config or {}) - self.logger = logging.getLogger("pyXCP") - self.logger.setLevel(self.config.get("LOGLEVEL")) - disable_error_handling(self.config.get("DISABLE_ERROR_HANDLING")) + self.config = config.general + self.logger = config.log - self.transport = createTransport(transportName, config, policy) - self.transport_name = transportName + disable_error_handling(self.config.disable_error_handling) + self.transport_name = transport_name.lower() + transport_config = config.transport + self.transport = create_transport(transport_name, transport_config, policy, transport_layer_interface) + + self.stim = Stim(self.config.stim_support) + self.stim.clear() + self.stim.set_policy_feeder(self.transport.policy.feed) + self.stim.set_frame_sender(self.transport.block_request) # In some cases the transport-layer needs to communicate with us. self.transport.parent = self self.service = None + # Policies may issue XCP commands on there own. + self.transport.policy.xcp_master = self + # (D)Word (un-)packers are byte-order dependent # -- byte-order is returned by CONNECT_Resp (COMM_MODE_BASIC) self.BYTE_pack = None @@ -119,11 +104,13 @@ def __init__(self, transportName, config=None, policy=None): self.mta = types.MtaType(None, None) self.currentDaqPtr = None self.currentProtectionStatus = None - self.seedNKeyDLL = self.config.get("SEED_N_KEY_DLL") - self.seedNKeyDLL_same_bit_width = self.config.get("SEED_N_KEY_DLL_SAME_BIT_WIDTH") - self.disconnect_response_optional = self.config.get("DISCONNECT_RESPONSE_OPTIONAL") + self.seed_n_key_dll = self.config.seed_n_key_dll + self.seed_n_key_function = self.config.seed_n_key_function + self.seed_n_key_dll_same_bit_width = self.config.seed_n_key_dll_same_bit_width + self.disconnect_response_optional = self.config.disconnect_response_optional self.slaveProperties = SlaveProperties() self.slaveProperties.pgmProcessor = SlaveProperties() + self.slaveProperties.transport_layer = self.transport_name.upper() def __enter__(self): """Context manager entry part.""" @@ -460,7 +447,7 @@ def upload(self, length: int): response += data[1 : rem + 1] rem = byte_count - len(response) else: - sleep(SHORT_SLEEP) + short_sleep() return response @wrapped @@ -580,7 +567,7 @@ def fetch(self, length: int, limitPayload: int = None): # TODO: pull address is not included because of services implicitly setting address information like :meth:`getID` . """ if limitPayload and limitPayload < 8: - raise ValueError("Payload must be at least 8 bytes - given: {}".format(limitPayload)) + raise ValueError(f"Payload must be at least 8 bytes - given: {limitPayload}") slaveBlockMode = self.slaveProperties.slaveBlockMode if slaveBlockMode: @@ -977,14 +964,15 @@ def copyCalPage(self, srcSegment, srcPage, dstSegment, dstPage): # DAQ @wrapped - def setDaqPtr(self, daqListNumber, odtNumber, odtEntryNumber): + def setDaqPtr(self, daqListNumber: int, odtNumber: int, odtEntryNumber: int): self.currentDaqPtr = types.DaqPtr(daqListNumber, odtNumber, odtEntryNumber) # Needed for errorhandling. daqList = self.WORD_pack(daqListNumber) response = self.transport.request(types.Command.SET_DAQ_PTR, 0, *daqList, odtNumber, odtEntryNumber) + self.stim.setDaqPtr(daqListNumber, odtNumber, odtEntryNumber) return response @wrapped - def clearDaqList(self, daqListNumber): + def clearDaqList(self, daqListNumber: int): """Clear DAQ list configuration. Parameters @@ -992,10 +980,12 @@ def clearDaqList(self, daqListNumber): daqListNumber : int """ daqList = self.WORD_pack(daqListNumber) - return self.transport.request(types.Command.CLEAR_DAQ_LIST, 0, *daqList) + result = self.transport.request(types.Command.CLEAR_DAQ_LIST, 0, *daqList) + self.stim.clearDaqList(daqListNumber) + return result @wrapped - def writeDaq(self, bitOffset, entrySize, addressExt, address): + def writeDaq(self, bitOffset: int, entrySize: int, addressExt: int, address: int): """Write element in ODT entry. Parameters @@ -1008,12 +998,15 @@ def writeDaq(self, bitOffset, entrySize, addressExt, address): address : int """ addr = self.DWORD_pack(address) - return self.transport.request(types.Command.WRITE_DAQ, bitOffset, entrySize, addressExt, *addr) + result = self.transport.request(types.Command.WRITE_DAQ, bitOffset, entrySize, addressExt, *addr) + self.stim.writeDaq(bitOffset, entrySize, addressExt, address) + return result @wrapped def setDaqListMode(self, mode, daqListNumber, eventChannelNumber, prescaler, priority): dln = self.WORD_pack(daqListNumber) ecn = self.WORD_pack(eventChannelNumber) + self.stim.setDaqListMode(mode, daqListNumber, eventChannelNumber, prescaler, priority) return self.transport.request(types.Command.SET_DAQ_LIST_MODE, mode, *dln, *ecn, prescaler, priority) @wrapped @@ -1033,7 +1026,7 @@ def getDaqListMode(self, daqListNumber): return types.GetDaqListModeResponse.parse(response, byteOrder=self.slaveProperties.byteOrder) @wrapped - def startStopDaqList(self, mode, daqListNumber): + def startStopDaqList(self, mode: int, daqListNumber: int): """Start /stop/select DAQ list. Parameters @@ -1046,7 +1039,10 @@ def startStopDaqList(self, mode, daqListNumber): """ dln = self.WORD_pack(daqListNumber) response = self.transport.request(types.Command.START_STOP_DAQ_LIST, mode, *dln) - return types.StartStopDaqListResponse.parse(response, byteOrder=self.slaveProperties.byteOrder) + self.stim.startStopDaqList(mode, daqListNumber) + firstPid = types.StartStopDaqListResponse.parse(response, byteOrder=self.slaveProperties.byteOrder) + self.stim.set_first_pid(daqListNumber, firstPid.firstPid) + return firstPid @wrapped def startStopSynch(self, mode): @@ -1059,7 +1055,9 @@ def startStopSynch(self, mode): 1 = start selected 2 = stop selected """ - return self.transport.request(types.Command.START_STOP_SYNCH, mode) + res = self.transport.request(types.Command.START_STOP_SYNCH, mode) + self.stim.startStopSynch(mode) + return res @wrapped def writeDaqMultiple(self, daqElements): @@ -1070,7 +1068,7 @@ def writeDaqMultiple(self, daqElements): daqElements : list of `dict` containing the following keys: *bitOffset*, *size*, *address*, *addressExt*. """ if len(daqElements) > self.slaveProperties.maxWriteDaqMultipleElements: - raise ValueError("At most {} daqElements are permitted.".format(self.slaveProperties.maxWriteDaqMultipleElements)) + raise ValueError(f"At most {self.slaveProperties.maxWriteDaqMultipleElements} daqElements are permitted.") data = bytearray() data.append(len(daqElements)) @@ -1217,10 +1215,12 @@ def getDaqPackedMode(self, daqListNumber): @wrapped def freeDaq(self): """Clear dynamic DAQ configuration.""" - return self.transport.request(types.Command.FREE_DAQ) + result = self.transport.request(types.Command.FREE_DAQ) + self.stim.freeDaq() + return result @wrapped - def allocDaq(self, daqCount): + def allocDaq(self, daqCount: int): """Allocate DAQ lists. Parameters @@ -1229,17 +1229,23 @@ def allocDaq(self, daqCount): number of DAQ lists to be allocated """ dq = self.WORD_pack(daqCount) - return self.transport.request(types.Command.ALLOC_DAQ, 0, *dq) + result = self.transport.request(types.Command.ALLOC_DAQ, 0, *dq) + self.stim.allocDaq(daqCount) + return result @wrapped - def allocOdt(self, daqListNumber, odtCount): + def allocOdt(self, daqListNumber: int, odtCount: int): dln = self.WORD_pack(daqListNumber) - return self.transport.request(types.Command.ALLOC_ODT, 0, *dln, odtCount) + result = self.transport.request(types.Command.ALLOC_ODT, 0, *dln, odtCount) + self.stim.allocOdt(daqListNumber, odtCount) + return result @wrapped - def allocOdtEntry(self, daqListNumber, odtNumber, odtEntriesCount): + def allocOdtEntry(self, daqListNumber: int, odtNumber: int, odtEntriesCount: int): dln = self.WORD_pack(daqListNumber) - return self.transport.request(types.Command.ALLOC_ODT_ENTRY, 0, *dln, odtNumber, odtEntriesCount) + result = self.transport.request(types.Command.ALLOC_ODT_ENTRY, 0, *dln, odtNumber, odtEntriesCount) + self.stim.allocOdtEntry(daqListNumber, odtNumber, odtEntriesCount) + return result # PGM @wrapped @@ -1621,12 +1627,13 @@ def timeCorrelationProperties(self, setProperties, getPropertiesRequest, cluster @broadcasted @wrapped def getSlaveID(self, mode: int): - self.transportLayerCmd(types.TransportLayerCommands.GET_SLAVE_ID, "X", "C", "P", mode) + response = self.transportLayerCmd(types.TransportLayerCommands.GET_SLAVE_ID, ord("X"), ord("C"), ord("P"), mode) + return types.GetSlaveIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder) def getDaqId(self, daqListNumber: int): response = self.transportLayerCmd(types.TransportLayerCommands.GET_DAQ_ID, *self.WORD_pack(daqListNumber)) - if response: - return types.GetDaqIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder) + # if response: + return types.GetDaqIdResponse.parse(response, byteOrder=self.slaveProperties.byteOrder) def setDaqId(self, daqListNumber: int, identifier: int): response = self.transportLayerCmd( @@ -1650,11 +1657,11 @@ def verify(self, addr, length): """ self.setMta(addr) cs = self.buildChecksum(length) - self.logger.debug("BuildChecksum return'd: 0x{:08X} [{}]".format(cs.checksum, cs.checksumType)) + self.logger.debug(f"BuildChecksum return'd: 0x{cs.checksum:08X} [{cs.checksumType}]") self.setMta(addr) data = self.fetch(length) cc = checksum.check(data, cs.checksumType) - self.logger.debug("Our checksum : 0x{:08X}".format(cc)) + self.logger.debug(f"Our checksum : 0x{cc:08X}") return cs.checksum == cc def getDaqInfo(self): @@ -1696,10 +1703,18 @@ def getDaqInfo(self): }, } result["resolution"] = resolutionInfo - channels = [] + daq_events = [] for ecn in range(dpi.maxEventChannel): eci = self.getDaqEventInfo(ecn) + cycle = eci["eventChannelTimeCycle"] + maxDaqList = eci["maxDaqList"] + priority = eci["eventChannelPriority"] + time_unit = eci["eventChannelTimeUnit"] + consistency = eci["daqEventProperties"]["consistency"] + daq_supported = eci["daqEventProperties"]["daq"] + stim_supported = eci["daqEventProperties"]["stim"] + packed_supported = eci["daqEventProperties"]["packed"] name = self.fetch(eci.eventChannelNameLength) if name: name = decode_bytes(name) @@ -1710,14 +1725,27 @@ def getDaqInfo(self): "cycle": eci["eventChannelTimeCycle"], "maxDaqList": eci["maxDaqList"], "properties": { - "consistency": eci["daqEventProperties"]["consistency"], - "daq": eci["daqEventProperties"]["daq"], - "stim": eci["daqEventProperties"]["stim"], - "packed": eci["daqEventProperties"]["packed"], + "consistency": consistency, + "daq": daq_supported, + "stim": stim_supported, + "packed": packed_supported, }, } + daq_event_info = DaqEventInfo( + name, + types.EVENT_CHANNEL_TIME_UNIT_TO_EXP[time_unit], + cycle, + maxDaqList, + priority, + consistency, + daq_supported, + stim_supported, + packed_supported, + ) + daq_events.append(daq_event_info) channels.append(channel) result["channels"] = channels + self.stim.setDaqEventInfo(daq_events) return result def getCurrentProtectionStatus(self): @@ -1760,12 +1788,14 @@ def cond_unlock(self, resources=None): In case of DLL related issues. """ import re - from pyxcp.dllif import getKey, SeedNKeyResult, SeedNKeyError + + from pyxcp.dllif import SeedNKeyError, SeedNKeyResult, getKey MAX_PAYLOAD = self.slaveProperties["maxCto"] - 2 - if not self.seedNKeyDLL: - raise RuntimeError("No seed and key DLL specified, cannot proceed.") + protection_status = self.getCurrentProtectionStatus() + if any(protection_status.values()) and (not (self.seed_n_key_dll or self.seed_n_key_function)): + raise RuntimeError("Neither seed-and-key DLL nor function specified, cannot proceed.") # TODO: ConfigurationError if resources is None: result = [] if self.slaveProperties["supportsCalpag"]: @@ -1777,11 +1807,10 @@ def cond_unlock(self, resources=None): if self.slaveProperties["supportsPgm"]: result.append("pgm") resources = ",".join(result) - protection_status = self.getCurrentProtectionStatus() resource_names = [r.lower() for r in re.split(r"[ ,]", resources) if r] for name in resource_names: if name not in types.RESOURCE_VALUES: - raise ValueError("Invalid resource name '{}'.".format(name)) + raise ValueError(f"Invalid resource name {name!r}.") if not protection_status[name]: continue resource_value = types.RESOURCE_VALUES[name] @@ -1795,25 +1824,33 @@ def cond_unlock(self, resources=None): while remaining > 0: result = self.getSeed(types.XcpGetSeedMode.REMAINING, resource_value) seed.extend(list(result.seed)) - remaining = result.length - result, key = getKey( - self.logger, - self.seedNKeyDLL, - resource_value, - bytes(seed), - self.seedNKeyDLL_same_bit_width, - ) + remaining -= result.length + self.logger.debug(f"Got seed {seed!r} for resource {resource_value!r}.") + if self.seed_n_key_function: + key = self.seed_n_key_function(resource_value, bytes(seed)) + self.logger.debug(f"Using seed and key function {self.seed_n_key_function.__name__!r}().") + result = SeedNKeyResult.ACK + elif self.seed_n_key_dll: + self.logger.debug(f"Using seed and key DLL {self.seed_n_key_dll!r}.") + result, key = getKey( + self.logger, + self.seed_n_key_dll, + resource_value, + bytes(seed), + self.seed_n_key_dll_same_bit_width, + ) if result == SeedNKeyResult.ACK: key = list(key) - total_length = len(key) - offset = 0 - while offset < total_length: - data = key[offset : offset + MAX_PAYLOAD] - key_length = len(data) - offset += key_length - self.unlock(key_length, data) + self.logger.debug(f"Unlocking resource {resource_value!r} with key {key!r}.") + remaining = len(key) + while key: + data = key[:MAX_PAYLOAD] + key_len = len(data) + self.unlock(remaining, data) + key = key[MAX_PAYLOAD:] + remaining -= key_len else: - raise SeedNKeyError("SeedAndKey DLL returned: {}".format(SeedNKeyResult(result).name)) + raise SeedNKeyError(f"SeedAndKey DLL returned: {SeedNKeyResult(result).name!r}") def identifier(self, id_value: int) -> str: """Return the identifier for the given value. @@ -1891,18 +1928,61 @@ def generate(): gen = make_generator(scan_ranges) for id_value, name in gen: - response = b"" - try: - response = self.identifier(id_value) - except types.XcpResponseError: - # don't depend on confirming implementation, i.e.: ID not implemented ==> empty response. - pass - except Exception: - raise - if response: + status, response = self.try_command(self.identifier, id_value) + if status == types.TryCommandResult.OK and response: result[name] = response + elif status == types.TryCommandResult.XCP_ERROR and response.error_code == types.XcpError.ERR_CMD_UNKNOWN: + break # Nothing to do here. + elif status == types.TryCommandResult.OTHER_ERROR: + raise RuntimeError(f"Error while scanning for ID {id_value}: {response!r}") return result + @property + def start_datetime(self) -> int: + """""" + return self.transport.start_datetime + + def try_command(self, cmd: Callable, *args, **kws) -> Tuple[types.TryCommandResult, Any]: + """Call master functions and handle XCP errors more gracefuly. + + Parameter + --------- + cmd: Callable + args: list + variable length arguments to `cmd`. + kws: dict + keyword arguments to `cmd`. + + `extra_msg`: str + Additional info to log message (not passed to `cmd`). + + Returns + ------- + + Note + ---- + Mainly used for plug-and-play applications, e.g. `id_scanner` may confronted with `ERR_OUT_OF_RANGE` errors, which + is normal for this kind of applications -- or to test for optional commands. + Use carefuly not to hide serious error causes. + """ + try: + extra_msg: Optional[str] = kws.get("extra_msg") + if extra_msg: + kws.pop("extra_msg") + res = cmd(*args, **kws) + except SystemExit as e: + if e.error_code == types.XcpError.ERR_CMD_UNKNOWN: + # This is a rather common use-case, so let the user know that there is some functionality missing. + if extra_msg: + self.logger.warning(f"Optional command {cmd.__name__!r} not implemented -- {extra_msg!r}") + else: + self.logger.warning(f"Optional command {cmd.__name__!r} not implemented.") + return (types.TryCommandResult.XCP_ERROR, e) + except Exception as e: + return (types.TryCommandResult.OTHER_ERROR, e) + else: + return (types.TryCommandResult.OK, res) + def ticks_to_seconds(ticks, resolution): """Convert DAQ timestamp/tick value to seconds. @@ -1916,6 +1996,7 @@ def ticks_to_seconds(ticks, resolution): warnings.warn( "ticks_to_seconds() deprecated, use factory :func:`make_tick_converter` instead.", Warning, + stacklevel=1, ) return (10 ** types.DAQ_TIMESTAMP_UNIT_TO_EXP[resolution.timestampMode.unit]) * resolution.timestampTicks * ticks diff --git a/pyxcp/py.typed b/pyxcp/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/pyxcp/recorder/__init__.py b/pyxcp/recorder/__init__.py index 81473bad..b9075705 100644 --- a/pyxcp/recorder/__init__.py +++ b/pyxcp/recorder/__init__.py @@ -1,13 +1,12 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """XCP Frame Recording Facility. """ from dataclasses import dataclass -from enum import IntEnum from typing import Union from pyxcp.types import FrameCategory + try: import pandas as pd except ImportError: @@ -15,13 +14,28 @@ else: HAS_PANDAS = True -import rekorder as rec +from pyxcp.recorder.rekorder import DaqOnlinePolicy # noqa: F401 +from pyxcp.recorder.rekorder import ( + DaqRecorderPolicy, + Deserializer, + MeasurementParameters, + ValueHolder, + XcpLogFileDecoder, + _PyXcpLogFileReader, + _PyXcpLogFileWriter, + data_types, +) + + +DATA_TYPES = data_types() @dataclass class XcpLogFileHeader: """ """ + version: int + options: int num_containers: int record_count: int size_uncompressed: int @@ -36,11 +50,18 @@ class XcpLogFileReader: """ """ def __init__(self, file_name): - self._reader = rec._PyXcpLogFileReader(file_name) + self._reader = _PyXcpLogFileReader(file_name) + + @property + def header(self): + return self._reader.get_header() def get_header(self): return XcpLogFileHeader(*self._reader.get_header_as_tuple()) + def get_metadata(self): + return self._reader.get_metadata() + def __iter__(self): while True: frames = self._reader.next_block() @@ -66,8 +87,8 @@ def as_dataframe(self): class XcpLogFileWriter: """ """ - def __init__(self, file_name: str, prealloc=10, chunk_size=1): - self._writer = rec._PyXcpLogFileWriter(file_name, prealloc, chunk_size) + def __init__(self, file_name: str, prealloc=500, chunk_size=1): + self._writer = _PyXcpLogFileWriter(file_name, prealloc, chunk_size) self._finalized = False def __del__(self): diff --git a/pyxcp/recorder/converter/__init__.py b/pyxcp/recorder/converter/__init__.py new file mode 100644 index 00000000..108bb0a4 --- /dev/null +++ b/pyxcp/recorder/converter/__init__.py @@ -0,0 +1,37 @@ +import logging +from array import array +from dataclasses import dataclass, field +from typing import Any, List + + +MAP_TO_ARRAY = { + "U8": "B", + "I8": "b", + "U16": "H", + "I16": "h", + "U32": "L", + "I32": "l", + "U64": "Q", + "I64": "q", + "F32": "f", + "F64": "d", + "F16": "f", + "BF16": "f", +} + +logger = logging.getLogger("PyXCP") + + +@dataclass +class Storage: + name: str + arrow_type: Any + arr: array + + +@dataclass +class StorageContainer: + name: str + arr: List[Storage] = field(default_factory=[]) + ts0: List[int] = field(default_factory=lambda: array("Q")) + ts1: List[int] = field(default_factory=lambda: array("Q")) diff --git a/pyxcp/recorder/lz4.c b/pyxcp/recorder/lz4.c index 0982f952..a2f7abee 100644 --- a/pyxcp/recorder/lz4.c +++ b/pyxcp/recorder/lz4.c @@ -1,6 +1,6 @@ /* LZ4 - Fast LZ compression algorithm - Copyright (C) 2011-2020, Yann Collet. + Copyright (C) 2011-2023, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) @@ -79,7 +79,7 @@ ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \ || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) # define LZ4_FORCE_MEMORY_ACCESS 2 -# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) || defined(_MSC_VER) # define LZ4_FORCE_MEMORY_ACCESS 1 # endif #endif @@ -106,15 +106,13 @@ # define LZ4_SRC_INCLUDED 1 #endif -#ifndef LZ4_STATIC_LINKING_ONLY -#define LZ4_STATIC_LINKING_ONLY -#endif - #ifndef LZ4_DISABLE_DEPRECATE_WARNINGS -#define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */ +# define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */ #endif -#define LZ4_STATIC_LINKING_ONLY /* LZ4_DISTANCE_MAX */ +#ifndef LZ4_STATIC_LINKING_ONLY +# define LZ4_STATIC_LINKING_ONLY +#endif #include "lz4.h" /* see also "memory routines" below */ @@ -126,14 +124,17 @@ # include /* only present in VS2005+ */ # pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ # pragma warning(disable : 6237) /* disable: C6237: conditional expression is always 0 */ +# pragma warning(disable : 6239) /* disable: C6239: ( && ) always evaluates to the result of */ +# pragma warning(disable : 6240) /* disable: C6240: ( && ) always evaluates to the result of */ +# pragma warning(disable : 6326) /* disable: C6326: Potential comparison of a constant with another constant */ #endif /* _MSC_VER */ #ifndef LZ4_FORCE_INLINE -# ifdef _MSC_VER /* Visual Studio */ +# if defined (_MSC_VER) && !defined (__clang__) /* MSVC */ # define LZ4_FORCE_INLINE static __forceinline # else # if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ -# ifdef __GNUC__ +# if defined (__GNUC__) || defined (__clang__) # define LZ4_FORCE_INLINE static inline __attribute__((always_inline)) # else # define LZ4_FORCE_INLINE static inline @@ -365,6 +366,11 @@ static unsigned LZ4_isLittleEndian(void) return one.c[0]; } +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +#define LZ4_PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__)) +#elif defined(_MSC_VER) +#define LZ4_PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop)) +#endif #if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) /* lie to the compiler about data alignment; use with caution */ @@ -380,9 +386,9 @@ static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } /* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ /* currently only defined for gcc and icc */ -typedef struct { U16 u16; } __attribute__((packed)) LZ4_unalign16; -typedef struct { U32 u32; } __attribute__((packed)) LZ4_unalign32; -typedef struct { reg_t uArch; } __attribute__((packed)) LZ4_unalignST; +LZ4_PACK(typedef struct { U16 u16; }) LZ4_unalign16; +LZ4_PACK(typedef struct { U32 u32; }) LZ4_unalign32; +LZ4_PACK(typedef struct { reg_t uArch; }) LZ4_unalignST; static U16 LZ4_read16(const void* ptr) { return ((const LZ4_unalign16*)ptr)->u16; } static U32 LZ4_read32(const void* ptr) { return ((const LZ4_unalign32*)ptr)->u32; } @@ -427,10 +433,22 @@ static U16 LZ4_readLE16(const void* memPtr) return LZ4_read16(memPtr); } else { const BYTE* p = (const BYTE*)memPtr; - return (U16)((U16)p[0] + (p[1]<<8)); + return (U16)((U16)p[0] | (p[1]<<8)); } } +#ifdef LZ4_STATIC_LINKING_ONLY_ENDIANNESS_INDEPENDENT_OUTPUT +static U32 LZ4_readLE32(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read32(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U32)p[0] | (p[1]<<8) | (p[2]<<16) | (p[3]<<24); + } +} +#endif + static void LZ4_writeLE16(void* memPtr, U16 value) { if (LZ4_isLittleEndian()) { @@ -512,7 +530,7 @@ LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd) /* LZ4_memcpy_using_offset() presumes : * - dstEnd >= dstPtr + MINMATCH - * - there is at least 8 bytes available to write after dstEnd */ + * - there is at least 12 bytes available to write after dstEnd */ LZ4_FORCE_INLINE void LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset) { @@ -527,12 +545,12 @@ LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const si case 2: LZ4_memcpy(v, srcPtr, 2); LZ4_memcpy(&v[2], srcPtr, 2); -#if defined(_MSC_VER) && (_MSC_VER <= 1936) /* MSVC 2022 ver 17.6 or earlier */ +#if defined(_MSC_VER) && (_MSC_VER <= 1937) /* MSVC 2022 ver 17.7 or earlier */ # pragma warning(push) # pragma warning(disable : 6385) /* warning C6385: Reading invalid data from 'v'. */ #endif LZ4_memcpy(&v[4], v, 4); -#if defined(_MSC_VER) && (_MSC_VER <= 1936) /* MSVC 2022 ver 17.6 or earlier */ +#if defined(_MSC_VER) && (_MSC_VER <= 1937) /* MSVC 2022 ver 17.7 or earlier */ # pragma warning(pop) #endif break; @@ -779,7 +797,12 @@ LZ4_FORCE_INLINE U32 LZ4_hash5(U64 sequence, tableType_t const tableType) LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) { if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + +#ifdef LZ4_STATIC_LINKING_ONLY_ENDIANNESS_INDEPENDENT_OUTPUT + return LZ4_hash4(LZ4_readLE32(p), tableType); +#else return LZ4_hash4(LZ4_read32(p), tableType); +#endif } LZ4_FORCE_INLINE void LZ4_clearHash(U32 h, void* tableBase, tableType_t const tableType) @@ -898,7 +921,7 @@ LZ4_prepareTable(LZ4_stream_t_internal* const cctx, cctx->dictSize = 0; } -/** LZ4_compress_generic() : +/** LZ4_compress_generic_validated() : * inlined, to ensure branches are decided at compilation time. * The following conditions are presumed already validated: * - source != NULL @@ -1080,7 +1103,10 @@ LZ4_FORCE_INLINE int LZ4_compress_generic_validated( /* Catch up */ filledIp = ip; - while (((ip>anchor) & (match > lowLimit)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + assert(ip > anchor); /* this is always true as ip has been advanced before entering the main loop */ + if ((match > lowLimit) && unlikely(ip[-1] == match[-1])) { + do { ip--; match--; } while (((ip > anchor) & (match > lowLimit)) && (unlikely(ip[-1] == match[-1]))); + } /* Encode Literals */ { unsigned const litLength = (unsigned)(ip - anchor); @@ -1095,7 +1121,7 @@ LZ4_FORCE_INLINE int LZ4_compress_generic_validated( goto _last_literals; } if (litLength >= RUN_MASK) { - int len = (int)(litLength - RUN_MASK); + unsigned len = litLength - RUN_MASK; *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; *op++ = (BYTE)len; @@ -1452,22 +1478,30 @@ int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacit /* Note!: This function leaves the stream in an unclean/broken state! * It is not safe to subsequently use the same state with a _fastReset() or * _continue() call without resetting it. */ -static int LZ4_compress_destSize_extState (LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize) +static int LZ4_compress_destSize_extState_internal(LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration) { void* const s = LZ4_initStream(state, sizeof (*state)); assert(s != NULL); (void)s; if (targetDstSize >= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */ - return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1); + return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, acceleration); } else { if (*srcSizePtr < LZ4_64Klimit) { - return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, 1); + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, acceleration); } else { tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; - return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, 1); + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, acceleration); } } } +int LZ4_compress_destSize_extState(void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration) +{ + int const r = LZ4_compress_destSize_extState_internal((LZ4_stream_t*)state, src, dst, srcSizePtr, targetDstSize, acceleration); + /* clean the state on exit */ + LZ4_initStream(state, sizeof (LZ4_stream_t)); + return r; +} + int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) { @@ -1479,7 +1513,7 @@ int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targe LZ4_stream_t* const ctx = &ctxBody; #endif - int result = LZ4_compress_destSize_extState(ctx, src, dst, srcSizePtr, targetDstSize); + int result = LZ4_compress_destSize_extState_internal(ctx, src, dst, srcSizePtr, targetDstSize, 1); #if (LZ4_HEAPMODE) FREEMEM(ctx); @@ -1548,8 +1582,11 @@ int LZ4_freeStream (LZ4_stream_t* LZ4_stream) #endif +typedef enum { _ld_fast, _ld_slow } LoadDict_mode_e; #define HASH_UNIT sizeof(reg_t) -int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) +int LZ4_loadDict_internal(LZ4_stream_t* LZ4_dict, + const char* dictionary, int dictSize, + LoadDict_mode_e _ld) { LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; const tableType_t tableType = byU32; @@ -1585,13 +1622,39 @@ int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) while (p <= dictEnd-HASH_UNIT) { U32 const h = LZ4_hashPosition(p, tableType); + /* Note: overwriting => favors positions end of dictionary */ LZ4_putIndexOnHash(idx32, h, dict->hashTable, tableType); p+=3; idx32+=3; } + if (_ld == _ld_slow) { + /* Fill hash table with additional references, to improve compression capability */ + p = dict->dictionary; + idx32 = dict->currentOffset - dict->dictSize; + while (p <= dictEnd-HASH_UNIT) { + U32 const h = LZ4_hashPosition(p, tableType); + U32 const limit = dict->currentOffset - 64 KB; + if (LZ4_getIndexOnHash(h, dict->hashTable, tableType) <= limit) { + /* Note: not overwriting => favors positions beginning of dictionary */ + LZ4_putIndexOnHash(idx32, h, dict->hashTable, tableType); + } + p++; idx32++; + } + } + return (int)dict->dictSize; } +int LZ4_loadDict(LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) +{ + return LZ4_loadDict_internal(LZ4_dict, dictionary, dictSize, _ld_fast); +} + +int LZ4_loadDictSlow(LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) +{ + return LZ4_loadDict_internal(LZ4_dict, dictionary, dictSize, _ld_slow); +} + void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream) { const LZ4_stream_t_internal* dictCtx = (dictionaryStream == NULL) ? NULL : @@ -1923,6 +1986,17 @@ read_variable_length(const BYTE** ip, const BYTE* ilimit, if (initial_check && unlikely((*ip) >= ilimit)) { /* read limit reached */ return rvl_error; } + s = **ip; + (*ip)++; + length += s; + if (unlikely((*ip) > ilimit)) { /* read limit reached */ + return rvl_error; + } + /* accumulator overflow detection (32-bit mode only) */ + if ((sizeof(length) < 8) && unlikely(length > ((Rvl_t)(-1)/2)) ) { + return rvl_error; + } + if (likely(s != 255)) return length; do { s = **ip; (*ip)++; @@ -1931,10 +2005,10 @@ read_variable_length(const BYTE** ip, const BYTE* ilimit, return rvl_error; } /* accumulator overflow detection (32-bit mode only) */ - if ((sizeof(length)<8) && unlikely(length > ((Rvl_t)(-1)/2)) ) { + if ((sizeof(length) < 8) && unlikely(length > ((Rvl_t)(-1)/2)) ) { return rvl_error; } - } while (s==255); + } while (s == 255); return length; } @@ -2000,7 +2074,7 @@ LZ4_decompress_generic( * note : fast loop may show a regression for some client arm chips. */ #if LZ4_FAST_DEC_LOOP if ((oend - op) < FASTLOOP_SAFE_DISTANCE) { - DEBUGLOG(6, "skip fast decode loop"); + DEBUGLOG(6, "move to safe decode loop"); goto safe_decode; } @@ -2012,6 +2086,7 @@ LZ4_decompress_generic( assert(ip < iend); token = *ip++; length = token >> ML_BITS; /* literal length */ + DEBUGLOG(7, "blockPos%6u: litLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length); /* decode literal length */ if (length == RUN_MASK) { @@ -2025,49 +2100,47 @@ LZ4_decompress_generic( if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ /* copy literals */ - cpy = op+length; LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); - if ((cpy>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; } - LZ4_wildCopy32(op, ip, cpy); - ip += length; op = cpy; - } else { - cpy = op+length; - DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length); + if ((op+length>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; } + LZ4_wildCopy32(op, ip, op+length); + ip += length; op += length; + } else if (ip <= iend-(16 + 1/*max lit + offset + nextToken*/)) { /* We don't need to check oend, since we check it once for each loop below */ - if (ip > iend-(16 + 1/*max lit + offset + nextToken*/)) { goto safe_literal_copy; } + DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length); /* Literals can only be <= 14, but hope compilers optimize better when copy by a register size */ LZ4_memcpy(op, ip, 16); - ip += length; op = cpy; + ip += length; op += length; + } else { + goto safe_literal_copy; } /* get offset */ offset = LZ4_readLE16(ip); ip+=2; - DEBUGLOG(6, " offset = %zu", offset); + DEBUGLOG(6, "blockPos%6u: offset = %u", (unsigned)(op-(BYTE*)dst), (unsigned)offset); match = op - offset; assert(match <= op); /* overflow check */ /* get matchlength */ length = token & ML_MASK; + DEBUGLOG(7, " match length token = %u (len==%u)", (unsigned)length, (unsigned)length+MINMATCH); if (length == ML_MASK) { size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0); if (addl == rvl_error) { - DEBUGLOG(6, "error reading long match length"); + DEBUGLOG(5, "error reading long match length"); goto _output_error; } length += addl; length += MINMATCH; + DEBUGLOG(7, " long match length == %u", (unsigned)length); if (unlikely((uptrval)(op)+length<(uptrval)op)) { goto _output_error; } /* overflow detection */ - if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) { - DEBUGLOG(6, "Error : offset outside buffers"); - goto _output_error; - } if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { goto safe_match_copy; } } else { length += MINMATCH; if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + DEBUGLOG(7, "moving to safe_match_copy (ml==%u)", (unsigned)length); goto safe_match_copy; } @@ -2086,7 +2159,7 @@ LZ4_decompress_generic( } } } if ( checkOffset && (unlikely(match + dictSize < lowPrefix)) ) { - DEBUGLOG(6, "Error : pos=%zi, offset=%zi => outside buffers", op-lowPrefix, op-match); + DEBUGLOG(5, "Error : pos=%zi, offset=%zi => outside buffers", op-lowPrefix, op-match); goto _output_error; } /* match starting within external dictionary */ @@ -2143,6 +2216,7 @@ LZ4_decompress_generic( assert(ip < iend); token = *ip++; length = token >> ML_BITS; /* literal length */ + DEBUGLOG(7, "blockPos%6u: litLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length); /* A two-stage shortcut for the most common case: * 1) If the literal length is 0..14, and there is enough space, @@ -2163,6 +2237,7 @@ LZ4_decompress_generic( /* The second stage: prepare for match copying, decode full info. * If it doesn't work out, the info won't be wasted. */ length = token & ML_MASK; /* match length */ + DEBUGLOG(7, "blockPos%6u: matchLength token = %u (len=%u)", (unsigned)(op-(BYTE*)dst), (unsigned)length, (unsigned)length + 4); offset = LZ4_readLE16(ip); ip += 2; match = op - offset; assert(match <= op); /* check overflow */ @@ -2194,11 +2269,12 @@ LZ4_decompress_generic( if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ } - /* copy literals */ - cpy = op+length; #if LZ4_FAST_DEC_LOOP safe_literal_copy: #endif + /* copy literals */ + cpy = op+length; + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); if ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) { /* We've either hit the input parsing restriction or the output parsing restriction. @@ -2234,9 +2310,10 @@ LZ4_decompress_generic( * so check that we exactly consume the input and don't overrun the output buffer. */ if ((ip+length != iend) || (cpy > oend)) { - DEBUGLOG(6, "should have been last run of literals") - DEBUGLOG(6, "ip(%p) + length(%i) = %p != iend (%p)", ip, (int)length, ip+length, iend); - DEBUGLOG(6, "or cpy(%p) > oend(%p)", cpy, oend); + DEBUGLOG(5, "should have been last run of literals") + DEBUGLOG(5, "ip(%p) + length(%i) = %p != iend (%p)", ip, (int)length, ip+length, iend); + DEBUGLOG(5, "or cpy(%p) > (oend-MFLIMIT)(%p)", cpy, oend-MFLIMIT); + DEBUGLOG(5, "after writing %u bytes / %i bytes available", (unsigned)(op-(BYTE*)dst), outputSize); goto _output_error; } } @@ -2262,6 +2339,7 @@ LZ4_decompress_generic( /* get matchlength */ length = token & ML_MASK; + DEBUGLOG(7, "blockPos%6u: matchLength token = %u", (unsigned)(op-(BYTE*)dst), (unsigned)length); _copy_match: if (length == ML_MASK) { @@ -2351,7 +2429,7 @@ LZ4_decompress_generic( while (op < cpy) { *op++ = *match++; } } else { LZ4_memcpy(op, match, 8); - if (length > 16) { LZ4_wildCopy8(op+8, match+8, cpy); } + if (length > 16) { LZ4_wildCopy8(op+8, match+8, cpy); } } op = cpy; /* wildcopy correction */ } diff --git a/pyxcp/recorder/lz4.h b/pyxcp/recorder/lz4.h index f85b0389..5c799723 100644 --- a/pyxcp/recorder/lz4.h +++ b/pyxcp/recorder/lz4.h @@ -1,7 +1,7 @@ /* * LZ4 - Fast LZ compression algorithm * Header File - * Copyright (C) 2011-2020, Yann Collet. + * Copyright (C) 2011-2023, Yann Collet. BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) @@ -130,7 +130,7 @@ extern "C" { /*------ Version ------*/ #define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ #define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */ -#define LZ4_VERSION_RELEASE 4 /* for tweaks, bug-fixes, or development */ +#define LZ4_VERSION_RELEASE 5 /* for tweaks, bug-fixes, or development */ #define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) @@ -144,23 +144,25 @@ LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; /*-************************************ -* Tuning parameter +* Tuning memory usage **************************************/ -#define LZ4_MEMORY_USAGE_MIN 10 -#define LZ4_MEMORY_USAGE_DEFAULT 14 -#define LZ4_MEMORY_USAGE_MAX 20 - /*! * LZ4_MEMORY_USAGE : - * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; ) - * Increasing memory usage improves compression ratio, at the cost of speed. + * Can be selected at compile time, by setting LZ4_MEMORY_USAGE. + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB) + * Increasing memory usage improves compression ratio, generally at the cost of speed. * Reduced memory usage may improve speed at the cost of ratio, thanks to better cache locality. - * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + * Default value is 14, for 16KB, which nicely fits into most L1 caches. */ #ifndef LZ4_MEMORY_USAGE # define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT #endif +/* These are absolute limits, they should not be changed by users */ +#define LZ4_MEMORY_USAGE_MIN 10 +#define LZ4_MEMORY_USAGE_DEFAULT 14 +#define LZ4_MEMORY_USAGE_MAX 20 + #if (LZ4_MEMORY_USAGE < LZ4_MEMORY_USAGE_MIN) # error "LZ4_MEMORY_USAGE is too small !" #endif @@ -191,7 +193,7 @@ LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int /*! LZ4_decompress_safe() : * @compressedSize : is the exact complete size of the compressed block. * @dstCapacity : is the size of destination buffer (which must be already allocated), - * is an upper bound of decompressed size. + * presumed an upper bound of decompressed size. * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) * If destination buffer is not large enough, decoding will stop and output an error code (negative value). * If the source stream is detected malformed, the function will stop decoding and return a negative result. @@ -243,17 +245,17 @@ LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int d LZ4LIB_API int LZ4_sizeofState(void); LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); - /*! LZ4_compress_destSize() : * Reverse the logic : compresses as much data as possible from 'src' buffer - * into already allocated buffer 'dst', of size >= 'targetDestSize'. + * into already allocated buffer 'dst', of size >= 'dstCapacity'. * This function either compresses the entire 'src' content into 'dst' if it's large enough, * or fill 'dst' buffer completely with as much data as possible from 'src'. * note: acceleration parameter is fixed to "default". * - * *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * *srcSizePtr : in+out parameter. Initially contains size of input. + * Will be modified to indicate how many bytes where read from 'src' to fill 'dst'. * New value is necessarily <= input value. - * @return : Nb bytes written into 'dst' (necessarily <= targetDestSize) + * @return : Nb bytes written into 'dst' (necessarily <= dstCapacity) * or 0 if compression fails. * * Note : from v1.8.2 to v1.9.1, this function had a bug (fixed in v1.9.2+): @@ -267,8 +269,7 @@ LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* d * a dstCapacity which is > decompressedSize, by at least 1 byte. * See https://github.com/lz4/lz4/issues/859 for details */ -LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); - +LZ4LIB_API int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize); /*! LZ4_decompress_safe_partial() : * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', @@ -312,7 +313,7 @@ LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcS ***********************************************/ typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ -/** +/*! Note about RC_INVOKED - RC_INVOKED is predefined symbol of rc.exe (the resource compiler which is part of MSVC/Visual Studio). @@ -362,13 +363,22 @@ LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. * The same dictionary will have to be loaded on decompression side for successful decoding. * Dictionary are useful for better compression of small data (KB range). - * While LZ4 accept any input as dictionary, - * results are generally better when using Zstandard's Dictionary Builder. + * While LZ4 itself accepts any input as dictionary, dictionary efficiency is also a topic. + * When in doubt, employ the Zstandard's Dictionary Builder. * Loading a size of 0 is allowed, and is the same as reset. - * @return : loaded dictionary size, in bytes (necessarily <= 64 KB) + * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) */ LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); +/*! LZ4_loadDictSlow() : v1.9.5+ + * Same as LZ4_loadDict(), + * but uses a bit more cpu to reference the dictionary content more thoroughly. + * This is expected to slightly improve compression ratio. + * The extra-cpu cost is likely worth it if the dictionary is re-used across multiple sessions. + * @return : loaded dictionary size, in bytes (note: only the last 64 KB are loaded) + */ +LZ4LIB_API int LZ4_loadDictSlow(LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + /*! LZ4_compress_fast_continue() : * Compress 'src' content using data from previously compressed blocks, for better compression ratio. * 'dst' buffer must be already allocated. @@ -546,9 +556,9 @@ LZ4_decompress_safe_partial_usingDict(const char* src, char* dst, #define LZ4_STATIC_3504398509 #ifdef LZ4_PUBLISH_STATIC_FUNCTIONS -#define LZ4LIB_STATIC_API LZ4LIB_API +# define LZ4LIB_STATIC_API LZ4LIB_API #else -#define LZ4LIB_STATIC_API +# define LZ4LIB_STATIC_API #endif @@ -564,6 +574,12 @@ LZ4_decompress_safe_partial_usingDict(const char* src, char* dst, */ LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); +/*! LZ4_compress_destSize_extState() : + * Same as LZ4_compress_destSize(), but using an externally allocated state. + * Also: exposes @acceleration + */ +int LZ4_compress_destSize_extState(void* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize, int acceleration); + /*! LZ4_attach_dictionary() : * This is an experimental API that allows * efficient use of a static dictionary many times. @@ -705,7 +721,7 @@ struct LZ4_stream_t_internal { /* Implicit padding to ensure structure is aligned */ }; -#define LZ4_STREAM_MINSIZE ((1UL << LZ4_MEMORY_USAGE) + 32) /* static size, for inter-version compatibility */ +#define LZ4_STREAM_MINSIZE ((1UL << (LZ4_MEMORY_USAGE)) + 32) /* static size, for inter-version compatibility */ union LZ4_stream_u { char minStateSize[LZ4_STREAM_MINSIZE]; LZ4_stream_t_internal internal_donotuse; @@ -726,7 +742,7 @@ union LZ4_stream_u { * Note2: An LZ4_stream_t structure guarantees correct alignment and size. * Note3: Before v1.9.0, use LZ4_resetStream() instead **/ -LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* buffer, size_t size); +LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* stateBuffer, size_t size); /*! LZ4_streamDecode_t : @@ -838,11 +854,12 @@ LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4 * But they may happen if input data is invalid (error or intentional tampering). * As a consequence, use these functions in trusted environments with trusted data **only**. */ -LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead") +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial() instead") LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); -LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead") +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider migrating towards LZ4_decompress_safe_continue() instead. " + "Note that the contract will change (requires block's compressed size, instead of decompressed size)") LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); -LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_usingDict() instead") +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_partial_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); /*! LZ4_resetStream() : diff --git a/pyxcp/recorder/lz4hc.c b/pyxcp/recorder/lz4hc.c index 651f190a..3a266fbc 100644 --- a/pyxcp/recorder/lz4hc.c +++ b/pyxcp/recorder/lz4hc.c @@ -52,19 +52,19 @@ /*=== Dependency ===*/ #define LZ4_HC_STATIC_LINKING_ONLY #include "lz4hc.h" +#include -/*=== Common definitions ===*/ -#if defined(__GNUC__) +/*=== Shared lz4.c code ===*/ +#ifndef LZ4_SRC_INCLUDED +# if defined(__GNUC__) # pragma GCC diagnostic ignored "-Wunused-function" -#endif -#if defined (__clang__) +# endif +# if defined (__clang__) # pragma clang diagnostic ignored "-Wunused-function" -#endif - -#define LZ4_COMMONDEFS_ONLY -#ifndef LZ4_SRC_INCLUDED -#include "lz4.c" /* LZ4_count, constants, mem */ +# endif +# define LZ4_COMMONDEFS_ONLY +# include "lz4.c" /* LZ4_count, constants, mem */ #endif @@ -80,18 +80,122 @@ typedef enum { noDictCtx, usingDictCtxHc } dictCtx_directive; /*=== Macros ===*/ #define MIN(a,b) ( (a) < (b) ? (a) : (b) ) #define MAX(a,b) ( (a) > (b) ? (a) : (b) ) -#define HASH_FUNCTION(i) (((i) * 2654435761U) >> ((MINMATCH*8)-LZ4HC_HASH_LOG)) -#define DELTANEXTMAXD(p) chainTable[(p) & LZ4HC_MAXD_MASK] /* flexible, LZ4HC_MAXD dependent */ #define DELTANEXTU16(table, pos) table[(U16)(pos)] /* faster */ /* Make fields passed to, and updated by LZ4HC_encodeSequence explicit */ #define UPDATABLE(ip, op, anchor) &ip, &op, &anchor + +/*=== Hashing ===*/ #define LZ4HC_HASHSIZE 4 +#define HASH_FUNCTION(i) (((i) * 2654435761U) >> ((MINMATCH*8)-LZ4HC_HASH_LOG)) static U32 LZ4HC_hashPtr(const void* ptr) { return HASH_FUNCTION(LZ4_read32(ptr)); } +#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) +/* lie to the compiler about data alignment; use with caution */ +static U64 LZ4_read64(const void* memPtr) { return *(const U64*) memPtr; } + +#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1) +/* __pack instructions are safer, but compiler specific */ +LZ4_PACK(typedef struct { U64 u64; }) LZ4_unalign64; +static U64 LZ4_read64(const void* ptr) { return ((const LZ4_unalign64*)ptr)->u64; } + +#else /* safe and portable access using memcpy() */ +static U64 LZ4_read64(const void* memPtr) +{ + U64 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +#endif /* LZ4_FORCE_MEMORY_ACCESS */ + +#define LZ4MID_HASHSIZE 8 +#define LZ4MID_HASHLOG (LZ4HC_HASH_LOG-1) +#define LZ4MID_HASHTABLESIZE (1 << LZ4MID_HASHLOG) + +static U32 LZ4MID_hash4(U32 v) { return (v * 2654435761U) >> (32-LZ4MID_HASHLOG); } +static U32 LZ4MID_hash4Ptr(const void* ptr) { return LZ4MID_hash4(LZ4_read32(ptr)); } +/* note: hash7 hashes the lower 56-bits. + * It presumes input was read using little endian.*/ +static U32 LZ4MID_hash7(U64 v) { return (U32)(((v << (64-56)) * 58295818150454627ULL) >> (64-LZ4MID_HASHLOG)) ; } +static U64 LZ4_readLE64(const void* memPtr); +static U32 LZ4MID_hash8Ptr(const void* ptr) { return LZ4MID_hash7(LZ4_readLE64(ptr)); } + +static U64 LZ4_readLE64(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read64(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + /* note: relies on the compiler to simplify this expression */ + return (U64)p[0] | ((U64)p[1]<<8) | ((U64)p[2]<<16) | ((U64)p[3]<<24) + | ((U64)p[4]<<32) | ((U64)p[5]<<40) | ((U64)p[6]<<48) | ((U64)p[7]<<56); + } +} + + +/*=== Count match length ===*/ +LZ4_FORCE_INLINE +unsigned LZ4HC_NbCommonBytes32(U32 val) +{ + assert(val != 0); + if (LZ4_isLittleEndian()) { +# if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanReverse(&r, val); + return (unsigned)((31 - r) >> 3); +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clz(val) >> 3; +# else + val >>= 8; + val = ((((val + 0x00FFFF00) | 0x00FFFFFF) + val) | + (val + 0x00FF0000)) >> 24; + return (unsigned)val ^ 3; +# endif + } else { +# if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward(&r, val); + return (unsigned)(r >> 3); +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctz(val) >> 3; +# else + const U32 m = 0x01010101; + return (unsigned)((((val - 1) ^ val) & (m - 1)) * m) >> 24; +# endif + } +} + +/** LZ4HC_countBack() : + * @return : negative value, nb of common bytes before ip/match */ +LZ4_FORCE_INLINE +int LZ4HC_countBack(const BYTE* const ip, const BYTE* const match, + const BYTE* const iMin, const BYTE* const mMin) +{ + int back = 0; + int const min = (int)MAX(iMin - ip, mMin - match); + assert(min <= 0); + assert(ip >= iMin); assert((size_t)(ip-iMin) < (1U<<31)); + assert(match >= mMin); assert((size_t)(match - mMin) < (1U<<31)); + + while ((back - min) > 3) { + U32 const v = LZ4_read32(ip + back - 4) ^ LZ4_read32(match + back - 4); + if (v) { + return (back - (int)LZ4HC_NbCommonBytes32(v)); + } else back -= 4; /* 4-byte step */ + } + /* check remainder if any */ + while ( (back > min) + && (ip[back-1] == match[back-1]) ) + back--; + return back; +} + /************************************** -* HC Compression +* Init **************************************/ static void LZ4HC_clearTables (LZ4HC_CCtx_internal* hc4) { @@ -119,6 +223,394 @@ static void LZ4HC_init_internal (LZ4HC_CCtx_internal* hc4, const BYTE* start) } +/************************************** +* Encode +**************************************/ +/* LZ4HC_encodeSequence() : + * @return : 0 if ok, + * 1 if buffer issue detected */ +LZ4_FORCE_INLINE int LZ4HC_encodeSequence ( + const BYTE** _ip, + BYTE** _op, + const BYTE** _anchor, + int matchLength, + int offset, + limitedOutput_directive limit, + BYTE* oend) +{ +#define ip (*_ip) +#define op (*_op) +#define anchor (*_anchor) + + size_t length; + BYTE* const token = op++; + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG >= 6) + static const BYTE* start = NULL; + static U32 totalCost = 0; + U32 const pos = (start==NULL) ? 0 : (U32)(anchor - start); + U32 const ll = (U32)(ip - anchor); + U32 const llAdd = (ll>=15) ? ((ll-15) / 255) + 1 : 0; + U32 const mlAdd = (matchLength>=19) ? ((matchLength-19) / 255) + 1 : 0; + U32 const cost = 1 + llAdd + ll + 2 + mlAdd; + if (start==NULL) start = anchor; /* only works for single segment */ + /* g_debuglog_enable = (pos >= 2228) & (pos <= 2262); */ + DEBUGLOG(6, "pos:%7u -- literals:%4u, match:%4i, offset:%5i, cost:%4u + %5u", + pos, + (U32)(ip - anchor), matchLength, offset, + cost, totalCost); + totalCost += cost; +#endif + + /* Encode Literal length */ + length = (size_t)(ip - anchor); + LZ4_STATIC_ASSERT(notLimited == 0); + /* Check output limit */ + if (limit && ((op + (length / 255) + length + (2 + 1 + LASTLITERALS)) > oend)) { + DEBUGLOG(6, "Not enough room to write %i literals (%i bytes remaining)", + (int)length, (int)(oend - op)); + return 1; + } + if (length >= RUN_MASK) { + size_t len = length - RUN_MASK; + *token = (RUN_MASK << ML_BITS); + for(; len >= 255 ; len -= 255) *op++ = 255; + *op++ = (BYTE)len; + } else { + *token = (BYTE)(length << ML_BITS); + } + + /* Copy Literals */ + LZ4_wildCopy8(op, anchor, op + length); + op += length; + + /* Encode Offset */ + assert(offset <= LZ4_DISTANCE_MAX ); + assert(offset > 0); + LZ4_writeLE16(op, (U16)(offset)); op += 2; + + /* Encode MatchLength */ + assert(matchLength >= MINMATCH); + length = (size_t)matchLength - MINMATCH; + if (limit && (op + (length / 255) + (1 + LASTLITERALS) > oend)) { + DEBUGLOG(6, "Not enough room to write match length"); + return 1; /* Check output limit */ + } + if (length >= ML_MASK) { + *token += ML_MASK; + length -= ML_MASK; + for(; length >= 510 ; length -= 510) { *op++ = 255; *op++ = 255; } + if (length >= 255) { length -= 255; *op++ = 255; } + *op++ = (BYTE)length; + } else { + *token += (BYTE)(length); + } + + /* Prepare next loop */ + ip += matchLength; + anchor = ip; + + return 0; + +#undef ip +#undef op +#undef anchor +} + + +typedef struct { + int off; + int len; + int back; /* negative value */ +} LZ4HC_match_t; + +LZ4HC_match_t LZ4HC_searchExtDict(const BYTE* ip, U32 ipIndex, + const BYTE* const iLowLimit, const BYTE* const iHighLimit, + const LZ4HC_CCtx_internal* dictCtx, U32 gDictEndIndex, + int currentBestML, int nbAttempts) +{ + size_t const lDictEndIndex = (size_t)(dictCtx->end - dictCtx->prefixStart) + dictCtx->dictLimit; + U32 lDictMatchIndex = dictCtx->hashTable[LZ4HC_hashPtr(ip)]; + U32 matchIndex = lDictMatchIndex + gDictEndIndex - (U32)lDictEndIndex; + int offset = 0, sBack = 0; + assert(lDictEndIndex <= 1 GB); + if (lDictMatchIndex>0) + DEBUGLOG(7, "lDictEndIndex = %zu, lDictMatchIndex = %u", lDictEndIndex, lDictMatchIndex); + while (ipIndex - matchIndex <= LZ4_DISTANCE_MAX && nbAttempts--) { + const BYTE* const matchPtr = dictCtx->prefixStart - dictCtx->dictLimit + lDictMatchIndex; + + if (LZ4_read32(matchPtr) == LZ4_read32(ip)) { + int mlt; + int back = 0; + const BYTE* vLimit = ip + (lDictEndIndex - lDictMatchIndex); + if (vLimit > iHighLimit) vLimit = iHighLimit; + mlt = (int)LZ4_count(ip+MINMATCH, matchPtr+MINMATCH, vLimit) + MINMATCH; + back = (ip > iLowLimit) ? LZ4HC_countBack(ip, matchPtr, iLowLimit, dictCtx->prefixStart) : 0; + mlt -= back; + if (mlt > currentBestML) { + currentBestML = mlt; + offset = (int)(ipIndex - matchIndex); + sBack = back; + DEBUGLOG(7, "found match of length %i within extDictCtx", currentBestML); + } } + + { U32 const nextOffset = DELTANEXTU16(dictCtx->chainTable, lDictMatchIndex); + lDictMatchIndex -= nextOffset; + matchIndex -= nextOffset; + } } + + { LZ4HC_match_t md; + md.len = currentBestML; + md.off = offset; + md.back = sBack; + return md; + } +} + +/************************************** +* Mid Compression (level 2) +**************************************/ + +LZ4_FORCE_INLINE void +LZ4MID_addPosition(U32* hTable, U32 hValue, U32 index) +{ + hTable[hValue] = index; +} + +#define ADDPOS8(_p, _idx) LZ4MID_addPosition(hash8Table, LZ4MID_hash8Ptr(_p), _idx) +#define ADDPOS4(_p, _idx) LZ4MID_addPosition(hash4Table, LZ4MID_hash4Ptr(_p), _idx) + +static int LZ4HC_compress_2hashes ( + LZ4HC_CCtx_internal* const ctx, + const char* const src, + char* const dst, + int* srcSizePtr, + int const maxOutputSize, + const limitedOutput_directive limit, + const dictCtx_directive dict + ) +{ + U32* const hash4Table = ctx->hashTable; + U32* const hash8Table = hash4Table + LZ4MID_HASHTABLESIZE; + const BYTE* ip = (const BYTE*)src; + const BYTE* anchor = ip; + const BYTE* const iend = ip + *srcSizePtr; + const BYTE* const mflimit = iend - MFLIMIT; + const BYTE* const matchlimit = (iend - LASTLITERALS); + const BYTE* const ilimit = (iend - LZ4MID_HASHSIZE); + BYTE* op = (BYTE*)dst; + BYTE* oend = op + maxOutputSize; + + const BYTE* const prefixPtr = ctx->prefixStart; + const U32 prefixIdx = ctx->dictLimit; + const U32 ilimitIdx = (U32)(ilimit - prefixPtr) + prefixIdx; + const U32 gDictEndIndex = ctx->lowLimit; + unsigned matchLength; + unsigned matchDistance; + + /* input sanitization */ + DEBUGLOG(5, "LZ4HC_compress_2hashes (%i bytes)", *srcSizePtr); + assert(*srcSizePtr >= 0); + if (*srcSizePtr) assert(src != NULL); + if (maxOutputSize) assert(dst != NULL); + if (*srcSizePtr < 0) return 0; /* invalid */ + if (maxOutputSize < 0) return 0; /* invalid */ + if (*srcSizePtr > LZ4_MAX_INPUT_SIZE) { + /* forbidden: no input is allowed to be that large */ + return 0; + } + if (limit == fillOutput) oend -= LASTLITERALS; /* Hack for support LZ4 format restriction */ + if (*srcSizePtr < LZ4_minLength) + goto _lz4mid_last_literals; /* Input too small, no compression (all literals) */ + + /* main loop */ + while (ip <= mflimit) { + const U32 ipIndex = (U32)(ip - prefixPtr) + prefixIdx; + /* search long match */ + { U32 h8 = LZ4MID_hash8Ptr(ip); + U32 pos8 = hash8Table[h8]; + assert(h8 < LZ4MID_HASHTABLESIZE); + assert(h8 < ipIndex); + LZ4MID_addPosition(hash8Table, h8, ipIndex); + if ( ipIndex - pos8 <= LZ4_DISTANCE_MAX + && pos8 >= prefixIdx /* note: currently only search within prefix */ + ) { + /* match candidate found */ + const BYTE* matchPtr = prefixPtr + pos8 - prefixIdx; + assert(matchPtr < ip); + matchLength = LZ4_count(ip, matchPtr, matchlimit); + if (matchLength >= MINMATCH) { + DEBUGLOG(7, "found candidate match at pos %u (len=%u)", pos8, matchLength); + matchDistance = ipIndex - pos8; + goto _lz4mid_encode_sequence; + } + } } + /* search short match */ + { U32 h4 = LZ4MID_hash4Ptr(ip); + U32 pos4 = hash4Table[h4]; + assert(h4 < LZ4MID_HASHTABLESIZE); + assert(pos4 < ipIndex); + LZ4MID_addPosition(hash4Table, h4, ipIndex); + if (ipIndex - pos4 <= LZ4_DISTANCE_MAX + && pos4 >= prefixIdx /* only search within prefix */ + ) { + /* match candidate found */ + const BYTE* const matchPtr = prefixPtr + (pos4 - prefixIdx); + assert(matchPtr < ip); + assert(matchPtr >= prefixPtr); + matchLength = LZ4_count(ip, matchPtr, matchlimit); + if (matchLength >= MINMATCH) { + /* short match found, let's just check ip+1 for longer */ + U32 const h8 = LZ4MID_hash8Ptr(ip+1); + U32 const pos8 = hash8Table[h8]; + U32 const m2Distance = ipIndex + 1 - pos8; + matchDistance = ipIndex - pos4; + if ( m2Distance <= LZ4_DISTANCE_MAX + && pos8 >= prefixIdx /* only search within prefix */ + && likely(ip < mflimit) + ) { + const BYTE* const m2Ptr = prefixPtr + (pos8 - prefixIdx); + unsigned ml2 = LZ4_count(ip+1, m2Ptr, matchlimit); + if (ml2 > matchLength) { + LZ4MID_addPosition(hash8Table, h8, ipIndex+1); + ip++; + matchLength = ml2; + matchDistance = m2Distance; + } } + goto _lz4mid_encode_sequence; + } + } } + /* no match found in prefix */ + if ( (dict == usingDictCtxHc) + && (ipIndex - gDictEndIndex < LZ4_DISTANCE_MAX - 8) ) { + /* search a match in dictionary */ + LZ4HC_match_t dMatch = LZ4HC_searchExtDict(ip, ipIndex, + anchor, matchlimit, + ctx->dictCtx, gDictEndIndex, + 0, 2); + if (dMatch.len >= MINMATCH) { + DEBUGLOG(7, "found Dictionary match (offset=%i)", dMatch.off); + ip += dMatch.back; + assert(ip >= anchor); + matchLength = (unsigned)dMatch.len; + matchDistance = (unsigned)dMatch.off; + goto _lz4mid_encode_sequence; + } + } + /* no match found */ + ip += 1 + ((ip-anchor) >> 9); /* skip faster over incompressible data */ + continue; + +_lz4mid_encode_sequence: + /* catch back */ + while (((ip > anchor) & ((U32)(ip-prefixPtr) > matchDistance)) && (unlikely(ip[-1] == ip[-(int)matchDistance-1]))) { + ip--; matchLength++; + }; + + /* fill table with beginning of match */ + ADDPOS8(ip+1, ipIndex+1); + ADDPOS8(ip+2, ipIndex+2); + ADDPOS4(ip+1, ipIndex+1); + + /* encode */ + { BYTE* const saved_op = op; + /* LZ4HC_encodeSequence always updates @op; on success, it updates @ip and @anchor */ + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), + (int)matchLength, (int)matchDistance, + limit, oend) ) { + op = saved_op; /* restore @op value before failed LZ4HC_encodeSequence */ + goto _lz4mid_dest_overflow; + } + } + + /* fill table with end of match */ + { U32 endMatchIdx = (U32)(ip-prefixPtr) + prefixIdx; + U32 pos_m2 = endMatchIdx - 2; + if (pos_m2 < ilimitIdx) { + if (likely(ip - prefixPtr > 5)) { + ADDPOS8(ip-5, endMatchIdx - 5); + } + ADDPOS8(ip-3, endMatchIdx - 3); + ADDPOS8(ip-2, endMatchIdx - 2); + ADDPOS4(ip-2, endMatchIdx - 2); + ADDPOS4(ip-1, endMatchIdx - 1); + } + } + } + +_lz4mid_last_literals: + /* Encode Last Literals */ + { size_t lastRunSize = (size_t)(iend - anchor); /* literals */ + size_t llAdd = (lastRunSize + 255 - RUN_MASK) / 255; + size_t const totalSize = 1 + llAdd + lastRunSize; + if (limit == fillOutput) oend += LASTLITERALS; /* restore correct value */ + if (limit && (op + totalSize > oend)) { + if (limit == limitedOutput) return 0; /* not enough space in @dst */ + /* adapt lastRunSize to fill 'dest' */ + lastRunSize = (size_t)(oend - op) - 1 /*token*/; + llAdd = (lastRunSize + 256 - RUN_MASK) / 256; + lastRunSize -= llAdd; + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRunSize); + ip = anchor + lastRunSize; /* can be != iend if limit==fillOutput */ + + if (lastRunSize >= RUN_MASK) { + size_t accumulator = lastRunSize - RUN_MASK; + *op++ = (RUN_MASK << ML_BITS); + for(; accumulator >= 255 ; accumulator -= 255) + *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRunSize << ML_BITS); + } + assert(lastRunSize <= (size_t)(oend - op)); + LZ4_memcpy(op, anchor, lastRunSize); + op += lastRunSize; + } + + /* End */ + DEBUGLOG(5, "compressed %i bytes into %i bytes", *srcSizePtr, (int)((char*)op - dst)); + assert(ip >= (const BYTE*)src); + assert(ip <= iend); + *srcSizePtr = (int)(ip - (const BYTE*)src); + assert((char*)op >= dst); + assert(op <= oend); + assert((char*)op - dst < INT_MAX); + return (int)((char*)op - dst); + +_lz4mid_dest_overflow: + if (limit == fillOutput) { + /* Assumption : @ip, @anchor, @optr and @matchLength must be set correctly */ + size_t const ll = (size_t)(ip - anchor); + size_t const ll_addbytes = (ll + 240) / 255; + size_t const ll_totalCost = 1 + ll_addbytes + ll; + BYTE* const maxLitPos = oend - 3; /* 2 for offset, 1 for token */ + DEBUGLOG(6, "Last sequence is overflowing : %u literals, %u remaining space", + (unsigned)ll, (unsigned)(oend-op)); + if (op + ll_totalCost <= maxLitPos) { + /* ll validated; now adjust match length */ + size_t const bytesLeftForMl = (size_t)(maxLitPos - (op+ll_totalCost)); + size_t const maxMlSize = MINMATCH + (ML_MASK-1) + (bytesLeftForMl * 255); + assert(maxMlSize < INT_MAX); + if ((size_t)matchLength > maxMlSize) matchLength= (unsigned)maxMlSize; + if ((oend + LASTLITERALS) - (op + ll_totalCost + 2) - 1 + matchLength >= MFLIMIT) { + DEBUGLOG(6, "Let's encode a last sequence (ll=%u, ml=%u)", (unsigned)ll, matchLength); + LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), + (int)matchLength, (int)matchDistance, + notLimited, oend); + } } + DEBUGLOG(6, "Let's finish with a run of literals (%u bytes left)", (unsigned)(oend-op)); + goto _lz4mid_last_literals; + } + /* compression failed */ + return 0; +} + + +/************************************** +* HC Compression - Search +**************************************/ + /* Update chains up to ip (excluded) */ LZ4_FORCE_INLINE void LZ4HC_Insert (LZ4HC_CCtx_internal* hc4, const BYTE* ip) { @@ -143,23 +635,6 @@ LZ4_FORCE_INLINE void LZ4HC_Insert (LZ4HC_CCtx_internal* hc4, const BYTE* ip) hc4->nextToUpdate = target; } -/** LZ4HC_countBack() : - * @return : negative value, nb of common bytes before ip/match */ -LZ4_FORCE_INLINE -int LZ4HC_countBack(const BYTE* const ip, const BYTE* const match, - const BYTE* const iMin, const BYTE* const mMin) -{ - int back = 0; - int const min = (int)MAX(iMin - ip, mMin - match); - assert(min <= 0); - assert(ip >= iMin); assert((size_t)(ip-iMin) < (1U<<31)); - assert(match >= mMin); assert((size_t)(match - mMin) < (1U<<31)); - while ( (back > min) - && (ip[back-1] == match[back-1]) ) - back--; - return back; -} - #if defined(_MSC_VER) # define LZ4HC_rotl32(x,r) _rotl(x,r) #else @@ -239,10 +714,6 @@ static int LZ4HC_protectDictEnd(U32 const dictLimit, U32 const matchIndex) typedef enum { rep_untested, rep_not, rep_confirmed } repeat_state_e; typedef enum { favorCompressionRatio=0, favorDecompressionSpeed } HCfavor_e; -typedef struct { - int off; - int len; -} LZ4HC_match_t; LZ4_FORCE_INLINE LZ4HC_match_t LZ4HC_InsertAndGetWiderMatch ( @@ -250,7 +721,6 @@ LZ4HC_InsertAndGetWiderMatch ( const BYTE* const ip, const BYTE* const iLowLimit, const BYTE* const iHighLimit, int longest, - const BYTE** startpos, const int maxNbAttempts, const int patternAnalysis, const int chainSwap, const dictCtx_directive dict, @@ -258,7 +728,7 @@ LZ4HC_InsertAndGetWiderMatch ( { U16* const chainTable = hc4->chainTable; U32* const hashTable = hc4->hashTable; - const LZ4HC_CCtx_internal * const dictCtx = hc4->dictCtx; + const LZ4HC_CCtx_internal* const dictCtx = hc4->dictCtx; const BYTE* const prefixPtr = hc4->prefixStart; const U32 prefixIdx = hc4->dictLimit; const U32 ipIndex = (U32)(ip - prefixPtr) + prefixIdx; @@ -274,11 +744,9 @@ LZ4HC_InsertAndGetWiderMatch ( U32 matchIndex; repeat_state_e repeat = rep_untested; size_t srcPatternLength = 0; - int offset = 0; + int offset = 0, sBack = 0; DEBUGLOG(7, "LZ4HC_InsertAndGetWiderMatch"); - assert(startpos != NULL); - *startpos = ip; /* in case there is no solution */ /* First Match */ LZ4HC_Insert(hc4, ip); /* insert all prior positions up to ip (excluded) */ matchIndex = hashTable[LZ4HC_hashPtr(ip)]; @@ -304,7 +772,7 @@ LZ4HC_InsertAndGetWiderMatch ( if (matchLength > longest) { longest = matchLength; offset = (int)(ipIndex - matchIndex); - *startpos = ip + back; + sBack = back; DEBUGLOG(7, "Found match of len=%i within prefix, offset=%i, back=%i", longest, offset, -back); } } } } else { /* lowestMatchIndex <= matchIndex < dictLimit : within Ext Dict */ @@ -323,7 +791,7 @@ LZ4HC_InsertAndGetWiderMatch ( if (matchLength > longest) { longest = matchLength; offset = (int)(ipIndex - matchIndex); - *startpos = ip + back; + sBack = back; DEBUGLOG(7, "Found match of len=%i within dict, offset=%i, back=%i", longest, offset, -back); } } } @@ -413,7 +881,7 @@ LZ4HC_InsertAndGetWiderMatch ( assert(maxML < 2 GB); longest = (int)maxML; offset = (int)(ipIndex - matchIndex); - *startpos = ip; + assert(sBack == 0); DEBUGLOG(7, "Found repeat pattern match of len=%i, offset=%i", longest, offset); } { U32 const distToNextPattern = DELTANEXTU16(chainTable, matchIndex); @@ -431,7 +899,7 @@ LZ4HC_InsertAndGetWiderMatch ( if ( dict == usingDictCtxHc && nbAttempts > 0 - && ipIndex - lowestMatchIndex < LZ4_DISTANCE_MAX) { + && withinStartDistance) { size_t const dictEndOffset = (size_t)(dictCtx->end - dictCtx->prefixStart) + dictCtx->dictLimit; U32 dictMatchIndex = dictCtx->hashTable[LZ4HC_hashPtr(ip)]; assert(dictEndOffset <= 1 GB); @@ -451,7 +919,7 @@ LZ4HC_InsertAndGetWiderMatch ( if (mlt > longest) { longest = mlt; offset = (int)(ipIndex - matchIndex); - *startpos = ip + back; + sBack = back; DEBUGLOG(7, "found match of length %i within extDictCtx", longest); } } @@ -464,6 +932,7 @@ LZ4HC_InsertAndGetWiderMatch ( assert(longest >= 0); md.len = longest; md.off = offset; + md.back = sBack; return md; } } @@ -475,103 +944,13 @@ LZ4HC_InsertAndFindBestMatch(LZ4HC_CCtx_internal* const hc4, /* Index table wi const int patternAnalysis, const dictCtx_directive dict) { - const BYTE* uselessPtr = ip; DEBUGLOG(7, "LZ4HC_InsertAndFindBestMatch"); /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), * but this won't be the case here, as we define iLowLimit==ip, * so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ - return LZ4HC_InsertAndGetWiderMatch(hc4, ip, ip, iLimit, MINMATCH-1, &uselessPtr, maxNbAttempts, patternAnalysis, 0 /*chainSwap*/, dict, favorCompressionRatio); + return LZ4HC_InsertAndGetWiderMatch(hc4, ip, ip, iLimit, MINMATCH-1, maxNbAttempts, patternAnalysis, 0 /*chainSwap*/, dict, favorCompressionRatio); } -/* LZ4HC_encodeSequence() : - * @return : 0 if ok, - * 1 if buffer issue detected */ -LZ4_FORCE_INLINE int LZ4HC_encodeSequence ( - const BYTE** _ip, - BYTE** _op, - const BYTE** _anchor, - int matchLength, - int offset, - limitedOutput_directive limit, - BYTE* oend) -{ -#define ip (*_ip) -#define op (*_op) -#define anchor (*_anchor) - - size_t length; - BYTE* const token = op++; - -#if defined(LZ4_DEBUG) && (LZ4_DEBUG >= 6) - static const BYTE* start = NULL; - static U32 totalCost = 0; - U32 const pos = (start==NULL) ? 0 : (U32)(anchor - start); - U32 const ll = (U32)(ip - anchor); - U32 const llAdd = (ll>=15) ? ((ll-15) / 255) + 1 : 0; - U32 const mlAdd = (matchLength>=19) ? ((matchLength-19) / 255) + 1 : 0; - U32 const cost = 1 + llAdd + ll + 2 + mlAdd; - if (start==NULL) start = anchor; /* only works for single segment */ - /* g_debuglog_enable = (pos >= 2228) & (pos <= 2262); */ - DEBUGLOG(6, "pos:%7u -- literals:%4u, match:%4i, offset:%5i, cost:%4u + %5u", - pos, - (U32)(ip - anchor), matchLength, offset, - cost, totalCost); - totalCost += cost; -#endif - - /* Encode Literal length */ - length = (size_t)(ip - anchor); - LZ4_STATIC_ASSERT(notLimited == 0); - /* Check output limit */ - if (limit && ((op + (length / 255) + length + (2 + 1 + LASTLITERALS)) > oend)) { - DEBUGLOG(6, "Not enough room to write %i literals (%i bytes remaining)", - (int)length, (int)(oend - op)); - return 1; - } - if (length >= RUN_MASK) { - size_t len = length - RUN_MASK; - *token = (RUN_MASK << ML_BITS); - for(; len >= 255 ; len -= 255) *op++ = 255; - *op++ = (BYTE)len; - } else { - *token = (BYTE)(length << ML_BITS); - } - - /* Copy Literals */ - LZ4_wildCopy8(op, anchor, op + length); - op += length; - - /* Encode Offset */ - assert(offset <= LZ4_DISTANCE_MAX ); - assert(offset > 0); - LZ4_writeLE16(op, (U16)(offset)); op += 2; - - /* Encode MatchLength */ - assert(matchLength >= MINMATCH); - length = (size_t)matchLength - MINMATCH; - if (limit && (op + (length / 255) + (1 + LASTLITERALS) > oend)) { - DEBUGLOG(6, "Not enough room to write match length"); - return 1; /* Check output limit */ - } - if (length >= ML_MASK) { - *token += ML_MASK; - length -= ML_MASK; - for(; length >= 510 ; length -= 510) { *op++ = 255; *op++ = 255; } - if (length >= 255) { length -= 255; *op++ = 255; } - *op++ = (BYTE)length; - } else { - *token += (BYTE)(length); - } - - /* Prepare next loop */ - ip += matchLength; - anchor = ip; - - return 0; -} -#undef ip -#undef op -#undef anchor LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( LZ4HC_CCtx_internal* const ctx, @@ -601,7 +980,7 @@ LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( const BYTE* start2 = NULL; const BYTE* start3 = NULL; LZ4HC_match_t m0, m1, m2, m3; - const LZ4HC_match_t nomatch = {0, 0}; + const LZ4HC_match_t nomatch = {0, 0, 0}; /* init */ DEBUGLOG(5, "LZ4HC_compress_hashChain (dict?=>%i)", dict); @@ -620,16 +999,21 @@ LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( _Search2: DEBUGLOG(7, "_Search2 (currently found match of size %i)", m1.len); if (ip+m1.len <= mflimit) { + start2 = ip + m1.len - 2; m2 = LZ4HC_InsertAndGetWiderMatch(ctx, - ip + m1.len - 2, ip + 0, matchlimit, m1.len, &start2, + start2, ip + 0, matchlimit, m1.len, maxNbAttempts, patternAnalysis, 0, dict, favorCompressionRatio); + start2 += m2.back; } else { m2 = nomatch; /* do not search further */ } if (m2.len <= m1.len) { /* No better match => encode ML1 immediately */ optr = op; - if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), m1.len, m1.off, limit, oend)) goto _dest_overflow; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), + m1.len, m1.off, + limit, oend) ) + goto _dest_overflow; continue; } @@ -660,9 +1044,11 @@ LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( } if (start2 + m2.len <= mflimit) { + start3 = start2 + m2.len - 3; m3 = LZ4HC_InsertAndGetWiderMatch(ctx, - start2 + m2.len - 3, start2, matchlimit, m2.len, &start3, + start3, start2, matchlimit, m2.len, maxNbAttempts, patternAnalysis, 0, dict, favorCompressionRatio); + start3 += m3.back; } else { m3 = nomatch; /* do not search further */ } @@ -672,11 +1058,15 @@ LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( if (start2 < ip+m1.len) m1.len = (int)(start2 - ip); /* Now, encode 2 sequences */ optr = op; - if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), m1.len, m1.off, limit, oend)) + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), + m1.len, m1.off, + limit, oend) ) goto _dest_overflow; ip = start2; optr = op; - if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), m2.len, m2.off, limit, oend)) { + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), + m2.len, m2.off, + limit, oend) ) { m1 = m2; goto _dest_overflow; } @@ -696,7 +1086,10 @@ LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( } optr = op; - if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), m1.len, m1.off, limit, oend)) goto _dest_overflow; + if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), + m1.len, m1.off, + limit, oend) ) + goto _dest_overflow; ip = start3; m1 = m3; @@ -731,7 +1124,10 @@ LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( } } optr = op; - if (LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), m1.len, m1.off, limit, oend)) goto _dest_overflow; + if ( LZ4HC_encodeSequence(UPDATABLE(ip, op, anchor), + m1.len, m1.off, + limit, oend) ) + goto _dest_overflow; /* ML2 becomes ML1 */ ip = start2; m1 = m2; @@ -777,7 +1173,7 @@ LZ4_FORCE_INLINE int LZ4HC_compress_hashChain ( _dest_overflow: if (limit == fillOutput) { - /* Assumption : ip, anchor, ml and ref must be set correctly */ + /* Assumption : @ip, @anchor, @optr and @m1 must be set correctly */ size_t const ll = (size_t)(ip - anchor); size_t const ll_addbytes = (ll + 240) / 255; size_t const ll_totalCost = 1 + ll_addbytes + ll; @@ -821,16 +1217,16 @@ LZ4HC_compress_generic_internal ( const dictCtx_directive dict ) { - typedef enum { lz4hc, lz4opt } lz4hc_strat_e; + typedef enum { lz4mid, lz4hc, lz4opt } lz4hc_strat_e; typedef struct { lz4hc_strat_e strat; int nbSearches; U32 targetLength; } cParams_t; static const cParams_t clTable[LZ4HC_CLEVEL_MAX+1] = { - { lz4hc, 2, 16 }, /* 0, unused */ - { lz4hc, 2, 16 }, /* 1, unused */ - { lz4hc, 2, 16 }, /* 2, unused */ + { lz4mid, 2, 16 }, /* 0, unused */ + { lz4mid, 2, 16 }, /* 1, unused */ + { lz4mid, 2, 16 }, /* 2 */ { lz4hc, 4, 16 }, /* 3 */ { lz4hc, 8, 16 }, /* 4 */ { lz4hc, 16, 16 }, /* 5 */ @@ -850,13 +1246,20 @@ LZ4HC_compress_generic_internal ( if ((U32)*srcSizePtr > (U32)LZ4_MAX_INPUT_SIZE) return 0; /* Unsupported input size (too large or negative) */ ctx->end += *srcSizePtr; - if (cLevel < 1) cLevel = LZ4HC_CLEVEL_DEFAULT; /* note : convention is different from lz4frame, maybe something to review */ + /* note : clevel convention is a bit different from lz4frame, + * possibly something worth revisiting for consistency */ + if (cLevel < 1) + cLevel = LZ4HC_CLEVEL_DEFAULT; cLevel = MIN(LZ4HC_CLEVEL_MAX, cLevel); { cParams_t const cParam = clTable[cLevel]; HCfavor_e const favor = ctx->favorDecSpeed ? favorDecompressionSpeed : favorCompressionRatio; int result; - if (cParam.strat == lz4hc) { + if (cParam.strat == lz4mid) { + result = LZ4HC_compress_2hashes(ctx, + src, dst, srcSizePtr, dstCapacity, + limit, dict); + } else if (cParam.strat == lz4hc) { result = LZ4HC_compress_hashChain(ctx, src, dst, srcSizePtr, dstCapacity, cParam.nbSearches, limit, dict); @@ -1321,14 +1724,15 @@ LZ4HC_FindLongerMatch(LZ4HC_CCtx_internal* const ctx, const dictCtx_directive dict, const HCfavor_e favorDecSpeed) { - LZ4HC_match_t const match0 = { 0 , 0 }; + LZ4HC_match_t const match0 = { 0 , 0, 0 }; /* note : LZ4HC_InsertAndGetWiderMatch() is able to modify the starting position of a match (*startpos), * but this won't be the case here, as we define iLowLimit==ip, ** so LZ4HC_InsertAndGetWiderMatch() won't be allowed to search past ip */ - LZ4HC_match_t md = LZ4HC_InsertAndGetWiderMatch(ctx, ip, ip, iHighLimit, minLen, &ip, nbSearches, 1 /*patternAnalysis*/, 1 /*chainSwap*/, dict, favorDecSpeed); + LZ4HC_match_t md = LZ4HC_InsertAndGetWiderMatch(ctx, ip, ip, iHighLimit, minLen, nbSearches, 1 /*patternAnalysis*/, 1 /*chainSwap*/, dict, favorDecSpeed); + assert(md.back == 0); if (md.len <= minLen) return match0; if (favorDecSpeed) { - if ((md.len>18) & (md.len<=36)) md.len=18; /* favor shortcut */ + if ((md.len>18) & (md.len<=36)) md.len=18; /* favor dec.speed (shortcut) */ } return md; } @@ -1407,11 +1811,11 @@ static int LZ4HC_compress_optimal ( LZ4HC_CCtx_internal* ctx, rPos, cost, opt[rPos].litlen); } } /* set prices using initial match */ - { int mlen = MINMATCH; - int const matchML = firstMatch.len; /* necessarily < sufficient_len < LZ4_OPT_NUM */ + { int const matchML = firstMatch.len; /* necessarily < sufficient_len < LZ4_OPT_NUM */ int const offset = firstMatch.off; + int mlen; assert(matchML < LZ4_OPT_NUM); - for ( ; mlen <= matchML ; mlen++) { + for (mlen = MINMATCH ; mlen <= matchML ; mlen++) { int const cost = LZ4HC_sequencePrice(llen, mlen); opt[mlen].mlen = mlen; opt[mlen].off = offset; @@ -1631,7 +2035,7 @@ if (limit == fillOutput) { } _return_label: #if defined(LZ4HC_HEAPMODE) && LZ4HC_HEAPMODE==1 - FREEMEM(opt); + if (opt) FREEMEM(opt); #endif return retval; } diff --git a/pyxcp/recorder/lz4hc.h b/pyxcp/recorder/lz4hc.h index e937acfe..f23db5f6 100644 --- a/pyxcp/recorder/lz4hc.h +++ b/pyxcp/recorder/lz4hc.h @@ -44,7 +44,7 @@ extern "C" { /* --- Useful constants --- */ -#define LZ4HC_CLEVEL_MIN 3 +#define LZ4HC_CLEVEL_MIN 2 #define LZ4HC_CLEVEL_DEFAULT 9 #define LZ4HC_CLEVEL_OPT_MIN 10 #define LZ4HC_CLEVEL_MAX 12 diff --git a/pyxcp/recorder/mio.hpp b/pyxcp/recorder/mio.hpp index a1752580..31fa49fb 100644 --- a/pyxcp/recorder/mio.hpp +++ b/pyxcp/recorder/mio.hpp @@ -43,13 +43,13 @@ */ #ifndef MIO_PAGE_HEADER -#define MIO_PAGE_HEADER + #define MIO_PAGE_HEADER -#ifdef _WIN32 -# include -#else -# include -#endif + #ifdef _WIN32 + #include + #else + #include + #endif namespace mio { @@ -57,8 +57,7 @@ namespace mio { * This is used by `basic_mmap` to determine whether to create a read-only or * a read-write memory mapping. */ -enum class access_mode -{ +enum class access_mode { read, write }; @@ -70,17 +69,15 @@ enum class access_mode * to determine the page size, caches the value, and returns it. Any subsequent call to * this function serves the cached value, so no further syscalls are made. */ -inline size_t page_size() -{ - static const size_t page_size = [] - { -#ifdef _WIN32 +inline size_t page_size() { + static const size_t page_size = [] { + #ifdef _WIN32 SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); return SystemInfo.dwAllocationGranularity; -#else + #else return sysconf(_SC_PAGE_SIZE); -#endif + #endif }(); return page_size; } @@ -90,37 +87,37 @@ inline size_t page_size() * difference until the nearest page boundary before `offset`, or does nothing if * `offset` is already page aligned. */ -inline size_t make_offset_page_aligned(size_t offset) noexcept -{ +inline size_t make_offset_page_aligned(size_t offset) noexcept { const size_t page_size_ = page_size(); // Use integer division to round down to the nearest page alignment. return offset / page_size_ * page_size_; } -} // namespace mio - -#endif // MIO_PAGE_HEADER +} // namespace mio +#endif // MIO_PAGE_HEADER +#include #include #include #include -#include #ifdef _WIN32 -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif // WIN32_LEAN_AND_MEAN -# include -#else // ifdef _WIN32 -# define INVALID_HANDLE_VALUE -1 -#endif // ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif // WIN32_LEAN_AND_MEAN + #include +#else // ifdef _WIN32 + #define INVALID_HANDLE_VALUE -1 +#endif // ifdef _WIN32 namespace mio { // This value may be provided as the `length` parameter to the constructor or // `map`, in which case a memory mapping of the entire file is created. -enum { map_entire_file = 0 }; +enum { + map_entire_file = 0 +}; #ifdef _WIN32 using file_handle_type = HANDLE; @@ -133,31 +130,31 @@ using file_handle_type = int; const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; template -struct basic_mmap -{ - using value_type = ByteT; - using size_type = size_t; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = value_type*; - using const_pointer = const value_type*; - using difference_type = std::ptrdiff_t; - using iterator = pointer; - using const_iterator = const_pointer; - using reverse_iterator = std::reverse_iterator; +struct basic_mmap { + using value_type = ByteT; + using size_type = size_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using difference_type = std::ptrdiff_t; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - using iterator_category = std::random_access_iterator_tag; - using handle_type = file_handle_type; + using iterator_category = std::random_access_iterator_tag; + using handle_type = file_handle_type; static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); -private: + private: + // Points to the first requested byte, and not to the actual start of the mapping. pointer data_ = nullptr; // Length--in bytes--requested by user (which may not be the length of the // full mapping) and the length of the full mapping. - size_type length_ = 0; + size_type length_ = 0; size_type mapped_length_ = 0; // Letting user map a file using both an existing file handle and a path @@ -178,7 +175,8 @@ struct basic_mmap // close `file_handle_`. bool is_handle_internal_; -public: + public: + /** * The default constructed mmap object is in a non-mapped state, that is, * any operation that attempts to access nonexistent underlying data will @@ -193,11 +191,12 @@ struct basic_mmap * thrown. */ template - basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) - { + basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(path, offset, length, error); - if(error) { throw std::system_error(error); } + if (error) { + throw std::system_error(error); + } } /** @@ -205,13 +204,14 @@ struct basic_mmap * while establishing the mapping is wrapped in a `std::system_error` and is * thrown. */ - basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) - { + basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(handle, offset, length, error); - if(error) { throw std::system_error(error); } + if (error) { + throw std::system_error(error); + } } -#endif // __cpp_exceptions +#endif // __cpp_exceptions /** * `basic_mmap` has single-ownership semantics, so transferring ownership @@ -233,18 +233,25 @@ struct basic_mmap * however, a mapped region of a file gets its own handle, which is returned by * 'mapping_handle'. */ - handle_type file_handle() const noexcept { return file_handle_; } + handle_type file_handle() const noexcept { + return file_handle_; + } + handle_type mapping_handle() const noexcept; /** Returns whether a valid memory mapping has been created. */ - bool is_open() const noexcept { return file_handle_ != invalid_handle; } + bool is_open() const noexcept { + return file_handle_ != invalid_handle; + } /** * Returns true if no mapping was established, that is, conceptually the * same as though the length that was mapped was 0. This function is * provided so that this class has Container semantics. */ - bool empty() const noexcept { return length() == 0; } + bool empty() const noexcept { + return length() == 0; + } /** Returns true if a mapping was established. */ bool is_mapped() const noexcept; @@ -255,13 +262,20 @@ struct basic_mmap * bytes that were mapped which is a multiple of the underlying operating system's * page allocation granularity. */ - size_type size() const noexcept { return length(); } - size_type length() const noexcept { return length_; } - size_type mapped_length() const noexcept { return mapped_length_; } + size_type size() const noexcept { + return length(); + } + + size_type length() const noexcept { + return length_; + } + + size_type mapped_length() const noexcept { + return mapped_length_; + } /** Returns the offset relative to the start of the mapping. */ - size_type mapping_offset() const noexcept - { + size_type mapping_offset() const noexcept { return mapped_length_ - length_; } @@ -269,68 +283,96 @@ struct basic_mmap * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping * exists. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > pointer data() noexcept { return data_; } - const_pointer data() const noexcept { return data_; } + template< access_mode A = AccessMode, typename = typename std::enable_if::type > + pointer data() noexcept { + return data_; + } + + const_pointer data() const noexcept { + return data_; + } /** * Returns an iterator to the first requested byte, if a valid memory mapping * exists, otherwise this function call is undefined behaviour. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > iterator begin() noexcept { return data(); } - const_iterator begin() const noexcept { return data(); } - const_iterator cbegin() const noexcept { return data(); } + template< access_mode A = AccessMode, typename = typename std::enable_if::type > + iterator begin() noexcept { + return data(); + } + + const_iterator begin() const noexcept { + return data(); + } + + const_iterator cbegin() const noexcept { + return data(); + } /** * Returns an iterator one past the last requested byte, if a valid memory mapping * exists, otherwise this function call is undefined behaviour. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > iterator end() noexcept { return data() + length(); } - const_iterator end() const noexcept { return data() + length(); } - const_iterator cend() const noexcept { return data() + length(); } + template< access_mode A = AccessMode, typename = typename std::enable_if::type > + iterator end() noexcept { + return data() + length(); + } + + const_iterator end() const noexcept { + return data() + length(); + } + + const_iterator cend() const noexcept { + return data() + length(); + } /** * Returns a reverse iterator to the last memory mapped byte, if a valid * memory mapping exists, otherwise this function call is undefined * behaviour. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } - const_reverse_iterator rbegin() const noexcept - { return const_reverse_iterator(end()); } - const_reverse_iterator crbegin() const noexcept - { return const_reverse_iterator(end()); } + template< access_mode A = AccessMode, typename = typename std::enable_if::type > + reverse_iterator rbegin() noexcept { + return reverse_iterator(end()); + } + + const_reverse_iterator rbegin() const noexcept { + return const_reverse_iterator(end()); + } + + const_reverse_iterator crbegin() const noexcept { + return const_reverse_iterator(end()); + } /** * Returns a reverse iterator past the first mapped byte, if a valid memory * mapping exists, otherwise this function call is undefined behaviour. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > reverse_iterator rend() noexcept { return reverse_iterator(begin()); } - const_reverse_iterator rend() const noexcept - { return const_reverse_iterator(begin()); } - const_reverse_iterator crend() const noexcept - { return const_reverse_iterator(begin()); } + template< access_mode A = AccessMode, typename = typename std::enable_if::type > + reverse_iterator rend() noexcept { + return reverse_iterator(begin()); + } + + const_reverse_iterator rend() const noexcept { + return const_reverse_iterator(begin()); + } + + const_reverse_iterator crend() const noexcept { + return const_reverse_iterator(begin()); + } /** * Returns a reference to the `i`th byte from the first requested byte (as returned * by `data`). If this is invoked when no valid memory mapping has been created * prior to this call, undefined behaviour ensues. */ - reference operator[](const size_type i) noexcept { return data_[i]; } - const_reference operator[](const size_type i) const noexcept { return data_[i]; } + reference operator[](const size_type i) noexcept { + return data_[i]; + } + + const_reference operator[](const size_type i) const noexcept { + return data_[i]; + } /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the @@ -353,8 +395,7 @@ struct basic_mmap * case a mapping of the entire file is created. */ template - void map(const String& path, const size_type offset, - const size_type length, std::error_code& error); + void map(const String& path, const size_type offset, const size_type length, std::error_code& error); /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the @@ -369,8 +410,7 @@ struct basic_mmap * The entire file is mapped. */ template - void map(const String& path, std::error_code& error) - { + void map(const String& path, std::error_code& error) { map(path, 0, map_entire_file, error); } @@ -393,8 +433,7 @@ struct basic_mmap * `length` is the number of bytes to map. It may be `map_entire_file`, in which * case a mapping of the entire file is created. */ - void map(const handle_type handle, const size_type offset, - const size_type length, std::error_code& error); + void map(const handle_type handle, const size_type offset, const size_type length, std::error_code& error); /** * Establishes a memory mapping with AccessMode. If the mapping is @@ -407,8 +446,7 @@ struct basic_mmap * * The entire file is mapped. */ - void map(const handle_type handle, std::error_code& error) - { + void map(const handle_type handle, std::error_code& error) { map(handle, 0, map_entire_file, error); } @@ -427,25 +465,21 @@ struct basic_mmap /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ template - typename std::enable_if::type - sync(std::error_code& error); + typename std::enable_if::type sync(std::error_code& error); /** * All operators compare the address of the first byte and size of the two mapped * regions. */ -private: - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > pointer get_mapping_start() noexcept - { + private: + + template< access_mode A = AccessMode, typename = typename std::enable_if::type > + pointer get_mapping_start() noexcept { return !data() ? nullptr : data() - mapping_offset(); } - const_pointer get_mapping_start() const noexcept - { + const_pointer get_mapping_start() const noexcept { return !data() ? nullptr : data() - mapping_offset(); } @@ -455,35 +489,28 @@ struct basic_mmap * do SFINAE in a dedicated function, where one syncs and the other is a noop. */ template - typename std::enable_if::type - conditional_sync(); + typename std::enable_if::type conditional_sync(); template typename std::enable_if::type conditional_sync(); }; template -bool operator==(const basic_mmap& a, - const basic_mmap& b); +bool operator==(const basic_mmap& a, const basic_mmap& b); template -bool operator!=(const basic_mmap& a, - const basic_mmap& b); +bool operator!=(const basic_mmap& a, const basic_mmap& b); template -bool operator<(const basic_mmap& a, - const basic_mmap& b); +bool operator<(const basic_mmap& a, const basic_mmap& b); template -bool operator<=(const basic_mmap& a, - const basic_mmap& b); +bool operator<=(const basic_mmap& a, const basic_mmap& b); template -bool operator>(const basic_mmap& a, - const basic_mmap& b); +bool operator>(const basic_mmap& a, const basic_mmap& b); template -bool operator>=(const basic_mmap& a, - const basic_mmap& b); +bool operator>=(const basic_mmap& a, const basic_mmap& b); /** * This is the basis for all read-only mmap objects and should be preferred over @@ -503,22 +530,18 @@ using basic_mmap_sink = basic_mmap; * These aliases cover the most common use cases, both representing a raw byte stream * (either with a char or an unsigned char/uint8_t). */ -using mmap_source = basic_mmap_source; +using mmap_source = basic_mmap_source; using ummap_source = basic_mmap_source; -using mmap_sink = basic_mmap_sink; +using mmap_sink = basic_mmap_sink; using ummap_sink = basic_mmap_sink; /** * Convenience factory method that constructs a mapping for any `basic_mmap` or * `basic_mmap` type. */ -template< - typename MMap, - typename MappingToken -> MMap make_mmap(const MappingToken& token, - int64_t offset, int64_t length, std::error_code& error) -{ +template< typename MMap, typename MappingToken > +MMap make_mmap(const MappingToken& token, int64_t offset, int64_t length, std::error_code& error) { MMap mmap; mmap.map(token, offset, length, error); return mmap; @@ -532,15 +555,14 @@ template< * `mmap_source::handle_type`. */ template -mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, - mmap_source::size_type length, std::error_code& error) -{ +mmap_source make_mmap_source( + const MappingToken& token, mmap_source::size_type offset, mmap_source::size_type length, std::error_code& error +) { return make_mmap(token, offset, length, error); } template -mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) -{ +mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) { return make_mmap_source(token, 0, map_entire_file, error); } @@ -552,19 +574,18 @@ mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) * `mmap_sink::handle_type`. */ template -mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, - mmap_sink::size_type length, std::error_code& error) -{ +mmap_sink make_mmap_sink( + const MappingToken& token, mmap_sink::size_type offset, mmap_sink::size_type length, std::error_code& error +) { return make_mmap(token, offset, length, error); } template -mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) -{ +mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) { return make_mmap_sink(token, 0, map_entire_file, error); } -} // namespace mio +} // namespace mio // #include "detail/mmap.ipp" /* Copyright 2017 https://github.com/mandreyel @@ -588,615 +609,534 @@ mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) */ #ifndef MIO_BASIC_MMAP_IMPL -#define MIO_BASIC_MMAP_IMPL + #define MIO_BASIC_MMAP_IMPL -// #include "mio/mmap.hpp" + // #include "mio/mmap.hpp" -// #include "mio/page.hpp" + // #include "mio/page.hpp" -// #include "mio/detail/string_util.hpp" -/* Copyright 2017 https://github.com/mandreyel - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this - * software and associated documentation files (the "Software"), to deal in the Software - * without restriction, including without limitation the rights to use, copy, modify, - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be included in all copies - * or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE - * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ + // #include "mio/detail/string_util.hpp" + /* Copyright 2017 https://github.com/mandreyel + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this + * software and associated documentation files (the "Software"), to deal in the Software + * without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be included in all copies + * or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ -#ifndef MIO_STRING_UTIL_HEADER -#define MIO_STRING_UTIL_HEADER + #ifndef MIO_STRING_UTIL_HEADER + #define MIO_STRING_UTIL_HEADER -#include + #include namespace mio { namespace detail { -template< - typename S, - typename C = typename std::decay::type, - typename = decltype(std::declval().data()), - typename = typename std::enable_if< - std::is_same::value -#ifdef _WIN32 - || std::is_same::value -#endif - >::type -> struct char_type_helper { - using type = typename C::value_type; -}; - -template -struct char_type { - using type = typename char_type_helper::type; -}; - -// TODO: can we avoid this brute force approach? -template<> -struct char_type { - using type = char; -}; - -template<> -struct char_type { - using type = char; -}; - -template -struct char_type { - using type = char; -}; - -template -struct char_type { - using type = char; -}; - -#ifdef _WIN32 -template<> -struct char_type { - using type = wchar_t; -}; - -template<> -struct char_type { - using type = wchar_t; -}; - -template -struct char_type { - using type = wchar_t; -}; - -template -struct char_type { - using type = wchar_t; -}; -#endif // _WIN32 - -template -struct is_c_str_helper -{ - static constexpr bool value = std::is_same< - CharT*, - // TODO: I'm so sorry for this... Can this be made cleaner? - typename std::add_pointer< - typename std::remove_cv< - typename std::remove_pointer< - typename std::decay< - S - >::type - >::type - >::type - >::type - >::value; -}; - -template -struct is_c_str -{ - static constexpr bool value = is_c_str_helper::value; -}; - -#ifdef _WIN32 -template -struct is_c_wstr -{ - static constexpr bool value = is_c_str_helper::value; -}; -#endif // _WIN32 - -template -struct is_c_str_or_c_wstr -{ - static constexpr bool value = is_c_str::value -#ifdef _WIN32 - || is_c_wstr::value -#endif - ; -}; - -template< - typename String, - typename = decltype(std::declval().data()), - typename = typename std::enable_if::value>::type -> const typename char_type::type* c_str(const String& path) -{ - return path.data(); -} + template< + typename S, typename C = typename std::decay::type, typename = decltype(std::declval().data()), + typename = typename std::enable_if< + std::is_same::value + #ifdef _WIN32 + || std::is_same::value + #endif + >::type > + struct char_type_helper { + using type = typename C::value_type; + }; + + template + struct char_type { + using type = typename char_type_helper::type; + }; + + // TODO: can we avoid this brute force approach? + template<> + struct char_type { + using type = char; + }; + + template<> + struct char_type { + using type = char; + }; + + template + struct char_type { + using type = char; + }; + + template + struct char_type { + using type = char; + }; + + #ifdef _WIN32 + template<> + struct char_type { + using type = wchar_t; + }; + + template<> + struct char_type { + using type = wchar_t; + }; + + template + struct char_type { + using type = wchar_t; + }; + + template + struct char_type { + using type = wchar_t; + }; + #endif // _WIN32 + + template + struct is_c_str_helper { + static constexpr bool value = std::is_same< + CharT*, + // TODO: I'm so sorry for this... Can this be made cleaner? + typename std::add_pointer< typename std::remove_cv< + typename std::remove_pointer< typename std::decay< S >::type >::type >::type >::type >::value; + }; + + template + struct is_c_str { + static constexpr bool value = is_c_str_helper::value; + }; + + #ifdef _WIN32 + template + struct is_c_wstr { + static constexpr bool value = is_c_str_helper::value; + }; + #endif // _WIN32 + + template + struct is_c_str_or_c_wstr { + static constexpr bool value = is_c_str::value + #ifdef _WIN32 + || is_c_wstr::value + #endif + ; + }; -template< - typename String, - typename = decltype(std::declval().empty()), - typename = typename std::enable_if::value>::type -> bool empty(const String& path) -{ - return path.empty(); -} + template< + typename String, typename = decltype(std::declval().data()), + typename = typename std::enable_if::value>::type > + const typename char_type::type* c_str(const String& path) { + return path.data(); + } -template< - typename String, - typename = typename std::enable_if::value>::type -> const typename char_type::type* c_str(String path) -{ - return path; -} + template< + typename String, typename = decltype(std::declval().empty()), + typename = typename std::enable_if::value>::type > + bool empty(const String& path) { + return path.empty(); + } -template< - typename String, - typename = typename std::enable_if::value>::type -> bool empty(String path) -{ - return !path || (*path == 0); -} + template< typename String, typename = typename std::enable_if::value>::type > + const typename char_type::type* c_str(String path) { + return path; + } -} // namespace detail -} // namespace mio + template< typename String, typename = typename std::enable_if::value>::type > + bool empty(String path) { + return !path || (*path == 0); + } -#endif // MIO_STRING_UTIL_HEADER +} // namespace detail +} // namespace mio + #endif // MIO_STRING_UTIL_HEADER -#include + #include -#ifndef _WIN32 -# include -# include -# include -# include -#endif + #ifndef _WIN32 + #include + #include + #include + #include + #endif namespace mio { namespace detail { -#ifdef _WIN32 -namespace win { + #ifdef _WIN32 + namespace win { -/** Returns the 4 upper bytes of an 8-byte integer. */ -inline DWORD int64_high(int64_t n) noexcept -{ - return n >> 32; -} - -/** Returns the 4 lower bytes of an 8-byte integer. */ -inline DWORD int64_low(int64_t n) noexcept -{ - return n & 0xffffffff; -} + /** Returns the 4 upper bytes of an 8-byte integer. */ + inline DWORD int64_high(int64_t n) noexcept { + return n >> 32; + } -std::wstring s_2_ws(const std::string& s) -{ - if (s.empty()) - return{}; - const auto s_length = static_cast(s.length()); - auto buf = std::vector(s_length); - const auto wide_char_count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), s_length, buf.data(), s_length); - return std::wstring(buf.data(), wide_char_count); -} + /** Returns the 4 lower bytes of an 8-byte integer. */ + inline DWORD int64_low(int64_t n) noexcept { + return n & 0xffffffff; + } -template< - typename String, - typename = typename std::enable_if< - std::is_same::type, char>::value - >::type -> file_handle_type open_file_helper(const String& path, const access_mode mode) -{ - return ::CreateFileW(s_2_ws(path).c_str(), - mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - 0, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - 0); -} + std::wstring s_2_ws(const std::string& s) { + if (s.empty()) + return {}; + const auto s_length = static_cast(s.length()); + auto buf = std::vector(s_length); + const auto wide_char_count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), s_length, buf.data(), s_length); + return std::wstring(buf.data(), wide_char_count); + } -template -typename std::enable_if< - std::is_same::type, wchar_t>::value, - file_handle_type ->::type open_file_helper(const String& path, const access_mode mode) -{ - return ::CreateFileW(c_str(path), - mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, - 0, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - 0); -} + template< + typename String, + typename = typename std::enable_if< std::is_same::type, char>::value >::type > + file_handle_type open_file_helper(const String& path, const access_mode mode) { + return ::CreateFileW( + s_2_ws(path).c_str(), mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 + ); + } -} // win -#endif // _WIN32 + template + typename std::enable_if< std::is_same::type, wchar_t>::value, file_handle_type >::type + open_file_helper(const String& path, const access_mode mode) { + return ::CreateFileW( + c_str(path), mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 + ); + } -/** - * Returns the last platform specific system error (errno on POSIX and - * GetLastError on Win) as a `std::error_code`. - */ -inline std::error_code last_error() noexcept -{ - std::error_code error; -#ifdef _WIN32 - error.assign(GetLastError(), std::system_category()); -#else - error.assign(errno, std::system_category()); -#endif - return error; -} + } // namespace win + #endif // _WIN32 -template -file_handle_type open_file(const String& path, const access_mode mode, - std::error_code& error) -{ - error.clear(); - if(detail::empty(path)) - { - error = std::make_error_code(std::errc::invalid_argument); - return invalid_handle; - } -#ifdef _WIN32 - const auto handle = win::open_file_helper(path, mode); -#else // POSIX - const auto handle = ::open(c_str(path), - mode == access_mode::read ? O_RDONLY : O_RDWR); -#endif - if(handle == invalid_handle) - { - error = detail::last_error(); + /** + * Returns the last platform specific system error (errno on POSIX and + * GetLastError on Win) as a `std::error_code`. + */ + inline std::error_code last_error() noexcept { + std::error_code error; + #ifdef _WIN32 + error.assign(GetLastError(), std::system_category()); + #else + error.assign(errno, std::system_category()); + #endif + return error; } - return handle; -} -inline size_t query_file_size(file_handle_type handle, std::error_code& error) -{ - error.clear(); -#ifdef _WIN32 - LARGE_INTEGER file_size; - if(::GetFileSizeEx(handle, &file_size) == 0) - { - error = detail::last_error(); - return 0; - } - return static_cast(file_size.QuadPart); -#else // POSIX - struct stat sbuf; - if(::fstat(handle, &sbuf) == -1) - { - error = detail::last_error(); - return 0; + template + file_handle_type open_file(const String& path, const access_mode mode, std::error_code& error) { + error.clear(); + if (detail::empty(path)) { + error = std::make_error_code(std::errc::invalid_argument); + return invalid_handle; + } + #ifdef _WIN32 + const auto handle = win::open_file_helper(path, mode); + #else // POSIX + const auto handle = ::open(c_str(path), mode == access_mode::read ? O_RDONLY : O_RDWR); + #endif + if (handle == invalid_handle) { + error = detail::last_error(); + } + return handle; } - return sbuf.st_size; -#endif -} -struct mmap_context -{ - char* data; - int64_t length; - int64_t mapped_length; -#ifdef _WIN32 - file_handle_type file_mapping_handle; -#endif -}; + inline size_t query_file_size(file_handle_type handle, std::error_code& error) { + error.clear(); + #ifdef _WIN32 + LARGE_INTEGER file_size; + if (::GetFileSizeEx(handle, &file_size) == 0) { + error = detail::last_error(); + return 0; + } + return static_cast(file_size.QuadPart); + #else // POSIX + struct stat sbuf; + if (::fstat(handle, &sbuf) == -1) { + error = detail::last_error(); + return 0; + } + return sbuf.st_size; + #endif + } -inline mmap_context memory_map(const file_handle_type file_handle, const int64_t offset, - const int64_t length, const access_mode mode, std::error_code& error) -{ - const int64_t aligned_offset = make_offset_page_aligned(offset); - const int64_t length_to_map = offset - aligned_offset + length; -#ifdef _WIN32 - const int64_t max_file_size = offset + length; - const auto file_mapping_handle = ::CreateFileMapping( - file_handle, - 0, - mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, - win::int64_high(max_file_size), - win::int64_low(max_file_size), - 0); - if(file_mapping_handle == invalid_handle) - { - error = detail::last_error(); - return {}; - } - char* mapping_start = static_cast(::MapViewOfFile( - file_mapping_handle, - mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, - win::int64_high(aligned_offset), - win::int64_low(aligned_offset), - length_to_map)); - if(mapping_start == nullptr) - { - // Close file handle if mapping it failed. - ::CloseHandle(file_mapping_handle); - error = detail::last_error(); - return {}; - } -#else // POSIX - char* mapping_start = static_cast(::mmap( - 0, // Don't give hint as to where to map. - length_to_map, - mode == access_mode::read ? PROT_READ : PROT_WRITE, - MAP_SHARED, - file_handle, - aligned_offset)); - if(mapping_start == MAP_FAILED) - { - error = detail::last_error(); - return {}; + struct mmap_context { + char* data; + int64_t length; + int64_t mapped_length; + #ifdef _WIN32 + file_handle_type file_mapping_handle; + #endif + }; + + inline mmap_context memory_map( + const file_handle_type file_handle, const int64_t offset, const int64_t length, const access_mode mode, + std::error_code& error + ) { + const int64_t aligned_offset = make_offset_page_aligned(offset); + const int64_t length_to_map = offset - aligned_offset + length; + #ifdef _WIN32 + const int64_t max_file_size = offset + length; + const auto file_mapping_handle = ::CreateFileMapping( + file_handle, 0, mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, win::int64_high(max_file_size), + win::int64_low(max_file_size), 0 + ); + if (file_mapping_handle == invalid_handle) { + error = detail::last_error(); + return {}; + } + char* mapping_start = static_cast(::MapViewOfFile( + file_mapping_handle, mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, win::int64_high(aligned_offset), + win::int64_low(aligned_offset), length_to_map + )); + if (mapping_start == nullptr) { + // Close file handle if mapping it failed. + ::CloseHandle(file_mapping_handle); + error = detail::last_error(); + return {}; + } + #else // POSIX + char* mapping_start = static_cast(::mmap( + 0, // Don't give hint as to where to map. + length_to_map, mode == access_mode::read ? PROT_READ : PROT_WRITE, MAP_SHARED, file_handle, aligned_offset + )); + if (mapping_start == MAP_FAILED) { + error = detail::last_error(); + return {}; + } + #endif + mmap_context ctx; + ctx.data = mapping_start + offset - aligned_offset; + ctx.length = length; + ctx.mapped_length = length_to_map; + #ifdef _WIN32 + ctx.file_mapping_handle = file_mapping_handle; + #endif + return ctx; } -#endif - mmap_context ctx; - ctx.data = mapping_start + offset - aligned_offset; - ctx.length = length; - ctx.mapped_length = length_to_map; -#ifdef _WIN32 - ctx.file_mapping_handle = file_mapping_handle; -#endif - return ctx; -} -} // namespace detail +} // namespace detail // -- basic_mmap -- template -basic_mmap::~basic_mmap() -{ +basic_mmap::~basic_mmap() { conditional_sync(); unmap(); } template -basic_mmap::basic_mmap(basic_mmap&& other) - : data_(std::move(other.data_)) - , length_(std::move(other.length_)) - , mapped_length_(std::move(other.mapped_length_)) - , file_handle_(std::move(other.file_handle_)) -#ifdef _WIN32 - , file_mapping_handle_(std::move(other.file_mapping_handle_)) -#endif - , is_handle_internal_(std::move(other.is_handle_internal_)) -{ - other.data_ = nullptr; +basic_mmap::basic_mmap(basic_mmap&& other) : + data_(std::move(other.data_)), + length_(std::move(other.length_)), + mapped_length_(std::move(other.mapped_length_)), + file_handle_(std::move(other.file_handle_)) + #ifdef _WIN32 + , + file_mapping_handle_(std::move(other.file_mapping_handle_)) + #endif + , + is_handle_internal_(std::move(other.is_handle_internal_)) { + other.data_ = nullptr; other.length_ = other.mapped_length_ = 0; - other.file_handle_ = invalid_handle; -#ifdef _WIN32 + other.file_handle_ = invalid_handle; + #ifdef _WIN32 other.file_mapping_handle_ = invalid_handle; -#endif + #endif } template -basic_mmap& -basic_mmap::operator=(basic_mmap&& other) -{ - if(this != &other) - { +basic_mmap& basic_mmap::operator=(basic_mmap&& other) { + if (this != &other) { // First the existing mapping needs to be removed. unmap(); - data_ = std::move(other.data_); - length_ = std::move(other.length_); + data_ = std::move(other.data_); + length_ = std::move(other.length_); mapped_length_ = std::move(other.mapped_length_); - file_handle_ = std::move(other.file_handle_); -#ifdef _WIN32 + file_handle_ = std::move(other.file_handle_); + #ifdef _WIN32 file_mapping_handle_ = std::move(other.file_mapping_handle_); -#endif + #endif is_handle_internal_ = std::move(other.is_handle_internal_); // The moved from basic_mmap's fields need to be reset, because // otherwise other's destructor will unmap the same mapping that was // just moved into this. - other.data_ = nullptr; + other.data_ = nullptr; other.length_ = other.mapped_length_ = 0; - other.file_handle_ = invalid_handle; -#ifdef _WIN32 + other.file_handle_ = invalid_handle; + #ifdef _WIN32 other.file_mapping_handle_ = invalid_handle; -#endif + #endif other.is_handle_internal_ = false; } return *this; } template -typename basic_mmap::handle_type -basic_mmap::mapping_handle() const noexcept -{ -#ifdef _WIN32 +typename basic_mmap::handle_type basic_mmap::mapping_handle() const noexcept { + #ifdef _WIN32 return file_mapping_handle_; -#else + #else return file_handle_; -#endif + #endif } template template -void basic_mmap::map(const String& path, const size_type offset, - const size_type length, std::error_code& error) -{ +void basic_mmap::map( + const String& path, const size_type offset, const size_type length, std::error_code& error +) { error.clear(); - if(detail::empty(path)) - { + if (detail::empty(path)) { error = std::make_error_code(std::errc::invalid_argument); return; } const auto handle = detail::open_file(path, AccessMode, error); - if(error) - { + if (error) { return; } map(handle, offset, length, error); // This MUST be after the call to map, as that sets this to true. - if(!error) - { + if (!error) { is_handle_internal_ = true; } } template -void basic_mmap::map(const handle_type handle, - const size_type offset, const size_type length, std::error_code& error) -{ +void basic_mmap::map( + const handle_type handle, const size_type offset, const size_type length, std::error_code& error +) { error.clear(); - if(handle == invalid_handle) - { + if (handle == invalid_handle) { error = std::make_error_code(std::errc::bad_file_descriptor); return; } const auto file_size = detail::query_file_size(handle, error); - if(error) - { + if (error) { return; } - if(offset + length > file_size) - { + if (offset + length > file_size) { error = std::make_error_code(std::errc::invalid_argument); return; } - const auto ctx = detail::memory_map(handle, offset, - length == map_entire_file ? (file_size - offset) : length, - AccessMode, error); - if(!error) - { + const auto ctx = + detail::memory_map(handle, offset, length == map_entire_file ? (file_size - offset) : length, AccessMode, error); + if (!error) { // We must unmap the previous mapping that may have existed prior to this call. // Note that this must only be invoked after a new mapping has been created in // order to provide the strong guarantee that, should the new mapping fail, the // `map` function leaves this instance in a state as though the function had // never been invoked. unmap(); - file_handle_ = handle; + file_handle_ = handle; is_handle_internal_ = false; - data_ = reinterpret_cast(ctx.data); - length_ = ctx.length; - mapped_length_ = ctx.mapped_length; -#ifdef _WIN32 + data_ = std::bit_cast(ctx.data); + length_ = ctx.length; + mapped_length_ = ctx.mapped_length; + #ifdef _WIN32 file_mapping_handle_ = ctx.file_mapping_handle; -#endif + #endif } } template template -typename std::enable_if::type -basic_mmap::sync(std::error_code& error) -{ +typename std::enable_if::type basic_mmap::sync(std::error_code& error) { error.clear(); - if(!is_open()) - { + if (!is_open()) { error = std::make_error_code(std::errc::bad_file_descriptor); return; } - if(data()) - { -#ifdef _WIN32 - if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 - || ::FlushFileBuffers(file_handle_) == 0) -#else // POSIX - if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) -#endif + if (data()) { + #ifdef _WIN32 + if (::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 || ::FlushFileBuffers(file_handle_) == 0) + #else // POSIX + if (::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) + #endif { error = detail::last_error(); return; } } -#ifdef _WIN32 - if(::FlushFileBuffers(file_handle_) == 0) - { + #ifdef _WIN32 + if (::FlushFileBuffers(file_handle_) == 0) { error = detail::last_error(); } -#endif + #endif } template -void basic_mmap::unmap() -{ - if(!is_open()) { return; } - // TODO do we care about errors here? -#ifdef _WIN32 - if(is_mapped()) - { +void basic_mmap::unmap() { + if (!is_open()) { + return; + } + // TODO do we care about errors here? + #ifdef _WIN32 + if (is_mapped()) { ::UnmapViewOfFile(get_mapping_start()); ::CloseHandle(file_mapping_handle_); } -#else // POSIX - if(data_) { ::munmap(const_cast(get_mapping_start()), mapped_length_); } -#endif + #else // POSIX + if (data_) { + ::munmap(const_cast(get_mapping_start()), mapped_length_); + } + #endif // If `file_handle_` was obtained by our opening it (when map is called with // a path, rather than an existing file handle), we need to close it, // otherwise it must not be closed as it may still be used outside this // instance. - if(is_handle_internal_) - { -#ifdef _WIN32 + if (is_handle_internal_) { + #ifdef _WIN32 ::CloseHandle(file_handle_); -#else // POSIX + #else // POSIX ::close(file_handle_); -#endif + #endif } // Reset fields to their default values. - data_ = nullptr; + data_ = nullptr; length_ = mapped_length_ = 0; - file_handle_ = invalid_handle; -#ifdef _WIN32 + file_handle_ = invalid_handle; + #ifdef _WIN32 file_mapping_handle_ = invalid_handle; -#endif + #endif } template -bool basic_mmap::is_mapped() const noexcept -{ -#ifdef _WIN32 +bool basic_mmap::is_mapped() const noexcept { + #ifdef _WIN32 return file_mapping_handle_ != invalid_handle; -#else // POSIX + #else // POSIX return is_open(); -#endif + #endif } template -void basic_mmap::swap(basic_mmap& other) -{ - if(this != &other) - { +void basic_mmap::swap(basic_mmap& other) { + if (this != &other) { using std::swap; swap(data_, other.data_); swap(file_handle_, other.file_handle_); -#ifdef _WIN32 + #ifdef _WIN32 swap(file_mapping_handle_, other.file_mapping_handle_); -#endif + #endif swap(length_, other.length_); swap(mapped_length_, other.mapped_length_); swap(is_handle_internal_, other.is_handle_internal_); @@ -1205,9 +1145,7 @@ void basic_mmap::swap(basic_mmap& other) template template -typename std::enable_if::type -basic_mmap::conditional_sync() -{ +typename std::enable_if::type basic_mmap::conditional_sync() { // This is invoked from the destructor, so not much we can do about // failures here. std::error_code ec; @@ -1216,63 +1154,51 @@ basic_mmap::conditional_sync() template template -typename std::enable_if::type -basic_mmap::conditional_sync() -{ +typename std::enable_if::type basic_mmap::conditional_sync() { // noop } template -bool operator==(const basic_mmap& a, - const basic_mmap& b) -{ - return a.data() == b.data() - && a.size() == b.size(); +bool operator==(const basic_mmap& a, const basic_mmap& b) { + return a.data() == b.data() && a.size() == b.size(); } template -bool operator!=(const basic_mmap& a, - const basic_mmap& b) -{ +bool operator!=(const basic_mmap& a, const basic_mmap& b) { return !(a == b); } template -bool operator<(const basic_mmap& a, - const basic_mmap& b) -{ - if(a.data() == b.data()) { return a.size() < b.size(); } +bool operator<(const basic_mmap& a, const basic_mmap& b) { + if (a.data() == b.data()) { + return a.size() < b.size(); + } return a.data() < b.data(); } template -bool operator<=(const basic_mmap& a, - const basic_mmap& b) -{ +bool operator<=(const basic_mmap& a, const basic_mmap& b) { return !(a > b); } template -bool operator>(const basic_mmap& a, - const basic_mmap& b) -{ - if(a.data() == b.data()) { return a.size() > b.size(); } +bool operator>(const basic_mmap& a, const basic_mmap& b) { + if (a.data() == b.data()) { + return a.size() > b.size(); + } return a.data() > b.data(); } template -bool operator>=(const basic_mmap& a, - const basic_mmap& b) -{ +bool operator>=(const basic_mmap& a, const basic_mmap& b) { return !(a < b); } -} // namespace mio +} // namespace mio -#endif // MIO_BASIC_MMAP_IMPL +#endif // MIO_BASIC_MMAP_IMPL - -#endif // MIO_MMAP_HEADER +#endif // MIO_MMAP_HEADER /* Copyright 2017 https://github.com/mandreyel * * Permission is hereby granted, free of charge, to any person obtaining a copy of this @@ -1297,9 +1223,9 @@ bool operator>=(const basic_mmap& a, #define MIO_PAGE_HEADER #ifdef _WIN32 -# include + #include #else -# include + #include #endif namespace mio { @@ -1308,8 +1234,7 @@ namespace mio { * This is used by `basic_mmap` to determine whether to create a read-only or * a read-write memory mapping. */ -enum class access_mode -{ +enum class access_mode { read, write }; @@ -1321,10 +1246,8 @@ enum class access_mode * to determine the page size, caches the value, and returns it. Any subsequent call to * this function serves the cached value, so no further syscalls are made. */ -inline size_t page_size() -{ - static const size_t page_size = [] - { +inline size_t page_size() { + static const size_t page_size = [] { #ifdef _WIN32 SYSTEM_INFO SystemInfo; GetSystemInfo(&SystemInfo); @@ -1341,16 +1264,15 @@ inline size_t page_size() * difference until the nearest page boundary before `offset`, or does nothing if * `offset` is already page aligned. */ -inline size_t make_offset_page_aligned(size_t offset) noexcept -{ +inline size_t make_offset_page_aligned(size_t offset) noexcept { const size_t page_size_ = page_size(); // Use integer division to round down to the nearest page alignment. return offset / page_size_ * page_size_; } -} // namespace mio +} // namespace mio -#endif // MIO_PAGE_HEADER +#endif // MIO_PAGE_HEADER /* Copyright 2017 https://github.com/mandreyel * * Permission is hereby granted, free of charge, to any person obtaining a copy of this @@ -1376,9 +1298,8 @@ inline size_t make_offset_page_aligned(size_t offset) noexcept // #include "mio/mmap.hpp" - -#include // std::error_code -#include // std::shared_ptr +#include // std::shared_ptr +#include // std::error_code namespace mio { @@ -1389,54 +1310,50 @@ namespace mio { * This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if * shared semantics are not required. */ -template< - access_mode AccessMode, - typename ByteT -> class basic_shared_mmap -{ +template< access_mode AccessMode, typename ByteT > +class basic_shared_mmap { using impl_type = basic_mmap; std::shared_ptr pimpl_; -public: - using value_type = typename impl_type::value_type; - using size_type = typename impl_type::size_type; - using reference = typename impl_type::reference; - using const_reference = typename impl_type::const_reference; - using pointer = typename impl_type::pointer; - using const_pointer = typename impl_type::const_pointer; - using difference_type = typename impl_type::difference_type; - using iterator = typename impl_type::iterator; - using const_iterator = typename impl_type::const_iterator; - using reverse_iterator = typename impl_type::reverse_iterator; + public: + + using value_type = typename impl_type::value_type; + using size_type = typename impl_type::size_type; + using reference = typename impl_type::reference; + using const_reference = typename impl_type::const_reference; + using pointer = typename impl_type::pointer; + using const_pointer = typename impl_type::const_pointer; + using difference_type = typename impl_type::difference_type; + using iterator = typename impl_type::iterator; + using const_iterator = typename impl_type::const_iterator; + using reverse_iterator = typename impl_type::reverse_iterator; using const_reverse_iterator = typename impl_type::const_reverse_iterator; - using iterator_category = typename impl_type::iterator_category; - using handle_type = typename impl_type::handle_type; - using mmap_type = impl_type; + using iterator_category = typename impl_type::iterator_category; + using handle_type = typename impl_type::handle_type; + using mmap_type = impl_type; - basic_shared_mmap() = default; - basic_shared_mmap(const basic_shared_mmap&) = default; + basic_shared_mmap() = default; + basic_shared_mmap(const basic_shared_mmap&) = default; basic_shared_mmap& operator=(const basic_shared_mmap&) = default; - basic_shared_mmap(basic_shared_mmap&&) = default; - basic_shared_mmap& operator=(basic_shared_mmap&&) = default; + basic_shared_mmap(basic_shared_mmap&&) = default; + basic_shared_mmap& operator=(basic_shared_mmap&&) = default; /** Takes ownership of an existing mmap object. */ - basic_shared_mmap(mmap_type&& mmap) - : pimpl_(std::make_shared(std::move(mmap))) - {} + basic_shared_mmap(mmap_type&& mmap) : pimpl_(std::make_shared(std::move(mmap))) { + } /** Takes ownership of an existing mmap object. */ - basic_shared_mmap& operator=(mmap_type&& mmap) - { + basic_shared_mmap& operator=(mmap_type&& mmap) { pimpl_ = std::make_shared(std::move(mmap)); return *this; } /** Initializes this object with an already established shared mmap. */ - basic_shared_mmap(std::shared_ptr mmap) : pimpl_(std::move(mmap)) {} + basic_shared_mmap(std::shared_ptr mmap) : pimpl_(std::move(mmap)) { + } /** Initializes this object with an already established shared mmap. */ - basic_shared_mmap& operator=(std::shared_ptr mmap) - { + basic_shared_mmap& operator=(std::shared_ptr mmap) { pimpl_ = std::move(mmap); return *this; } @@ -1448,11 +1365,12 @@ template< * thrown. */ template - basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) - { + basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(path, offset, length, error); - if(error) { throw std::system_error(error); } + if (error) { + throw std::system_error(error); + } } /** @@ -1460,13 +1378,14 @@ template< * while establishing the mapping is wrapped in a `std::system_error` and is * thrown. */ - basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) - { + basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) { std::error_code error; map(handle, offset, length, error); - if(error) { throw std::system_error(error); } + if (error) { + throw std::system_error(error); + } } -#endif // __cpp_exceptions +#endif // __cpp_exceptions /** * If this is a read-write mapping and the last reference to the mapping, @@ -1476,32 +1395,36 @@ template< ~basic_shared_mmap() = default; /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */ - std::shared_ptr get_shared_ptr() { return pimpl_; } + std::shared_ptr get_shared_ptr() { + return pimpl_; + } /** * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, * however, a mapped region of a file gets its own handle, which is returned by * 'mapping_handle'. */ - handle_type file_handle() const noexcept - { + handle_type file_handle() const noexcept { return pimpl_ ? pimpl_->file_handle() : invalid_handle; } - handle_type mapping_handle() const noexcept - { + handle_type mapping_handle() const noexcept { return pimpl_ ? pimpl_->mapping_handle() : invalid_handle; } /** Returns whether a valid memory mapping has been created. */ - bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); } + bool is_open() const noexcept { + return pimpl_ && pimpl_->is_open(); + } /** * Returns true if no mapping was established, that is, conceptually the * same as though the length that was mapped was 0. This function is * provided so that this class has Container semantics. */ - bool empty() const noexcept { return !pimpl_ || pimpl_->empty(); } + bool empty() const noexcept { + return !pimpl_ || pimpl_->empty(); + } /** * `size` and `length` both return the logical length, i.e. the number of bytes @@ -1509,10 +1432,15 @@ template< * bytes that were mapped which is a multiple of the underlying operating system's * page allocation granularity. */ - size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; } - size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; } - size_type mapped_length() const noexcept - { + size_type size() const noexcept { + return pimpl_ ? pimpl_->length() : 0; + } + + size_type length() const noexcept { + return pimpl_ ? pimpl_->length() : 0; + } + + size_type mapped_length() const noexcept { return pimpl_ ? pimpl_->mapped_length() : 0; } @@ -1520,61 +1448,95 @@ template< * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping * exists. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > pointer data() noexcept { return pimpl_->data(); } - const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; } + template< access_mode A = AccessMode, typename = typename std::enable_if::type > + pointer data() noexcept { + return pimpl_->data(); + } + + const_pointer data() const noexcept { + return pimpl_ ? pimpl_->data() : nullptr; + } /** * Returns an iterator to the first requested byte, if a valid memory mapping * exists, otherwise this function call is undefined behaviour. */ - iterator begin() noexcept { return pimpl_->begin(); } - const_iterator begin() const noexcept { return pimpl_->begin(); } - const_iterator cbegin() const noexcept { return pimpl_->cbegin(); } + iterator begin() noexcept { + return pimpl_->begin(); + } + + const_iterator begin() const noexcept { + return pimpl_->begin(); + } + + const_iterator cbegin() const noexcept { + return pimpl_->cbegin(); + } /** * Returns an iterator one past the last requested byte, if a valid memory mapping * exists, otherwise this function call is undefined behaviour. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > iterator end() noexcept { return pimpl_->end(); } - const_iterator end() const noexcept { return pimpl_->end(); } - const_iterator cend() const noexcept { return pimpl_->cend(); } + template< access_mode A = AccessMode, typename = typename std::enable_if::type > + iterator end() noexcept { + return pimpl_->end(); + } + + const_iterator end() const noexcept { + return pimpl_->end(); + } + + const_iterator cend() const noexcept { + return pimpl_->cend(); + } /** * Returns a reverse iterator to the last memory mapped byte, if a valid * memory mapping exists, otherwise this function call is undefined * behaviour. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > reverse_iterator rbegin() noexcept { return pimpl_->rbegin(); } - const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); } - const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); } + template< access_mode A = AccessMode, typename = typename std::enable_if::type > + reverse_iterator rbegin() noexcept { + return pimpl_->rbegin(); + } + + const_reverse_iterator rbegin() const noexcept { + return pimpl_->rbegin(); + } + + const_reverse_iterator crbegin() const noexcept { + return pimpl_->crbegin(); + } /** * Returns a reverse iterator past the first mapped byte, if a valid memory * mapping exists, otherwise this function call is undefined behaviour. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > reverse_iterator rend() noexcept { return pimpl_->rend(); } - const_reverse_iterator rend() const noexcept { return pimpl_->rend(); } - const_reverse_iterator crend() const noexcept { return pimpl_->crend(); } + template< access_mode A = AccessMode, typename = typename std::enable_if::type > + reverse_iterator rend() noexcept { + return pimpl_->rend(); + } + + const_reverse_iterator rend() const noexcept { + return pimpl_->rend(); + } + + const_reverse_iterator crend() const noexcept { + return pimpl_->crend(); + } /** * Returns a reference to the `i`th byte from the first requested byte (as returned * by `data`). If this is invoked when no valid memory mapping has been created * prior to this call, undefined behaviour ensues. */ - reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; } - const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; } + reference operator[](const size_type i) noexcept { + return (*pimpl_)[i]; + } + + const_reference operator[](const size_type i) const noexcept { + return (*pimpl_)[i]; + } /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the @@ -1597,9 +1559,7 @@ template< * case a mapping of the entire file is created. */ template - void map(const String& path, const size_type offset, - const size_type length, std::error_code& error) - { + void map(const String& path, const size_type offset, const size_type length, std::error_code& error) { map_impl(path, offset, length, error); } @@ -1616,8 +1576,7 @@ template< * The entire file is mapped. */ template - void map(const String& path, std::error_code& error) - { + void map(const String& path, std::error_code& error) { map_impl(path, 0, map_entire_file, error); } @@ -1640,9 +1599,7 @@ template< * `length` is the number of bytes to map. It may be `map_entire_file`, in which * case a mapping of the entire file is created. */ - void map(const handle_type handle, const size_type offset, - const size_type length, std::error_code& error) - { + void map(const handle_type handle, const size_type offset, const size_type length, std::error_code& error) { map_impl(handle, offset, length, error); } @@ -1657,8 +1614,7 @@ template< * * The entire file is mapped. */ - void map(const handle_type handle, std::error_code& error) - { + void map(const handle_type handle, std::error_code& error) { map_impl(handle, 0, map_entire_file, error); } @@ -1671,61 +1627,59 @@ template< * mapping was created using a file path. If, on the other hand, an existing * file handle was used to create the mapping, the file handle is not closed. */ - void unmap() { if(pimpl_) pimpl_->unmap(); } + void unmap() { + if (pimpl_) + pimpl_->unmap(); + } - void swap(basic_shared_mmap& other) { pimpl_.swap(other.pimpl_); } + void swap(basic_shared_mmap& other) { + pimpl_.swap(other.pimpl_); + } /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > void sync(std::error_code& error) { if(pimpl_) pimpl_->sync(error); } + template< access_mode A = AccessMode, typename = typename std::enable_if::type > + void sync(std::error_code& error) { + if (pimpl_) + pimpl_->sync(error); + } /** All operators compare the underlying `basic_mmap`'s addresses. */ - friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b) - { + friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b) { return a.pimpl_ == b.pimpl_; } - friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b) - { + friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b) { return !(a == b); } - friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b) - { + friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b) { return a.pimpl_ < b.pimpl_; } - friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b) - { + friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b) { return a.pimpl_ <= b.pimpl_; } - friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b) - { + friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b) { return a.pimpl_ > b.pimpl_; } - friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b) - { + friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b) { return a.pimpl_ >= b.pimpl_; } -private: + private: + template - void map_impl(const MappingToken& token, const size_type offset, - const size_type length, std::error_code& error) - { - if(!pimpl_) - { + void map_impl(const MappingToken& token, const size_type offset, const size_type length, std::error_code& error) { + if (!pimpl_) { mmap_type mmap = make_mmap(token, offset, length, error); - if(error) { return; } + if (error) { + return; + } pimpl_ = std::make_shared(std::move(mmap)); - } - else - { + } else { pimpl_->map(token, offset, length, error); } } @@ -1749,12 +1703,12 @@ using basic_shared_mmap_sink = basic_shared_mmap; * These aliases cover the most common use cases, both representing a raw byte stream * (either with a char or an unsigned char/uint8_t). */ -using shared_mmap_source = basic_shared_mmap_source; +using shared_mmap_source = basic_shared_mmap_source; using shared_ummap_source = basic_shared_mmap_source; -using shared_mmap_sink = basic_shared_mmap_sink; +using shared_mmap_sink = basic_shared_mmap_sink; using shared_ummap_sink = basic_shared_mmap_sink; -} // namespace mio +} // namespace mio -#endif // MIO_SHARED_MMAP_HEADER +#endif // MIO_SHARED_MMAP_HEADER diff --git a/pyxcp/recorder/reader.hpp b/pyxcp/recorder/reader.hpp new file mode 100644 index 00000000..6f8012f3 --- /dev/null +++ b/pyxcp/recorder/reader.hpp @@ -0,0 +1,139 @@ + +#ifndef RECORDER_READER_HPP +#define RECORDER_READER_HPP + +#include + +class XcpLogFileReader { + public: + + explicit XcpLogFileReader(const std::string &file_name) { + if (!file_name.ends_with(detail::FILE_EXTENSION)) { + m_file_name = file_name + detail::FILE_EXTENSION; + } else { + m_file_name = file_name; + } + + m_mmap = new mio::mmap_source(m_file_name); + blob_t magic[detail::MAGIC_SIZE + 1]; + + read_bytes(0UL, detail::MAGIC_SIZE, magic); + if (memcmp(detail::MAGIC.c_str(), magic, detail::MAGIC_SIZE) != 0) { + throw std::runtime_error("Invalid file magic."); + } + m_offset = detail::MAGIC_SIZE; + + read_bytes(m_offset, detail::FILE_HEADER_SIZE, reinterpret_cast(&m_header)); + // printf("Sizes: %u %u %.3f\n", m_header.size_uncompressed, + // m_header.size_compressed, + // float(m_header.size_uncompressed) / float(m_header.size_compressed)); + if (m_header.hdr_size != detail::FILE_HEADER_SIZE + detail::MAGIC_SIZE) { + throw std::runtime_error("File header size does not match."); + } + if (detail::VERSION != m_header.version) { + throw std::runtime_error("File version mismatch."); + } + + if (m_header.num_containers < 1) { + throw std::runtime_error("At least one container required."); + } + + m_offset += detail::FILE_HEADER_SIZE; + + if ((m_header.options & XMRAW_HAS_METADATA) == XMRAW_HAS_METADATA) { + std::size_t metadata_length = 0; + std::size_t data_start = m_offset + sizeof(std::size_t); + + read_bytes(m_offset, sizeof(std::size_t), reinterpret_cast(&metadata_length)); + + std::copy(ptr(data_start), ptr(data_start + metadata_length), std::back_inserter(m_metadata)); + // std::cout << "Metadata: " << m_metadata << std::endl; + m_offset += (metadata_length + sizeof(std::size_t)); + } + } + + [[nodiscard]] FileHeaderType get_header() const noexcept { + return m_header; + } + + [[nodiscard]] auto get_header_as_tuple() const noexcept -> HeaderTuple { + auto hdr = get_header(); + + return std::make_tuple( + hdr.version, hdr.options, hdr.num_containers, hdr.record_count, hdr.size_uncompressed, hdr.size_compressed, + (double)((std::uint64_t)(((double)hdr.size_uncompressed / (double)hdr.size_compressed * 100.0) + 0.5)) / 100.0 + ); + } + + [[nodiscard]] auto get_metadata() const noexcept { + return m_metadata; + } + + void reset() noexcept { + m_current_container = 0; + m_offset = file_header_size(); + } + + std::optional next_block() { + auto container = ContainerHeaderType{}; + auto frame = frame_header_t{}; + std::uint64_t boffs = 0; + auto result = FrameVector{}; + payload_t payload; + + if (m_current_container >= m_header.num_containers) { + return std::nullopt; + } + read_bytes(m_offset, detail::CONTAINER_SIZE, reinterpret_cast(&container)); + __ALIGN auto buffer = new blob_t[container.size_uncompressed]; + m_offset += detail::CONTAINER_SIZE; + result.reserve(container.record_count); + const int uc_size = ::LZ4_decompress_safe( + reinterpret_cast(ptr(m_offset)), reinterpret_cast(buffer), container.size_compressed, + container.size_uncompressed + ); + if (uc_size < 0) { + throw std::runtime_error("LZ4 decompression failed."); + } + boffs = 0; + for (std::uint64_t idx = 0; idx < container.record_count; ++idx) { + _fcopy(reinterpret_cast(&frame), reinterpret_cast(&(buffer[boffs])), sizeof(frame_header_t)); + boffs += sizeof(frame_header_t); + result.emplace_back( + frame.category, frame.counter, frame.timestamp, frame.length, create_payload(frame.length, &buffer[boffs]) + ); + boffs += frame.length; + } + m_offset += container.size_compressed; + m_current_container += 1; + delete[] buffer; + + return std::optional{ result }; + } + + ~XcpLogFileReader() noexcept { + delete m_mmap; + } + + protected: + + [[nodiscard]] blob_t const *ptr(std::uint64_t pos = 0) const { + return reinterpret_cast(m_mmap->data() + pos); + } + + void read_bytes(std::uint64_t pos, std::uint64_t count, blob_t *buf) const { + auto addr = reinterpret_cast(ptr(pos)); + _fcopy(reinterpret_cast(buf), addr, count); + } + + private: + + std::string m_file_name; + std::uint64_t m_offset{ 0 }; + std::uint64_t m_current_container{ 0 }; + mio::mmap_source *m_mmap{ nullptr }; + FileHeaderType m_header; + std::string m_metadata; +}; + +#endif // RECORDER_READER_HPP diff --git a/pyxcp/recorder/reco.py b/pyxcp/recorder/reco.py index 8404a6f3..8697ce8f 100644 --- a/pyxcp/recorder/reco.py +++ b/pyxcp/recorder/reco.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Raw XCP traffic recorder. Data is stored in LZ4 compressed containers. @@ -15,8 +14,6 @@ """ import enum import mmap -import os -import pathlib import struct from collections import namedtuple @@ -27,7 +24,7 @@ MAGIC = b"ASAMINT::XCP_RAW" -FILE_HEADER_STRUCT = struct.Struct("<{:d}sHHHLLLL".format(len(MAGIC))) +FILE_HEADER_STRUCT = struct.Struct(f"<{len(MAGIC):d}sHHHLLLL") FileHeader = namedtuple( "FileHeader", "magic hdr_size version options num_containers record_count size_compressed size_uncompressed", @@ -82,7 +79,7 @@ def __init__( ): self._is_closed = True try: - self._of = open("{}{}".format(file_name, FILE_EXTENSION), "w+b") + self._of = open(f"{file_name}{FILE_EXTENSION}", "w+b") except Exception: raise else: @@ -161,7 +158,7 @@ def set(self, address: int, data: bytes): try: self._mapping[address : address + length] = data except IndexError: - raise XcpLogFileCapacityExceededError("Maximum file size of {} MBytes exceeded.".format(self.prealloc)) from None + raise XcpLogFileCapacityExceededError(f"Maximum file size of {self.prealloc} MBytes exceeded.") from None def _write_header( self, @@ -201,7 +198,7 @@ class XcpLogFileReader: def __init__(self, file_name): self._is_closed = True try: - self._log_file = open("{}{}".format(file_name, FILE_EXTENSION), "r+b") + self._log_file = open(f"{file_name}{FILE_EXTENSION}", "r+b") except Exception: raise else: @@ -218,7 +215,7 @@ def __init__(self, file_name): self.total_size_uncompressed, ) = FILE_HEADER_STRUCT.unpack(self.get(0, FILE_HEADER_STRUCT.size)) if magic != MAGIC: - raise XcpLogFileParseError("Invalid file magic: '{}'.".format(magic)) + raise XcpLogFileParseError(f"Invalid file magic: {magic!r}.") def __del__(self): if not self._is_closed: diff --git a/pyxcp/recorder/rekorder.cpp b/pyxcp/recorder/rekorder.cpp index 28289851..6e9a6658 100644 --- a/pyxcp/recorder/rekorder.cpp +++ b/pyxcp/recorder/rekorder.cpp @@ -3,28 +3,24 @@ #include "rekorder.hpp" - -void some_records(XcpLogFileWriter& writer) -{ - const auto COUNT = 1024 * 100 * 5; - unsigned filler = 0x00; - char buffer[1024]; +void some_records(XcpLogFileWriter& writer) { + const auto COUNT = 1024 * 100 * 5; + unsigned filler = 0x00; + char buffer[1024]; for (auto idx = 0; idx < COUNT; ++idx) { - auto fr = frame_header_t{}; - fr.category = 1; - fr.counter = idx; + auto fr = frame_header_t{}; + fr.category = 1; + fr.counter = idx; fr.timestamp = std::clock(); - fr.length = 10 + (rand() % 240); - filler = (filler + 1) % 16; + fr.length = 10 + (rand() % 240); + filler = (filler + 1) % 16; memset(buffer, filler, fr.length); - writer.add_frame(fr.category, fr.counter, fr.timestamp, fr.length, std::bit_cast(&buffer)); + writer.add_frame(fr.category, fr.counter, fr.timestamp, fr.length, std::bit_cast(&buffer)); } } - -int main() -{ +int main() { srand(42); printf("\nWRITER\n"); @@ -49,15 +45,15 @@ int main() printf("size/uncompressed: %u\n", header.size_uncompressed); printf("compression ratio: %.2f\n", static_cast(header.size_uncompressed) / static_cast(header.size_compressed)); - while (true) { - const auto& frames = reader.next_block(); - if (!frames) { - break; - } - for (const auto& frame: frames.value()) { + while (true) { + const auto& frames = reader.next_block(); + if (!frames) { + break; + } + for (const auto& frame : frames.value()) { auto const& [category, counter, timestamp, length, payload] = frame; } - } + } printf("---\n"); printf("Finished.\n"); } diff --git a/pyxcp/recorder/rekorder.hpp b/pyxcp/recorder/rekorder.hpp index 662eb3b0..380eeaf4 100644 --- a/pyxcp/recorder/rekorder.hpp +++ b/pyxcp/recorder/rekorder.hpp @@ -1,661 +1,274 @@ - - -#if !defined(__REKORDER_HPP) -#define __REKORDER_HPP - -#if !defined(STANDALONE_REKORDER) - #define STANDALONE_REKORDER 0 -#endif /* STANDALONE_REKORDER */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - - -#if defined(_WIN32) - #include - #include - - #include -#endif /* _WIN32 */ - - -#include "lz4.h" -#include "mio.hpp" - -#if STANDALONE_REKORDER == 0 - #include - #include - #include - - namespace py = pybind11; - using namespace pybind11::literals; -#endif /* STANDALONE_REKORDER */ - -#if !defined(__BIGGEST_ALIGNMENT__) - #define __BIGGEST_ALIGNMENT__ (8) -#endif - - -#define __ALIGNMENT_REQUIREMENT __BIGGEST_ALIGNMENT__ -#define __ALIGN alignas(__ALIGNMENT_REQUIREMENT) - - -constexpr auto kilobytes(std::uint32_t value) -> std::uint32_t -{ - return value * 1024; -} - -constexpr auto megabytes(std::uint32_t value) -> std::uint32_t -{ - return kilobytes(value) * 1024; -} - -constexpr uint16_t XCP_PAYLOAD_MAX = 0xFFFF; - -/* -byte-order is, where applicable little ending (LSB first). -*/ -#pragma pack(push) -#pragma pack(1) -struct FileHeaderType -{ - uint16_t hdr_size; - uint16_t version; - uint16_t options; - uint32_t num_containers; - uint32_t record_count; - uint32_t size_compressed; - uint32_t size_uncompressed; -}; - -using HeaderTuple = std::tuple; - -static_assert(sizeof(FileHeaderType) == 22); - -struct ContainerHeaderType -{ - uint32_t record_count; - uint32_t size_compressed; - uint32_t size_uncompressed; -}; - -using blob_t = unsigned char; - -#if STANDALONE_REKORDER == 1 - using payload_t = std::shared_ptr; -#else - using payload_t = py::array_t; -#endif /* STANDALONE_REKORDER */ - - -struct frame_header_t -{ - uint8_t category {0}; - uint16_t counter {0}; - double timestamp {0.0}; - uint16_t length {0}; -}; -#pragma pack(pop) - -using FrameTuple = std::tuple; -using FrameVector = std::vector; -using FrameTupleWriter = std::tuple; - -enum class FrameCategory : std::uint8_t { - META, - CMD, - RES, - ERR, - EV, - SERV, - DAQ, - STIM, -}; - -namespace detail -{ - const std::string FILE_EXTENSION(".xmraw"); - const std::string MAGIC{"ASAMINT::XCP_RAW"}; - constexpr auto MAGIC_SIZE = 16; - constexpr auto VERSION = 0x0100; - constexpr auto FILE_HEADER_SIZE = sizeof(FileHeaderType); - constexpr auto CONTAINER_SIZE = sizeof(ContainerHeaderType); -} - - -constexpr auto file_header_size() -> std::uint32_t { - return (detail::FILE_HEADER_SIZE + detail::MAGIC_SIZE); -} - -using rounding_func_t = std::function; - -inline rounding_func_t create_rounding_func(std::uint32_t multiple) { - return [multiple](std::uint32_t value) { - return (value + (multiple - 1)) & ~(multiple -1 ); - }; -} - -const auto round_to_alignment = create_rounding_func(__ALIGNMENT_REQUIREMENT); - - -inline void _fcopy(char * dest, char const * src, std::uint32_t n) noexcept -{ - for (std::uint32_t i = 0; i < n; ++i) { - dest[i] = src[i]; - } -} - -#if STANDALONE_REKORDER == 1 - inline blob_t * get_payload_ptr(const payload_t& payload) noexcept { - return payload.get(); - } - - inline payload_t create_payload(std::uint32_t size, blob_t const * data) noexcept { - auto pl = std::make_shared(size); - _fcopy(reinterpret_cast(pl.get()), reinterpret_cast(data), size); - return pl; - } -#else - inline payload_t create_payload(std::uint32_t size, blob_t const * data) { - return py::array_t(size, data); - } - - inline blob_t * get_payload_ptr(const payload_t& payload) noexcept { - py::buffer_info buf = payload.request(); - - return static_cast(buf.ptr); - } -#endif /* STANDALONE_REKORDER */ - -inline void hexdump(blob_t const * buf, std::uint16_t sz) { - for (std::uint16_t idx = 0; idx < sz; ++idx) - { - printf("%02X ", buf[idx]); - } - printf("\n\r"); -} - - -template -class TsQueue { -public: - TsQueue() = default; - - TsQueue(const TsQueue& other) noexcept { - std::scoped_lock lock(other.m_mtx); - m_queue = other.m_queue; - } - - void put(T value) noexcept { - std::scoped_lock lock(m_mtx); - m_queue.push(value); - m_cond.notify_one(); - } - - std::shared_ptr get() noexcept { - std::unique_lock lock(m_mtx); - m_cond.wait(lock, [this]{return !m_queue.empty();}); - std::shared_ptr result(std::make_shared(m_queue.front())); - m_queue.pop(); - return result; - } - - bool empty() const noexcept { - std::scoped_lock lock(m_mtx); - return m_queue.empty(); - } - -private: - mutable std::mutex m_mtx; - std::queue m_queue; - std::condition_variable m_cond; -}; - - -class Event { -public: - - Event(const Event& other) noexcept { - std::scoped_lock lock(other.m_mtx); - m_flag = other.m_flag; - } - - ~Event() = default; - Event() = default; - - void signal() noexcept { - std::scoped_lock lock(m_mtx); - m_flag = true; - m_cond.notify_one(); - } - - void wait() noexcept { - std::unique_lock lock(m_mtx); - m_cond.wait(lock, [this]{return m_flag;}); - m_flag = false; - } - - bool state() const noexcept { - std::scoped_lock lock(m_mtx); - return m_flag; - } - -private: - mutable std::mutex m_mtx {}; - bool m_flag {false}; - std::condition_variable m_cond {}; -}; - - -/* - * - * Super simplicistic block memory manager. - * - */ -template -class BlockMemory { - -public: - - using mem_block_t = std::array; - - explicit BlockMemory() noexcept : m_memory{nullptr}, m_allocation_count{0} { - m_memory = new T[_IS * _NB]; - } - - ~BlockMemory() noexcept { - if (m_memory) { - delete[] m_memory; - } - } - BlockMemory(const BlockMemory&) = delete; - - T * acquire() noexcept { - const std::scoped_lock lock(m_mtx); - - if (m_allocation_count >= _NB) { - return nullptr; - } - T * ptr = reinterpret_cast(m_memory + (m_allocation_count * _IS)); - m_allocation_count++; - return ptr; - } - - void release() noexcept { - const std::scoped_lock lock(m_mtx); - if (m_allocation_count == 0) { - return; - } - m_allocation_count--; - } - -private: - - T * m_memory; - std::uint32_t m_allocation_count; - std::mutex m_mtx; - -}; - - -/** - */ -class XcpLogFileWriter -{ -public: - explicit XcpLogFileWriter(const std::string& file_name, uint32_t prealloc = 10UL, uint32_t chunk_size = 1) noexcept - { - if (!file_name.ends_with(detail::FILE_EXTENSION)) { - m_file_name = file_name + detail::FILE_EXTENSION; - } else { - m_file_name = file_name; - } - -#if defined(_WIN32) - m_fd = CreateFileA( - m_file_name.c_str(), - GENERIC_READ | GENERIC_WRITE, - 0, - (LPSECURITY_ATTRIBUTES)nullptr, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, - nullptr - ); -#else - m_fd = open(m_file_name.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0666); -#endif - truncate(megabytes(prealloc)); - m_mmap = new mio::mmap_sink(m_fd); - m_chunk_size = megabytes(chunk_size); - m_intermediate_storage = new blob_t[m_chunk_size + megabytes(1)]; - m_offset = detail::FILE_HEADER_SIZE + detail::MAGIC_SIZE; - - start_thread(); - } - - ~XcpLogFileWriter() noexcept { - finalize(); - #ifdef __APPLE__ - if (collector_thread.joinable()) { - collector_thread.join(); - } - #endif - } - - void finalize() { - if (!m_finalized) { - m_finalized = true; - stop_thread(); - if (m_container_record_count) { - compress_frames(); - } - write_header(detail::VERSION, 0x0000, m_num_containers, m_record_count, m_total_size_compressed, m_total_size_uncompressed); - m_mmap->unmap(); - truncate(m_offset); -#if defined(_WIN32) - CloseHandle(m_fd); -#else - close(m_fd); -#endif - delete m_mmap; - delete[] m_intermediate_storage; - } - } - - void add_frame(uint8_t category, uint16_t counter, double timestamp, uint16_t length, char const * data) noexcept { - auto payload= new char[length]; - //auto payload = mem.acquire(); - - _fcopy(payload, data, length); - my_queue.put( - std::make_tuple(category, counter, timestamp, length, payload) - ); - } - -protected: - void truncate(off_t size) const noexcept - { -#if defined(_WIN32) - if (SetFilePointer(m_fd, size, nullptr, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { - // TODO: Errorhandling. - } - if (SetEndOfFile(m_fd) == 0) { - // TODO: Errorhandling. - } -#else - ftruncate(m_fd, size); -#endif - } - - blob_t * ptr(std::uint32_t pos = 0) const noexcept - { - return (blob_t *)(m_mmap->data() + pos); - } - - template - void store_im(T const * data, std::uint32_t length) noexcept { - _fcopy(reinterpret_cast(m_intermediate_storage + m_intermediate_storage_offset), reinterpret_cast(data), length); - m_intermediate_storage_offset += length; - } - - void compress_frames() { - auto container = ContainerHeaderType{}; - //printf("Compressing %u frames... [%d]\n", m_container_record_count, m_intermediate_storage_offset); - const int cp_size = ::LZ4_compress_default( - reinterpret_cast(m_intermediate_storage), reinterpret_cast(ptr(m_offset + detail::CONTAINER_SIZE)), - m_intermediate_storage_offset, LZ4_COMPRESSBOUND(m_intermediate_storage_offset) - ); - if (cp_size < 0) { - throw std::runtime_error("LZ4 compression failed."); - } - //printf("comp: %d %d [%f]\n", m_intermediate_storage_offset, cp_size, double(m_intermediate_storage_offset) / double(cp_size)); - container.record_count = m_container_record_count; - container.size_compressed = cp_size; - container.size_uncompressed = m_container_size_uncompressed; - _fcopy(reinterpret_cast(ptr(m_offset)), reinterpret_cast(&container), detail::CONTAINER_SIZE); - m_offset += (detail::CONTAINER_SIZE + cp_size); - m_total_size_uncompressed += m_container_size_uncompressed; - m_total_size_compressed += cp_size; - m_record_count += m_container_record_count; - m_container_size_uncompressed = 0; - m_container_size_compressed = 0; - m_container_record_count = 0; - m_intermediate_storage_offset = 0; - m_num_containers += 1; - } - - void write_bytes(std::uint32_t pos, std::uint32_t count, char const * buf) const noexcept - { - auto addr = reinterpret_cast(ptr(pos)); - - _fcopy(addr, buf, count); - } - - void write_header(uint16_t version, uint16_t options, uint32_t num_containers, - uint32_t record_count, uint32_t size_compressed, uint32_t size_uncompressed) noexcept { - auto header = FileHeaderType{}; - write_bytes(0x00000000UL, detail::MAGIC_SIZE, detail::MAGIC.c_str()); - header.hdr_size = detail::FILE_HEADER_SIZE + detail::MAGIC_SIZE; - header.version = version; - header.options = options; - header.num_containers = num_containers; - header.record_count = record_count; - header.size_compressed = size_compressed; - header.size_uncompressed = size_uncompressed; - write_bytes(0x00000000UL + detail::MAGIC_SIZE, detail::FILE_HEADER_SIZE, reinterpret_cast(&header)); - } - - bool start_thread() noexcept { - if (collector_thread.joinable()) { - return false; - } - stop_collector_thread_flag = false; - #ifdef __APPLE__ - collector_thread = std::thread([this]() { - #else - collector_thread = std::jthread([this]() { - #endif - while (!stop_collector_thread_flag) { - auto item = my_queue.get(); - const auto content = item.get(); - if (stop_collector_thread_flag == true) - { - break; - } - const auto [category, counter, timestamp, length, payload] = *content; - const frame_header_t frame{ category, counter, timestamp, length }; - store_im(&frame, sizeof(frame)); - store_im(payload, length); - delete[] payload; - m_container_record_count += 1; - m_container_size_uncompressed += (sizeof(frame) + length); - if (m_container_size_uncompressed > m_chunk_size) { - compress_frames(); - } - } - }); - return true; - } - - bool stop_thread() noexcept { - if (!collector_thread.joinable()) { - return false; - } - stop_collector_thread_flag = true; - my_queue.put(FrameTupleWriter{}); // Put something into the queue, otherwise the thread will hang forever. - collector_thread.join(); - return true; - } - -private: - std::string m_file_name; - std::uint32_t m_offset{0}; - std::uint32_t m_chunk_size{0}; - std::uint32_t m_num_containers{0}; - std::uint32_t m_record_count{0UL}; - std::uint32_t m_container_record_count{0UL}; - std::uint32_t m_total_size_uncompressed{0UL}; - std::uint32_t m_total_size_compressed{0UL}; - std::uint32_t m_container_size_uncompressed{0UL}; - std::uint32_t m_container_size_compressed{0UL}; - __ALIGN blob_t * m_intermediate_storage{nullptr}; - std::uint32_t m_intermediate_storage_offset{0}; - mio::file_handle_type m_fd{INVALID_HANDLE_VALUE}; - mio::mmap_sink * m_mmap{nullptr}; - bool m_finalized{false}; - #ifdef __APPLE__ - std::thread collector_thread{}; - #else - std::jthread collector_thread{}; - #endif - std::mutex mtx; - TsQueue my_queue; - BlockMemory mem{}; - std::atomic_bool stop_collector_thread_flag{false}; -}; - - -/** - */ -class XcpLogFileReader -{ -public: - explicit XcpLogFileReader(const std::string& file_name) - { - if (!file_name.ends_with(detail::FILE_EXTENSION)) { - m_file_name = file_name + detail::FILE_EXTENSION; - } else { - m_file_name = file_name; - } - - m_mmap = new mio::mmap_source(m_file_name); - blob_t magic[detail::MAGIC_SIZE + 1]; - - read_bytes(0UL, detail::MAGIC_SIZE, magic); - if (memcmp(detail::MAGIC.c_str(), magic, detail::MAGIC_SIZE) != 0) { - throw std::runtime_error("Invalid file magic."); - } - m_offset = detail::MAGIC_SIZE; - - read_bytes(m_offset, detail::FILE_HEADER_SIZE, reinterpret_cast(&m_header)); - //printf("Sizes: %u %u %.3f\n", m_header.size_uncompressed, - // m_header.size_compressed, - // float(m_header.size_uncompressed) / float(m_header.size_compressed)); - if (m_header.hdr_size != detail::FILE_HEADER_SIZE + detail::MAGIC_SIZE) - { - throw std::runtime_error("File header size does not match."); - } - if (detail::VERSION != m_header.version) - { - throw std::runtime_error("File version mismatch."); - } - - if (m_header.num_containers < 1) { - throw std::runtime_error("At least one container required."); - } - - m_offset += detail::FILE_HEADER_SIZE; - } - - [[nodiscard]] - FileHeaderType get_header() const noexcept { - return m_header; - } - - [[nodiscard]] - auto get_header_as_tuple() const noexcept -> HeaderTuple { - auto hdr = get_header(); - - return std::make_tuple( - hdr.num_containers, - hdr.record_count, - hdr.size_uncompressed, - hdr.size_compressed, - (double)((std::uint64_t)(((double)hdr.size_uncompressed / (double)hdr.size_compressed * 100.0) + 0.5)) / 100.0 - ); - } - - void reset() noexcept { - m_current_container = 0; - m_offset = file_header_size(); - } - - std::optional next_block() { - auto container = ContainerHeaderType{}; - auto frame = frame_header_t{}; - std::uint32_t boffs = 0; - auto result = FrameVector{}; - payload_t payload; - - if (m_current_container >= m_header.num_containers) { - return std::nullopt; - } - read_bytes(m_offset, detail::CONTAINER_SIZE, reinterpret_cast(&container)); - __ALIGN auto buffer = new blob_t[container.size_uncompressed]; - m_offset += detail::CONTAINER_SIZE; - result.reserve(container.record_count); - const int uc_size = ::LZ4_decompress_safe(reinterpret_cast(ptr(m_offset)), reinterpret_cast(buffer), container.size_compressed, container.size_uncompressed); - if (uc_size < 0) { - throw std::runtime_error("LZ4 decompression failed."); - } - boffs = 0; - for (std::uint32_t idx = 0; idx < container.record_count; ++idx) { - _fcopy(reinterpret_cast(&frame), reinterpret_cast(&(buffer[boffs])), sizeof(frame_header_t)); - boffs += sizeof(frame_header_t); - result.emplace_back(frame.category, frame.counter, frame.timestamp, frame.length, create_payload(frame.length, &buffer[boffs])); - boffs += frame.length; - } - m_offset += container.size_compressed; - m_current_container += 1; - delete[] buffer; - - return std::optional{result}; - } - - ~XcpLogFileReader() noexcept - { - delete m_mmap; - } - -protected: - [[nodiscard]] - blob_t const *ptr(std::uint32_t pos = 0) const - { - return reinterpret_cast(m_mmap->data() + pos); - } - - void read_bytes(std::uint32_t pos, std::uint32_t count, blob_t * buf) const - { - auto addr = reinterpret_cast(ptr(pos)); - _fcopy(reinterpret_cast(buf), addr, count); - } - -private: - std::string m_file_name; - std::uint32_t m_offset{0}; - std::uint32_t m_current_container{0}; - mio::mmap_source * m_mmap{nullptr}; - FileHeaderType m_header; -}; - -#endif // __REKORDER_HPP + +#if !defined(__REKORDER_HPP) + #define __REKORDER_HPP + + #if !defined(STANDALONE_REKORDER) + #define STANDALONE_REKORDER 0 + #endif /* STANDALONE_REKORDER */ + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include "blockmem.hpp" + #include "event.hpp" + #include "tsqueue.hpp" + + #if defined(_WIN32) + #include + #include + #include + #endif /* _WIN32 */ + + #include "lz4hc.h" + #include "mio.hpp" + + #if STANDALONE_REKORDER == 0 + #include + #include + #include + +namespace py = pybind11; +using namespace pybind11::literals; + #endif /* STANDALONE_REKORDER */ + + #if !defined(__BIGGEST_ALIGNMENT__) + #define __BIGGEST_ALIGNMENT__ (8) + #endif + + #define __ALIGNMENT_REQUIREMENT __BIGGEST_ALIGNMENT__ + #define __ALIGN alignas(__ALIGNMENT_REQUIREMENT) + +constexpr auto kilobytes(std::uint32_t value) -> std::uint32_t { + return value * 1024; +} + +constexpr auto megabytes(std::uint32_t value) -> std::uint32_t { + return kilobytes(value) * 1024; +} + +constexpr std::uint16_t XCP_PAYLOAD_MAX = 0xFFFFUL; + +constexpr std::uint16_t XMRAW_RELATIVE_TIMESTAMPS = 0x0002UL; +constexpr std::uint16_t XMRAW_HAS_METADATA = 0x0004UL; + + /* + byte-order is, where applicable little ending (LSB first). + */ + #pragma pack(push) + #pragma pack(1) + +struct FileHeaderType { + std::uint16_t hdr_size; + std::uint16_t version; + std::uint16_t options; + std::uint64_t num_containers; + std::uint64_t record_count; + std::uint64_t size_compressed; + std::uint64_t size_uncompressed; +}; + +using HeaderTuple = std::tuple; + +static_assert(sizeof(FileHeaderType) == 38); + +struct ContainerHeaderType { + std::uint32_t record_count; + std::uint32_t size_compressed; + std::uint32_t size_uncompressed; +}; + +using blob_t = unsigned char; +using blob_string = std::basic_string; + + #if STANDALONE_REKORDER == 1 +using payload_t = std::shared_ptr; + #else +using payload_t = py::array_t; + #endif /* STANDALONE_REKORDER */ + +struct frame_header_t { + std::uint8_t category{ 0 }; + std::uint16_t counter{ 0U }; + std::uint64_t timestamp{ 0ULL }; + std::uint16_t length{ 0U }; +}; + + #pragma pack(pop) + +using FrameTuple = std::tuple; +using FrameVector = std::vector; +using FrameTupleWriter = std::tuple; + +enum class FrameCategory : std::uint8_t { + META, + CMD, + RES, + ERR, + EV, + SERV, + DAQ, + STIM, +}; + +namespace detail { +const std::string FILE_EXTENSION(".xmraw"); +const std::string MAGIC{ "ASAMINT::XCP_RAW" }; +constexpr auto MAGIC_SIZE = 16; +constexpr auto VERSION = 0x0100; +constexpr auto FILE_HEADER_SIZE = sizeof(FileHeaderType); +constexpr auto CONTAINER_SIZE = sizeof(ContainerHeaderType); +} // namespace detail + +constexpr auto file_header_size() -> std::uint64_t { + return (detail::FILE_HEADER_SIZE + detail::MAGIC_SIZE); +} + +using rounding_func_t = std::function; + +inline rounding_func_t create_rounding_func(std::uint64_t multiple) { + return [multiple](std::uint64_t value) { + return (value + (multiple - 1)) & ~(multiple - 1); + }; +} + +const auto round_to_alignment = create_rounding_func(__ALIGNMENT_REQUIREMENT); + +inline void _fcopy(char* dest, char const * src, std::uint64_t n) noexcept { + for (std::uint64_t i = 0; i < n; ++i) { + dest[i] = src[i]; + } +} + + #if STANDALONE_REKORDER == 1 +inline blob_t* get_payload_ptr(const payload_t& payload) noexcept { + return payload.get(); +} + +inline payload_t create_payload(std::uint64_t size, blob_t const * data) noexcept { + auto pl = std::make_shared(size); + _fcopy(reinterpret_cast(pl.get()), reinterpret_cast(data), size); + return pl; +} + #else +inline payload_t create_payload(std::uint64_t size, blob_t const * data) { + return py::array_t(size, data); +} + +inline blob_t* get_payload_ptr(const payload_t& payload) noexcept { + py::buffer_info buf = payload.request(); + + return static_cast(buf.ptr); +} + #endif /* STANDALONE_REKORDER */ + + #ifdef _WIN32 +inline std::string error_string(std::string_view func, std::error_code error_code) { + LPSTR messageBuffer = nullptr; + std::ostringstream ss; + + size_t size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, + static_cast(error_code.value()), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL + ); + + std::string message(messageBuffer, size); + LocalFree(messageBuffer); + + ss << "[ERROR] "; + ss << func << ": "; + ss << message; + return ss.str(); +} + +inline std::error_code get_last_error() { + return std::error_code(GetLastError(), std::system_category()); +} + + #else +inline std::string error_string(std::string_view func, std::error_code error_code) { + std::ostringstream ss; + + auto message = strerror(static_cast(error_code.value())); + + ss << "[ERROR] "; + ss << func << ": "; + ss << message; + return ss.str(); +} + +inline std::error_code get_last_error() { + return std::error_code(errno, std::system_category()); +} + + #endif // _WIN32 + +inline std::string& ltrim(std::string& s) { + auto it = std::find_if(s.begin(), s.end(), [](char c) { return !std::isspace(c, std::locale::classic()); }); + s.erase(s.begin(), it); + return s; +} + +inline std::string& rtrim(std::string& s) { + auto it = std::find_if(s.rbegin(), s.rend(), [](char c) { return !std::isspace(c, std::locale::classic()); }); + s.erase(it.base(), s.end()); + return s; +} + +inline std::string& trim(std::string& s) { + return ltrim(rtrim(s)); +} + +inline std::string current_timestamp() { + std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + + #if defined(_MSC_VER) + errno_t err; + char tbuf[128]; + std::string result{}; + + err = ::ctime_s(tbuf, 128, &now); + if (err == 0) { + result = tbuf; + } + + #else + std::string result{ ::ctime(&now) }; + #endif + + // result.erase(std::remove_if(result.begin(), result.end(), ::isspace), result.end()); + return trim(result); +} + +inline void hexdump(blob_t const * buf, std::uint16_t sz) { + for (std::uint16_t idx = 0; idx < sz; ++idx) { + printf("%02X ", buf[idx]); + } + printf("\n\r"); +} + + #include "reader.hpp" + #include "unfolder.hpp" + #include "writer.hpp" + +#endif // __REKORDER_HPP diff --git a/pyxcp/recorder/setup.py b/pyxcp/recorder/setup.py index 8dbc8c50..d60938b1 100644 --- a/pyxcp/recorder/setup.py +++ b/pyxcp/recorder/setup.py @@ -1,17 +1,18 @@ -import os -import subprocess - -from distutils.core import Extension +import subprocess # nosec from distutils.core import setup -from pybind11.setup_helpers import build_ext -from pybind11.setup_helpers import naive_recompile -from pybind11.setup_helpers import ParallelCompile -from pybind11.setup_helpers import Pybind11Extension + +from pybind11.setup_helpers import ( + ParallelCompile, + Pybind11Extension, + build_ext, + naive_recompile, +) + # ParallelCompile("NPY_NUM_BUILD_JOBS").install() ParallelCompile("NPY_NUM_BUILD_JOBS", needs_recompile=naive_recompile).install() -INCLUDE_DIRS = subprocess.getoutput("pybind11-config --include") +INCLUDE_DIRS = subprocess.getoutput("pybind11-config --include") # nosec # os.environ ["CFLAGS"] = '' @@ -23,7 +24,7 @@ Pybind11Extension( EXT_NAMES[0], include_dirs=[INCLUDE_DIRS], - sources=["lz4.cpp", "wrap.cpp"], + sources=["lz4.c", "wrap.cpp"], define_macros=[("EXTENSION_NAME", EXT_NAMES[0])], cxx_std=20, # Extension will use C++20 generators/coroutines. ), diff --git a/pyxcp/recorder/test_reko.py b/pyxcp/recorder/test_reko.py index 5da321d5..31cb746b 100644 --- a/pyxcp/recorder/test_reko.py +++ b/pyxcp/recorder/test_reko.py @@ -1,8 +1,7 @@ from time import perf_counter -from pyxcp.recorder import FrameCategory -from pyxcp.recorder import XcpLogFileReader -from pyxcp.recorder import XcpLogFileWriter +from pyxcp.recorder import FrameCategory, XcpLogFileReader, XcpLogFileWriter + # Pre-allocate a 100MB file -- Note: due to the nature of memory-mapped files, this is a HARD limit. # Chunk size is 1MB (i.e. compression granularity). diff --git a/pyxcp/recorder/unfolder.hpp b/pyxcp/recorder/unfolder.hpp new file mode 100644 index 00000000..57bd0a28 --- /dev/null +++ b/pyxcp/recorder/unfolder.hpp @@ -0,0 +1,1249 @@ + +#ifndef RECORDER_UNFOLDER_HPP +#define RECORDER_UNFOLDER_HPP + +#include +#include +#include +#include +#include +#include +#if __has_include() + #include +#endif +#include + +#include "daqlist.hpp" +#include "helper.hpp" +#include "mcobject.hpp" +#include "writer.hpp" + +using measurement_value_t = std::variant; +using measurement_tuple_t = std::tuple>; +using measurement_callback_t = std::function)>; + +template +auto get_value(blob_t const * buf, std::uint64_t offset) -> Ty { + return *reinterpret_cast(&buf[offset]); +} + +template +auto get_value_swapped(blob_t const * buf, std::uint64_t offset) -> Ty { + return _bswap(get_value(buf, offset)); +} + +#if HAS_FLOAT16 == 1 +template<> +auto get_value(blob_t const * buf, std::uint64_t offset) -> std::float16_t { + auto tmp = get_value(buf, offset); + + return std::bit_cast(tmp); +} +#endif + +#if HAS_BFLOAT16 == 1 +template<> +auto get_value(blob_t const * buf, std::uint64_t offset) -> std::bfloat16_t { + auto tmp = get_value(buf, offset); + + return std::bit_cast(tmp); +} +#endif + +template<> +auto get_value(blob_t const * buf, std::uint64_t offset) -> float { + auto tmp = get_value(buf, offset); + + return std::bit_cast(tmp); +} + +template<> +auto get_value(blob_t const * buf, std::uint64_t offset) -> double { + auto tmp = get_value(buf, offset); + + return std::bit_cast(tmp); +} + +#if HAS_FLOAT16 == 1 +template<> +auto get_value_swapped(blob_t const * buf, std::uint64_t offset) -> std::float16_t { + auto tmp = get_value_swapped(buf, offset); + + return std::bit_cast(tmp); +} +#endif + +#if HAS_BFLOAT16 == 1 +template<> +auto get_value_swapped(blob_t const * buf, std::uint64_t offset) -> std::bfloat16_t { + auto tmp = get_value_swapped(buf, offset); + + return std::bit_cast(tmp); +} +#endif + +template<> +auto get_value_swapped(blob_t const * buf, std::uint64_t offset) -> float { + auto tmp = get_value_swapped(buf, offset); + + return std::bit_cast(tmp); +} + +template<> +auto get_value_swapped(blob_t const * buf, std::uint64_t offset) -> double { + auto tmp = get_value_swapped(buf, offset); + + return std::bit_cast(tmp); +} + +template<> +auto get_value(blob_t const * buf, std::uint64_t offset) -> std::int16_t { + return static_cast(get_value(buf, offset)); +} + +template<> +auto get_value_swapped(blob_t const * buf, std::uint64_t offset) -> std::int16_t { + return static_cast(get_value_swapped(buf, offset)); +} + +template<> +auto get_value(blob_t const * buf, std::uint64_t offset) -> std::int32_t { + return static_cast(get_value(buf, offset)); +} + +template<> +auto get_value_swapped(blob_t const * buf, std::uint64_t offset) -> std::int32_t { + return static_cast(get_value_swapped(buf, offset)); +} + +template<> +auto get_value(blob_t const * buf, std::uint64_t offset) -> std::int64_t { + return static_cast(get_value(buf, offset)); +} + +template<> +auto get_value_swapped(blob_t const * buf, std::uint64_t offset) -> std::int64_t { + return static_cast(get_value_swapped(buf, offset)); +} + +///////////////////////////////////////////////////// +///////////////////////////////////////////////////// +template +void set_value(blob_t* buf, std::uint64_t offset, Ty value) { + ::memcpy(&buf[offset], &value, sizeof(Ty)); +} + +template +void set_value_swapped(blob_t* buf, std::uint64_t offset, Ty value) { + set_value(buf, offset, _bswap(value)); +} + +template<> +void set_value(blob_t* buf, std::uint64_t offset, std::int8_t value) { + buf[offset] = static_cast(value); +} + +template<> +void set_value(blob_t* buf, std::uint64_t offset, std::uint8_t value) { + buf[offset] = static_cast(value); +} + +template<> +void set_value(blob_t* buf, std::uint64_t offset, std::int16_t value) { + set_value(buf, offset, static_cast(value)); +} + +template<> +void set_value_swapped(blob_t* buf, std::uint64_t offset, std::int16_t value) { + set_value_swapped(buf, offset, static_cast(value)); +} + +template<> +void set_value(blob_t* buf, std::uint64_t offset, std::int32_t value) { + set_value(buf, offset, static_cast(value)); +} + +template<> +void set_value_swapped(blob_t* buf, std::uint64_t offset, std::int32_t value) { + set_value_swapped(buf, offset, static_cast(value)); +} + +template<> +void set_value(blob_t* buf, std::uint64_t offset, std::int64_t value) { + set_value(buf, offset, static_cast(value)); +} + +template<> +void set_value_swapped(blob_t* buf, std::uint64_t offset, std::int64_t value) { + set_value_swapped(buf, offset, static_cast(value)); +} + +#if HAS_FLOAT16 == 1 +template<> +void set_value(blob_t* buf, std::uint64_t offset, std::float16_t value) { + // set_value(buf, offset, *reinterpret_cast(&value)); + set_value(buf, offset, std::bit_cast(value)); +} + +template<> +void set_value_swapped(blob_t* buf, std::uint64_t offset, std::float16_t value) { + // set_value_swapped(buf, offset, *reinterpret_cast(&value)); + set_value_swapped(buf, offset, std::bit_cast(value)); +} +#endif + +#if HAS_BFLOAT16 == 1 +template<> +void set_value(blob_t* buf, std::uint64_t offset, std::bfloat16_t value) { + // set_value(buf, offset, *reinterpret_cast(&value)); + set_value(buf, offset, std::bit_cast(value)); +} + +template<> +void set_value_swapped(blob_t* buf, std::uint64_t offset, std::bfloat16_t value) { + // set_value_swapped(buf, offset, *reinterpret_cast(&value)); + set_value_swapped(buf, offset, std::bit_cast(value)); +} +#endif + +template<> +void set_value(blob_t* buf, std::uint64_t offset, float value) { + // set_value(buf, offset, *reinterpret_cast(&value)); + set_value(buf, offset, std::bit_cast(value)); +} + +template<> +void set_value_swapped(blob_t* buf, std::uint64_t offset, float value) { + // set_value_swapped(buf, offset, *reinterpret_cast(&value)); + set_value_swapped(buf, offset, std::bit_cast(value)); +} + +template<> +void set_value(blob_t* buf, std::uint64_t offset, double value) { + // set_value(buf, offset, *reinterpret_cast(&value)); + set_value(buf, offset, std::bit_cast(value)); +} + +template<> +void set_value_swapped(blob_t* buf, std::uint64_t offset, double value) { + // set_value_swapped(buf, offset, *reinterpret_cast(&value)); + set_value_swapped(buf, offset, std::bit_cast(value)); +} + +/* +** Get primitive datatypes, consider byte-order. +*/ +struct Getter { + Getter() = default; + + explicit Getter(bool requires_swap, std::uint8_t id_size, std::uint8_t ts_size) : m_id_size(id_size), m_ts_size(ts_size) { + int8 = get_value; + uint8 = get_value; + + if (requires_swap) { + int16 = get_value_swapped; + int32 = get_value_swapped; + int64 = get_value_swapped; + uint16 = get_value_swapped; + uint32 = get_value_swapped; + uint64 = get_value_swapped; + float_ = get_value_swapped; + double_ = get_value_swapped; +#if HAS_FLOAT16 == 1 + float16 = get_value_swapped; +#endif +#if HAS_BFLOAT16 == 1 + bfloat16 = get_value_swapped; +#endif + } else { + int16 = get_value; + int32 = get_value; + int64 = get_value; + uint16 = get_value; + uint32 = get_value; + uint64 = get_value; + float_ = get_value; + double_ = get_value; +#if HAS_FLOAT16 == 1 + float16 = get_value; +#endif +#if HAS_BFLOAT16 == 1 + bfloat16 = get_value; +#endif + } + } + + std::uint32_t get_timestamp(blob_t const * buf) { + switch (m_ts_size) { + case 0: + return 0; + case 1: + return uint8(buf, m_id_size); + case 2: + return uint16(buf, m_id_size); + case 4: + return uint32(buf, m_id_size); + default: + throw std::runtime_error("Unsupported timestamp size: " + std::to_string(m_ts_size)); + } + } + + measurement_value_t reader(std::uint16_t tp, blob_t const * buf, std::uint16_t offset) const { + switch (tp) { + case 0: + return static_cast(uint8(buf, offset)); + case 1: + return int8(buf, offset); + case 2: + return static_cast(uint16(buf, offset)); + case 3: + return int16(buf, offset); + case 4: + return static_cast(uint32(buf, offset)); + case 5: + return int32(buf, offset); + case 6: + return uint64(buf, offset); + case 7: + return int64(buf, offset); + case 8: + return float_(buf, offset); + case 9: + return double_(buf, offset); +#if HAS_FLOAT16 == 1 + case 10: + return float16(buf, offset); +#endif +#if HAS_BFLOAT16 == 1 + case 11: + return bfloat16(buf, offset); +#endif + default: + throw std::runtime_error("Unsupported data type: " + std::to_string(tp)); + } + } + + void set_first_pids(const std::vector& daq_lists, const std::vector& first_pids) { + m_first_pids = first_pids; + + if (m_id_size == 1) { + // In case of 1-byte ID field (absolute ODT number) we need a mapping. + std::uint16_t daq_list_num = 0; + for (const auto& daq_list : daq_lists) { + auto first_pid = m_first_pids[daq_list_num]; + + for (std::uint16_t idx = first_pid; idx < daq_list.get_odt_count() + first_pid; ++idx) { + m_odt_to_daq_map[idx] = { daq_list_num, (idx - first_pid) }; + } + daq_list_num++; + } + } + } + + std::tuple get_id(blob_t const * buf) { + std::uint16_t odt_num = 0; + + switch (m_id_size) { + case 1: + odt_num = uint8(buf, 0); // Get 1-byte ODT number... + return m_odt_to_daq_map[odt_num]; // ...and return mapped values. + case 2: + return { uint8(buf, 1), uint8(buf, 0) }; + case 3: + return { uint16(buf, 1), uint8(buf, 0) }; + case 4: + return { uint16(buf, 2), uint8(buf, 0) }; + default: + throw std::runtime_error("Unsupported ID size: " + std::to_string(m_id_size)); + } + } + + std::uint8_t m_id_size; + std::uint8_t m_ts_size; + std::function int8; + std::function uint8; + std::function int16; + std::function int32; + std::function int64; + std::function uint16; + std::function uint32; + std::function uint64; + std::function float_; + std::function double_; +#if HAS_FLOAT16 == 1 + std::function float16; +#endif +#if HAS_BFLOAT16 == 1 + std::function bfloat16; +#endif + std::vector m_first_pids; + std::map> m_odt_to_daq_map; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +struct Setter { + Setter() = default; + + explicit Setter(bool requires_swap, std::uint8_t id_size, std::uint8_t ts_size) : m_id_size(id_size), m_ts_size(ts_size) { + int8 = set_value; + uint8 = set_value; + + if (requires_swap) { + int16 = set_value_swapped; + int32 = set_value_swapped; + int64 = set_value_swapped; + uint16 = set_value_swapped; + uint32 = set_value_swapped; + uint64 = set_value_swapped; + float_ = set_value_swapped; + double_ = set_value_swapped; +#if HAS_FLOAT16 == 1 + float16 = set_value_swapped; +#endif +#if HAS_BFLOAT16 == 1 + bfloat16 = set_value_swapped; +#endif + } else { + int16 = set_value; + int32 = set_value; + int64 = set_value; + uint16 = set_value; + uint32 = set_value; + uint64 = set_value; + float_ = set_value; + double_ = set_value; +#if HAS_FLOAT16 == 1 + float16 = set_value; +#endif +#if HAS_BFLOAT16 == 1 + bfloat16 = set_value; +#endif + } + } + + void set_timestamp(blob_t* buf, std::uint32_t timestamp) { + switch (m_ts_size) { + case 0: + break; + case 1: + uint8(buf, m_id_size, timestamp); + break; + case 2: + uint16(buf, m_id_size, timestamp); + break; + case 4: + uint32(buf, m_id_size, timestamp); + break; + default: + throw std::runtime_error("Unsupported timestamp size: " + std::to_string(m_ts_size)); + } + } + + void writer(std::uint16_t tp, blob_t* buf, std::uint16_t offset, const measurement_value_t& value) { + switch (tp) { + case 0: + uint8(buf, offset, static_cast(std::get(value))); + break; + case 1: + int8(buf, offset, static_cast(std::get(value))); + break; + case 2: + uint16(buf, offset, static_cast(std::get(value))); + break; + case 3: + int16(buf, offset, static_cast(std::get(value))); + break; + case 4: + uint32(buf, offset, static_cast(std::get(value))); + break; + case 5: + int32(buf, offset, static_cast(std::get(value))); + break; + case 6: + uint64(buf, offset, std::get(value)); + break; + case 7: + int64(buf, offset, std::get(value)); + break; + case 8: + float_(buf, offset, static_cast(std::get(value))); + break; + case 9: + double_(buf, offset, static_cast(std::get(value))); + break; +#if HAS_FLOAT16 == 1 + case 10: + float16(buf, offset, static_cast(std::get(value))); + break; +#endif +#if HAS_BFLOAT16 == 1 + case 11: + bfloat16(buf, offset, static_cast(std::get(value))); + break; +#endif + default: + throw std::runtime_error("Unsupported data type: " + std::to_string(tp)); + } + } + +#if 0 + std::tuple set_id(blob_t const * buf) { + std::uint16_t odt_num = 0; + + switch (m_id_size) { + case 1: + odt_num = uint8(buf, 0); // Get 1-byte ODT number... + return m_odt_to_daq_map[odt_num]; // ...and return mapped values. + case 2: + return { uint8(buf, 1), uint8(buf, 0) }; + case 3: + return { uint16(buf, 1), uint8(buf, 0) }; + case 4: + return { uint16(buf, 2), uint8(buf, 0) }; + default: + throw std::runtime_error("Unsupported ID size: " + std::to_string(m_id_size)); + } + } +#endif + std::uint8_t m_id_size; + std::uint8_t m_ts_size; + std::function int8; + std::function uint8; + std::function int16; + std::function int32; + std::function int64; + std::function uint16; + std::function uint32; + std::function uint64; + std::function float_; + std::function double_; +#if HAS_FLOAT16 == 1 + std::function float16; +#endif +#if HAS_BFLOAT16 == 1 + std::function bfloat16; +#endif + std::map> m_odt_to_daq_map; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// + +struct MeasurementParameters { + MeasurementParameters() = default; + + MeasurementParameters(MeasurementParameters&&) = default; + MeasurementParameters(const MeasurementParameters&) = default; + MeasurementParameters& operator=(MeasurementParameters&&) = default; + MeasurementParameters& operator=(const MeasurementParameters&) = default; + + explicit MeasurementParameters( + std::uint8_t byte_order, std::uint8_t id_field_size, bool timestamps_supported, bool ts_fixed, bool prescaler_supported, + bool selectable_timestamps, double ts_scale_factor, std::uint8_t ts_size, std::uint16_t min_daq, + const TimestampInfo& timestamp_info, const std::vector& daq_lists, const std::vector& first_pids + ) : + m_byte_order(byte_order), + m_id_field_size(id_field_size), + m_timestamps_supported(timestamps_supported), + m_ts_fixed(ts_fixed), + m_prescaler_supported(prescaler_supported), + m_selectable_timestamps(selectable_timestamps), + m_ts_scale_factor(ts_scale_factor), + m_ts_size(ts_size), + m_min_daq(min_daq), + m_timestamp_info(timestamp_info), + m_daq_lists(daq_lists), + m_first_pids(first_pids) { + } + + std::string dumps() const { + std::stringstream ss; + + ss << to_binary(m_byte_order); + ss << to_binary(m_id_field_size); + ss << to_binary(m_timestamps_supported); + ss << to_binary(m_ts_fixed); + ss << to_binary(m_prescaler_supported); + ss << to_binary(m_selectable_timestamps); + ss << to_binary(m_ts_scale_factor); + ss << to_binary(m_ts_size); + ss << to_binary(m_min_daq); + std::size_t dl_count = m_daq_lists.size(); + ss << to_binary(dl_count); + //// + ss << to_binary(m_timestamp_info.get_timestamp_ns()); + ss << to_binary(m_timestamp_info.get_timezone()); + ss << to_binary(m_timestamp_info.get_utc_offset()); + ss << to_binary(m_timestamp_info.get_dst_offset()); + //// + + for (const auto& daq_list : m_daq_lists) { + ss << daq_list.dumps(); + } + + std::size_t fp_count = m_first_pids.size(); + ss << to_binary(fp_count); + for (const auto& fp : m_first_pids) { + ss << to_binary(fp); + } + + return to_binary(std::size(ss.str())) + ss.str(); + } + + auto get_byte_order() const noexcept { + return m_byte_order; + } + + auto get_id_field_size() const noexcept { + return m_id_field_size; + } + + auto get_timestamps_supported() const noexcept { + return m_timestamps_supported; + } + + auto get_ts_fixed() const noexcept { + return m_ts_fixed; + } + + auto get_prescaler_supported() const noexcept { + return m_prescaler_supported; + } + + auto get_selectable_timestamps() const noexcept { + return m_selectable_timestamps; + } + + auto get_ts_scale_factor() const noexcept { + return m_ts_scale_factor; + } + + auto get_ts_size() const noexcept { + return m_ts_size; + } + + auto get_min_daq() const noexcept { + return m_min_daq; + } + + auto get_timestamp_info() const noexcept { + return m_timestamp_info; + } + + auto get_daq_lists() const noexcept { + return m_daq_lists; + } + + auto get_first_pids() const noexcept { + return m_first_pids; + } + + std::uint8_t m_byte_order; + std::uint8_t m_id_field_size; + bool m_timestamps_supported; + bool m_ts_fixed; + bool m_prescaler_supported; + bool m_selectable_timestamps; + double m_ts_scale_factor; + std::uint8_t m_ts_size; + std::uint16_t m_min_daq; + TimestampInfo m_timestamp_info; + std::vector m_daq_lists; + std::vector m_first_pids; +}; + +class Deserializer { + public: + + explicit Deserializer(const std::string& buf) : m_buf(buf) { + } + + MeasurementParameters run() { + std::uint8_t byte_order; + std::uint8_t id_field_size; + bool timestamps_supported; + bool ts_fixed; + bool prescaler_supported; + bool selectable_timestamps; + double ts_scale_factor; + std::uint8_t ts_size; + std::uint16_t min_daq; + std::size_t dl_count; + std::vector daq_lists; + std::size_t fp_count; + std::uint64_t timestamp_ns; + std::int16_t utc_offset; + std::int16_t dst_offset; + std::string timezone; + std::vector first_pids; + // TimestampInfo timestamp_info{ 0 }; + + byte_order = from_binary(); + id_field_size = from_binary(); + timestamps_supported = from_binary(); + ts_fixed = from_binary(); + prescaler_supported = from_binary(); + selectable_timestamps = from_binary(); + ts_scale_factor = from_binary(); + ts_size = from_binary(); + min_daq = from_binary(); + dl_count = from_binary(); + + //// + timestamp_ns = from_binary(); + // std::cout << "TS: " << timestamp_ns << std::endl; + timezone = from_binary_str(); + // std::cout << "TZ: " << timezone << std::endl; + utc_offset = from_binary(); + // std::cout << "UTC:" << utc_offset << std::endl; + dst_offset = from_binary(); + // std::cout << "DST:" << dst_offset << std::endl; + + TimestampInfo timestamp_info{ timestamp_ns, timezone, utc_offset, dst_offset }; + + for (std::size_t i = 0; i < dl_count; i++) { + daq_lists.push_back(create_daq_list()); + } + + fp_count = from_binary(); + for (std::size_t i = 0; i < fp_count; i++) { + first_pids.push_back(from_binary()); + } + + return MeasurementParameters( + byte_order, id_field_size, timestamps_supported, ts_fixed, prescaler_supported, selectable_timestamps, ts_scale_factor, + ts_size, min_daq, timestamp_info, daq_lists, first_pids + ); + } + + protected: + + DaqList create_daq_list() { + std::string name; + std::uint16_t event_num; + bool stim; + bool enable_timestamps; + std::vector measurements; + std::vector measurements_opt; + std::vector header_names; + + std::uint16_t odt_count; + std::uint16_t total_entries; + std::uint16_t total_length; + + flatten_odts_t flatten_odts; + + std::vector initializer_list{}; + + name = from_binary_str(); + event_num = from_binary(); + stim = from_binary(); + enable_timestamps = from_binary(); + + odt_count = from_binary(); // not used + total_entries = from_binary(); // not used + total_length = from_binary(); // not used + + std::size_t meas_size = from_binary(); + for (std::size_t i = 0; i < meas_size; ++i) { + // name, address, ext, dt_name + auto meas = create_mc_object(); + measurements.push_back(meas); + initializer_list.push_back({ meas.get_name(), meas.get_address(), meas.get_ext(), meas.get_data_type() }); + } + + std::size_t meas_opt_size = from_binary(); + for (std::size_t i = 0; i < meas_opt_size; ++i) { + measurements_opt.emplace_back(create_bin()); + } + + std::size_t hname_size = from_binary(); + for (std::size_t i = 0; i < hname_size; ++i) { + auto header = from_binary_str(); + header_names.push_back(header); + } + + auto odts = create_flatten_odts(); + + auto result = DaqList(name, event_num, stim, enable_timestamps, initializer_list); + result.set_measurements_opt(measurements_opt); + return result; + } + + flatten_odts_t create_flatten_odts() { + std::string name; + std::uint32_t address; + std::uint8_t ext; + std::uint16_t size; + std::int16_t type_index; + + flatten_odts_t odts; + + std::size_t odt_count = from_binary(); + for (std::size_t i = 0; i < odt_count; ++i) { + std::vector> flatten_odt{}; + std::size_t odt_entry_count = from_binary(); + for (std::size_t j = 0; j < odt_entry_count; ++j) { + name = from_binary_str(); + address = from_binary(); + ext = from_binary(); + size = from_binary(); + type_index = from_binary(); + flatten_odt.push_back(std::make_tuple(name, address, ext, size, type_index)); + } + odts.push_back(flatten_odt); + } + + return odts; + } + + McObject create_mc_object() { + std::string name; + std::uint32_t address; + std::uint8_t ext; + std::uint16_t length; + std::string data_type; + std::int16_t type_index; + std::vector components{}; + + name = from_binary_str(); + address = from_binary(); + ext = from_binary(); + length = from_binary(); + data_type = from_binary_str(); + type_index = from_binary(); // not used + std::size_t comp_size = from_binary(); + for (auto i = 0U; i < comp_size; i++) { + components.push_back(create_mc_object()); + } + + return McObject(name, address, ext, length, data_type, components); + } + + Bin create_bin() { + std::uint16_t size; + std::uint16_t residual_capacity; + std::vector entries{}; + + size = from_binary(); + residual_capacity = from_binary(); + std::size_t entry_size = from_binary(); + for (auto i = 0U; i < entry_size; i++) { + entries.push_back(create_mc_object()); + } + + return Bin(size, residual_capacity, entries); + } + + template + inline T from_binary() { + auto tmp = *reinterpret_cast(&m_buf[m_offset]); + m_offset += sizeof(T); + return tmp; + } + + inline std::string from_binary_str() { + auto length = from_binary(); + std::string result; + auto start = m_buf.cbegin() + m_offset; + + std::copy(start, start + length, std::back_inserter(result)); + m_offset += length; + return result; + } + + private: + + std::string m_buf; + std::size_t m_offset = 0; +}; + +class DaqListState { + public: + + enum class state_t : std::uint8_t { + IDLE = 0, + COLLECTING = 1, + FINISHED = 2, + _IGNORE = 3, // Duplicate frame. + _ERROR = 4, // Out-of-order/missing sequence/ODT number. + }; + + DaqListState( + std::uint16_t daq_list_num, std::uint16_t num_odts, std::uint16_t total_entries, bool enable_timestamps, + std::uint16_t initial_offset, const flatten_odts_t& flatten_odts, const Getter& getter, MeasurementParameters params + ) : + m_daq_list_num(daq_list_num), + m_num_odts(num_odts), + m_total_entries(total_entries), + m_enable_timestamps(enable_timestamps), + m_initial_offset(initial_offset), + m_next_odt(0), + m_current_idx(0), + m_timestamp0(0ULL), + m_timestamp1(0ULL), + m_state(state_t::IDLE), + m_buffer{}, + m_flatten_odts(flatten_odts), + m_getter(getter), + m_params(params) { + m_buffer.resize(m_total_entries); + } + + state_t check_state(uint16_t odt_num) { + if ((m_state == state_t::IDLE) && (odt_num == 0x00)) { + // "synch pulse". + if (m_num_odts == 0x01) { + resetSM(); + return state_t::FINISHED; + } else { + m_state = state_t::COLLECTING; + m_next_odt = 1; + } + } else if (m_state == state_t::COLLECTING) { + if (odt_num == m_next_odt) { + m_next_odt++; + if (m_next_odt == m_num_odts) { + resetSM(); + return state_t::FINISHED; + } + } else { + resetSM(); + return state_t::_ERROR; + } + } + return m_state; + } + + bool feed(uint16_t odt_num, std::uint64_t timestamp, const std::string& payload) { + auto state = check_state(odt_num); + auto finished = false; + + if (state == state_t::COLLECTING) { + m_timestamp0 = timestamp; + parse_Odt(odt_num, payload); + } else if (state == state_t::FINISHED) { + m_timestamp0 = timestamp; + parse_Odt(odt_num, payload); + finished = true; + } + return finished; + } + + void add_result(std::vector& result_buffer) { + result_buffer.emplace_back(m_daq_list_num, m_timestamp0, m_timestamp1, m_buffer); + } + + void add_result(measurement_tuple_t& result_buffer) { + result_buffer = { m_daq_list_num, m_timestamp0, m_timestamp1, m_buffer }; + } + + protected: + + void resetSM() { + m_state = state_t::IDLE; + m_next_odt = 0; + m_timestamp0 = 0ULL; + } + + void parse_Odt(uint16_t odt_num, const std::string& payload) { + auto offset = m_initial_offset; // consider ID field size. + auto payload_data = reinterpret_cast(payload.data()); + auto payload_size = std::size(payload); + + if (odt_num == 0) { + m_current_idx = 0; + if (m_params.m_timestamps_supported && + (m_params.m_ts_fixed || (m_params.m_selectable_timestamps && m_enable_timestamps == true))) { + m_timestamp1 = static_cast(m_getter.get_timestamp(payload_data) * m_params.m_ts_scale_factor); + offset += m_params.m_ts_size; + } else { + m_timestamp1 = 0ULL; + } + } + + for (const auto& param : m_flatten_odts[odt_num]) { + const auto& [name, address, ext, size, type_index] = param; + + if (offset >= payload_size) { + throw std::runtime_error( + "Offset is out of range! " + std::to_string(offset) + " >= " + std::to_string(payload_size) + ); + } + + m_buffer[m_current_idx++] = m_getter.reader(type_index, payload_data, offset); + offset += size; + } + } + + private: + + std::uint16_t m_daq_list_num = 0; + std::uint16_t m_num_odts = 0; + std::uint16_t m_total_entries = 0; + bool m_enable_timestamps = false; + std::uint16_t m_initial_offset; + std::uint16_t m_next_odt = 0; + std::uint16_t m_current_idx = 0; + std::uint64_t m_timestamp0 = 0ULL; + std::uint64_t m_timestamp1 = 0ULL; + state_t m_state = state_t::IDLE; + std::vector m_buffer; + flatten_odts_t m_flatten_odts; + Getter m_getter; + MeasurementParameters m_params; +}; + +auto requires_swap(std::uint8_t byte_order) -> bool { + // INTEL(LITTLE)=0, MOTOROLA(BIG)=1 + std::endian target_byte_order = (byte_order == 1) ? std::endian::big : std::endian::little; + return (target_byte_order != std::endian::native) ? true : false; +} + +class DAQProcessor { + public: + + explicit DAQProcessor(const MeasurementParameters& params) : m_params(params) { + create_state_vars(params); + } + + DAQProcessor() = delete; + virtual ~DAQProcessor() = default; + + std::optional feed(std::uint64_t timestamp, const std::string& payload) noexcept { + const auto data = reinterpret_cast(payload.data()); + auto [daq_num, odt_num] = m_getter.get_id(data); + + if (m_state[daq_num].feed(odt_num, timestamp, payload)) { + // DAQ list completed. + measurement_tuple_t result; + + m_state[daq_num].add_result(result); // get_result()??? + return result; + } + return std::nullopt; + } + + private: + + void create_state_vars(const MeasurementParameters& params) noexcept { + m_getter = Getter(requires_swap(params.m_byte_order), params.m_id_field_size, params.m_ts_size); + for (std::uint16_t idx = 0; idx < params.m_daq_lists.size(); ++idx) { + m_state.emplace_back(DaqListState( + idx, params.m_daq_lists[idx].get_odt_count(), params.m_daq_lists[idx].get_total_entries(), + params.m_daq_lists[idx].get_enable_timestamps(), params.m_id_field_size, params.m_daq_lists[idx].get_flatten_odts(), + m_getter, params + )); + } + m_getter.set_first_pids(m_params.m_daq_lists, m_params.m_first_pids); + } + + MeasurementParameters m_params; + Getter m_getter; + std::map m_first_pids; + std::vector m_state; +}; + +class DAQPolicyBase { + public: + + virtual ~DAQPolicyBase() { + } + + virtual void set_parameters(const MeasurementParameters& params) noexcept { + initialize(); + } + + virtual void feed(std::uint8_t frame_cat, std::uint16_t counter, std::uint64_t timestamp, const std::string& payload) = 0; + + virtual void initialize() = 0; + + virtual void finalize() = 0; +}; + +class DaqRecorderPolicy : public DAQPolicyBase { + public: + + ~DaqRecorderPolicy() { + finalize(); + } + + DaqRecorderPolicy() = default; + + void set_parameters(const MeasurementParameters& params) noexcept override { + m_params = params; + DAQPolicyBase::set_parameters(params); + } + + void feed(std::uint8_t frame_cat, std::uint16_t counter, std::uint64_t timestamp, const std::string& payload) override { + if (frame_cat != static_cast(FrameCategory::DAQ)) { + // Only record DAQ frames for now. + return; + } + m_writer->add_frame(frame_cat, counter, timestamp, static_cast(payload.size()), payload.c_str()); + } + + void create_writer(const std::string& file_name, std::uint32_t prealloc, std::uint32_t chunk_size, std::string_view metadata) { + m_writer = std::make_unique(file_name, prealloc, chunk_size, metadata); + } + + void initialize() override { + m_initialized = true; + } + + void finalize() override { + if (!m_initialized) { + return; + } + m_writer->finalize(); + m_initialized = false; + } + + private: + + std::unique_ptr m_writer{ nullptr }; + MeasurementParameters m_params; + bool m_initialized{ false }; +}; + +class DaqOnlinePolicy : public DAQPolicyBase { + public: + + ~DaqOnlinePolicy() { + } + + DaqOnlinePolicy() = default; + + void set_parameters(const MeasurementParameters& params) noexcept { + m_decoder = std::make_unique(params); + DAQPolicyBase::set_parameters(params); + } + + virtual void on_daq_list( + std::uint16_t daq_list_num, std::uint64_t timestamp0, std::uint64_t timestamp1, + const std::vector& measurement + ) = 0; + + void feed(std::uint8_t frame_cat, std::uint16_t counter, std::uint64_t timestamp, const std::string& payload) { + if (frame_cat != static_cast(FrameCategory::DAQ)) { + return; + } + auto result = m_decoder->feed(timestamp, payload); + if (result) { + const auto& [daq_list, ts0, ts1, meas] = *result; + on_daq_list(daq_list, ts0, ts1, meas); + } + } + + virtual void initialize() { + } + + virtual void finalize() { + } + + private: + + std::unique_ptr m_decoder; +}; + +struct ValueHolder { + ValueHolder() = delete; + ValueHolder(const ValueHolder&) = default; + + ValueHolder(const std::any& value) : m_value(value) { + } + + ValueHolder(std::any&& value) : m_value(std::move(value)) { + } + + std::any get_value() const noexcept { + return m_value; + } + + private: + + std::any m_value; +}; + +class XcpLogFileDecoder { + public: + + explicit XcpLogFileDecoder(const std::string& file_name) : m_reader(file_name) { + auto metadata = m_reader.get_metadata(); + if (metadata != "") { + auto des = Deserializer(metadata); + m_params = des.run(); + m_decoder = std::make_unique(m_params); + } else { + // cannot proceed!!! + } + } + + XcpLogFileDecoder() = delete; + virtual ~XcpLogFileDecoder() = default; + + virtual void initialize() { + } + + virtual void finalize() { + } + + void run() { + initialize(); + const auto converter = [](const blob_t* in_str, std::size_t length) -> std::string { + std::string result; + result.resize(length); + + for (std::size_t idx = 0; idx < length; ++idx) { + result[idx] = static_cast(in_str[idx]); + } + + return result; + }; + + while (true) { + const auto& block = m_reader.next_block(); + if (!block) { + finalize(); + return; + } + + for (const auto& [frame_cat, counter, timestamp, length, payload] : block.value()) { + auto str_data = converter(payload.data(), std::size(payload)); + if (frame_cat != static_cast(FrameCategory::DAQ)) { + continue; + } + auto result = m_decoder->feed(timestamp, str_data); + if (result) { + const auto& [daq_list, ts0, ts1, meas] = *result; + on_daq_list(daq_list, ts0, ts1, meas); + } + } + } + return; + } + + virtual void on_daq_list( + std::uint16_t daq_list_num, std::uint64_t timestamp0, std::uint64_t timestamp1, + const std::vector& measurement + ) = 0; + + MeasurementParameters get_parameters() const { + return m_params; + } + + auto get_daq_lists() const { + return m_params.m_daq_lists; + } + + auto get_header() const { + return m_reader.get_header(); + } + + private: + + XcpLogFileReader m_reader; + std::unique_ptr m_decoder; + MeasurementParameters m_params; +}; + +#endif // RECORDER_UNFOLDER_HPP diff --git a/pyxcp/recorder/wrap.cpp b/pyxcp/recorder/wrap.cpp index 80999ea0..256fc5ef 100644 --- a/pyxcp/recorder/wrap.cpp +++ b/pyxcp/recorder/wrap.cpp @@ -1,27 +1,189 @@ - -#include - -#include -#include -#include - -#include "rekorder.hpp" - -namespace py = pybind11; -using namespace pybind11::literals; - - -PYBIND11_MODULE(rekorder, m) { - m.doc() = "XCP raw frame recorder."; - py::class_(m, "_PyXcpLogFileReader") - .def(py::init()) - .def("next_block", &XcpLogFileReader::next_block) - .def("reset", &XcpLogFileReader::reset) - .def("get_header_as_tuple", &XcpLogFileReader::get_header_as_tuple) - ; - py::class_(m, "_PyXcpLogFileWriter") - .def(py::init()) - .def("finalize", &XcpLogFileWriter::finalize) - .def("add_frame", &XcpLogFileWriter::add_frame) - ; -} + +#include +#include +#include +#include + +#include + +#include "rekorder.hpp" + +namespace py = pybind11; +using namespace pybind11::literals; + +PYBIND11_MAKE_OPAQUE(ValueHolder); + +class PyDaqOnlinePolicy : public DaqOnlinePolicy { + public: + + using DaqOnlinePolicy::DaqOnlinePolicy; + + void on_daq_list( + std::uint16_t daq_list_num, std::uint64_t timestamp0, std::uint64_t timestamp1, + const std::vector& measurement + ) override { + PYBIND11_OVERRIDE_PURE(void, DaqOnlinePolicy, on_daq_list, daq_list_num, timestamp0, timestamp1, measurement); + } + + void initialize() override { + PYBIND11_OVERRIDE(void, DaqOnlinePolicy, initialize); + } + + void finalize() override { + PYBIND11_OVERRIDE(void, DaqOnlinePolicy, finalize); + } +}; + +class PyDaqRecorderPolicy : public DaqRecorderPolicy { + public: + + using DaqRecorderPolicy::DaqRecorderPolicy; + + void initialize() override { + PYBIND11_OVERRIDE(void, DaqRecorderPolicy, initialize); + } + + void finalize() override { + PYBIND11_OVERRIDE(void, DaqRecorderPolicy, finalize); + } +}; + +class PyXcpLogFileDecoder : public XcpLogFileDecoder { + public: + + using XcpLogFileDecoder::XcpLogFileDecoder; + + void on_daq_list( + std::uint16_t daq_list_num, std::uint64_t timestamp0, std::uint64_t timestamp1, + const std::vector& measurement + ) override { + PYBIND11_OVERRIDE_PURE(void, XcpLogFileDecoder, on_daq_list, daq_list_num, timestamp0, timestamp1, measurement); + } + + void initialize() override { + PYBIND11_OVERRIDE(void, XcpLogFileDecoder, initialize); + } + + void finalize() override { + PYBIND11_OVERRIDE(void, XcpLogFileDecoder, finalize); + } +}; + +PYBIND11_MODULE(rekorder, m) { + m.doc() = "XCP raw frame recorder."; + m.def("data_types", get_data_types); + + py::class_(m, "FileHeaderType") + .def(py::init()) + .def("__repr__", [](const FileHeaderType& self) { + std::stringstream ss; + ss << "FileHeaderType(" << std::endl; + ss << " hdr_size=" << self.hdr_size << "," << std::endl; + ss << " version=" << self.version << "," << std::endl; + ss << " options=" << self.options << "," << std::endl; + ss << " num_containers=" << self.num_containers << "," << std::endl; + ss << " record_count=" << self.record_count << "," << std::endl; + ss << " size_compressed=" << self.size_compressed << "," << std::endl; + ss << " size_uncompressed=" << self.size_uncompressed << "," << std::endl; + ss << " compression_ratio=" + << (double)((std::uint64_t)(((double)self.size_uncompressed / (double)self.size_compressed * 100.0) + 0.5)) / 100.0 + << std::endl; + ss << ")" << std::endl; + return ss.str(); + }); + + py::class_(m, "Deserializer").def(py::init()).def("run", &Deserializer::run); + + py::class_(m, "_PyXcpLogFileReader") + .def(py::init()) + .def("next_block", &XcpLogFileReader::next_block) + .def("reset", &XcpLogFileReader::reset) + .def("get_header_as_tuple", &XcpLogFileReader::get_header_as_tuple) + .def("get_metadata", [](const XcpLogFileReader& self) { return py::bytes(self.get_metadata()); }); + + py::class_(m, "_PyXcpLogFileWriter") + .def( + py::init(), py::arg("filename"), + py::arg("prealloc"), py::arg("chunk_size"), py::arg("metadata") = "" + ) + .def("finalize", &XcpLogFileWriter::finalize) + .def("add_frame", &XcpLogFileWriter::add_frame); + + py::class_(m, "MeasurementParameters") + .def(py::init< + std::uint8_t, std::uint8_t, bool, bool, bool, bool, double, std::uint8_t, std::uint16_t, const TimestampInfo&, + const std::vector&, const std::vector&>()) + .def("dumps", [](const MeasurementParameters& self) { return py::bytes(self.dumps()); }) + .def( + "__repr__", + [](const MeasurementParameters& self) { + std::stringstream ss; + ss << "MeasurementParameters("; + ss << "byte_order=\"" << byte_order_to_string(self.m_byte_order) << "\", "; + ss << "id_field_size=" << static_cast(self.m_id_field_size) << ", "; + ss << "timestamps_supported=" << bool_to_string(self.m_timestamps_supported) << ", "; + ss << "ts_fixed=" << bool_to_string(self.m_ts_fixed) << ", "; + ss << "prescaler_supported=" << bool_to_string(self.m_prescaler_supported) << ", "; + ss << "selectable_timestamps=" << bool_to_string(self.m_selectable_timestamps) << ", "; + ss << "ts_scale_factor=" << self.m_ts_scale_factor << ", "; + ss << "ts_size=" << static_cast(self.m_ts_size) << ", "; + ss << "min_daq=" << static_cast(self.m_min_daq) << ", "; + ss << "timestamp_info=" << self.get_timestamp_info().to_string() << ", "; + ss << "daq_lists=[\n"; + for (const auto& dl : self.m_daq_lists) { + ss << dl.to_string() << ",\n"; + } + ss << "],\n"; + ss << "first_pids=["; + for (auto fp : self.m_first_pids) { + ss << fp << ", "; + } + ss << "]"; + return ss.str(); + } + ) + .def_property_readonly("byte_order", &MeasurementParameters::get_byte_order) + .def_property_readonly("id_field_size", &MeasurementParameters::get_id_field_size) + .def_property_readonly("timestamps_supported", &MeasurementParameters::get_timestamps_supported) + .def_property_readonly("ts_fixed", &MeasurementParameters::get_ts_fixed) + .def_property_readonly("prescaler_supported", &MeasurementParameters::get_prescaler_supported) + .def_property_readonly("selectable_timestamps", &MeasurementParameters::get_selectable_timestamps) + .def_property_readonly("ts_scale_factor", &MeasurementParameters::get_ts_scale_factor) + .def_property_readonly("ts_size", &MeasurementParameters::get_ts_size) + .def_property_readonly("min_daq", &MeasurementParameters::get_min_daq) + .def_property_readonly("timestamp_info", &MeasurementParameters::get_timestamp_info) + .def_property_readonly("daq_lists", &MeasurementParameters::get_daq_lists) + .def_property_readonly("first_pids", &MeasurementParameters::get_first_pids) + .def_property_readonly("timestamp_info", &MeasurementParameters::get_timestamp_info); + + py::class_(m, "DaqRecorderPolicy", py::dynamic_attr()) + .def(py::init<>()) + .def("create_writer", &DaqRecorderPolicy::create_writer) + .def("feed", &DaqRecorderPolicy::feed) + .def("set_parameters", &DaqRecorderPolicy::set_parameters) + .def("initialize", &DaqRecorderPolicy::initialize) + .def("finalize", &DaqRecorderPolicy::finalize); + + py::class_(m, "DaqOnlinePolicy", py::dynamic_attr()) + .def(py::init<>()) + .def("on_daq_list", &DaqOnlinePolicy::on_daq_list) + .def("feed", &DaqOnlinePolicy::feed) + .def("finalize", &DaqOnlinePolicy::finalize) + .def("set_parameters", &DaqOnlinePolicy::set_parameters) + .def("initialize", &DaqOnlinePolicy::initialize); + + py::class_(m, "XcpLogFileDecoder", py::dynamic_attr()) + .def(py::init()) + .def("run", &XcpLogFileDecoder::run) + .def("on_daq_list", &XcpLogFileDecoder::on_daq_list) + .def_property_readonly("parameters", &XcpLogFileDecoder::get_parameters) + .def_property_readonly("daq_lists", &XcpLogFileDecoder::get_daq_lists) + .def("get_header", &XcpLogFileDecoder::get_header) + .def("initialize", &XcpLogFileDecoder::initialize) + .def("finalize", &XcpLogFileDecoder::finalize); + + py::class_(m, "ValueHolder") + //.def(py::init()) + .def(py::init()) + .def_property_readonly("value", &ValueHolder::get_value); +} diff --git a/pyxcp/recorder/writer.hpp b/pyxcp/recorder/writer.hpp new file mode 100644 index 00000000..208e04d3 --- /dev/null +++ b/pyxcp/recorder/writer.hpp @@ -0,0 +1,302 @@ + +#ifndef RECORDER_WRITER_HPP +#define RECORDER_WRITER_HPP + +constexpr std::uint64_t MASK32 = (1ULL << 32) - 1; + +class XcpLogFileWriter { + public: + + explicit XcpLogFileWriter( + const std::string &file_name, uint32_t prealloc = 10UL, uint32_t chunk_size = 1, std::string_view metadata = "" + ) { + if (!file_name.ends_with(detail::FILE_EXTENSION)) { + m_file_name = file_name + detail::FILE_EXTENSION; + } else { + m_file_name = file_name; + } + m_opened = false; + +#if defined(_WIN32) + m_fd = CreateFileA( + m_file_name.c_str(), GENERIC_READ | GENERIC_WRITE, 0, (LPSECURITY_ATTRIBUTES) nullptr, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS, nullptr + ); + if (m_fd == INVALID_HANDLE_VALUE) { + throw std::runtime_error(error_string("XcpLogFileWriter::CreateFileA", get_last_error())); + } else { + m_opened = true; + } +#else + m_fd = open(m_file_name.c_str(), O_CREAT | O_RDWR | O_TRUNC, 0666); + if (m_fd == -1) { + throw std::runtime_error(error_string("XcpLogFileWriter::open", get_last_error())); + } else { + m_opened = true; + } +#endif + m_hard_limit = megabytes(prealloc); + resize(m_hard_limit); + m_mmap = new mio::mmap_sink(m_fd); + m_chunk_size = 512 * 1024; // megabytes(chunk_size); + m_intermediate_storage = new blob_t[m_chunk_size + megabytes(1)]; + m_offset = detail::FILE_HEADER_SIZE + detail::MAGIC_SIZE; + m_metadata = metadata; + + if (!metadata.empty()) { + m_offset += std::size(metadata); + write_metadata(); + } + start_thread(); + } + + ~XcpLogFileWriter() { + finalize(); +#ifdef __APPLE__ + if (collector_thread.joinable()) { + collector_thread.join(); + } +#endif + } + + void finalize() { + std::error_code ec; + if (!m_finalized) { + m_finalized = true; + stop_thread(); + + if (!m_opened) { + return; + } + + if (m_container_record_count) { + compress_frames(); + } + + std::uint16_t options = m_metadata.empty() ? 0 : XMRAW_HAS_METADATA; + + write_header( + detail::VERSION, options, m_num_containers, m_record_count, m_total_size_compressed, m_total_size_uncompressed + ); + m_mmap->unmap(); + ec = mio::detail::last_error(); + if (ec.value() != 0) { + throw std::runtime_error(error_string("XcpLogFileWriter::mio::unmap", ec)); + } + + resize(m_offset); +#if defined(_WIN32) + if (!CloseHandle(m_fd)) { + throw std::runtime_error(error_string("XcpLogFileWriter::CloseHandle", get_last_error())); + } +#else + if (close(m_fd) == -1) { + throw std::runtime_error(error_string("XcpLogFileWriter::close", get_last_error())); + } +#endif + delete m_mmap; + delete[] m_intermediate_storage; + } + } + + void add_frame(uint8_t category, uint16_t counter, std::uint64_t timestamp, uint16_t length, char const *data) { + auto payload = new char[length]; + + _fcopy(payload, data, length); + my_queue.put(std::make_tuple(category, counter, timestamp, length, payload)); + } + + protected: + + void resize(std::uint64_t size, bool remap = false) { + std::error_code ec; + + if (remap) { + m_mmap->unmap(); + ec = mio::detail::last_error(); + if (ec.value() != 0) { + throw std::runtime_error(error_string("XcpLogFileWriter::mio::unmap", ec)); + } + } + +#if defined(_WIN32) + LONG low_part = (MASK32 & size); + LONG high_part = size >> 32; + + if (SetFilePointer(m_fd, low_part, &high_part, FILE_BEGIN) == INVALID_SET_FILE_POINTER) { + auto err = get_last_error(); + + if (err.value() != NO_ERROR) { + throw std::runtime_error(error_string("XcpLogFileWriter::SetFilePointer", err)); + } + } + if (SetEndOfFile(m_fd) == 0) { + throw std::runtime_error(error_string("XcpLogFileWriter::SetEndOfFile", get_last_error())); + } +#else + if (ftruncate(m_fd, size) == -1) { + throw std::runtime_error(error_string("XcpLogFileWriter::ftruncate", get_last_error())); + } +#endif + if (remap) { + m_mmap->map(m_fd, 0, size, ec); + if (ec.value() != 0) { + throw std::runtime_error(error_string("XcpLogFileWriter::mio::map", ec)); + } + } + } + + blob_t *ptr(std::uint64_t pos = 0) const { + return (blob_t *)(m_mmap->data() + pos); + } + + template + void store_im(T const *data, std::uint32_t length) { + _fcopy( + reinterpret_cast(m_intermediate_storage + m_intermediate_storage_offset), reinterpret_cast(data), + length + ); + m_intermediate_storage_offset += length; + } + + void compress_frames() { + auto container = ContainerHeaderType{}; + // printf("Compressing %u frames... [%d]\n", m_container_record_count, m_intermediate_storage_offset); + const int cp_size = ::LZ4_compress_HC( + reinterpret_cast(m_intermediate_storage), + reinterpret_cast(ptr(m_offset + detail::CONTAINER_SIZE)), m_intermediate_storage_offset, + LZ4_COMPRESSBOUND(m_intermediate_storage_offset), LZ4HC_CLEVEL_MAX + ); + + if (cp_size < 0) { + throw std::runtime_error("XcpLogFileWriter - LZ4 compression failed."); + } + + if (m_offset > (m_hard_limit >> 1)) { + std::cout << "[INFO] " << current_timestamp() << ": Doubling measurement file size." << std::endl; + m_hard_limit <<= 1; + resize(m_hard_limit, true); + write_header( + detail::VERSION, m_metadata.empty() ? 0 : XMRAW_HAS_METADATA, m_num_containers, m_record_count, + m_total_size_compressed, m_total_size_uncompressed + ); + } + container.record_count = m_container_record_count; + container.size_compressed = cp_size; + container.size_uncompressed = m_container_size_uncompressed; + + _fcopy(reinterpret_cast(ptr(m_offset)), reinterpret_cast(&container), detail::CONTAINER_SIZE); + + m_offset += (detail::CONTAINER_SIZE + cp_size); + m_total_size_uncompressed += m_container_size_uncompressed; + m_total_size_compressed += cp_size; + m_record_count += m_container_record_count; + m_container_size_uncompressed = 0; + m_container_size_compressed = 0; + m_container_record_count = 0; + m_intermediate_storage_offset = 0; + m_num_containers += 1; + } + + void write_bytes(std::uint64_t pos, std::uint64_t count, char const *buf) const { + auto addr = reinterpret_cast(ptr(pos)); + + _fcopy(addr, buf, count); + } + + void write_header( + std::uint16_t version, std::uint16_t options, std::uint64_t num_containers, std::uint64_t record_count, + std::uint64_t size_compressed, std::uint64_t size_uncompressed + ) { + auto header = FileHeaderType{}; + write_bytes(0x00000000UL, detail::MAGIC_SIZE, detail::MAGIC.c_str()); + header.hdr_size = detail::FILE_HEADER_SIZE + detail::MAGIC_SIZE; + header.version = version; + header.options = options; + header.num_containers = num_containers; + header.record_count = record_count; + header.size_compressed = size_compressed; + header.size_uncompressed = size_uncompressed; + write_bytes(0x00000000UL + detail::MAGIC_SIZE, detail::FILE_HEADER_SIZE, reinterpret_cast(&header)); + } + + void write_metadata() { + if (!m_metadata.empty()) { + write_bytes(detail::MAGIC_SIZE + detail::FILE_HEADER_SIZE, m_metadata.size(), m_metadata.c_str()); + } + } + + bool start_thread() { + if (collector_thread.joinable()) { + return false; + } + stop_collector_thread_flag = false; +#ifdef __APPLE__ + collector_thread = std::thread([this]() { +#else + collector_thread = std::jthread([this]() { +#endif + while (!stop_collector_thread_flag) { + auto item = my_queue.get(); + const auto content = item.get(); + if (stop_collector_thread_flag == true) { + break; + } + const auto [category, counter, timestamp, length, payload] = *content; + const frame_header_t frame{ category, counter, timestamp, length }; + store_im(&frame, sizeof(frame)); + store_im(payload, length); + delete[] payload; + m_container_record_count += 1; + m_container_size_uncompressed += (sizeof(frame) + length); + if (m_container_size_uncompressed > m_chunk_size) { + compress_frames(); + } + } + }); + + return true; + } + + bool stop_thread() { + if (!collector_thread.joinable()) { + return false; + } + stop_collector_thread_flag = true; + my_queue.put(FrameTupleWriter{}); // Put something into the queue, otherwise the thread will hang forever. + collector_thread.join(); + return true; + } + + private: + + std::string m_file_name; + std::uint64_t m_offset{ 0 }; + std::uint32_t m_chunk_size{ 0 }; + std::string m_metadata; + bool m_opened{ false }; + std::uint64_t m_num_containers{ 0 }; + std::uint64_t m_record_count{ 0UL }; + std::uint32_t m_container_record_count{ 0UL }; + std::uint64_t m_total_size_uncompressed{ 0UL }; + std::uint64_t m_total_size_compressed{ 0UL }; + std::uint32_t m_container_size_uncompressed{ 0UL }; + std::uint32_t m_container_size_compressed{ 0UL }; + __ALIGN blob_t *m_intermediate_storage{ nullptr }; + std::uint32_t m_intermediate_storage_offset{ 0 }; + std::uint64_t m_hard_limit{ 0 }; + mio::file_handle_type m_fd{ INVALID_HANDLE_VALUE }; + mio::mmap_sink *m_mmap{ nullptr }; + bool m_finalized{ false }; +#ifdef __APPLE__ + std::thread collector_thread{}; +#else + std::jthread collector_thread{}; +#endif + std::mutex mtx; + TsQueue my_queue; + BlockMemory mem{}; + std::atomic_bool stop_collector_thread_flag{ false }; +}; + +#endif // RECORDER_WRITER_HPP diff --git a/pyxcp/scripts/pyxcp_probe_can_drivers.py b/pyxcp/scripts/pyxcp_probe_can_drivers.py index e2934729..143779c4 100644 --- a/pyxcp/scripts/pyxcp_probe_can_drivers.py +++ b/pyxcp/scripts/pyxcp_probe_can_drivers.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from pprint import pprint import can diff --git a/pyxcp/scripts/xcp_fetch_a2l.py b/pyxcp/scripts/xcp_fetch_a2l.py index 2ab6ddb1..052d9d49 100644 --- a/pyxcp/scripts/xcp_fetch_a2l.py +++ b/pyxcp/scripts/xcp_fetch_a2l.py @@ -1,11 +1,10 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- - """Fetch A2L file from XCP slave (if supported). """ - -from pathlib import Path import sys +from pathlib import Path + +from rich.prompt import Confirm from pyxcp.cmdline import ArgumentParser from pyxcp.types import XcpGetIdType @@ -21,15 +20,21 @@ def main(): file_name = x.identifier(XcpGetIdType.FILENAME) content = x.identifier(XcpGetIdType.FILE_TO_UPLOAD) x.disconnect() + if not content: + sys.exit(f"Empty response from ID '{XcpGetIdType.FILE_TO_UPLOAD!r}'.") + if not file_name: + file_name = "output.a2l" if not file_name.lower().endswith(".a2l"): file_name += ".a2l" - phile = Path(file_name) - if phile.exists(): - print(f"{file_name} already exists.") - sys.exit(1) - with phile.open("wt", encoding = "utf-8") as of: + dest = Path(file_name) + if dest.exists(): + if not Confirm.ask(f"Destination file [green]{dest.name!r}[/green] already exists. Do you want to overwrite it?"): + print("Aborting...") + exit(1) + with dest.open("wt", encoding="utf-8") as of: of.write(content) - print(f"Created {file_name}") + print(f"A2L data written to {file_name!r}.") + if __name__ == "__main__": main() diff --git a/pyxcp/scripts/xcp_id_scanner.py b/pyxcp/scripts/xcp_id_scanner.py index e6f91cde..27353c62 100644 --- a/pyxcp/scripts/xcp_id_scanner.py +++ b/pyxcp/scripts/xcp_id_scanner.py @@ -1,8 +1,6 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """Scan for available IDs. """ -from pprint import pprint from pyxcp.cmdline import ArgumentParser @@ -12,10 +10,8 @@ def main(): with ap.run() as x: x.connect() result = x.id_scanner() - print("\n") - print("Implemented IDs".center(80)) - print("=" * 80) - pprint(result) + for key, value in result.items(): + print(f"{key}: {value}", end="\n\n") x.disconnect() diff --git a/pyxcp/scripts/xcp_info.py b/pyxcp/scripts/xcp_info.py index 73732408..d7b76b0a 100644 --- a/pyxcp/scripts/xcp_info.py +++ b/pyxcp/scripts/xcp_info.py @@ -1,74 +1,109 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -"""Very basic hello-world example. + +"""XCP info/exploration tool. """ + from pprint import pprint from pyxcp.cmdline import ArgumentParser +from pyxcp.types import TryCommandResult -daq_info = False +def main(): + ap = ArgumentParser(description="XCP info/exploration tool.") -def callout(master, args): - global daq_info - if args.daq_info: - daq_info = True + with ap.run() as x: + x.connect() + if x.slaveProperties.optionalCommMode: + x.try_command(x.getCommModeInfo, extra_msg="availability signaled by CONNECT, this may be a slave configuration error.") + print("\nSlave Properties:") + print("=================") + pprint(x.slaveProperties) + result = x.id_scanner() + print("\n") + print("Implemented IDs:") + print("================") + for key, value in result.items(): + print(f"{key}: {value}", end="\n\n") + cps = x.getCurrentProtectionStatus() + print("\nProtection Status") + print("=================") + for k, v in cps.items(): + print(f" {k:6s}: {v}") + x.cond_unlock() + print("\nDAQ Info:") + print("=========") + daq_info = x.getDaqInfo() + pprint(daq_info) -ap = ArgumentParser(description="pyXCP hello world.", callout=callout) -ap.parser.add_argument( - "-d", - "--daq-info", - dest="daq_info", - help="Display DAQ-info", - default=False, - action="store_true", -) -with ap.run() as x: - x.connect() - if x.slaveProperties.optionalCommMode: - x.getCommModeInfo() - identifier = x.identifier(0x01) - print("\nSlave Properties:") - print("=================") - print(f"ID: '{identifier}'") - pprint(x.slaveProperties) - cps = x.getCurrentProtectionStatus() - print("\nProtection Status") - print("=================") - for k, v in cps.items(): - print(f" {k:6s}: {v}") - if daq_info: - dqp = x.getDaqProcessorInfo() - print("\nDAQ Processor Info:") - print("===================") - print(dqp) - print("\nDAQ Events:") - print("===========") - for idx in range(dqp.maxEventChannel): - evt = x.getDaqEventInfo(idx) - length = evt.eventChannelNameLength - name = x.pull(length).decode("utf-8") - dq = "DAQ" if evt.daqEventProperties.daq else "" - st = "STIM" if evt.daqEventProperties.stim else "" - dq_st = dq + " " + st - print(f' [{idx:04}] "{name:s}"') - print(f" dir: {dq_st}") - print(f" packed: {evt.daqEventProperties.packed}") - PFX_CONS = "CONSISTENCY_" - print(f" consistency: {evt.daqEventProperties.consistency.strip(PFX_CONS)}") - print(f" max. DAQ lists: {evt.maxDaqList}") - PFX_TU = "EVENT_CHANNEL_TIME_UNIT_" - print(f" unit: {evt.eventChannelTimeUnit.strip(PFX_TU)}") - print(f" cycle: {evt.eventChannelTimeCycle or 'SPORADIC'}") - print(f" priority {evt.eventChannelPriority}") - - dqr = x.getDaqResolutionInfo() - print("\nDAQ Resolution Info:") + daq_pro = daq_info["processor"] + daq_properties = daq_pro["properties"] + if x.slaveProperties.transport_layer == "CAN": + print("") + if daq_properties["pidOffSupported"]: + print("*** pidOffSupported -- i.e. one CAN-ID per DAQ-list.") + else: + print("*** NO support for PID_OFF") + num_predefined = daq_pro["minDaq"] + print("\nPredefined DAQ-Lists") print("====================") - print(dqr) - for idx in range(dqp.maxDaq): - print(f"\nDAQ List Info #{idx}") - print("=================") - print(f"{x.getDaqListInfo(idx)}") - x.disconnect() + if num_predefined > 0: + print(f"There are {num_predefined} predefined DAQ-lists") + for idx in range(num_predefined): + print(f"DAQ-List #{idx}\n____________\n") + status, dm = x.try_command(x.getDaqListMode, idx) + if status == TryCommandResult.OK: + print(dm) + status, di = x.try_command(x.getDaqListInfo, idx) + if status == TryCommandResult.OK: + print(di) + else: + print("*** NO Predefined DAQ-Lists") + print("\nPAG Info:") + print("=========") + if x.slaveProperties.supportsCalpag: + status, pag = x.try_command(x.getPagProcessorInfo) + if status == TryCommandResult.OK: + print(pag) + # for idx in range(pag.maxSegments): + # x.getSegmentInfo(0x01, idx, 0, 0) + else: + print("*** PAGING IS NOT SUPPORTED.") + + print("\nPGM Info:") + print("=========") + if x.slaveProperties.supportsPgm: + status, pgm = x.try_command(x.getPgmProcessorInfo) + if status == TryCommandResult.OK: + print(pgm) + else: + print("*** FLASH PROGRAMMING IS NOT SUPPORTED.") + + if x.slaveProperties.transport_layer == "CAN": + print("\nTransport-Layer CAN:") + print("====================") + status, res = x.try_command(x.getSlaveID, 0) + if status == TryCommandResult.OK: + print("CAN identifier for CMD/STIM:\n", res) + else: + print("*** GET_SLAVE_ID() IS NOT SUPPORTED.") # no response from bc address ??? + + print("\nPer DAQ-list Identifier") + print("-----------------------") + daq_id = 0 + while True: + status, res = x.try_command(x.getDaqId, daq_id) + if status == TryCommandResult.OK: + print(f"DAQ-list #{daq_id}:", res) + daq_id += 1 + else: + break + if daq_id == 0: + print("N/A") + x.disconnect() + print("\nDone.") + + +if __name__ == "__main__": + main() diff --git a/pyxcp/scripts/xcp_profile.py b/pyxcp/scripts/xcp_profile.py new file mode 100644 index 00000000..5041e72e --- /dev/null +++ b/pyxcp/scripts/xcp_profile.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +"""Create / convert pyxcp profiles (configurations). +""" + +import sys + +from pyxcp.cmdline import ArgumentParser + + +def main(): + if len(sys.argv) == 1: + sys.argv.append("profile") + elif len(sys.argv) >= 2 and sys.argv[1] != "profile": + sys.argv.insert(1, "profile") + + ap = ArgumentParser(description="Create / convert pyxcp profiles (configurations).") + + try: + with ap.run() as x: # noqa: F841 + pass + except FileNotFoundError as e: + print(f"Error: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/pyxcp/stim/__init__.py b/pyxcp/stim/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyxcp/tests/test_asam_types.py b/pyxcp/tests/test_asam_types.py index ed765795..e2edf0c1 100644 --- a/pyxcp/tests/test_asam_types.py +++ b/pyxcp/tests/test_asam_types.py @@ -1,7 +1,7 @@ -from pyxcp.asam import types - import pytest +from pyxcp.asam import types + def testEncodeUint32_0(): assert types.A_Uint32("<").encode(3415750566) == b"\xa67\x98\xcb" diff --git a/pyxcp/tests/test_binpacking.py b/pyxcp/tests/test_binpacking.py new file mode 100644 index 00000000..201393ee --- /dev/null +++ b/pyxcp/tests/test_binpacking.py @@ -0,0 +1,184 @@ +import pytest + +from pyxcp.daq_stim.optimize import McObject, make_continuous_blocks +from pyxcp.daq_stim.optimize.binpacking import Bin, first_fit_decreasing + + +@pytest.fixture +def blocks(): + return [ + McObject(name="", address=0x000E10BA, length=2), + McObject(name="", address=0x000E10BE, length=2), + McObject(name="", address=0x000E41F4, length=4), + McObject(name="", address=0x000E51FC, length=4), + McObject(name="", address=0x00125288, length=4), + McObject(name="", address=0x00125294, length=4), + McObject(name="", address=0x001252A1, length=1), + McObject(name="", address=0x001252A4, length=4), + McObject(name="", address=0x00125438, length=3), + McObject(name="", address=0x0012543C, length=1), + ] + + +def test_pack_to_single_bin(blocks): + BIN_SIZE = 253 + bins = first_fit_decreasing(items=blocks, bin_size=BIN_SIZE) + + assert len(bins) == 1 + bin0 = bins[0] + assert bin0.residual_capacity == BIN_SIZE - 29 + assert bin0.entries == [ + McObject(name="", address=0x000E41F4, length=4), + McObject(name="", address=0x000E51FC, length=4), + McObject(name="", address=0x00125288, length=4), + McObject(name="", address=0x00125294, length=4), + McObject(name="", address=0x001252A4, length=4), + McObject(name="", address=0x00125438, length=3), + McObject(name="", address=0x000E10BA, length=2), + McObject(name="", address=0x000E10BE, length=2), + McObject(name="", address=0x001252A1, length=1), + McObject(name="", address=0x0012543C, length=1), + ] + + +def test_pack_empty_block_set(): + BIN_SIZE = 253 + bins = first_fit_decreasing(items=[], bin_size=BIN_SIZE) + assert bins == [Bin(size=BIN_SIZE)] + + +def test_pack_to_multiple_bins1(blocks): + BIN_SIZE = 6 + bins = first_fit_decreasing(items=blocks, bin_size=BIN_SIZE) + assert len(bins) == 6 + bin0, bin1, bin2, bin3, bin4, bin5 = bins + assert bin0.residual_capacity == 0 + assert bin0.entries == [ + McObject(name="", address=0x000E41F4, length=4), + McObject(name="", address=0x000E10BA, length=2), + ] + assert bin1.residual_capacity == 0 + assert bin1.entries == [ + McObject(name="", address=0x000E51FC, length=4), + McObject(name="", address=0x000E10BE, length=2), + ] + assert bin2.residual_capacity == 0 + assert bin2.entries == [ + McObject(name="", address=0x00125288, length=4), + McObject(name="", address=0x001252A1, length=1), + McObject(name="", address=0x0012543C, length=1), + ] + assert bin3.residual_capacity == 2 + assert bin3.entries == [McObject(name="", address=0x00125294, length=4)] + assert bin4.residual_capacity == 2 + assert bin4.entries == [McObject(name="", address=0x001252A4, length=4)] + assert bin5.residual_capacity == 3 + assert bin5.entries == [McObject(name="", address=0x00125438, length=3)] + + +def test_binpacking_raises(blocks): + BIN_SIZE = 7 + with pytest.raises(ValueError): + first_fit_decreasing(items=[McObject(name="", address=0x1000, length=32)], bin_size=BIN_SIZE) + + +def test_binpacking_works(blocks): + BIN_SIZE = 7 + first_fit_decreasing(items=[McObject(name="", address=0x1000, length=7)], bin_size=BIN_SIZE) + + +def test_make_continuous_blocks1(): + BLOCKS = [ + McObject(name="", address=0x000E0002, length=2), + McObject(name="", address=0x000E0008, ext=23, length=4), + McObject(name="", address=0x000E0004, length=4), + McObject(name="", address=0x000E000C, ext=23, length=4), + McObject(name="", address=0x000E0000, length=2), + ] + bins = make_continuous_blocks(chunks=BLOCKS) + assert bins == [ + McObject( + name="", + address=917504, + ext=0, + length=8, + components=[ + McObject(name="", address=917504, ext=0, length=2, components=[]), + McObject(name="", address=917506, ext=0, length=2, components=[]), + McObject(name="", address=917508, ext=0, length=4, components=[]), + ], + ), + McObject( + name="", + address=917512, + ext=23, + length=8, + components=[ + McObject(name="", address=917512, ext=23, length=4, components=[]), + McObject(name="", address=917516, ext=23, length=4, components=[]), + ], + ), + ] + + +def test_make_continuous_blocks2(): + BLOCKS = [ + McObject(name="", address=0x000E0002, length=2), + McObject(name="", address=0x000E0008, length=4), + McObject(name="", address=0x000E0004, length=4), + McObject(name="", address=0x000E000C, length=4), + McObject(name="", address=0x000E0000, length=2), + ] + bins = make_continuous_blocks(chunks=BLOCKS) + assert bins == [ + McObject( + name="", + address=917504, + ext=0, + length=16, + components=[ + McObject(name="", address=917504, ext=0, length=2, components=[]), + McObject(name="", address=917506, ext=0, length=2, components=[]), + McObject(name="", address=917508, ext=0, length=4, components=[]), + McObject(name="", address=917512, ext=0, length=4, components=[]), + McObject(name="", address=917516, ext=0, length=4, components=[]), + ], + ) + ] + + +def test_make_continuous_blocks3(): + BLOCKS = [ + McObject(name="", address=0x000E0002, ext=0x01, length=2), + McObject(name="", address=0x000E0008, ext=0x03, length=4), + McObject(name="", address=0x000E0004, ext=0x02, length=4), + McObject(name="", address=0x000E000C, ext=0x04, length=4), + McObject(name="", address=0x000E0000, ext=0x00, length=2), + ] + bins = make_continuous_blocks(chunks=BLOCKS) + assert bins == [ + McObject( + name="", address=917504, ext=0, length=2, components=[McObject(name="", address=917504, ext=0, length=2, components=[])] + ), + McObject( + name="", address=917506, ext=1, length=2, components=[McObject(name="", address=917506, ext=1, length=2, components=[])] + ), + McObject( + name="", address=917508, ext=2, length=4, components=[McObject(name="", address=917508, ext=2, length=4, components=[])] + ), + McObject( + name="", address=917512, ext=3, length=4, components=[McObject(name="", address=917512, ext=3, length=4, components=[])] + ), + McObject( + name="", address=917516, ext=4, length=4, components=[McObject(name="", address=917516, ext=4, length=4, components=[])] + ), + ] + + +def test_mc_object_len_zero(): + with pytest.raises(ValueError): + McObject(name="", address=0, ext=0, length=0) + + +def test_mc_object_ok(): + McObject(name="", address=0, ext=0, length=1) diff --git a/pyxcp/tests/test_can.py b/pyxcp/tests/test_can.py index 8a99af35..f55c35f6 100644 --- a/pyxcp/tests/test_can.py +++ b/pyxcp/tests/test_can.py @@ -1,111 +1,114 @@ -from pyxcp.transport.can import calculateFilter -from pyxcp.transport.can import CAN_EXTENDED_ID -from pyxcp.transport.can import Identifier -from pyxcp.transport.can import IdentifierOutOfRangeError -from pyxcp.transport.can import isExtendedIdentifier -from pyxcp.transport.can import MAX_11_BIT_IDENTIFIER -from pyxcp.transport.can import MAX_29_BIT_IDENTIFIER -from pyxcp.transport.can import setDLC -from pyxcp.transport.can import stripIdentifier - import pytest +from pyxcp.transport.can import ( + CAN_EXTENDED_ID, + MAX_11_BIT_IDENTIFIER, + MAX_29_BIT_IDENTIFIER, + Identifier, + IdentifierOutOfRangeError, + calculate_filter, + is_extended_identifier, + pad_frame, + set_DLC, + stripIdentifier, +) + def testSet0(): - assert setDLC(0) == 0 + assert set_DLC(0) == 0 def testSet4(): - assert setDLC(4) == 4 + assert set_DLC(4) == 4 def testSet8(): - assert setDLC(8) == 8 + assert set_DLC(8) == 8 def testSet9(): - assert setDLC(9) == 12 + assert set_DLC(9) == 12 def testSet12(): - assert setDLC(12) == 12 + assert set_DLC(12) == 12 def testSet13(): - assert setDLC(13) == 16 + assert set_DLC(13) == 16 def testSet16(): - assert setDLC(16) == 16 + assert set_DLC(16) == 16 def testSet17(): - assert setDLC(17) == 20 + assert set_DLC(17) == 20 def testSet20(): - assert setDLC(20) == 20 + assert set_DLC(20) == 20 def testSet23(): - assert setDLC(23) == 24 + assert set_DLC(23) == 24 def testSet24(): - assert setDLC(24) == 24 + assert set_DLC(24) == 24 def testSet25(): - assert setDLC(25) == 32 + assert set_DLC(25) == 32 def testSet32(): - assert setDLC(32) == 32 + assert set_DLC(32) == 32 def testSet33(): - assert setDLC(33) == 48 + assert set_DLC(33) == 48 def testSet48(): - assert setDLC(48) == 48 + assert set_DLC(48) == 48 def testSet49(): - assert setDLC(49) == 64 + assert set_DLC(49) == 64 def testSet64(): - assert setDLC(64) == 64 + assert set_DLC(64) == 64 def testSet128(): with pytest.raises(ValueError): - setDLC(128) + set_DLC(128) def testNegative(): with pytest.raises(ValueError): - setDLC(-1) + set_DLC(-1) def testfilter1(): - assert calculateFilter([0x101, 0x102, 0x103]) == (0x100, 0x7FC) + assert calculate_filter([0x101, 0x102, 0x103]) == (0x100, 0x7FC) def testfilter2(): - assert calculateFilter([0x101, 0x102 | CAN_EXTENDED_ID, 0x103]) == ( + assert calculate_filter([0x101, 0x102 | CAN_EXTENDED_ID, 0x103]) == ( 0x100, 0x1FFFFFFC, ) def testfilter3(): - assert calculateFilter([0x1567 | CAN_EXTENDED_ID]) == (0x1567, 0x1FFFFFFF) + assert calculate_filter([0x1567 | CAN_EXTENDED_ID]) == (0x1567, 0x1FFFFFFF) def testfilter4(): - assert calculateFilter( + assert calculate_filter( [ 0x1560 | CAN_EXTENDED_ID, 0x1561, @@ -128,18 +131,18 @@ def testfilter4(): def testfilter5(): - assert calculateFilter([0x1560 | CAN_EXTENDED_ID, 0x1561, 0x1562, 0x1563, 0x1564, 0x1565, 0x1566, 0x1567]) == ( + assert calculate_filter([0x1560 | CAN_EXTENDED_ID, 0x1561, 0x1562, 0x1563, 0x1564, 0x1565, 0x1566, 0x1567]) == ( 0x1560, 0x1FFFFFF8, ) def testIsExtendedIdentifier1(): - assert isExtendedIdentifier(0x280) is False + assert is_extended_identifier(0x280) is False def testIsExtendedIdentifier2(): - assert isExtendedIdentifier(0x280 | CAN_EXTENDED_ID) is True + assert is_extended_identifier(0x280 | CAN_EXTENDED_ID) is True def testStripIdentifier1(): @@ -181,6 +184,7 @@ def test_identifier_max_works2(): assert i.is_extended is False assert i.raw_id == MAX_11_BIT_IDENTIFIER + @pytest.mark.skip def test_identifier_outof_range_raises1(): with pytest.raises(IdentifierOutOfRangeError): @@ -222,9 +226,1099 @@ def test_identifier_repr_works2(capsys): def test_identifier_repr_does_the_trick1(capsys): i = Identifier(101) - assert eval(repr(i)) == Identifier(101) + assert eval(repr(i)) == Identifier(101) # nosec def test_identifier_repr_does_the_trick2(capsys): i = Identifier(101 | CAN_EXTENDED_ID) - assert eval(repr(i)) == Identifier(101 | CAN_EXTENDED_ID) + assert eval(repr(i)) == Identifier(101 | CAN_EXTENDED_ID) # nosec + + +def test_filter_for_identifier_standard(capsys): + i = Identifier(0x101) + assert i.create_filter_from_id() == {"can_id": 0x101, "can_mask": 0x7FF, "extended": False} + + +def test_filter_for_identifier_extended(capsys): + i = Identifier(0x101 | CAN_EXTENDED_ID) + assert i.create_filter_from_id() == {"can_id": 0x101, "can_mask": 0x1FFFFFFF, "extended": True} + + +def test_pad_frame_no_padding_1(): + frame = bytearray(b"\xaa") + padded_frame = bytearray(b"\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_2(): + frame = bytearray(b"\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_3(): + frame = bytearray(b"\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_4(): + frame = bytearray(b"\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_5(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_6(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_7(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_8(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_9(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_10(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_11(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_12(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_13(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_14(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_15(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_16(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_17(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_18(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_19(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_20(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_21(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_22(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_23(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_24(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_25(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_26(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_27(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_28(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_29(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_30(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_31(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_32(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_33(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_34(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_35(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_36(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_37(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_38(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_39(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_40(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_41(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_42(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_43(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_44(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_45(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_46(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_47(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_48(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_49(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_50(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_51(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_52(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_53(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_54(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_55(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_56(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_57(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_58(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_59(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_60(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_61(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_62(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_63(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_no_padding_64(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + assert pad_frame(frame, False, 0x00) == padded_frame + + +def test_pad_frame_padded_1(): + frame = bytearray(b"\xaa") + padded_frame = bytearray(b"\xaa\x00\x00\x00\x00\x00\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_2(): + frame = bytearray(b"\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\x00\x00\x00\x00\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_3(): + frame = bytearray(b"\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\x00\x00\x00\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_4(): + frame = bytearray(b"\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\x00\x00\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_5(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\x00\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_6(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_7(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_8(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_9(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_10(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_11(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_12(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_13(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_14(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_15(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_16(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_17(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_18(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_19(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_20(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_21(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_22(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_23(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_24(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_25(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_26(): + frame = bytearray(b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa") + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_27(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_28(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_29(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_30(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_31(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_32(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_33(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_34(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_35(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_36(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_37(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_38(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_39(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_40(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_41(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_42(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_43(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_44(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_45(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_46(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_47(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_48(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_49(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_50(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_51(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_52(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_53(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_54(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_55(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_56(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_57(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_58(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_59(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_60(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_61(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_62(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_63(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\x00" + ) + assert pad_frame(frame, True, 0x00) == padded_frame + + +def test_pad_frame_padded_64(): + frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + padded_frame = bytearray( + b"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa" + ) + assert pad_frame(frame, True, 0x00) == padded_frame diff --git a/pyxcp/tests/test_checksum.py b/pyxcp/tests/test_checksum.py index 60793d32..eb34c50e 100644 --- a/pyxcp/tests/test_checksum.py +++ b/pyxcp/tests/test_checksum.py @@ -1,6 +1,7 @@ +import pytest + from pyxcp import checksum -import pytest """ XCP_ADD_11 0x10 0x10 diff --git a/pyxcp/tests/test_config.py b/pyxcp/tests/test_config.py deleted file mode 100644 index bddc44db..00000000 --- a/pyxcp/tests/test_config.py +++ /dev/null @@ -1,62 +0,0 @@ -from collections import OrderedDict -from io import StringIO -from pyxcp.config import readConfiguration - -JSON = """{ - "PORT": "COM10", - "BITRATE": 38400, - "BYTESIZE": 8, - "PARITY": "N", - "STOPBITS": 1, - "CREATE_DAQ_TIMESTAMPS": false -}""" - -TOML = """PORT = "COM10" -BITRATE = 38400 -PARITY = "N" -BYTESIZE = 8 -STOPBITS = 1 -CREATE_DAQ_TIMESTAMPS = false -""" - -CONF_JSON = StringIO(JSON) -CONF_JSON.name = "hello.json" - -CONF_TOML = StringIO(TOML) -CONF_TOML.name = "hello.toml" - - -def test_read_empty_config(): - assert readConfiguration(None) == {} - assert readConfiguration({}) == {} - - -def test_read_json_config(): - assert readConfiguration(CONF_JSON) == { - "BITRATE": 38400, - "BYTESIZE": 8, - "CREATE_DAQ_TIMESTAMPS": False, - "PARITY": "N", - "PORT": "COM10", - "STOPBITS": 1, - } - - -def test_read_toml_config(): - assert readConfiguration(CONF_TOML) == { - "BITRATE": 38400, - "BYTESIZE": 8, - "CREATE_DAQ_TIMESTAMPS": False, - "PARITY": "N", - "PORT": "COM10", - "STOPBITS": 1, - } - - -def test_read_dict(): - assert readConfiguration({"A": 1, "B": 2, "C": 3}) == {"A": 1, "B": 2, "C": 3} - assert readConfiguration(OrderedDict({"A": 1, "B": 2, "C": 3})) == { - "A": 1, - "B": 2, - "C": 3, - } diff --git a/pyxcp/tests/test_daq.py b/pyxcp/tests/test_daq.py new file mode 100644 index 00000000..ec6619e7 --- /dev/null +++ b/pyxcp/tests/test_daq.py @@ -0,0 +1,188 @@ +#!/usr/bin/env python + +from pyxcp.daq_stim import Daq, DaqList + + +DAQ_INFO = { + "channels": [ + { + "cycle": 0, + "maxDaqList": 1, + "name": "Key T", + "priority": 0, + "properties": {"consistency": "CONSISTENCY_ODT", "daq": True, "packed": False, "stim": False}, + "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", + }, + { + "cycle": 10, + "maxDaqList": 1, + "name": "10 ms", + "priority": 1, + "properties": {"consistency": "CONSISTENCY_ODT", "daq": True, "packed": False, "stim": True}, + "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", + }, + { + "cycle": 100, + "maxDaqList": 1, + "name": "100ms", + "priority": 2, + "properties": {"consistency": "CONSISTENCY_ODT", "daq": True, "packed": False, "stim": True}, + "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", + }, + { + "cycle": 1, + "maxDaqList": 1, + "name": "1ms", + "priority": 3, + "properties": {"consistency": "CONSISTENCY_ODT", "daq": True, "packed": False, "stim": True}, + "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", + }, + { + "cycle": 10, + "maxDaqList": 1, + "name": "FilterBypassDaq", + "priority": 4, + "properties": {"consistency": "CONSISTENCY_ODT", "daq": True, "packed": False, "stim": True}, + "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", + }, + { + "cycle": 10, + "maxDaqList": 1, + "name": "FilterBypassStim", + "priority": 5, + "properties": {"consistency": "CONSISTENCY_ODT", "daq": False, "packed": False, "stim": True}, + "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", + }, + ], + "processor": { + "keyByte": { + "addressExtension": "AE_DIFFERENT_WITHIN_ODT", + "identificationField": "IDF_REL_ODT_NUMBER_ABS_DAQ_LIST_NUMBER_BYTE", + "optimisationType": "OM_DEFAULT", + }, + "maxDaq": 0, + "minDaq": 0, + "properties": { + "bitStimSupported": False, + "configType": "DYNAMIC", + "overloadEvent": False, + "overloadMsb": True, + "pidOffSupported": False, + "prescalerSupported": True, + "resumeSupported": True, + "timestampSupported": True, + }, + }, + "resolution": { + "granularityOdtEntrySizeDaq": 1, + "granularityOdtEntrySizeStim": 1, + "maxOdtEntrySizeDaq": 218, + "maxOdtEntrySizeStim": 218, + "timestampMode": {"fixed": False, "size": "S4", "unit": "DAQ_TIMESTAMP_UNIT_10US"}, + "timestampTicks": 10, + }, +} + +SLAVE_INFO = { + "addressGranularity": 0, + "byteOrder": 0, + "interleavedMode": False, + "masterBlockMode": True, + "maxBs": 2, + "maxCto": 255, + "maxDto": 1500, + "maxWriteDaqMultipleElements": 31, + "minSt": 0, + "optionalCommMode": True, + "pgmProcessor": {}, + "protocolLayerVersion": 1, + "queueSize": 0, + "slaveBlockMode": True, + "supportsCalpag": True, + "supportsDaq": True, + "supportsPgm": True, + "supportsStim": True, + "transportLayerVersion": 1, + "xcpDriverVersionNumber": 25, +} + + +class AttrDict(dict): + def __getattr__(self, name): + return self[name] + + +class MockMaster: + def __init__(self): + self.slaveProperties = AttrDict( + { + "maxDto": 1500, + "supportsDaq": True, + } + ) + + def getDaqInfo(self): + return DAQ_INFO + + def freeDaq(self): + pass + + def allocDaq(self, daq_count): + self.daq_count = daq_count + + def allocOdt(self, daq_num, odt_count): + pass + + def allocOdtEntry(self, daq_num, odt_num, entry_count): + pass + + def setDaqPtr(self, daqListNumber, odtNumber, odtEntryNumber): + pass + + def writeDaq(self, bitOffset, entrySize, addressExt, address): + pass + + def setDaqListMode(self, mode, daqListNumber, eventChannelNumber, prescaler, priority): + pass + + def startStopDaqList(self, mode, daqListNumber): + pass + + def startStopSynch(self, mode): + pass + + +DAQ_LISTS = [ + DaqList( + 1, + [ + ("channel1", 0x1BD004, 0, 4, "U32"), + ("channel2", 0x1BD008, 0, 4, "U32"), + ("PWMFiltered", 0x1BDDE2, 0, 1, "U8"), + ("PWM", 0x1BDDDF, 0, 1, "U8"), + ("Triangle", 0x1BDDDE, 0, 1, "U8"), + ], + ), + DaqList( + 3, + [ + ("TestWord_001", 0x1BE120, 0, 2, "U16"), + ("TestWord_003", 0x1BE128, 0, 2, "U16"), + ("TestWord_004", 0x1BE12C, 0, 2, "U16"), + ("TestWord_005", 0x1BE134, 0, 2, "U16"), + ("TestWord_006", 0x1BE134, 0, 2, "U16"), + ("TestWord_007", 0x1BE138, 0, 2, "U16"), + ("TestWord_008", 0x1BE13C, 0, 2, "U16"), + ("TestWord_009", 0x1BE140, 0, 2, "U16"), + ("TestWord_011", 0x1BE148, 0, 2, "U16"), + # ("", ), + ], + ), +] + +daq = Daq() +daq.set_master(MockMaster()) + +daq.add_daq_lists(DAQ_LISTS) +daq.setup() +daq.start() diff --git a/pyxcp/tests/test_frame_padding.py b/pyxcp/tests/test_frame_padding.py index 00f09f14..f92dae66 100644 --- a/pyxcp/tests/test_frame_padding.py +++ b/pyxcp/tests/test_frame_padding.py @@ -1,4 +1,4 @@ -from pyxcp.transport.can import padFrame +from pyxcp.transport.can import pad_frame def test_frame_padding_no_padding_length(): @@ -71,7 +71,7 @@ def test_frame_padding_no_padding_length(): ) for frame_len in range(65): frame = bytes(frame_len) - padded_frame = padFrame(frame, padding_value=0, padding_len=0) + padded_frame = pad_frame(frame, padding_value=0, padding_len=0) frame_len = len(padded_frame) _, expected_len = EXPECTED[frame_len] assert frame_len == expected_len @@ -147,7 +147,7 @@ def test_frame_padding_padding_length32(): ) for frame_len in range(65): frame = bytes(frame_len) - padded_frame = padFrame(frame, padding_value=0, padding_len=32) + padded_frame = pad_frame(frame, padding_value=0, padding_len=32) frame_len = len(padded_frame) _, expected_len = EXPECTED[frame_len] assert frame_len == expected_len diff --git a/pyxcp/tests/test_master.py b/pyxcp/tests/test_master.py index d6896d98..d1659e13 100644 --- a/pyxcp/tests/test_master.py +++ b/pyxcp/tests/test_master.py @@ -1,12 +1,24 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import struct import time from collections import deque +from unittest import mock + from pyxcp import types from pyxcp.master import Master -from pyxcp.transport.can import CanInterfaceBase -from unittest import mock + + +def create_config(): + # Exception: XCPonEth - Failed to resolve address : + config = mock.MagicMock() + config.general.return_value = mock.MagicMock() + config.transport.return_value = mock.MagicMock() + config.transport.eth.return_value = mock.MagicMock() + config.transport.eth.host = "localhost" + config.transport.eth.port = 5555 + config.transport.eth.bind_to_address = "" + config.transport.eth.bind_to_port = 0 + return config class MockSocket: @@ -48,7 +60,7 @@ def connect(self): pass -class MockCanInterface(CanInterfaceBase): +class MockCanInterface: # CanInterfaceBase def __init__(self): self.data = deque() self.receive_callback = None @@ -87,18 +99,17 @@ def connect(self): def read(self): pass - def getTimestampResolution(self): + def get_timestamp_resolution(self): pass class TestMaster: - DefaultConnectCmd = bytes([0x02, 0x00, 0x00, 0x00, 0xFF, 0x00]) DefaultConnectResponse = "FF 3D C0 FF DC 05 01 01" @mock.patch("pyxcp.transport.eth") def testConnect(self, eth): - with Master("eth") as xm: + with Master("eth", config=create_config()) as xm: xm.transport = eth() xm.transport.request.return_value = bytes([0x1D, 0xC0, 0xFF, 0xDC, 0x05, 0x01, 0x01]) @@ -121,7 +132,7 @@ def testConnect(self, eth): @mock.patch("pyxcp.transport.eth") def testDisconnect(self, eth): - with Master("eth") as xm: + with Master("eth", config=create_config()) as xm: xm.transport = eth() xm.transport.request.return_value = bytes([]) res = xm.disconnect() @@ -129,7 +140,7 @@ def testDisconnect(self, eth): @mock.patch("pyxcp.transport.eth") def testGetStatus(self, eth): - with Master("eth") as xm: + with Master("eth", config=create_config()) as xm: xm.transport = eth() xm.transport.request.return_value = bytes([0x1D, 0xC0, 0xFF, 0xDC, 0x05, 0x01, 0x01]) @@ -152,7 +163,7 @@ def testGetStatus(self, eth): @mock.patch("pyxcp.transport.eth") def testSync(self, eth): - with Master("eth") as xm: + with Master("eth", config=create_config()) as xm: xm.transport = eth() xm.transport.request.return_value = bytes([0x00]) res = xm.synch() @@ -160,7 +171,7 @@ def testSync(self, eth): @mock.patch("pyxcp.transport.eth") def testGetCommModeInfo(self, eth): - with Master("eth") as xm: + with Master("eth", config=create_config()) as xm: xm.transport = eth() xm.transport.request.return_value = bytes([0x1D, 0xC0, 0xFF, 0xDC, 0x05, 0x01, 0x01]) @@ -179,7 +190,7 @@ def testGetCommModeInfo(self, eth): @mock.patch("pyxcp.transport.eth") def testGetId(self, eth): - with Master("eth") as xm: + with Master("eth", config=create_config()) as xm: xm.transport = eth() xm.transport.MAX_DATAGRAM_SIZE = 512 xm.transport.request.return_value = bytes([0x1D, 0xC0, 0xFF, 0xDC, 0x05, 0x01, 0x01]) @@ -204,7 +215,7 @@ def testConnect2(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -248,7 +259,7 @@ def testDisconnect2(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -271,7 +282,7 @@ def testGetStatus2(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -303,7 +314,7 @@ def testSynch(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -326,7 +337,7 @@ def testGetCommModeInfo2(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -354,7 +365,7 @@ def testGetId2(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -417,7 +428,7 @@ def testSetRequest(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -440,7 +451,7 @@ def testGetSeed(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -464,7 +475,7 @@ def testUnlock(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -490,7 +501,7 @@ def testSetMta(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -530,7 +541,7 @@ def testUpload(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -569,7 +580,7 @@ def testShortUpload(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -609,7 +620,7 @@ def testBuildChecksum(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -650,7 +661,7 @@ def testTransportLayerCmd(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -674,7 +685,7 @@ def testUserCmd(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -698,7 +709,7 @@ def testGetVersion(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -724,7 +735,7 @@ def testDownload(self, mock_selector, mock_socket): mock_socket.return_value.recv.side_effect = ms.recv mock_selector.return_value.select.side_effect = ms.select - with Master("eth", config={"HOST": "localhost", "LOGLEVEL": "DEBUG"}) as xm: + with Master("eth", config=create_config()) as xm: ms.push_packet(self.DefaultConnectResponse) res = xm.connect() @@ -748,7 +759,7 @@ def testDownloadBlock(self): "CAN_USE_DEFAULT_LISTENER": False, } with Master("can", config=conf) as xm: - mock_caninterface = xm.transport.canInterface + mock_caninterface = xm.transport.can_interface mock_caninterface.push_packet(self.DefaultConnectResponse) xm.connect() @@ -817,7 +828,7 @@ def testDownloadNextBlock(self): "CAN_USE_DEFAULT_LISTENER": False, } with Master("can", config=conf) as xm: - mock_caninterface = xm.transport.canInterface + mock_caninterface = xm.transport.can_interface mock_caninterface.push_packet(self.DefaultConnectResponse) xm.connect() diff --git a/pyxcp/tests/test_transport.py b/pyxcp/tests/test_transport.py index 1f4321e8..82d1617e 100644 --- a/pyxcp/tests/test_transport.py +++ b/pyxcp/tests/test_transport.py @@ -1,13 +1,13 @@ -import pyxcp.transport.base as tr - import pytest +import pyxcp.transport.base as tr + def test_factory_works(): - assert isinstance(tr.createTransport("eth"), tr.BaseTransport) - assert isinstance(tr.createTransport("sxi"), tr.BaseTransport) + assert isinstance(tr.create_transport("eth"), tr.BaseTransport) + assert isinstance(tr.create_transport("sxi"), tr.BaseTransport) assert isinstance( - tr.createTransport( + tr.create_transport( "can", config={ "CAN_ID_MASTER": 1, @@ -20,10 +20,10 @@ def test_factory_works(): def test_factory_works_case_insensitive(): - assert isinstance(tr.createTransport("ETH"), tr.BaseTransport) - assert isinstance(tr.createTransport("SXI"), tr.BaseTransport) + assert isinstance(tr.create_transport("ETH"), tr.BaseTransport) + assert isinstance(tr.create_transport("SXI"), tr.BaseTransport) assert isinstance( - tr.createTransport( + tr.create_transport( "CAN", config={ "CAN_ID_MASTER": 1, @@ -37,11 +37,11 @@ def test_factory_works_case_insensitive(): def test_factory_invalid_transport_name_raises(): with pytest.raises(ValueError): - tr.createTransport("xCp") + tr.create_transport("xCp") def test_transport_names(): - transports = tr.availableTransports() + transports = tr.available_transports() assert "can" in transports assert "eth" in transports @@ -49,7 +49,7 @@ def test_transport_names(): def test_transport_names_are_lower_case_only(): - transports = tr.availableTransports() + transports = tr.available_transports() assert "CAN" not in transports assert "ETH" not in transports @@ -57,7 +57,7 @@ def test_transport_names_are_lower_case_only(): def test_transport_classes(): - transports = tr.availableTransports() + transports = tr.available_transports() assert issubclass(transports.get("can"), tr.BaseTransport) assert issubclass(transports.get("eth"), tr.BaseTransport) diff --git a/pyxcp/tests/test_utils.py b/pyxcp/tests/test_utils.py index 3b1caddb..1e181f67 100644 --- a/pyxcp/tests/test_utils.py +++ b/pyxcp/tests/test_utils.py @@ -1,10 +1,7 @@ -from pyxcp.utils import flatten -from pyxcp.utils import getPythonVersion -from pyxcp.utils import hexDump -from pyxcp.utils import PYTHON_VERSION -from pyxcp.utils import slicer from sys import version_info +from pyxcp.utils import PYTHON_VERSION, flatten, getPythonVersion, hexDump, slicer + def test_hexdump(capsys): print(hexDump(range(16)), end="") diff --git a/pyxcp/timing.py b/pyxcp/timing.py index 1e9b3429..ff912709 100644 --- a/pyxcp/timing.py +++ b/pyxcp/timing.py @@ -1,10 +1,8 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import time class Timing: - T_US = 1000 * 1000 T_MS = 1000 T_S = 1 diff --git a/pyxcp/transport/__init__.py b/pyxcp/transport/__init__.py index 871200a6..e652ca79 100644 --- a/pyxcp/transport/__init__.py +++ b/pyxcp/transport/__init__.py @@ -1,10 +1,10 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from .base import FrameAcquisitionPolicy -from .base import FrameRecorderPolicy -from .base import LegacyFrameAcquisitionPolicy -from .base import StdoutPolicy -from .can import Can -from .eth import Eth -from .sxi import SxI -from .usb_transport import Usb +from .base import FrameAcquisitionPolicy # noqa: F401 +from .base import FrameRecorderPolicy # noqa: F401 +from .base import LegacyFrameAcquisitionPolicy # noqa: F401 +from .base import NoOpPolicy # noqa: F401 +from .base import StdoutPolicy # noqa: F401 +from .can import Can # noqa: F401 +from .eth import Eth # noqa: F401 +from .sxi import SxI # noqa: F401 +from .usb_transport import Usb # noqa: F401 diff --git a/pyxcp/transport/base.py b/pyxcp/transport/base.py index cdcbcd03..5a19c56c 100644 --- a/pyxcp/transport/base.py +++ b/pyxcp/transport/base.py @@ -1,23 +1,21 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import abc import threading -import typing from collections import deque -from datetime import datetime -from enum import IntEnum -from time import perf_counter -from time import sleep -from time import time +from typing import Any, Dict, Optional, Set, Type import pyxcp.types as types -from ..logger import Logger + +from ..cpp_ext import Timestamp, TimestampType from ..recorder import XcpLogFileWriter from ..timing import Timing -from ..utils import flatten -from ..utils import hexDump -from ..utils import SHORT_SLEEP -from pyxcp.config import Configuration +from ..utils import ( + CurrentDatetime, + flatten, + hexDump, + seconds_to_nanoseconds, + short_sleep, +) class FrameAcquisitionPolicy: @@ -34,20 +32,20 @@ class FrameAcquisitionPolicy: ==> care only about DAQ frames. """ - def __init__(self, filter_out: typing.Optional[typing.Set[types.FrameCategory]] = None): + def __init__(self, filter_out: Optional[Set[types.FrameCategory]] = None): self._frame_types_to_filter_out = filter_out or set() @property - def filtered_out(self) -> typing.Set[types.FrameCategory]: + def filtered_out(self) -> Set[types.FrameCategory]: return self._frame_types_to_filter_out - def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: float, payload: bytes) -> None: - ... + def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: int, payload: bytes) -> None: ... # noqa: E704 def finalize(self) -> None: """ Finalize the frame acquisition policy (if required). """ + ... class NoOpPolicy(FrameAcquisitionPolicy): @@ -62,7 +60,7 @@ class LegacyFrameAcquisitionPolicy(FrameAcquisitionPolicy): Deprecated: Use only for compatibility reasons. """ - def __init__(self, filter_out: typing.Optional[typing.Set[types.FrameCategory]] = None): + def __init__(self, filter_out: Optional[Set[types.FrameCategory]] = None) -> None: super().__init__(filter_out) self.reqQueue = deque() self.resQueue = deque() @@ -83,10 +81,12 @@ def __init__(self, filter_out: typing.Optional[typing.Set[types.FrameCategory]] types.FrameCategory.STIM: self.stimQueue, } - def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: float, payload: bytes) -> None: + def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: int, payload: bytes) -> None: # print(f"{frame_type.name:8} {counter:6} {timestamp:7.7f} {hexDump(payload)}") if frame_type not in self.filtered_out: - self.QUEUE_MAP.get(frame_type).append((counter, timestamp, payload)) + queue = self.QUEUE_MAP.get(frame_type) + if queue: + queue.append((counter, timestamp, payload)) class FrameRecorderPolicy(FrameAcquisitionPolicy): @@ -95,14 +95,14 @@ class FrameRecorderPolicy(FrameAcquisitionPolicy): def __init__( self, file_name: str, - filter_out: typing.Optional[typing.Set[types.FrameCategory]] = None, + filter_out: Optional[Set[types.FrameCategory]] = None, prealloc: int = 10, chunk_size: int = 1, - ): + ) -> None: super().__init__(filter_out) self.recorder = XcpLogFileWriter(file_name, prealloc=prealloc, chunk_size=chunk_size) - def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: float, payload: bytes) -> None: + def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: int, payload: bytes) -> None: if frame_type not in self.filtered_out: self.recorder.add_frame(frame_type, counter, timestamp, payload) @@ -113,32 +113,18 @@ def finalize(self) -> None: class StdoutPolicy(FrameAcquisitionPolicy): """Frame acquisition policy that prints frames to stdout.""" - def __init__(self, filter_out: typing.Optional[typing.Set[types.FrameCategory]] = None): + def __init__(self, filter_out: Optional[Set[types.FrameCategory]] = None) -> None: super().__init__(filter_out) - def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: float, payload: bytes) -> None: + def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: int, payload: bytes) -> None: if frame_type not in self.filtered_out: - print(f"{frame_type.name:8} {counter:6} {timestamp:7.7f} {hexDump(payload)}") + print(f"{frame_type.name:8} {counter:6} {timestamp:8u} {hexDump(payload)}") class EmptyFrameError(Exception): """Raised when an empty frame is received.""" -def get(q, timeout, restart_event): - """Get an item from a deque considering a timeout condition.""" - start = time() - while not q: - if restart_event.is_set(): - start = time() - restart_event.clear() - if time() - start > timeout: - raise EmptyFrameError - sleep(SHORT_SLEEP) - item = q.popleft() - return item - - class BaseTransport(metaclass=abc.ABCMeta): """Base class for transport-layers (Can, Eth, Sxi). @@ -151,79 +137,91 @@ class BaseTransport(metaclass=abc.ABCMeta): """ - PARAMETER_MAP = { - # Type Req'd Default - "CREATE_DAQ_TIMESTAMPS": (bool, False, False), - "LOGLEVEL": (str, False, "WARN"), - "TIMEOUT": (float, False, 2.0), - "ALIGNMENT": (int, False, 1), - } - - def __init__(self, config=None, policy: FrameAcquisitionPolicy = None): + def __init__(self, config, policy: Optional[FrameAcquisitionPolicy] = None, transport_layer_interface: Optional[Any] = None): + self.has_user_supplied_interface: bool = transport_layer_interface is not None + self.transport_layer_interface: Optional[Any] = transport_layer_interface self.parent = None - self.config = Configuration(BaseTransport.PARAMETER_MAP or {}, config or {}) - self.policy = policy or LegacyFrameAcquisitionPolicy() - self.closeEvent = threading.Event() - - self.command_lock = threading.Lock() - loglevel = self.config.get("LOGLEVEL") - self._debug = loglevel == "DEBUG" - - self.logger = Logger("transport.Base") - self.logger.setLevel(loglevel) - self.counterSend = 0 - self.counterReceived = -1 - create_daq_timestamps = self.config.get("CREATE_DAQ_TIMESTAMPS") - self.create_daq_timestamps = False if create_daq_timestamps is None else create_daq_timestamps - timeout = self.config.get("TIMEOUT") - self.alignment = self.config.get("ALIGNMENT") - self.timeout = 2.0 if timeout is None else timeout - self.timer_restart_event = threading.Event() - self.timing = Timing() - self.resQueue = deque() - self.listener = threading.Thread( + self.policy: FrameAcquisitionPolicy = policy or LegacyFrameAcquisitionPolicy() + self.closeEvent: threading.Event = threading.Event() + + self.command_lock: threading.Lock = threading.Lock() + self.policy_lock: threading.Lock = threading.Lock() + + self.logger: Any = config.log + self._debug: bool = self.logger.level == 10 + if transport_layer_interface: + self.logger.info(f"Transport - User Supplied Transport-Layer Interface: '{transport_layer_interface!s}'") + self.counter_send: int = 0 + self.counter_received: int = -1 + self.create_daq_timestamps: bool = config.create_daq_timestamps + self.timestamp = Timestamp(TimestampType.ABSOLUTE_TS) + self._start_datetime: CurrentDatetime = CurrentDatetime(self.timestamp.initial_value) + self.alignment: int = config.alignment + self.timeout: int = seconds_to_nanoseconds(config.timeout) + self.timer_restart_event: threading.Event = threading.Event() + self.timing: Timing = Timing() + self.resQueue: deque = deque() + self.listener: threading.Thread = threading.Thread( target=self.listen, args=(), kwargs={}, ) - self.first_daq_timestamp = None - - self.timestamp_origin = time() - self.datetime_origin = datetime.fromtimestamp(self.timestamp_origin) - - self.pre_send_timestamp = time() - self.post_send_timestamp = time() - self.recv_timestamp = time() + self.first_daq_timestamp: Optional[int] = None + # self.timestamp_origin = self.timestamp.value + # self.datetime_origin = datetime.fromtimestamp(self.timestamp_origin) + self.pre_send_timestamp: int = self.timestamp.value + self.post_send_timestamp: int = self.timestamp.value + self.recv_timestamp: int = self.timestamp.value - def __del__(self): - self.finishListener() - self.closeConnection() + def __del__(self) -> None: + self.finish_listener() + self.close_connection() - def loadConfig(self, config): + def load_config(self, config) -> None: """Load configuration data.""" - self.config = Configuration(self.PARAMETER_MAP or {}, config or {}) + class_name: str = self.__class__.__name__.lower() + self.config: Any = getattr(config, class_name) - def close(self): + def close(self) -> None: """Close the transport-layer connection and event-loop.""" - self.finishListener() + self.finish_listener() if self.listener.is_alive(): self.listener.join() - self.closeConnection() + self.close_connection() @abc.abstractmethod - def connect(self): + def connect(self) -> None: pass - def startListener(self): + def get(self): + """Get an item from a deque considering a timeout condition.""" + start: int = self.timestamp.value + while not self.resQueue: + if self.timer_restart_event.is_set(): + start: int = self.timestamp.value + self.timer_restart_event.clear() + if self.timestamp.value - start > self.timeout: + raise EmptyFrameError + short_sleep() + item = self.resQueue.popleft() + # print("Q", item) + return item + + @property + def start_datetime(self) -> int: + """""" + return self._start_datetime + + def start_listener(self): if self.listener.is_alive(): - self.finishListener() + self.finish_listener() self.listener.join() self.listener = threading.Thread(target=self.listen) self.listener.start() - def finishListener(self): + def finish_listener(self): if hasattr(self, "closeEvent"): self.closeEvent.set() @@ -231,18 +229,16 @@ def _request_internal(self, cmd, ignore_timeout=False, *data): with self.command_lock: frame = self._prepare_request(cmd, *data) self.timing.start() - self.policy.feed(types.FrameCategory.CMD, self.counterSend, perf_counter(), frame) + with self.policy_lock: + self.policy.feed(types.FrameCategory.CMD, self.counter_send, self.timestamp.value, frame) self.send(frame) try: - xcpPDU = get( - self.resQueue, - timeout=self.timeout, - restart_event=self.timer_restart_event, - ) + xcpPDU = self.get() except EmptyFrameError: if not ignore_timeout: MSG = f"Response timed out (timeout={self.timeout}s)" - self.policy.feed(types.FrameCategory.METADATA, self.counterSend, perf_counter(), bytes(MSG, "ascii")) + with self.policy_lock: + self.policy.feed(types.FrameCategory.METADATA, self.counter_send, self.timestamp.value, bytes(MSG, "ascii")) raise types.XcpTimeoutError(MSG) from None else: self.timing.stop() @@ -250,10 +246,10 @@ def _request_internal(self, cmd, ignore_timeout=False, *data): self.timing.stop() pid = types.Response.parse(xcpPDU).type if pid == "ERR" and cmd.name != "SYNCH": - self.policy.feed(types.FrameCategory.ERROR, self.counterReceived, perf_counter(), xcpPDU[1:]) + with self.policy_lock: + self.policy.feed(types.FrameCategory.ERROR, self.counter_received, self.timestamp.value, xcpPDU[1:]) err = types.XcpError.parse(xcpPDU[1:]) raise types.XcpResponseError(err) - return xcpPDU[1:] def request(self, cmd, *data): @@ -277,9 +273,18 @@ def block_request(self, cmd, *data): if pid == "ERR" and cmd.name != "SYNCH": err = types.XcpError.parse(xcpPDU[1:]) raise types.XcpResponseError(err) - - frame = self._prepare_request(cmd, *data) - self.send(frame) + with self.command_lock: + if isinstance(*data, list): + data = data[0] # C++ interfacing. + frame = self._prepare_request(cmd, *data) + with self.policy_lock: + self.policy.feed( + types.FrameCategory.CMD if int(cmd) >= 0xC0 else types.FrameCategory.STIM, + self.counter_send, + self.timestamp.value, + frame, + ) + self.send(frame) def _prepare_request(self, cmd, *data): """ @@ -289,11 +294,11 @@ def _prepare_request(self, cmd, *data): self.logger.debug(cmd.name) self.parent._setService(cmd) - cmdlen = cmd.bit_length() // 8 # calculate bytes needed for cmd - packet = bytes(flatten(cmd.to_bytes(cmdlen, "big"), data)) + cmd_len = cmd.bit_length() // 8 # calculate bytes needed for cmd + packet = bytes(flatten(cmd.to_bytes(cmd_len, "big"), data)) - header = self.HEADER.pack(len(packet), self.counterSend) - self.counterSend = (self.counterSend + 1) & 0xFFFF + header = self.HEADER.pack(len(packet), self.counter_send) + self.counter_send = (self.counter_send + 1) & 0xFFFF frame = header + packet @@ -325,15 +330,15 @@ def block_receive(self, length_required: int) -> bytes: :class:`pyxcp.types.XcpTimeoutError` """ block_response = b"" - start = time() + start = self.timestamp.value while len(block_response) < length_required: if len(self.resQueue): partial_response = self.resQueue.popleft() block_response += partial_response[1:] else: - if time() - start > self.timeout: + if self.timestamp.value - start > self.timeout: raise types.XcpTimeoutError("Response timed out [block_receive].") from None - sleep(SHORT_SLEEP) + short_sleep() return block_response @abc.abstractmethod @@ -341,7 +346,7 @@ def send(self, frame): pass @abc.abstractmethod - def closeConnection(self): + def close_connection(self): """Does the actual connection shutdown. Needs to be implemented by any sub-class. """ @@ -358,26 +363,29 @@ def process_event_packet(self, packet): if ev_type == types.Event.EV_CMD_PENDING: self.timer_restart_event.set() - def processResponse(self, response, length, counter, recv_timestamp=None): - if counter == self.counterReceived: - self.logger.warn(f"Duplicate message counter {counter} received from the XCP slave") + def process_response(self, response: bytes, length: int, counter: int, recv_timestamp: int) -> None: + if counter == self.counter_received: + self.logger.warning(f"Duplicate message counter {counter} received from the XCP slave") if self._debug: self.logger.debug(f"<- L{length} C{counter} {hexDump(response[:512])}") return - self.counterReceived = counter + self.counter_received = counter pid = response[0] if pid >= 0xFC: if self._debug: self.logger.debug(f"<- L{length} C{counter} {hexDump(response)}") if pid >= 0xFE: self.resQueue.append(response) - self.policy.feed(types.FrameCategory.RESPONSE, self.counterReceived, perf_counter(), response) + with self.policy_lock: + self.policy.feed(types.FrameCategory.RESPONSE, self.counter_received, self.timestamp.value, response) self.recv_timestamp = recv_timestamp elif pid == 0xFD: self.process_event_packet(response) - self.policy.feed(types.FrameCategory.EVENT, self.counterReceived, perf_counter(), response) + with self.policy_lock: + self.policy.feed(types.FrameCategory.EVENT, self.counter_received, self.timestamp.value, response) elif pid == 0xFC: - self.policy.feed(types.FrameCategory.SERV, self.counterReceived, perf_counter(), response) + with self.policy_lock: + self.policy.feed(types.FrameCategory.SERV, self.counter_received, self.timestamp.value, response) else: if self._debug: self.logger.debug(f"<- L{length} C{counter} ODT_Data[0:8] {hexDump(response[:8])}") @@ -386,11 +394,21 @@ def processResponse(self, response, length, counter, recv_timestamp=None): if self.create_daq_timestamps: timestamp = recv_timestamp else: - timestamp = 0.0 - self.policy.feed(types.FrameCategory.DAQ, self.counterReceived, timestamp, response) + timestamp = 0 + with self.policy_lock: + self.policy.feed(types.FrameCategory.DAQ, self.counter_received, timestamp, response) + + # @abc.abstractproperty + # @property + # def transport_layer_interface(self) -> Any: + # pass + + # @transport_layer_interface.setter + # def transport_layer_interface(self, value: Any) -> None: + # self._transport_layer_interface = value -def createTransport(name, *args, **kws): +def create_transport(name: str, *args, **kws) -> BaseTransport: """Factory function for transports. Returns @@ -398,15 +416,15 @@ def createTransport(name, *args, **kws): :class:`BaseTransport` derived instance. """ name = name.lower() - transports = availableTransports() + transports = available_transports() if name in transports: - transportClass = transports[name] + transport_class: Type[BaseTransport] = transports[name] else: - raise ValueError(f"'{name}' is an invalid transport -- please choose one of [{' | '.join(transports.keys())}].") - return transportClass(*args, **kws) + raise ValueError(f"{name!r} is an invalid transport -- please choose one of [{' | '.join(transports.keys())}].") + return transport_class(*args, **kws) -def availableTransports(): +def available_transports() -> Dict[str, Type[BaseTransport]]: """List all subclasses of :class:`BaseTransport`. Returns diff --git a/pyxcp/transport/base_transport.hpp b/pyxcp/transport/base_transport.hpp new file mode 100644 index 00000000..e69de29b diff --git a/pyxcp/transport/can.py b/pyxcp/transport/can.py index 5464f45d..acaa3bba 100644 --- a/pyxcp/transport/can.py +++ b/pyxcp/transport/can.py @@ -1,18 +1,24 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ """ -import abc + import functools import operator from bisect import bisect_left -from collections import OrderedDict -from time import perf_counter -from time import time +from typing import Any, Dict, Optional, Type + +from can import CanError, CanInitializationError, Message, detect_available_configs +from can.bus import BusABC +from can.interface import _get_class_for_interface +from rich.console import Console -from pyxcp.config import Configuration +from pyxcp.config import CAN_INTERFACE_MAP from pyxcp.transport.base import BaseTransport +from ..utils import seconds_to_nanoseconds + + +console = Console() CAN_EXTENDED_ID = 0x80000000 MAX_11_BIT_IDENTIFIER = (1 << 11) - 1 @@ -27,7 +33,7 @@ class IdentifierOutOfRangeError(Exception): pass -def isExtendedIdentifier(identifier: int) -> bool: +def is_extended_identifier(identifier: int) -> bool: """Check for extendend CAN identifier. Parameters @@ -75,8 +81,8 @@ def samplePointToTsegs(tqs: int, samplePoint: float) -> tuple: return (tseg1, tseg2) -def padFrame(frame: bytes, padding_value: int, padding_len: int = 0) -> bytes: - """Pad frame to next discrete DLC value. +def pad_frame(frame: bytes, pad_frame: bool, padding_value: int) -> bytes: + """Pad frame to next discrete DLC value (CAN-FD) or on request (CAN-Classic). References: ----------- @@ -85,9 +91,9 @@ def padFrame(frame: bytes, padding_value: int, padding_len: int = 0) -> bytes: AUTOSAR CP Release 4.3.0, Specification of CAN Driver; [SWS_CAN_00502], [ECUC_Can_00485] AUTOSAR CP Release 4.3.0, Requirements on CAN; [SRS_Can_01073], [SRS_Can_01086], [SRS_Can_01160] """ - frame_len = max(len(frame), padding_len) + frame_len = len(frame) if frame_len <= MAX_DLC_CLASSIC: - actual_len = MAX_DLC_CLASSIC + actual_len = MAX_DLC_CLASSIC if pad_frame else frame_len else: actual_len = CAN_FD_DLCS[bisect_left(CAN_FD_DLCS, frame_len)] # append fill bytes up to MAX_DLC resp. next discrete FD DLC. @@ -112,13 +118,13 @@ class Identifier: def __init__(self, raw_id: int): self._raw_id = raw_id self._id = stripIdentifier(raw_id) - self._is_extended = isExtendedIdentifier(raw_id) + self._is_extended = is_extended_identifier(raw_id) if self._is_extended: if self._id > MAX_29_BIT_IDENTIFIER: - raise IdentifierOutOfRangeError("29-bit identifier '{}' is out of range".format(self._id)) + raise IdentifierOutOfRangeError(f"29-bit identifier {self._id!r} is out of range") else: if self._id > MAX_11_BIT_IDENTIFIER: - raise IdentifierOutOfRangeError("11-bit identifier '{}' is out of range".format(self._id)) + raise IdentifierOutOfRangeError(f"11-bit identifier {self._id!r} is out of range") @property def id(self) -> int: @@ -151,8 +157,20 @@ def is_extended(self) -> bool: """ return self._is_extended + @property + def type_str(self) -> str: + """ + + Returns + ------- + str + - "S" - 11-bit identifier. + - "E" - 29-bit identifier. + """ + return "E" if self.is_extended else "S" + @staticmethod - def make_identifier(identifier: int, extended: bool) -> int: + def make_identifier(identifier: int, extended: bool) -> "Identifier": """Factory method. Parameters @@ -174,81 +192,107 @@ def make_identifier(identifier: int, extended: bool) -> int: """ return Identifier(identifier if not extended else (identifier | CAN_EXTENDED_ID)) - def __eq__(self, other): + def create_filter_from_id(self) -> Dict: + """Create a single CAN filter entry. + s. https://python-can.readthedocs.io/en/stable/bus.html#filtering + """ + return { + "can_id": self.id, + "can_mask": MAX_29_BIT_IDENTIFIER if self.is_extended else MAX_11_BIT_IDENTIFIER, + "extended": self.is_extended, + } + + def __eq__(self, other) -> bool: return (self.id == other.id) and (self.is_extended == other.is_extended) - def __str__(self): - return "Identifier(id = 0x{:08x}, is_extended = {})".format(self.id, self.is_extended) + def __str__(self) -> str: + return f"Identifier(id = 0x{self.id:08x}, is_extended = {self.is_extended})" - def __repr__(self): - return "Identifier(0x{:08x})".format(self.raw_id) + def __repr__(self) -> str: + return f"Identifier(0x{self.raw_id:08x})" class Frame: """""" - def __init__(self, id_: Identifier, dlc: int, data: bytes, timestamp: int): - self.id = id_ - self.dlc = dlc - self.data = data - self.timestamp = timestamp + def __init__(self, id_: Identifier, dlc: int, data: bytes, timestamp: int) -> None: + self.id: Identifier = id_ + self.dlc: int = dlc + self.data: bytes = data + self.timestamp: int = timestamp - def __repr__(self): - return "Frame(id = 0x{:08x}, dlc = {}, data = {}, timestamp = {})".format(self.id, self.dlc, self.data, self.timestamp) + def __repr__(self) -> str: + return f"Frame(id = 0x{self.id:08x}, dlc = {self.dlc}, data = {self.data}, timestamp = {self.timestamp})" __str__ = __repr__ -class CanInterfaceBase(metaclass=abc.ABCMeta): - """ - Abstract CAN interface handler that can be implemented for any actual CAN device driver - """ - - PARAMETER_MAP = {} - - @abc.abstractmethod - def init(self, parent, receive_callback): - """ - Must implement any required action for initing the can interface - - Parameters - ---------- - parent: :class:`Can` - Refers to owner. - receive_callback: callable - Receive callback function to register with the following argument: payload: bytes - """ - - @abc.abstractmethod - def transmit(self, payload: bytes): - """ - Must transmit the given payload on the master can id. - - Parameters - ---------- - payload: bytes - payload to transmit - """ +class PythonCanWrapper: + """Wrapper around python-can - github.com/hardbyte/python-can""" - @abc.abstractmethod - def close(self): - """Must implement any required action for disconnecting from the can interface""" + def __init__(self, parent, interface_name: str, timeout: int, **parameters) -> None: + self.parent = parent + self.interface_name: str = interface_name + self.timeout: int = timeout + self.parameters = parameters + self.can_interface_class: Type[BusABC] = _get_class_for_interface(self.interface_name) + self.can_interface: BusABC + self.connected: bool = False - @abc.abstractmethod def connect(self): - """Open connection to can interface""" + if self.connected: + return + can_filters = [] + can_filters.append(self.parent.can_id_slave.create_filter_from_id()) # Primary CAN filter. + if self.parent.has_user_supplied_interface: + self.can_interface = self.parent.transport_layer_interface + else: + self.can_interface = self.can_interface_class(interface=self.interface_name, **self.parameters) + if self.parent.daq_identifier: + # Add filters for DAQ identifiers. + for daq_id in self.parent.daq_identifier: + can_filters.append(daq_id.create_filter_from_id()) + self.can_interface.set_filters(can_filters) + self.parent.logger.info(f"XCPonCAN - Using Interface: '{self.can_interface!s}'") + self.parent.logger.info(f"XCPonCAN - Filters used: {self.can_interface.filters}") + self.parent.logger.info(f"XCPonCAN - State: {self.can_interface.state!s}") + self.connected = True - @abc.abstractmethod - def read(self): - """Read incoming data""" + def close(self): + if self.connected and not self.parent.has_user_supplied_interface: + self.can_interface.shutdown() + self.connected = False + + def transmit(self, payload: bytes) -> None: + frame = Message( + arbitration_id=self.parent.can_id_master.id, + is_extended_id=True if self.parent.can_id_master.is_extended else False, + is_fd=self.parent.fd, + data=payload, + ) + self.can_interface.send(frame) - @abc.abstractmethod - def getTimestampResolution(self): - """Get timestamp resolution in nano seconds.""" + def read(self) -> Optional[Frame]: + if not self.connected: + return None + try: + frame = self.can_interface.recv(self.timeout) + except CanError: + return None + else: + if frame is None or not len(frame.data): + return None # Timeout condition. + extended = frame.is_extended_id + identifier = Identifier.make_identifier(frame.arbitration_id, extended) + return Frame( + id_=identifier, + dlc=frame.dlc, + data=frame.data, + timestamp=seconds_to_nanoseconds(frame.timestamp), + ) - def loadConfig(self, config): - """Load configuration data.""" - self.config = Configuration(self.PARAMETER_MAP or {}, config or {}) + def get_timestamp_resolution(self) -> int: + return 10 * 1000 class EmptyHeader: @@ -258,106 +302,114 @@ def pack(self, *args, **kwargs): return b"" -# can.detect_available_configs() - - class Can(BaseTransport): """""" - PARAMETER_MAP = { - # Type Req'd Default - "CAN_DRIVER": (str, True, None), - "CHANNEL": (str, False, ""), - "MAX_DLC_REQUIRED": (bool, False, False), - "MAX_CAN_FD_DLC": (int, False, 64), - "PADDING_VALUE": (int, False, 0), - "CAN_USE_DEFAULT_LISTENER": (bool, False, True), - # defaults to True, in this case the default listener thread is used. - # If the canInterface implements a listener service, this parameter - # can be set to False, and the default listener thread won't be started. - "CAN_ID_MASTER": (int, True, None), - "CAN_ID_SLAVE": (int, True, None), - "CAN_ID_BROADCAST": (int, False, None), - "BITRATE": (int, False, 250000), - "RECEIVE_OWN_MESSAGES": (bool, False, False), - } - - PARAMETER_TO_KW_ARG_MAP = { - "RECEIVE_OWN_MESSAGES": "receive_own_messages", - "CHANNEL": "channel", - "BITRATE": "bitrate", - } - MAX_DATAGRAM_SIZE = 7 HEADER = EmptyHeader() HEADER_SIZE = 0 - def __init__(self, config=None, policy=None): - """init for CAN transport - :param config: configuration - """ - super().__init__(config, policy) - self.loadConfig(config) - drivers = registered_drivers() - interfaceName = self.config.get("CAN_DRIVER") - if interfaceName not in drivers: - raise ValueError("{} is an invalid driver name -- choose from {}".format(interfaceName, [x for x in drivers.keys()])) - canInterfaceClass = drivers[interfaceName] - self.canInterface = canInterfaceClass() - self.useDefaultListener = self.config.get("CAN_USE_DEFAULT_LISTENER") - self.can_id_master = Identifier(self.config.get("CAN_ID_MASTER")) - self.can_id_slave = Identifier(self.config.get("CAN_ID_SLAVE")) - self.canInterface.loadConfig(config) - self.canInterface.init(self, self.dataReceived) - # + def __init__(self, config, policy=None, transport_layer_interface: Optional[BusABC] = None): + super().__init__(config, policy, transport_layer_interface) + self.load_config(config) + self.useDefaultListener = self.config.use_default_listener + self.can_id_master = Identifier(self.config.can_id_master) + self.can_id_slave = Identifier(self.config.can_id_slave) + # Regarding CAN-FD s. AUTOSAR CP Release 4.3.0, Requirements on CAN; [SRS_Can_01160] Padding of bytes due to discrete CAN FD DLC]: # "... If a PDU does not exactly match these configurable sizes the unused bytes shall be padded." # - self.max_dlc_required = self.config.get("MAX_DLC_REQUIRED") or self.canInterface.is_fd - self.padding_value = self.config.get("PADDING_VALUE") - self.padding_len = self.config.get("MAX_CAN_FD_DLC") if self.canInterface.is_fd else MAX_DLC_CLASSIC + self.fd = self.config.fd + self.daq_identifier = [] + if self.config.daq_identifier: + for daq_id in self.config.daq_identifier: + self.daq_identifier.append(Identifier(daq_id)) + self.max_dlc_required = self.config.max_dlc_required + self.padding_value = self.config.padding_value + self.interface_name = self.config.interface + self.interface_configuration = detect_available_configs(interfaces=[self.interface_name]) + parameters = self.get_interface_parameters() + self.can_interface = PythonCanWrapper(self, self.interface_name, config.timeout, **parameters) + self.logger.info(f"XCPonCAN - Interface-Type: {self.interface_name!r} Parameters: {list(parameters.items())}") + self.logger.info( + f"XCPonCAN - Master-ID (Tx): 0x{self.can_id_master.id:08X}{self.can_id_master.type_str} -- " + f"Slave-ID (Rx): 0x{self.can_id_slave.id:08X}{self.can_id_slave.type_str}" + ) - def dataReceived(self, payload: bytes, recv_timestamp: float = None): - self.processResponse( + def get_interface_parameters(self) -> Dict[str, Any]: + result = dict(channel=self.config.channel) + + can_interface_config_class = CAN_INTERFACE_MAP[self.interface_name] + + # Optional base class parameters. + optional_parameters = [(p, p.removeprefix("has_")) for p in can_interface_config_class.OPTIONAL_BASE_PARAMS] + for o, n in optional_parameters: + opt = getattr(can_interface_config_class, o) + value = getattr(self.config, n) + if opt: + if value is not None: + result[n] = value + elif value is not None: + self.logger.warning(f"XCPonCAN - {self.interface_name!r} has no support for parameter {n!r}.") + + # Parameter names that need to be mapped. + for base_name, name in can_interface_config_class.CAN_PARAM_MAP.items(): + value = getattr(self.config, base_name) + if value is not None: + result[name] = value + + # Interface specific parameters. + cxx = getattr(self.config, self.interface_name) + for name in can_interface_config_class.class_own_traits().keys(): + value = getattr(cxx, name) + if value is not None: + result[name] = value + return result + + def data_received(self, payload: bytes, recv_timestamp: int): + self.process_response( payload, len(payload), - counter=(self.counterReceived + 1) & 0xffff, + counter=(self.counter_received + 1) & 0xFFFF, recv_timestamp=recv_timestamp, ) def listen(self): while True: - if self.closeEvent.isSet(): + if self.closeEvent.is_set(): return - frame = self.canInterface.read() + frame = self.can_interface.read() if frame: - self.dataReceived(frame.data, frame.timestamp) + self.data_received(frame.data, frame.timestamp) def connect(self): if self.useDefaultListener: - self.startListener() - self.canInterface.connect() + self.start_listener() + try: + self.can_interface.connect() + except CanInitializationError: + console.print("[red]\nThere may be a problem with the configuration of your CAN-interface.\n") + console.print(f"[grey]Current configuration of interface {self.interface_name!r}:") + console.print(self.interface_configuration) + raise self.status = 1 # connected - def send(self, frame): - # XCP on CAN trailer: if required, FILL bytes must be appended - if self.max_dlc_required: - frame = padFrame(frame, self.padding_value, self.padding_len) + def send(self, frame: bytes) -> None: # send the request - self.pre_send_timestamp = time() - self.canInterface.transmit(payload=frame) - self.post_send_timestamp = time() + self.pre_send_timestamp = self.timestamp.value + self.can_interface.transmit(payload=pad_frame(frame, self.max_dlc_required, self.padding_value)) + self.post_send_timestamp = self.timestamp.value - def closeConnection(self): - if hasattr(self, "canInterface"): - self.canInterface.close() + def close_connection(self): + if hasattr(self, "can_interface"): + self.can_interface.close() def close(self): - self.finishListener() - self.closeConnection() + self.finish_listener() + self.close_connection() -def setDLC(length: int): +def set_DLC(length: int): """Return DLC value according to CAN-FD. :param length: Length value to be mapped to a valid CAN-FD DLC. @@ -376,41 +428,16 @@ def setDLC(length: int): raise ValueError("DLC could be at most 64.") -def calculateFilter(ids: list): +def calculate_filter(ids: list): """ :param ids: An iterable (usually list or tuple) containing CAN identifiers. :return: Calculated filter and mask. :rtype: tuple (int, int) """ - any_extended_ids = any(isExtendedIdentifier(i) for i in ids) + any_extended_ids = any(is_extended_identifier(i) for i in ids) raw_ids = [stripIdentifier(i) for i in ids] cfilter = functools.reduce(operator.and_, raw_ids) cmask = functools.reduce(operator.or_, raw_ids) ^ cfilter cmask ^= 0x1FFFFFFF if any_extended_ids else 0x7FF return (cfilter, cmask) - - -def try_to_install_system_supplied_drivers(): - """Register available pyxcp CAN drivers.""" - import importlib - import pkgutil - import pyxcp.transport.candriver as cdr - - for _, modname, _ in pkgutil.walk_packages(cdr.__path__, "{}.".format(cdr.__name__)): - try: - importlib.import_module(modname) - except Exception: - pass - - -def registered_drivers(): - """ - Returns - ------- - dict (name, class) - Dictionary containing CAN driver names and classes of all - available drivers (pyxcp supplied and user-defined). - """ - sub_classes = CanInterfaceBase.__subclasses__() - return OrderedDict(zip(([c.__name__ for c in sub_classes]), sub_classes)) diff --git a/pyxcp/transport/candriver/__init__.py b/pyxcp/transport/candriver/__init__.py deleted file mode 100644 index faa18be5..00000000 --- a/pyxcp/transport/candriver/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- diff --git a/pyxcp/transport/candriver/pc_canalystii.py b/pyxcp/transport/candriver/pc_canalystii.py deleted file mode 100644 index 96be4a1e..00000000 --- a/pyxcp/transport/candriver/pc_canalystii.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for CANalyst-II(+) by ZLG ZHIYUAN Electronics interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class Canalystii(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "BAUD ": (int, False, None), - "TIMING0": (int, False, None), - "TIMING1": (int, False, None), - } - - PARAMETER_TO_KW_ARG_MAP = { - "BAUD": "baud", - "TIMING0": "Timing0", - "TIMING1": "Timing1", - } - - def __init__(self): - super(Canalystii, self).__init__(bustype="canalystii") diff --git a/pyxcp/transport/candriver/pc_etas.py b/pyxcp/transport/candriver/pc_etas.py deleted file mode 100644 index 3057cd62..00000000 --- a/pyxcp/transport/candriver/pc_etas.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for ETAS BOA interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class Etas(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "FD": (bool, False, False), - "DATA_BITRATE": (int, False, None), - } - - PARAMETER_TO_KW_ARG_MAP = { - "FD": "fd", - "DATA_BITRATE": "data_bitrate", - } - - def __init__(self): - super(Etas, self).__init__(bustype="etas") diff --git a/pyxcp/transport/candriver/pc_gsusb.py b/pyxcp/transport/candriver/pc_gsusb.py deleted file mode 100644 index aa8e0bd1..00000000 --- a/pyxcp/transport/candriver/pc_gsusb.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for CAN driver for Geschwister Schneider USB/CAN devices and bytewerk.org candleLight USB CAN interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class GsUsb(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - """ - PARAMETER_MAP = { - # Type Req'd Default - } - - PARAMETER_TO_KW_ARG_MAP = { - } - """ - - def __init__(self): - super(GsUsb, self).__init__(bustype="gs_usb") diff --git a/pyxcp/transport/candriver/pc_iscan.py b/pyxcp/transport/candriver/pc_iscan.py deleted file mode 100644 index 5a1734b3..00000000 --- a/pyxcp/transport/candriver/pc_iscan.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for isCAN from Thorsis Technologies GmbH. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class IsCAN(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "POLL_INTERVAL": (float, False, 0.01), - } - - PARAMETER_TO_KW_ARG_MAP = { - "POLL_INTERVAL": "poll_interval", - } - - def __init__(self): - super(IsCAN, self).__init__(bustype="iscan") diff --git a/pyxcp/transport/candriver/pc_ixxat.py b/pyxcp/transport/candriver/pc_ixxat.py deleted file mode 100644 index 5522c16a..00000000 --- a/pyxcp/transport/candriver/pc_ixxat.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for Ixxat interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class Ixxat(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "UNIQUE_HARDWARE_ID": (str, False, None), - "RX_FIFO_SIZE": (int, False, 16), - "TX_FIFO_SIZE": (int, False, 16), - } - - PARAMETER_TO_KW_ARG_MAP = { - "UNIQUE_HARDWARE_ID": "UniqueHardwareId", - "RX_FIFO_SIZE": "rxFifoSize", - "TX_FIFO_SIZE": "txFifoSize", - } - - def __init__(self): - super(Ixxat, self).__init__(bustype="ixxat") diff --git a/pyxcp/transport/candriver/pc_kvaser.py b/pyxcp/transport/candriver/pc_kvaser.py deleted file mode 100644 index 78104d16..00000000 --- a/pyxcp/transport/candriver/pc_kvaser.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for Kvaser interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class Kvaser(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "ACCEPT_VIRTUAL": (bool, False, True), - "DRIVER_MODE": (bool, False, True), - "NO_SAMP": (int, False, 1), - "SJW": (int, False, 2), - "TSEG1": (int, False, 5), - "TSEG2": (int, False, 2), - "SINGLE_HANDLE": (bool, False, True), - "FD": (bool, False, False), - "DATA_BITRATE": (int, False, None), - } - - PARAMETER_TO_KW_ARG_MAP = { - "ACCEPT_VIRTUAL": "accept_virtual", - "TSEG1": "tseg1", - "TSEG2": "tseg2", - "SJW": "sjw", - "NO_SAMP": "no_samp", - "DRIVER_MODE": "driver_mode", - "SINGLE_HANDLE": "single_handle", - "FD": "fd", - "DATA_BITRATE": "data_bitrate", - } - - def __init__(self): - super(Kvaser, self).__init__(bustype="kvaser") diff --git a/pyxcp/transport/candriver/pc_neovi.py b/pyxcp/transport/candriver/pc_neovi.py deleted file mode 100644 index 80da5a0d..00000000 --- a/pyxcp/transport/candriver/pc_neovi.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for ICS NeoVi interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class Neovi(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "FD": (bool, False, False), - "DATA_BITRATE": (int, False, None), - "USE_SYSTEM_TIMESTAMP": (bool, False, False), - "SERIAL": (str, False, None), - "OVERRIDE_LIBRARY_NAME": (str, False, None), - } - - PARAMETER_TO_KW_ARG_MAP = { - "FD": "fd", - "DATA_BITRATE": "data_bitrate", - "USE_SYSTEM_TIMESTAMP": "use_system_timestamp", - "SERIAL": "serial", - "OVERRIDE_LIBRARY_NAME": "override_library_name", - } - - def __init__(self): - super(Neovi, self).__init__(bustype="neovi") diff --git a/pyxcp/transport/candriver/pc_nican.py b/pyxcp/transport/candriver/pc_nican.py deleted file mode 100644 index 8ee8a659..00000000 --- a/pyxcp/transport/candriver/pc_nican.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for National Instruments interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class NiCan(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "LOG_ERRORS": (bool, False, False), - } - - PARAMETER_TO_KW_ARG_MAP = { - "LOG_ERRORS": "log_errors", - } - - def __init__(self): - super(NiCan, self).__init__(bustype="nican") diff --git a/pyxcp/transport/candriver/pc_nixnet.py b/pyxcp/transport/candriver/pc_nixnet.py deleted file mode 100644 index 580ca3e5..00000000 --- a/pyxcp/transport/candriver/pc_nixnet.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for National Instruments xnet interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class NiXnet(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "LOG_ERRORS": (bool, False, False), - } - - PARAMETER_TO_KW_ARG_MAP = { - "LOG_ERRORS": "log_errors", - } - - def __init__(self): - super(NiXnet, self).__init__(bustype="nixnet") diff --git a/pyxcp/transport/candriver/pc_pcan.py b/pyxcp/transport/candriver/pc_pcan.py deleted file mode 100644 index 4b06e4bd..00000000 --- a/pyxcp/transport/candriver/pc_pcan.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for Peak System interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - -from can import BusState - - -class PCan(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "STATE": (BusState, False, BusState.ACTIVE), - } - - PARAMETER_TO_KW_ARG_MAP = { - "STATE": "state", - } - - def __init__(self): - super(PCan, self).__init__(bustype="pcan") diff --git a/pyxcp/transport/candriver/pc_seeed.py b/pyxcp/transport/candriver/pc_seeed.py deleted file mode 100644 index 3d7e29ff..00000000 --- a/pyxcp/transport/candriver/pc_seeed.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for USB-CAN Analyzer by Seeed Studio interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class Seeed(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # # Type Req'd Default - # "FD": (bool, False, False), - # "DATA_BITRATE": (int, False, None), - } - - PARAMETER_TO_KW_ARG_MAP = { - # "FD": "fd", - # "DATA_BITRATE": "data_bitrate", - } - - def __init__(self): - super(Seeed, self).__init__(bustype="seeedstudio") - - -# from can.interfaces.seeedstudio import SeeedBus diff --git a/pyxcp/transport/candriver/pc_serial.py b/pyxcp/transport/candriver/pc_serial.py deleted file mode 100644 index cdeb6554..00000000 --- a/pyxcp/transport/candriver/pc_serial.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver serial port connected interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class Serial(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "BAUDRATE": (int, False, 115200), - "TIMEOUT": (float, False, 0.1), - "RTSCTS": (bool, False, False), - } - - PARAMETER_TO_KW_ARG_MAP = { - "BAUDRATE": "baudrate", - "TIMEOUT": "timeout", - "RTSCTS": "rtscts", - } - - def __init__(self): - super(Serial, self).__init__(bustype="serial") diff --git a/pyxcp/transport/candriver/pc_slcan.py b/pyxcp/transport/candriver/pc_slcan.py deleted file mode 100644 index 828c83fe..00000000 --- a/pyxcp/transport/candriver/pc_slcan.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for CAN over Serial (like Lawicel) interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class SlCan(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "TTY_BAUDRATE": (int, False, 115200), - "POLL_INTERVAL": (float, False, 0.01), - "SLEEP_AFTER_OPEN": (float, False, 2.0), - "RTSCTS": (bool, False, False), - } - - PARAMETER_TO_KW_ARG_MAP = { - "TTY_BAUDRATE": "ttyBaudrate", - "POLL_INTERVAL": "poll_interval", - "SLEEP_AFTER_OPEN": "sleep_after_open", - "RTSCTS": "rtscts", - } - - def __init__(self): - super(SlCan, self).__init__(bustype="slcan") diff --git a/pyxcp/transport/candriver/pc_socketcan.py b/pyxcp/transport/candriver/pc_socketcan.py deleted file mode 100644 index 8ad8be1e..00000000 --- a/pyxcp/transport/candriver/pc_socketcan.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for Linux SocketCAN interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class SocketCAN(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "FD": (bool, False, False), - } - - PARAMETER_TO_KW_ARG_MAP = { - "FD": "fd", - } - - def __init__(self): - super(SocketCAN, self).__init__(bustype="socketcan") diff --git a/pyxcp/transport/candriver/pc_systec.py b/pyxcp/transport/candriver/pc_systec.py deleted file mode 100644 index 5cb262ad..00000000 --- a/pyxcp/transport/candriver/pc_systec.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for Systec interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class Systec(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "DEVICE_NUMBER": (int, False, 255), - "RX_BUFFER_ENTRIES": (int, False, 4096), - "TX_BUFFER_ENTRIES": (int, False, 4096), - "STATE": (str, False, "ACTIVE"), - } - - PARAMETER_TO_KW_ARG_MAP = { - "DEVICE_NUMBER": "device_number", - "RX_BUFFER_ENTRIES": "rx_buffer_entries", - "TX_BUFFER_ENTRIES": "tx_buffer_entries", - "STATE": "state", - } - - def __init__(self): - super(Systec, self).__init__(bustype="systec") diff --git a/pyxcp/transport/candriver/pc_usb2can.py b/pyxcp/transport/candriver/pc_usb2can.py deleted file mode 100644 index 1c1a9101..00000000 --- a/pyxcp/transport/candriver/pc_usb2can.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for 8devices USB2CAN interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class Usb2Can(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "FLAGS": (int, False, 0), - } - - PARAMETER_TO_KW_ARG_MAP = { - "FLAGS": "flags", - } - - """ - - :param int flags: - Flags to directly pass to open function of the usb2can abstraction layer. - - """ - - def __init__(self): - super(Usb2Can, self).__init__(bustype="usb2can") diff --git a/pyxcp/transport/candriver/pc_vector.py b/pyxcp/transport/candriver/pc_vector.py deleted file mode 100644 index cb0f6312..00000000 --- a/pyxcp/transport/candriver/pc_vector.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -python-can driver for Vector Informatik interfaces. -""" -import pyxcp.transport.can as can -import pyxcp.transport.candriver.python_can as python_can - - -class Vector(python_can.PythonCAN, can.CanInterfaceBase): - """""" - - PARAMETER_MAP = { - # Type Req'd Default - "POLL_INTERVAL": (float, False, 0.01), - "APP_NAME": (str, False, ""), - "SERIAL": (int, False, None), - "RX_QUEUE_SIZE": (int, False, 16384), - "FD": (bool, False, False), - "DATA_BITRATE": (int, False, 0), - "DATA_SAMPLE_POINT": (float, False, 0), - } - - PARAMETER_TO_KW_ARG_MAP = { - "POLL_INTERVAL": "poll_interval", - "RX_QUEUE_SIZE": "rx_queue_size", - "FD": "fd", - "DATA_BITRATE": "data_bitrate", - "APP_NAME": "app_name", - "SERIAL": "serial", - } - - def __init__(self): - super(Vector, self).__init__(bustype="vector") diff --git a/pyxcp/transport/candriver/python_can.py b/pyxcp/transport/candriver/python_can.py deleted file mode 100644 index 81b8392d..00000000 --- a/pyxcp/transport/candriver/python_can.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -""" -Support for python-can - github.com/hardbyte/python-can -""" -import pyxcp.transport.can as can -import re -from collections import OrderedDict - -from can import Bus -from can import CanError -from can import Message - -NUMBER = re.compile(r"(?P0[x|X])?(?P[0-9]+)", re.VERBOSE) - - -class PythonCAN: - """""" - - def __init__(self, bustype): - self.bustype = bustype - self.connected = False - - def init(self, parent, receive_callback): - self.parent = parent - self.is_fd = self.config.get("FD") - - def connect(self): - if self.connected: - return - - self.kwargs = OrderedDict() - # Fetch driver keyword arguments. - self._fetch_kwargs(False) - self._fetch_kwargs(True) - can_id = self.parent.can_id_master - can_filter = { - "can_id": can_id.id, - "can_mask": can.MAX_29_BIT_IDENTIFIER if can_id.is_extended else can.MAX_11_BIT_IDENTIFIER, - "extended": can_id.is_extended, - } - self.bus = Bus(bustype=self.bustype, **self.kwargs) - self.bus.set_filters([can_filter]) - self.parent.logger.debug("Python-CAN driver: {} - {}]".format(self.bustype, self.bus)) - self.connected = True - - def _fetch_kwargs(self, local): - if local: - base = self - else: - base = self.parent - for param, arg in base.PARAMETER_TO_KW_ARG_MAP.items(): - value = base.config.get(param) - # if param == "CHANNEL": - # value = self._handle_channel(value) - self.kwargs[arg] = value - - def _handle_channel(self, value): - match = NUMBER.match(value) - if match: - gd = match.groupdict() - base = 16 if not gd["hex"] is None else 10 - return int(value, base) - else: - return value - - def close(self): - if self.connected: - self.bus.shutdown() - self.connected = False - - def transmit(self, payload): - frame = Message( - arbitration_id=self.parent.can_id_slave.id, - is_extended_id=True if self.parent.can_id_slave.is_extended else False, - is_fd=self.is_fd, - data=payload, - ) - self.bus.send(frame) - - def read(self): - if not self.connected: - return None - try: - frame = self.bus.recv(5) - except CanError: - return None - else: - if frame is None or frame.arbitration_id != self.parent.can_id_master.id or not len(frame.data): - return None # Timeout condition. - extended = frame.is_extended_id - identifier = can.Identifier.make_identifier(frame.arbitration_id, extended) - return can.Frame( - id_=identifier, - dlc=frame.dlc, - data=frame.data, - timestamp=frame.timestamp, - ) - - def getTimestampResolution(self): - return 10 * 1000 diff --git a/pyxcp/transport/cxx_ext/CMakeLists.txt b/pyxcp/transport/cxx_ext/CMakeLists.txt deleted file mode 100644 index 422cce10..00000000 --- a/pyxcp/transport/cxx_ext/CMakeLists.txt +++ /dev/null @@ -1,51 +0,0 @@ -cmake_minimum_required(VERSION 3.12) -project(eth_booster VERSION 0.1.0 LANGUAGES CXX) - -set(BUILD_SHARED_LIBS false) - -set(BASE_DIR "../../cxx") - -if(WIN32) - set(_PS "win") -elseif(UNIX) - set(_PS "linux") -endif(WIN32) - - -add_library(eth - ${BASE_DIR}/utils.cpp -) -if(WIN32) - set(ADD_LIBS ws2_32) -elseif(UNIX) - set(ADD_LIBS pthread rt) -endif(WIN32) - -target_compile_features(eth PRIVATE cxx_std_14) -target_include_directories(eth PUBLIC ${BASE_DIR} ${BASE_DIR}/${_PS}) - -add_executable(test_timestamp tests/test_timestamp.cpp) -target_include_directories( - test_timestamp PUBLIC - ${eth_booster_SOURCE_DIR} - ${eth_booster_SOURCE_DIR}/${_PS} -) -target_link_libraries(test_timestamp eth) - - -add_executable(test_pool tests/test_pool.cpp) -target_include_directories( - test_pool PUBLIC - ${eth_booster_SOURCE_DIR} - ${eth_booster_SOURCE_DIR}/${_PS} -) -target_link_libraries(test_pool eth) - -add_executable(blocking_client ${BASE_DIR}/blocking_socket.cpp ${BASE_DIR}/blocking_client.cpp) -target_include_directories( - blocking_client PUBLIC - ${eth_booster_SOURCE_DIR} -# ${eth_booster_SOURCE_DIR}/${_PS} -) -target_link_libraries(blocking_client eth ${ADD_LIBS}) - diff --git a/pyxcp/transport/cxx_ext/setup.py b/pyxcp/transport/cxx_ext/setup.py deleted file mode 100644 index cf16c9c3..00000000 --- a/pyxcp/transport/cxx_ext/setup.py +++ /dev/null @@ -1,49 +0,0 @@ -import os -import subprocess -import sys - -from distutils.core import Extension -from distutils.core import setup -from pybind11.setup_helpers import build_ext -from pybind11.setup_helpers import Pybind11Extension - -try: - INCLUDE_DIRS = subprocess.getoutput("pybind11-config --include") -except Exception as e: - print("Error while executing pybind11-config ('{}').\npybind11 probably not installed?".format(str(e))) - sys.exit(1) - -pf = sys.platform -if pf.startswith("win32"): - LIBS = ["ws2_32"] -elif pf.startswith("linux"): - LIBS = ["pthread", "rt"] -else: - raise RuntimeError("Platform '{}' currently not supported.".format(pf)) - - -os.environ["CFLAGS"] = "" - -PKG_NAME = "eth_booster" -EXT_NAMES = ["eth_booster"] -__version__ = "0.0.1" - -ext_modules = [ - Pybind11Extension( - EXT_NAMES[0], - include_dirs=[INCLUDE_DIRS], - sources=["blocking_socket.cpp", "utils.cpp", "wrap.cpp"], - define_macros=[("EXTENSION_NAME", EXT_NAMES[0])], - extra_compile_args=["-O3", "-Wall", "-Weffc++", "-std=c++17"], - libraries=LIBS, - ), -] - -setup( - name=PKG_NAME, - version="0.0.1", - author="Christoph Schueler", - description="Example", - ext_modules=ext_modules, - cmdclass={"build_ext": build_ext}, -) diff --git a/pyxcp/transport/cxx_ext/tests/test_basic_socket.cpp b/pyxcp/transport/cxx_ext/tests/test_basic_socket.cpp deleted file mode 100644 index b40afe50..00000000 --- a/pyxcp/transport/cxx_ext/tests/test_basic_socket.cpp +++ /dev/null @@ -1,39 +0,0 @@ - - -#include "eth.hpp" -#include "socket.hpp" -#include "asynchiofactory.hpp" - -#include -#include - -using std::cout; -using std::endl; -using std::setw; -using std::internal; -using std::fixed; -using std::setfill; - -using namespace std; - -std::array hellomsg {"hello world!!!"}; - -Eth eth; - -int main(void) -{ - - CAddress address; - auto asio = createAsyncIoService(); - auto sock = Socket {PF_INET, SOCK_STREAM, IPPROTO_TCP}; - - //sock.getaddrinfo(PF_INET, SOCK_STREAM, IPPROTO_TCP, "localhost", 50007, address, 0); - sock.getaddrinfo(PF_INET, SOCK_STREAM, IPPROTO_TCP, "192.168.168.100", 50007, address, 0); - sock.connect(address); - asio->registerSocket(sock); - //sock.getaddrinfo(PF_INET, SOCK_STREAM, IPPROTO_TCP, "google.de", 80, address, 0); - //printf("addr: %x", address.address); - - sock.write(hellomsg); - Sleep(250); -} diff --git a/pyxcp/transport/cxx_ext/tests/test_pool.cpp b/pyxcp/transport/cxx_ext/tests/test_pool.cpp deleted file mode 100644 index 13ec4ed8..00000000 --- a/pyxcp/transport/cxx_ext/tests/test_pool.cpp +++ /dev/null @@ -1,39 +0,0 @@ - -#include "memoryblock.hpp" -#include "pool.hpp" - -#include -#include - - -typedef Pool, 8> Pool_t; - -void acquire_memory_blocks(Pool_t& pool) -{ - for (int i = 0; i < 8; ++i) { - auto obj = pool.acquire(); - pool.release(obj); - } - // Blocks should be released. -} - -int main() -{ - Pool_t pool; - - acquire_memory_blocks(pool); - - auto p0 = pool.acquire(); - auto p1 = pool.acquire(); - auto p2 = pool.acquire(); - auto p3 = pool.acquire(); - auto p4 = pool.acquire(); - auto p5 = pool.acquire(); - auto p6 = pool.acquire(); - auto p7 = pool.acquire(); - try { - auto p8 = pool.acquire(); // should throw CapacityExhaustedException. - } catch(CapacityExhaustedException) { - std::cout << "OK, caught CapacityExhaustedException as expected." << std::endl; - } -} diff --git a/pyxcp/transport/cxx_ext/tests/test_timestamp.cpp b/pyxcp/transport/cxx_ext/tests/test_timestamp.cpp deleted file mode 100644 index 215cef83..00000000 --- a/pyxcp/transport/cxx_ext/tests/test_timestamp.cpp +++ /dev/null @@ -1,27 +0,0 @@ - -#include "timestamp.hpp" -#include "utils.hpp" - -#include -#include - -using std::cout; -using std::endl; -using namespace std; - - -int main(void) -{ - auto ts = Timestamp(); - double previous = 0.0; - double current = 0.0; - - cout << fixed; - - for (uint16_t idx = 0; idx < 100; ++idx) { - current = ts.get(); - cout << "#" << setw(3) << setfill('0') << idx + 1 << " " << current << " diff: " << current -previous << endl; - Sleep(100); - previous = current; - } -} diff --git a/pyxcp/transport/eth.py b/pyxcp/transport/eth.py index 49415f1e..6494dde3 100644 --- a/pyxcp/transport/eth.py +++ b/pyxcp/transport/eth.py @@ -1,66 +1,78 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import selectors import socket import struct import threading from collections import deque -from time import perf_counter -from time import sleep -from time import time +from typing import Optional from pyxcp.transport.base import BaseTransport -from pyxcp.utils import SHORT_SLEEP +from pyxcp.utils import short_sleep + DEFAULT_XCP_PORT = 5555 RECV_SIZE = 8196 +def socket_to_str(sock: socket.socket) -> str: + peer = sock.getpeername() + local = sock.getsockname() + AF = { + socket.AF_INET: "AF_INET", + socket.AF_INET6: "AF_INET6", + } + TYPE = { + socket.SOCK_DGRAM: "SOCK_DGRAM", + socket.SOCK_STREAM: "SOCK_STREAM", + } + family = AF.get(sock.family, "OTHER") + typ = TYPE.get(sock.type, "UNKNOWN") + res = f"XCPonEth - Connected to: {peer[0]}:{peer[1]} local address: {local[0]}:{local[1]} [{family}][{typ}]" + return res + + class Eth(BaseTransport): """""" - PARAMETER_MAP = { - # Type Req'd Default - "HOST": (str, False, "localhost"), - "PORT": (int, False, 5555), - "BIND_TO_ADDRESS": (str, False, ""), - "BIND_TO_PORT": (int, False, 5555), - "PROTOCOL": (str, False, "TCP"), - "IPV6": (bool, False, False), - "TCP_NODELAY": (bool, False, False), - } - MAX_DATAGRAM_SIZE = 512 HEADER = struct.Struct(" None: + super().__init__(config, policy, transport_layer_interface) + self.load_config(config) + self.host: str = self.config.host + self.port: int = self.config.port + self.protocol: int = self.config.protocol + self.ipv6: bool = self.config.ipv6 + self.use_tcp_no_delay: bool = self.config.tcp_nodelay + address_to_bind: str = self.config.bind_to_address + bind_to_port: int = self.config.bind_to_port + self._local_address = (address_to_bind, bind_to_port) if address_to_bind else None if self.ipv6 and not socket.has_ipv6: - raise RuntimeError("IPv6 not supported by your platform.") + msg = "XCPonEth - IPv6 not supported by your platform." + self.logger.critical(msg) + raise RuntimeError(msg) else: address_family = socket.AF_INET6 if self.ipv6 else socket.AF_INET proto = socket.SOCK_STREAM if self.protocol == "TCP" else socket.SOCK_DGRAM if self.host.lower() == "localhost": self.host = "::1" if self.ipv6 else "localhost" - addrinfo = socket.getaddrinfo(self.host, self.port, address_family, proto) - ( - self.address_family, - self.socktype, - self.proto, - self.canonname, - self.sockaddr, - ) = addrinfo[0] - self.status = 0 + + try: + addrinfo = socket.getaddrinfo(self.host, self.port, address_family, proto) + ( + self.address_family, + self.socktype, + self.proto, + self.canonname, + self.sockaddr, + ) = addrinfo[0] + except BaseException as ex: # noqa: B036 + msg = f"XCPonEth - Failed to resolve address {self.host}:{self.port}" + self.logger.critical(msg) + raise Exception(msg) from ex + self.status: int = 0 self.sock = socket.socket(self.address_family, self.socktype, self.proto) self.selector = selectors.DefaultSelector() self.selector.register(self.sock, selectors.EVENT_READ) @@ -74,8 +86,10 @@ def __init__(self, config=None, policy=None): if self._local_address: try: self.sock.bind(self._local_address) - except BaseException as ex: - raise Exception(f"Failed to bind socket to given address {self._local_address}") from ex + except BaseException as ex: # noqa: B036 + msg = f"XCPonEth - Failed to bind socket to given address {self._local_address}" + self.logger.critical(msg) + raise Exception(msg) from ex self._packet_listener = threading.Thread( target=self._packet_listen, args=(), @@ -83,43 +97,40 @@ def __init__(self, config=None, policy=None): ) self._packets = deque() - def connect(self): + def connect(self) -> None: if self.status == 0: self.sock.connect(self.sockaddr) - self.startListener() + self.logger.info(socket_to_str(self.sock)) + self.start_listener() self.status = 1 # connected - def startListener(self): - super().startListener() + def start_listener(self) -> None: + super().start_listener() if self._packet_listener.is_alive(): self._packet_listener.join() self._packet_listener = threading.Thread(target=self._packet_listen) self._packet_listener.start() - def close(self): + def close(self) -> None: """Close the transport-layer connection and event-loop.""" - self.finishListener() + self.finish_listener() if self.listener.is_alive(): self.listener.join() if self._packet_listener.is_alive(): self._packet_listener.join() - self.closeConnection() + self.close_connection() - def _packet_listen(self): - use_tcp = self.use_tcp + def _packet_listen(self) -> None: + use_tcp: bool = self.use_tcp EVENT_READ = selectors.EVENT_READ - close_event_set = self.closeEvent.is_set socket_fileno = self.sock.fileno select = self.selector.select - _packets = self._packets - if use_tcp: sock_recv = self.sock.recv else: sock_recv = self.sock.recvfrom - while True: try: if close_event_set() or socket_fileno() == -1: @@ -127,8 +138,7 @@ def _packet_listen(self): sel = select(0.02) for _, events in sel: if events & EVENT_READ: - recv_timestamp = time() - + recv_timestamp = self.timestamp.value if use_tcp: response = sock_recv(RECV_SIZE) if not response: @@ -145,41 +155,33 @@ def _packet_listen(self): break else: _packets.append((response, recv_timestamp)) - except BaseException: + except BaseException: # noqa: B036 self.status = 0 # disconnected break - def listen(self): + def listen(self) -> None: HEADER_UNPACK_FROM = self.HEADER.unpack_from HEADER_SIZE = self.HEADER_SIZE - processResponse = self.processResponse + process_response = self.process_response popleft = self._packets.popleft - close_event_set = self.closeEvent.is_set socket_fileno = self.sock.fileno - _packets = self._packets - length, counter = None, None - - data = bytearray(b"") - + length: Optional[int] = None + counter: int = 0 + data: bytearray = bytearray(b"") while True: if close_event_set() or socket_fileno() == -1: return - - count = len(_packets) - + count: int = len(_packets) if not count: - sleep(SHORT_SLEEP) + short_sleep() continue - for _ in range(count): bts, timestamp = popleft() - data += bts - current_size = len(data) - current_position = 0 - + current_size: int = len(data) + current_position: int = 0 while True: if length is None: if current_size >= HEADER_SIZE: @@ -192,24 +194,20 @@ def listen(self): else: if current_size >= length: response = data[current_position : current_position + length] - processResponse(response, length, counter, timestamp) - + process_response(response, length, counter, timestamp) current_size -= length current_position += length - length = None - else: - data = data[current_position:] break - def send(self, frame): - self.pre_send_timestamp = time() + def send(self, frame) -> None: + self.pre_send_timestamp = self.timestamp.value self.sock.send(frame) - self.post_send_timestamp = time() + self.post_send_timestamp = self.timestamp.value - def closeConnection(self): + def close_connection(self) -> None: if not self.invalidSocket: # Seems to be problematic /w IPv6 # if self.status == 1: @@ -217,5 +215,5 @@ def closeConnection(self): self.sock.close() @property - def invalidSocket(self): + def invalidSocket(self) -> bool: return not hasattr(self, "sock") or self.sock.fileno() == -1 diff --git a/pyxcp/transport/sxi.py b/pyxcp/transport/sxi.py index 3fe03e03..de450791 100644 --- a/pyxcp/transport/sxi.py +++ b/pyxcp/transport/sxi.py @@ -1,8 +1,7 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- import struct -from time import perf_counter -from time import time +from collections import deque +from dataclasses import dataclass +from typing import Optional import serial @@ -10,78 +9,125 @@ from pyxcp.transport.base import BaseTransport +@dataclass +class HeaderValues: + length: int = 0 + counter: int = 0 + filler: int = 0 + + +RECV_SIZE = 16384 + + class SxI(BaseTransport): """""" - PARAMETER_MAP = { - # Type Req'd Default - "PORT": (str, False, "COM1"), - "BITRATE": (int, False, 38400), - "BYTESIZE": (int, False, 8), - "PARITY": (str, False, "N"), - "STOPBITS": (int, False, 1), - } - - MAX_DATAGRAM_SIZE = 512 - TIMEOUT = 0.75 - HEADER = struct.Struct(" None: + super().__init__(config, policy, transport_layer_interface) + self.load_config(config) + self.port_name = self.config.port + self.baudrate = self.config.bitrate + self.bytesize = self.config.bytesize + self.parity = self.config.parity + self.stopbits = self.config.stopbits + self.mode = self.config.mode + self.header_format = self.config.header_format + self.tail_format = self.config.tail_format + self.framing = self.config.framing + self.esc_sync = self.config.esc_sync + self.esc_esc = self.config.esc_esc + self.make_header() + self.comm_port: serial.Serial + + if self.has_user_supplied_interface and transport_layer_interface: + self.comm_port = transport_layer_interface + else: + self.logger.info(f"XCPonSxI - trying to open serial comm_port {self.port_name}.") + try: + self.comm_port = serial.Serial( + port=self.port_name, + baudrate=self.baudrate, + bytesize=self.bytesize, + parity=self.parity, + stopbits=self.stopbits, + timeout=self.timeout, + write_timeout=self.timeout, + ) + except serial.SerialException as e: + self.logger.critical(f"XCPonSxI - {e}") + raise + self._packets = deque() + + def __del__(self) -> None: + self.close_connection() + + def make_header(self) -> None: + def unpack_len(args): + (length,) = args + return HeaderValues(length=length) + + def unpack_len_counter(args): + length, counter = args + return HeaderValues(length=length, counter=counter) + + def unpack_len_filler(args): + length, filler = args + return HeaderValues(length=length, filler=filler) + + HEADER_FORMATS = { + "HEADER_LEN_BYTE": ("B", unpack_len), + "HEADER_LEN_CTR_BYTE": ("BB", unpack_len_counter), + "HEADER_LEN_FILL_BYTE": ("BB", unpack_len_filler), + "HEADER_LEN_WORD": ("H", unpack_len), + "HEADER_LEN_CTR_WORD": ("HH", unpack_len_counter), + "HEADER_LEN_FILL_WORD": ("HH", unpack_len_filler), + } + fmt, unpacker = HEADER_FORMATS[self.header_format] + self.HEADER = struct.Struct(f"<{fmt}") + self.HEADER_SIZE = self.HEADER.size + self.unpacker = unpacker + + def connect(self) -> None: + self.logger.info(f"XCPonSxI - serial comm_port openend: {self.comm_port.portstr}@{self.baudrate} Bits/Sec.") + self.start_listener() + + def output(self, enable) -> None: if enable: - self.commPort.rts = False - self.commPort.dtr = False + self.comm_port.rts = False + self.comm_port.dtr = False else: - self.commPort.rts = True - self.commPort.dtr = True + self.comm_port.rts = True + self.comm_port.dtr = True - def flush(self): - self.commPort.flush() + def flush(self) -> None: + self.comm_port.flush() - def listen(self): + def start_listener(self) -> None: + super().start_listener() + def listen(self) -> None: while True: - if self.closeEvent.isSet(): + if self.closeEvent.is_set(): return - if not self.commPort.inWaiting(): + if not self.comm_port.in_waiting(): continue - recv_timestamp = time() - length, counter = self.HEADER.unpack(self.commPort.read(self.HEADER_SIZE)) + recv_timestamp = self.timestamp.value + header_values = self.unpacker(self.HEADER.unpack(self.comm_port.read(self.HEADER_SIZE))) + length, counter, _ = header_values.length, header_values.counter, header_values.filler - response = self.commPort.read(length) + response = self.comm_port.read(length) self.timing.stop() if len(response) != length: raise types.FrameSizeError("Size mismatch.") + self.process_response(response, length, counter, recv_timestamp) - self.processResponse(response, length, counter, recv_timestamp) - - def send(self, frame): - self.pre_send_timestamp = time() - self.commPort.write(frame) - self.post_send_timestamp = time() + def send(self, frame) -> None: + self.pre_send_timestamp = self.timestamp.value + self.comm_port.write(frame) + self.post_send_timestamp = self.timestamp.value - def closeConnection(self): - if hasattr(self, "commPort") and self.commPort.isOpen(): - self.commPort.close() + def close_connection(self) -> None: + if hasattr(self, "comm_port") and self.comm_port.is_open() and not self.has_user_supplied_interface: + self.comm_port.close() diff --git a/pyxcp/transport/transport_wrapper.cpp b/pyxcp/transport/transport_wrapper.cpp new file mode 100644 index 00000000..e69de29b diff --git a/pyxcp/transport/usb_transport.py b/pyxcp/transport/usb_transport.py index e4fa25fa..28a08c0a 100644 --- a/pyxcp/transport/usb_transport.py +++ b/pyxcp/transport/usb_transport.py @@ -1,55 +1,57 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import struct import threading from array import array from collections import deque -from time import perf_counter -from time import sleep -from time import time +from typing import Optional -import usb.backend.libusb1 as libusb1 import usb.backend.libusb0 as libusb0 +import usb.backend.libusb1 as libusb1 import usb.backend.openusb as openusb import usb.core import usb.util +from usb.core import USBError, USBTimeoutError from pyxcp.transport.base import BaseTransport -from pyxcp.utils import SHORT_SLEEP +from pyxcp.utils import short_sleep + RECV_SIZE = 16384 +FIVE_MS = 5_000_000 # Five milliseconds in nanoseconds. class Usb(BaseTransport): """""" - PARAMETER_MAP = { - # Type Req'd Default - "serial_number": (str, True, ""), - "configuration_number": (int, True, 1), - "interface_number": (int, True, 2), - "command_endpoint_number": (int, True, 0), - "reply_endpoint_number": (int, True, 1), - "vendor_id": (int, False, 0), - "product_id": (int, False, 0), - "library": (str, False, "") # absolute path to USB shared library - } HEADER = struct.Struct("<2H") HEADER_SIZE = HEADER.size - def __init__(self, config=None, policy=None): - super(Usb, self).__init__(config, policy) - self.loadConfig(config) - self.serial_number = self.config.get("serial_number").strip() - self.vendor_id = self.config.get("vendor_id") - self.product_id = self.config.get("product_id") - self.configuration_number = self.config.get("configuration_number") - self.interface_number = self.config.get("interface_number") - self.command_endpoint_number = self.config.get("command_endpoint_number") - self.reply_endpoint_number = self.config.get("reply_endpoint_number") + def __init__(self, config=None, policy=None, transport_layer_interface: Optional[usb.core.Device] = None): + super().__init__(config, policy, transport_layer_interface) + self.load_config(config) + self.serial_number: str = self.config.serial_number + self.vendor_id: int = self.config.vendor_id + self.product_id: int = self.config.product_id + self.configuration_number: int = self.config.configuration_number + self.interface_number: int = self.config.interface_number + self.library: str = self.config.library + self.header_format: str = self.config.header_format self.library = self.config.get("library") self.device = None + ## IN-EP (RES/ERR, DAQ, and EV/SERV) Parameters. + self.in_ep_number: int = self.config.in_ep_number + self.in_ep_transfer_type = self.config.in_ep_transfer_type + self.in_ep_max_packet_size: int = self.config.in_ep_max_packet_size + self.in_ep_polling_interval: int = self.config.in_ep_polling_interval + self.in_ep_message_packing = self.config.in_ep_message_packing + self.in_ep_alignment = self.config.in_ep_alignment + self.in_ep_recommended_host_bufsize: int = self.config.in_ep_recommended_host_bufsize + + ## OUT-EP (CMD and STIM) Parameters. + self.out_ep_number: int = self.config.out_ep_number + + self.device: Optional[usb.core.Device] = None self.status = 0 self._packet_listener = threading.Thread( @@ -86,10 +88,10 @@ def connect(self): if device.serial_number.strip().strip("\0").lower() == self.serial_number.lower(): self.device = device break - except BaseException: + except (USBError, USBTimeoutError): continue else: - raise Exception("Device with serial {} not found".format(self.serial_number)) + raise Exception(f"XCPonUSB - device with serial {self.serial_number!r} not found") current_configuration = self.device.get_active_configuration() if current_configuration.bConfigurationValue != self.configuration_number: @@ -98,14 +100,14 @@ def connect(self): interface = cfg[(self.interface_number, 0)] - self.command_endpoint = interface[self.command_endpoint_number] - self.reply_endpoint = interface[self.reply_endpoint_number] + self.out_ep = interface[self.out_ep_number] + self.in_ep = interface[self.in_ep_number] - self.startListener() + self.start_listener() self.status = 1 # connected - def startListener(self): - super().startListener() + def start_listener(self): + super().start_listener() if self._packet_listener.is_alive(): self._packet_listener.join() self._packet_listener = threading.Thread(target=self._packet_listen) @@ -113,83 +115,68 @@ def startListener(self): def close(self): """Close the transport-layer connection and event-loop.""" - self.finishListener() + self.finish_listener() if self.listener.is_alive(): self.listener.join() if self._packet_listener.is_alive(): self._packet_listener.join() - self.closeConnection() + self.close_connection() def _packet_listen(self): - - close_event_set = self.closeEvent.isSet - + close_event_set = self.closeEvent.is_set _packets = self._packets - read = self.reply_endpoint.read - + read = self.in_ep.read buffer = array("B", bytes(RECV_SIZE)) buffer_view = memoryview(buffer) - while True: try: if close_event_set(): return - try: - recv_timestamp = time() + recv_timestamp = self.timestamp.value read_count = read(buffer, 100) # 100ms timeout if read_count != RECV_SIZE: _packets.append((buffer_view[:read_count].tobytes(), recv_timestamp)) else: _packets.append((buffer.tobytes(), recv_timestamp)) - except BaseException: + except (USBError, USBTimeoutError): # print(format_exc()) - sleep(SHORT_SLEEP) + short_sleep() continue - - except BaseException: + except BaseException: # noqa: B036 + # Note: catch-all only permitted if the intention is re-raising. self.status = 0 # disconnected break def listen(self): HEADER_UNPACK_FROM = self.HEADER.unpack_from HEADER_SIZE = self.HEADER_SIZE - popleft = self._packets.popleft - - processResponse = self.processResponse - close_event_set = self.closeEvent.isSet - + process_response = self.process_response + close_event_set = self.closeEvent.is_set _packets = self._packets - length, counter = None, None - - data = bytearray(b"") - - last_sleep = perf_counter() + length: Optional[int] = None + counter: int = 0 + data: bytearray = bytearray(b"") + last_sleep: int = self.timestamp.value while True: if close_event_set(): return - - count = len(_packets) - + count: int = len(_packets) if not count: - sleep(SHORT_SLEEP) - last_sleep = perf_counter() + short_sleep() + last_sleep = self.timestamp.value continue - for _ in range(count): bts, timestamp = popleft() - data += bts - current_size = len(data) - current_position = 0 - + current_size: int = len(data) + current_position: int = 0 while True: - if perf_counter() - last_sleep >= 0.005: - sleep(SHORT_SLEEP) - last_sleep = perf_counter() - + if self.timestamp.value - last_sleep >= FIVE_MS: + short_sleep() + last_sleep = self.timestamp.value if length is None: if current_size >= HEADER_SIZE: length, counter = HEADER_UNPACK_FROM(data, current_position) @@ -201,31 +188,26 @@ def listen(self): else: if current_size >= length: response = data[current_position : current_position + length] - processResponse(response, length, counter, timestamp) - + process_response(response, length, counter, timestamp) current_size -= length current_position += length - length = None - else: - data = data[current_position:] break def send(self, frame): - - self.pre_send_timestamp = time() + self.pre_send_timestamp = self.timestamp.value try: - self.command_endpoint.write(frame) - except BaseException: + self.out_ep.write(frame) + except (USBError, USBTimeoutError): # sometimes usb.core.USBError: [Errno 5] Input/Output Error is raised # even though the command is send and a reply is received from the device. # Ignore this here since a Timeout error will be raised anyway if # the device does not respond pass - self.post_send_timestamp = time() + self.post_send_timestamp = self.timestamp.value - def closeConnection(self): + def close_connection(self): if self.device is not None: usb.util.dispose_resources(self.device) diff --git a/pyxcp/types.py b/pyxcp/types.py index 26e3678c..f4489c4c 100644 --- a/pyxcp/types.py +++ b/pyxcp/types.py @@ -1,28 +1,30 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import enum from collections import namedtuple import construct -from construct import BitsInteger -from construct import BitStruct -from construct import Enum -from construct import Flag -from construct import GreedyBytes -from construct import GreedyRange -from construct import If -from construct import IfThenElse -from construct import Int16ub -from construct import Int16ul -from construct import Int32ub -from construct import Int32ul -from construct import Int64ub -from construct import Int64ul -from construct import Int8ul -from construct import Padding -from construct import Struct -from construct import Switch -from construct import this +from construct import ( + BitsInteger, + BitStruct, + Bytes, + Enum, + Flag, + GreedyBytes, + GreedyRange, + If, + IfThenElse, + Int8ul, + Int16ub, + Int16ul, + Int32ub, + Int32ul, + Int64ub, + Int64ul, + Padding, + Struct, + Switch, + this, +) if construct.version < (2, 8): @@ -735,6 +737,11 @@ class Event(enum.IntEnum): "eventChannelPriority" / Int8ul, ) +GetSlaveIdResponse = Struct( + "magic" / Bytes(3), + "identifier" / Int32u, +) + GetDaqIdResponse = Struct( "canIdFixed" / Enum( @@ -909,6 +916,7 @@ class Event(enum.IntEnum): DbgLlbtResponse = Struct(Padding(1), "length" / Int16u, "data" / Int8ul[this.length]) +# Convert to seconds. DAQ_TIMESTAMP_UNIT_TO_EXP = { "DAQ_TIMESTAMP_UNIT_1PS": -12, "DAQ_TIMESTAMP_UNIT_10PS": -11, @@ -925,6 +933,39 @@ class Event(enum.IntEnum): "DAQ_TIMESTAMP_UNIT_1S": 0, } +# Convert to nano-seconds. +DAQ_TIMESTAMP_UNIT_TO_NS = { + "DAQ_TIMESTAMP_UNIT_1PS": 0.001, + "DAQ_TIMESTAMP_UNIT_10PS": 0.01, + "DAQ_TIMESTAMP_UNIT_100PS": 0.1, + "DAQ_TIMESTAMP_UNIT_1NS": 1, + "DAQ_TIMESTAMP_UNIT_10NS": 10, + "DAQ_TIMESTAMP_UNIT_100NS": 100, + "DAQ_TIMESTAMP_UNIT_1US": 1000, + "DAQ_TIMESTAMP_UNIT_10US": 10 * 1000, + "DAQ_TIMESTAMP_UNIT_100US": 100 * 1000, + "DAQ_TIMESTAMP_UNIT_1MS": 1000 * 1000, + "DAQ_TIMESTAMP_UNIT_10MS": 10 * 1000 * 1000, + "DAQ_TIMESTAMP_UNIT_100MS": 100 * 1000 * 1000, + "DAQ_TIMESTAMP_UNIT_1S": 1000 * 1000 * 1000, +} + +EVENT_CHANNEL_TIME_UNIT_TO_EXP = { + "EVENT_CHANNEL_TIME_UNIT_1PS": -12, + "EVENT_CHANNEL_TIME_UNIT_10PS": -11, + "EVENT_CHANNEL_TIME_UNIT_100PS": -10, + "EVENT_CHANNEL_TIME_UNIT_1NS": -9, + "EVENT_CHANNEL_TIME_UNIT_10NS": -8, + "EVENT_CHANNEL_TIME_UNIT_100NS": -7, + "EVENT_CHANNEL_TIME_UNIT_1US": -6, + "EVENT_CHANNEL_TIME_UNIT_10US": -5, + "EVENT_CHANNEL_TIME_UNIT_100US": -4, + "EVENT_CHANNEL_TIME_UNIT_1MS": -3, + "EVENT_CHANNEL_TIME_UNIT_10MS": -2, + "EVENT_CHANNEL_TIME_UNIT_100MS": -1, + "EVENT_CHANNEL_TIME_UNIT_1S": 0, +} + class XcpGetSeedMode(enum.IntEnum): FIRST_PART = 0 @@ -942,3 +983,11 @@ class FrameCategory(enum.IntEnum): SERV = 5 DAQ = 6 STIM = 7 + + +class TryCommandResult(enum.IntEnum): + """ """ + + OK = 0 + XCP_ERROR = 1 + OTHER_ERROR = 2 diff --git a/pyxcp/utils.py b/pyxcp/utils.py index 1e2b9345..c475ec4f 100644 --- a/pyxcp/utils.py +++ b/pyxcp/utils.py @@ -1,12 +1,15 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- +import datetime +import functools +import operator import sys from binascii import hexlify -from time import get_clock_info -from time import perf_counter -from time import time +from time import perf_counter, sleep import chardet +import pytz + +from pyxcp.cpp_ext import TimestampInfo def hexDump(arr): @@ -14,7 +17,7 @@ def hexDump(arr): size = len(arr) try: arr = arr.hex() - except BaseException: + except BaseException: # noqa: B036 arr = hexlify(arr).decode("ascii") return "[{}]".format(" ".join([arr[i * 2 : (i + 1) * 2] for i in range(size)])) elif isinstance(arr, (list, tuple)): @@ -22,28 +25,34 @@ def hexDump(arr): size = len(arr) try: arr = arr.hex() - except BaseException: + except BaseException: # noqa: B036 arr = hexlify(arr).decode("ascii") return "[{}]".format(" ".join([arr[i * 2 : (i + 1) * 2] for i in range(size)])) else: - return "[{}]".format(" ".join(["{:02x}".format(x) for x in arr])) + return "[{}]".format(" ".join([f"{x:02x}" for x in arr])) + + +def seconds_to_nanoseconds(value: float) -> int: + return int(value * 1_000_000_000) def slicer(iterable, sliceLength, converter=None): if converter is None: converter = type(iterable) length = len(iterable) - return [converter((iterable[item : item + sliceLength])) for item in range(0, length, sliceLength)] + return [converter(iterable[item : item + sliceLength]) for item in range(0, length, sliceLength)] + + +def functools_reduce_iconcat(a): + return functools.reduce(operator.iconcat, a, []) def flatten(*args): - result = [] - for arg in list(args): - if hasattr(arg, "__iter__"): - result.extend(flatten(*arg)) - else: - result.append(arg) - return result + """Flatten a list of lists into a single list. + + s. https://stackoverflow.com/questions/952914/how-do-i-make-a-flat-list-out-of-a-list-of-lists + """ + return functools.reduce(operator.iconcat, args, []) def getPythonVersion(): @@ -60,7 +69,10 @@ def decode_bytes(byte_str: bytes) -> str: PYTHON_VERSION = getPythonVersion() -SHORT_SLEEP = 0.0005 + + +def short_sleep(): + sleep(0.0005) def delay(amount: float): @@ -69,3 +81,22 @@ def delay(amount: float): start = perf_counter() while perf_counter() < start + amount: pass + + +class CurrentDatetime(TimestampInfo): + + def __init__(self, timestamp_ns: int): + TimestampInfo.__init__(self, timestamp_ns) + timezone = pytz.timezone(self.timezone) + dt = datetime.datetime.fromtimestamp(timestamp_ns / 1_000_000_000.0) + self.utc_offset = int(timezone.utcoffset(dt).total_seconds() / 60) + self.dst_offset = int(timezone.dst(dt).total_seconds() / 60) + + def __str__(self): + return f"""CurrentDatetime( + datetime="{datetime.datetime.fromtimestamp(self.timestamp_ns / 1_000_000_000.0)!s}", + timezone="{self.timezone}", + timestamp_ns={self.timestamp_ns}, + utc_offset={self.utc_offset}, + dst_offset={self.dst_offset} +)""" diff --git a/pyxcp/vector/map.py b/pyxcp/vector/map.py index d03f1337..e9356f7a 100644 --- a/pyxcp/vector/map.py +++ b/pyxcp/vector/map.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -from enum import IntEnum MAP_NAMES = { 1: "BorlandC 16 Bit", @@ -81,4 +79,4 @@ def mapfile_name(name, counter, fmt): - return "{:2d}{:d}{:s}.map".format(fmt, counter, name) + return f"{fmt:2d}{counter:d}{name:s}.map" diff --git a/reformat.cmd b/reformat.cmd new file mode 100644 index 00000000..bd1a0beb --- /dev/null +++ b/reformat.cmd @@ -0,0 +1 @@ +clang-format -i .\pyxcp\cpp_ext\*.cpp .\pyxcp\recorder\*.cpp .\pyxcp\cpp_ext\*.hpp .\pyxcp\recorder\*.hpp .\pyxcp\daq_stim\*.hpp .\pyxcp\daq_stim\*.cpp diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 5c172903..00000000 --- a/requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -pybind11 -pytest -pytest-runner -construct>=2.9 -mako -traitlets -chardet -pyserial -numpydoc -sphinxcontrib-napoleon -toml -pyusb -win-precise-time; sys_platform == 'win32' diff --git a/selective_tests.py b/selective_tests.py index 461ebd7e..156c9127 100644 --- a/selective_tests.py +++ b/selective_tests.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- import sys from pathlib import Path @@ -16,7 +15,7 @@ def main(args): print("SELECTIVE_TESTS called with:", args) dirs = set() args = args[1:] - with open("st.txt", "wt") as of: + with open("st.txt", "w") as of: for arg in args: parent = str(Path(arg).parent) dirs.add(parent) diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index c0d459d4..00000000 --- a/setup.cfg +++ /dev/null @@ -1,23 +0,0 @@ -[aliases] -test=pytest - -[options] -python_requires = ">=3.7" - -[tool:pytest] -addopts = --verbose --tb=short --junitxml=result.xml -testpaths = pyxcp/tests -junit_family=legacy - -[flake8] -max-line-length = 132 -ignore = D203, E203, E266, E501, W503, F403, F401, BLK100 -select = B,C,E,F,W,T4,B9 -count = 1 -statistics = 1 -show-source = 1 -exclude=.git, __pycache__, .mypy_cache, .tox, .venv, .eggs, _build, build, docs, dist - -[black] -line-length = 132 -exclude = .git, .mypy_cache, .tox, .venv, _build, build, docs, __pypackages__, __pycache__, dist diff --git a/setup.py b/setup.py deleted file mode 100644 index aecab8f4..00000000 --- a/setup.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/bin/env python -import os -import platform -import subprocess -import sys - -import setuptools.command.build_py -import setuptools.command.develop - -if sys.platform == "darwin": - os.environ["CC"] = "clang++" - os.environ["CXX"] = "clang++" - -try: - from pybind11.setup_helpers import ( - Pybind11Extension, - build_ext, - ParallelCompile, - naive_recompile, - ) -except ImportError: - print("package 'pybind11' not installed, could not build recorder extension module.") - has_pybind11 = False -else: - has_pybind11 = True - ParallelCompile("NPY_NUM_BUILD_JOBS", needs_recompile=naive_recompile).install() - -try: - PYB11_INCLUDE_DIRS = subprocess.check_output(["pybind11-config", "--includes"]) -except Exception as e: - print(str(e), end=" -- ") - has_pybind11 = False - print("'pybind11-config' not properly working, could not build recorder extension module.") - -with open(os.path.join("pyxcp", "__init__.py"), "r") as f: - for line in f: - if line.startswith("__version__"): - version = line.split("=")[-1].strip().strip('"') - break - -with open("README.md", "r") as fh: - long_description = fh.read() - - -EXT_NAMES = ["rekorder"] - -if has_pybind11: - ext_modules = [ - Pybind11Extension( - EXT_NAMES[0], - include_dirs=[PYB11_INCLUDE_DIRS, "pyxcp/recorder"], - sources=["pyxcp/recorder/lz4.c", "pyxcp/recorder/wrap.cpp"], - define_macros=[("EXTENSION_NAME", EXT_NAMES[0]), ("NDEBUG", 1)], - optional=False, - cxx_std=20, - ), - ] -else: - ext_modules = [] - -install_reqs = [ - "pybind11", - "pyusb", - "construct >= 2.9.0", - "mako", - "pyserial", - "toml", - "python-can", - "uptime", - "chardet", - "traitlets", -] - - -class AsamKeyDllAutogen(setuptools.Command): - """Custom command to compile `asamkeydll.exe`.""" - - description = "Compile `asamkeydll.exe`." - - def initialize_options(self): - pass - - def finalize_options(self): - """Post-process options.""" - asamkeydll = os.path.join("pyxcp", "asamkeydll.c") - target = os.path.join("pyxcp", "asamkeydll.exe") - self.arguments = [asamkeydll, "-o{}".format(target)] - - def run(self): - """Run gcc""" - word_width, _ = platform.architecture() - if sys.platform == "win32" and word_width == "64bit": - gccCmd = ["gcc", "-m32", "-O3", "-Wall"] - self.announce(" ".join(gccCmd + self.arguments)) - try: - subprocess.check_call(gccCmd + self.arguments) - except Exception as e: - print("Building pyxcp/asamkeydll.exe failed: '{}'".format(str(e))) - else: - print("Successfully build pyxcp/asamkeydll.exe") - - -class CustomBuildPy(setuptools.command.build_py.build_py): - def run(self): - self.run_command("asamkeydll") - super().run() - - -class CustomDevelop(setuptools.command.develop.develop): - def run(self): - self.run_command("asamkeydll") - super().run() - - -setuptools.setup( - name="pyxcp", - version=version, - provides=["pyxcp"], - description="Universal Calibration Protocol for Python", - long_description=long_description, - long_description_content_type="text/markdown", - author="Christoph Schueler", - author_email="cpu12.gems@googlemail.com", - url="https://github.com/christoph2/pyxcp", - packages=setuptools.find_packages(), - cmdclass={ - "asamkeydll": AsamKeyDllAutogen, - "build_py": CustomBuildPy, - "develop": CustomDevelop, - }, - python_requires=">=3.7", - include_package_data=True, - install_requires=install_reqs, - extras_require={"docs": ["sphinxcontrib-napoleon"], "develop": ["bumpversion"]}, - ext_modules=ext_modules, - package_dir={"tests": "pyxcp/tests"}, - zip_safe=False, - tests_require=["pytest", "pytest-runner"], - test_suite="pyxcp.tests", - license="LGPLv3+", - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - # How mature is this project? Common values are - # 3 - Alpha - # 4 - Beta - # 5 - Production/Stable - "Development Status :: 5 - Production/Stable", - # Indicate who your project is intended for - "Intended Audience :: Developers", - "Topic :: Software Development", - "Topic :: Scientific/Engineering", - # Pick your license as you wish (should match "license" above) - "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", - # Specify the Python versions you support here. In particular, ensure - # that you indicate whether you support Python 2, Python 3 or both. - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - ], - entry_points={ - "console_scripts": [ - "pyxcp-probe-can-drivers = pyxcp.scripts.pyxcp_probe_can_drivers:main", - "xcp-id-scanner = pyxcp.scripts.xcp_id_scanner:main", - "xcp-fetch-a2l = pyxcp.scripts.xcp_fetch_a2l:main", - "xcp-info = pyxcp.scripts.xcp_info:main", - ], - }, -)