├── setup.cfg ├── docsrc ├── source │ ├── api │ │ ├── io.rst │ │ ├── s3.rst │ │ ├── mqtt5.rst │ │ ├── common.rst │ │ ├── crypto.rst │ │ ├── checksums.rst │ │ ├── websocket.rst │ │ ├── exceptions.rst │ │ ├── mqtt.rst │ │ ├── auth.rst │ │ ├── http.rst │ │ ├── eventstream.rst │ │ └── mqtt_request_response.rst │ ├── _static │ │ ├── .gitkeep │ │ └── css │ │ │ └── custom.css │ ├── index.rst │ └── conf.py ├── Makefile └── make.bat ├── test ├── resources │ ├── unittest.p12 │ ├── example_profile │ ├── example_config │ ├── ca.crt │ ├── unittest.key │ ├── unittest.crt │ └── rootCA.crt ├── test_common.py ├── test_exceptions.py ├── test_appexit.py ├── __init__.py ├── appexit_http.py ├── test_eventstream.py └── test_http_headers.py ├── NOTICE ├── awscrt ├── aio │ └── __init__.py ├── common.py ├── __init__.py ├── exceptions.py ├── checksums.py └── _test.py ├── scripts ├── install-dev.py ├── format-all.py ├── format-python.py ├── make-docs.py ├── utils.py └── clean.py ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── documentation.yml │ ├── feature-request.yml │ └── bug-report.yml └── workflows │ ├── handle-stale-discussions.yml │ ├── lint.yml │ ├── closed-issue-message.yml │ ├── issue-regression-labeler.yml │ ├── docs.yml │ └── stale_issue.yml ├── continuous-delivery ├── sanity-check-test-pypi.sh ├── test-pip-install.py ├── sanity-check-test-pypi.bat ├── build-wheels-win64.bat ├── build-wheels-manylinux2014-x86_64-jenkins.sh ├── build-wheels-musllinux-1-1-x86_64-jenkins.sh ├── build-wheels-manylinux2014-aarch64-jenkins.sh ├── build-wheels-musllinux-1-1-aarch64-jenkins.sh ├── build-wheels-win32.bat ├── update-version.py ├── build-wheels-osx.sh ├── test-version-exists ├── build-wheels-musllinux-1-1-aarch64.sh ├── build-wheels-musllinux-1-1-x86_64.sh ├── build-wheels-manylinux2014-x86_64.sh ├── build-wheels-manylinux2014-aarch64.sh ├── pip-install-with-retry.py └── pull-pypirc.py ├── CODE_OF_CONDUCT.md ├── guides ├── README.md └── dev │ ├── vscode │ └── tasks.json │ └── binding-simple.svg ├── codebuild ├── mqtt5-python-canary-test.sh ├── linux-integration-tests.sh ├── cd │ ├── test_version_exists.yml │ ├── windows-tee.yml │ ├── test_prod_pypi.yml │ ├── test_test_pypi.yml │ ├── manylinux1-tee.yml │ ├── manylinux-x86-build.yml │ ├── manylinux-x64-build.yml │ ├── publish_to_prod_pypi.yml │ └── publish_to_test_pypi.yml ├── linux-integration-tests.yml ├── mqtt5-python-canary-test.yml ├── CanaryWrapper_MetricFunctions.py └── mqtt5_test_setup.sh ├── source ├── common.h ├── mqtt_client.h ├── websocket.h ├── checksums.h ├── mqtt5_client.h ├── mqtt_request_response.h ├── s3.h ├── pkcs11_lib.c ├── mqtt_client_connection.h ├── common.c ├── event_stream.h ├── mqtt_client.c ├── crypto.h ├── http_proxy.c ├── auth.h ├── io.h ├── cbor.h ├── http.h ├── auth_signer.c ├── http_message.c └── crc.c ├── .clang-tidy ├── pyproject.toml ├── format-check.py ├── .gitmodules ├── .clang-format ├── MANIFEST.in ├── .builder └── actions │ └── aws_crt_python.py ├── CONTRIBUTING.md ├── crt └── CMakeLists.txt ├── mqtt_test.py ├── s3_benchmark.py └── README.md /setup.cfg: -------------------------------------------------------------------------------- 1 | [pep8] 2 | max-line-length = 120 3 | aggressive = 2 4 | -------------------------------------------------------------------------------- /docsrc/source/api/io.rst: -------------------------------------------------------------------------------- 1 | awscrt.io 2 | ========= 3 | 4 | .. automodule:: awscrt.io 5 | :members: 6 | -------------------------------------------------------------------------------- /docsrc/source/api/s3.rst: -------------------------------------------------------------------------------- 1 | awscrt.s3 2 | ========= 3 | 4 | .. automodule:: awscrt.s3 5 | :members: 6 | -------------------------------------------------------------------------------- /docsrc/source/_static/.gitkeep: -------------------------------------------------------------------------------- 1 | file exists so git will keep directory 2 | delete when anything else lives here -------------------------------------------------------------------------------- /test/resources/unittest.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/aws-crt-python/HEAD/test/resources/unittest.p12 -------------------------------------------------------------------------------- /docsrc/source/api/mqtt5.rst: -------------------------------------------------------------------------------- 1 | awscrt.mqtt5 2 | ============ 3 | 4 | .. automodule:: awscrt.mqtt5 5 | :members: 6 | -------------------------------------------------------------------------------- /docsrc/source/api/common.rst: -------------------------------------------------------------------------------- 1 | awscrt.common 2 | ============= 3 | 4 | .. automodule:: awscrt.common 5 | :members: 6 | -------------------------------------------------------------------------------- /docsrc/source/api/crypto.rst: -------------------------------------------------------------------------------- 1 | awscrt.crypto 2 | ============= 3 | 4 | .. automodule:: awscrt.crypto 5 | :members: 6 | -------------------------------------------------------------------------------- /docsrc/source/api/checksums.rst: -------------------------------------------------------------------------------- 1 | awscrt.checksums 2 | ================ 3 | 4 | .. automodule:: awscrt.checksums 5 | :members: 6 | -------------------------------------------------------------------------------- /docsrc/source/api/websocket.rst: -------------------------------------------------------------------------------- 1 | awscrt.websocket 2 | ================ 3 | 4 | .. automodule:: awscrt.websocket 5 | :members: 6 | -------------------------------------------------------------------------------- /docsrc/source/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | @import "../bizstyle.css"; 2 | 3 | span.pre { 4 | white-space: pre-wrap !important; 5 | } 6 | -------------------------------------------------------------------------------- /docsrc/source/api/exceptions.rst: -------------------------------------------------------------------------------- 1 | awscrt.exceptions 2 | ================= 3 | 4 | .. automodule:: awscrt.exceptions 5 | :members: 6 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | AWS Crt Python 2 | Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | SPDX-License-Identifier: Apache-2.0. 4 | -------------------------------------------------------------------------------- /docsrc/source/api/mqtt.rst: -------------------------------------------------------------------------------- 1 | awscrt.mqtt 2 | =========== 3 | 4 | .. automodule:: awscrt.mqtt 5 | :members: 6 | :exclude-members: reconnect -------------------------------------------------------------------------------- /awscrt/aio/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | __all__ = ['http'] 5 | -------------------------------------------------------------------------------- /docsrc/source/api/auth.rst: -------------------------------------------------------------------------------- 1 | awscrt.auth 2 | =========== 3 | 4 | .. automodule:: awscrt.auth 5 | :members: 6 | :exclude-members: AwsCredentialsProviderBase 7 | -------------------------------------------------------------------------------- /scripts/install-dev.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import utils 3 | 4 | utils.chdir_project_root() 5 | 6 | utils.run('python3 -m pip install --verbose --editable ".[dev]"') 7 | -------------------------------------------------------------------------------- /scripts/format-all.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import utils 3 | 4 | utils.chdir_project_root() 5 | 6 | utils.run('python3 format-check.py -i') 7 | utils.run('python3 scripts/format-python.py') 8 | -------------------------------------------------------------------------------- /docsrc/source/api/http.rst: -------------------------------------------------------------------------------- 1 | awscrt.http 2 | =========== 3 | 4 | .. automodule:: awscrt.http 5 | :members: 6 | :inherited-members: 7 | :exclude-members: HttpConnectionBase, HttpStreamBase, HttpMessageBase -------------------------------------------------------------------------------- /docsrc/source/api/eventstream.rst: -------------------------------------------------------------------------------- 1 | awscrt.eventstream 2 | ================== 3 | 4 | .. automodule:: awscrt.eventstream 5 | :members: 6 | 7 | .. automodule:: awscrt.eventstream.rpc 8 | :members: 9 | 10 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /test/resources/example_profile: -------------------------------------------------------------------------------- 1 | [default] 2 | aws_access_key_id = default_access_key_id 3 | aws_secret_access_key = default_secret_access_key 4 | 5 | [crt_user] 6 | aws_access_key_id = example_access_key_id 7 | aws_secret_access_key = example_secret_access_key -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 💬 General Question 4 | url: https://github.com/awslabs/aws-crt-python/discussions/categories/q-a 5 | about: Please ask and answer questions as a discussion thread 6 | -------------------------------------------------------------------------------- /continuous-delivery/sanity-check-test-pypi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | CURRENT_TAG_VERSION=$(git describe --tags | cut -f2 -dv) 4 | python3 continuous-delivery/pip-install-with-retry.py --no-cache-dir -i https://testpypi.python.org/simple --user awscrt==$CURRENT_TAG_VERSION 5 | python3 continuous-delivery/test-pip-install.py 6 | -------------------------------------------------------------------------------- /continuous-delivery/test-pip-install.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | import awscrt.io 5 | 6 | print('if the next statement does not explode, the pip install was successful') 7 | print('Is alpn supported? {}', awscrt.io.is_alpn_available()) 8 | -------------------------------------------------------------------------------- /docsrc/source/api/mqtt_request_response.rst: -------------------------------------------------------------------------------- 1 | awscrt.mqtt_request_response 2 | ============================ 3 | 4 | .. automodule:: awscrt.mqtt_request_response 5 | :members: 6 | :exclude-members: IncomingPublishEvent, IncomingPublishListener, StreamingOperationOptions, Response, ResponsePath, RequestOptions, Client, StreamingOperation -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /guides/README.md: -------------------------------------------------------------------------------- 1 | # AWS Common Runtime (CRT) for Python: Documentation 2 | ## CRT Usage 3 | - [General information](https://github.com/awslabs/aws-crt-python#readme) (installation, etc) 4 | - [API documentation](https://awslabs.github.io/aws-crt-python/) 5 | ## CRT Development 6 | - [Development guide](dev/README.md) for contributors to aws-crt-python's source code. 7 | -------------------------------------------------------------------------------- /test/resources/example_config: -------------------------------------------------------------------------------- 1 | [profile test_process_provider] 2 | credential_process=echo "{\"Version\": 1, \"AccessKeyId\": \"process_access_key_id\", \"SecretAccessKey\": \"process_secret_access_key\"}" 3 | 4 | [profile test_process_provider_win] 5 | credential_process=cmd.exe /c echo {"Version": 1, "AccessKeyId": "process_access_key_id", "SecretAccessKey": "process_secret_access_key"} 6 | -------------------------------------------------------------------------------- /codebuild/mqtt5-python-canary-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | env 6 | 7 | git submodule update --init 8 | 9 | # build package 10 | cd $CODEBUILD_SRC_DIR 11 | 12 | export AWS_TEST_S3=YES 13 | python -m pip install --verbose . 14 | python codebuild/CanaryWrapper.py --canary_executable 'python test/mqtt5_canary.py' --git_hash ${GIT_HASH} --git_repo_name $PACKAGE_NAME --codebuild_log_path $CODEBUILD_LOG_PATH 15 | -------------------------------------------------------------------------------- /codebuild/linux-integration-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euxo pipefail 4 | 5 | if test -f "/tmp/setup_proxy_test_env.sh"; then 6 | source /tmp/setup_proxy_test_env.sh 7 | fi 8 | 9 | env 10 | 11 | git submodule update --init 12 | 13 | # build package 14 | cd $CODEBUILD_SRC_DIR 15 | 16 | export AWS_TEST_S3=YES 17 | python -m pip install --verbose ".[dev]" 18 | python -m unittest discover --failfast --verbose 2>&1 | tee /tmp/tests.log 19 | -------------------------------------------------------------------------------- /scripts/format-python.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import utils 3 | 4 | FILES_AND_FOLDERS_TO_FORMAT = [ 5 | '.builder/', 6 | 'awscrt/', 7 | 'scripts/', 8 | 'test/', 9 | 'setup.py', 10 | ] 11 | 12 | utils.chdir_project_root() 13 | 14 | utils.run(['python3', 15 | '-m', 'autopep8', 16 | '--in-place', # edit files in place 17 | '--recursive', 18 | '--jobs', '0', # parallel with all CPUs 19 | *FILES_AND_FOLDERS_TO_FORMAT]) 20 | -------------------------------------------------------------------------------- /continuous-delivery/sanity-check-test-pypi.bat: -------------------------------------------------------------------------------- 1 | FOR /F "delims=" %%A in ('git describe --tags') do ( set TAG_VERSION=%%A ) 2 | set CURRENT_VERSION=%TAG_VERSION:v=% 3 | 4 | "C:\Program Files\Python38\python.exe" continuous-delivery\pip-install-with-retry.py --no-cache-dir -i https://testpypi.python.org/simple --user awscrt==%CURRENT_VERSION% || goto error 5 | "C:\Program Files\Python38\python.exe" continuous-delivery\test-pip-install.py || goto error 6 | 7 | goto :EOF 8 | 9 | :error 10 | echo Failed with error #%errorlevel%. 11 | exit /b %errorlevel% 12 | 13 | -------------------------------------------------------------------------------- /.github/workflows/handle-stale-discussions.yml: -------------------------------------------------------------------------------- 1 | name: HandleStaleDiscussions 2 | on: 3 | schedule: 4 | - cron: '0 9 * * 1' # minute hour dom month dow 5 | discussion_comment: 6 | types: [created] 7 | 8 | jobs: 9 | handle-stale-discussions: 10 | name: Handle stale discussions 11 | runs-on: ubuntu-latest 12 | permissions: 13 | discussions: write 14 | steps: 15 | - name: Stale discussions action 16 | uses: aws-github-ops/handle-stale-discussions@v1 17 | env: 18 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 19 | 20 | -------------------------------------------------------------------------------- /test/test_common.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | from test import NativeResourceTest 5 | from awscrt.common import * 6 | 7 | 8 | class TestSystemInfo(NativeResourceTest): 9 | def test_get_cpu_group_count(self): 10 | self.assertGreater(get_cpu_group_count(), 0) 11 | 12 | def test_get_cpu_count_for_group(self): 13 | group_count = get_cpu_group_count() 14 | for group_i in range(group_count): 15 | self.assertGreater(get_cpu_count_for_group(group_i), 0) 16 | -------------------------------------------------------------------------------- /codebuild/cd/test_version_exists.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | #this image assumes base Codebuild Ubuntu image 3 | #this build run simply verifies we haven't published something at this tag yet. 4 | #if we have we fail the build and stop the pipeline, if we haven't we allow the pipeline to run. 5 | phases: 6 | install: 7 | commands: 8 | pre_build: 9 | commands: 10 | build: 11 | commands: 12 | - echo Build started on `date` 13 | - cd aws-crt-python 14 | - bash ./continuous-delivery/test-version-exists 15 | post_build: 16 | commands: 17 | - echo Build completed on `date` 18 | 19 | -------------------------------------------------------------------------------- /source/common.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_COMMON_H 2 | #define AWS_CRT_PYTHON_COMMON_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | 8 | /** 9 | * This file includes definitions for common aws-c-common functions. 10 | */ 11 | 12 | #include "module.h" 13 | 14 | PyObject *aws_py_get_cpu_group_count(PyObject *self, PyObject *args); 15 | PyObject *aws_py_get_cpu_count_for_group(PyObject *self, PyObject *args); 16 | 17 | PyObject *aws_py_thread_join_all_managed(PyObject *self, PyObject *args); 18 | 19 | #endif /* AWS_CRT_PYTHON_COMMON_H */ 20 | -------------------------------------------------------------------------------- /source/mqtt_client.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_MQTT_CLIENT_H 2 | #define AWS_CRT_PYTHON_MQTT_CLIENT_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | 8 | #include "module.h" 9 | 10 | #include 11 | 12 | PyObject *aws_py_mqtt_client_new(PyObject *self, PyObject *args); 13 | 14 | /* Given a python object, return a pointer to its underlying native type. 15 | * If NULL is returned, a python error has been set */ 16 | struct aws_mqtt_client *aws_py_get_mqtt_client(PyObject *mqtt_client); 17 | 18 | #endif /* AWS_CRT_PYTHON_MQTT_CLIENT_H */ 19 | -------------------------------------------------------------------------------- /codebuild/cd/windows-tee.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | #this build spec assumes the manylinux1 image for pypi 3 | #additional packages we installed: cmake 3.5, libcrypto 1.1.0j, gcc 4.8.4 4 | phases: 5 | install: 6 | commands: 7 | pre_build: 8 | commands: 9 | - export CC=gcc 10 | build: 11 | commands: 12 | - echo Build started on `date` 13 | - mkdir dist 14 | - cp -r $CODEBUILD_SRC_DIR_aws_crt_python_win64/* dist/ 15 | - cp -r $CODEBUILD_SRC_DIR_aws_crt_python_win32/* dist/ 16 | post_build: 17 | commands: 18 | - echo Build completed on `date` 19 | 20 | artifacts: 21 | files: 22 | - 'dist/*' 23 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'main' 7 | - 'docs' 8 | 9 | jobs: 10 | lint: 11 | runs-on: ubuntu-24.04 # latest 12 | 13 | steps: 14 | - name: Checkout Source 15 | uses: actions/checkout@v4 16 | 17 | - name: Format and Check Diff 18 | # run formatting script, which edits files in place 19 | # then do `git diff` to show what changed (if anything) 20 | # then do `git diff --quiet` to fail if anything changed 21 | run: | 22 | python3 -m pip install --upgrade autopep8 23 | python3 scripts/format-all.py 24 | git diff 25 | git diff --quiet 26 | -------------------------------------------------------------------------------- /docsrc/source/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to awscrt's documentation! 2 | ================================== 3 | Python bindings for the AWS Common Runtime. 4 | 5 | GitHub: https://github.com/awslabs/aws-crt-python 6 | 7 | PyPI: https://pypi.org/project/awscrt/ 8 | 9 | API Reference 10 | ------------- 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | api/auth 15 | api/checksums 16 | api/common 17 | api/crypto 18 | api/exceptions 19 | api/eventstream 20 | api/http 21 | api/io 22 | api/mqtt 23 | api/mqtt5 24 | api/mqtt_request_response 25 | api/s3 26 | api/websocket 27 | 28 | Indices and tables 29 | ================== 30 | 31 | * :ref:`genindex` 32 | * :ref:`search` 33 | -------------------------------------------------------------------------------- /source/websocket.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_WEBSOCKET_H 2 | #define AWS_CRT_PYTHON_WEBSOCKET_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | #include "module.h" 8 | 9 | PyObject *aws_py_websocket_client_connect(PyObject *self, PyObject *args); 10 | PyObject *aws_py_websocket_close(PyObject *self, PyObject *args); 11 | PyObject *aws_py_websocket_send_frame(PyObject *self, PyObject *args); 12 | PyObject *aws_py_websocket_increment_read_window(PyObject *self, PyObject *args); 13 | PyObject *aws_py_websocket_create_handshake_request(PyObject *self, PyObject *args); 14 | 15 | #endif /* AWS_CRT_PYTHON_WEBSOCKET_H */ 16 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'clang-diagnostic-*,clang-analyzer-*,readability-*,modernize-*,bugprone-*,misc-*,google-runtime-int,llvm-header-guard,fuchsia-restrict-system-includes,-clang-analyzer-valist.Uninitialized,-clang-analyzer-security.insecureAPI.rand,-clang-analyzer-alpha.*' 3 | WarningsAsErrors: '*' 4 | HeaderFilterRegex: '\./*' 5 | FormatStyle: 'file' 6 | CheckOptions: 7 | - key: readability-braces-around-statements.ShortStatementLines 8 | value: '1' 9 | - key: google-runtime-int.TypeSufix 10 | value: '_t' 11 | - key: fuchsia-restrict-system-includes.Includes 12 | value: '*,-stdint.h,-stdbool.h' 13 | 14 | ... 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "📕 Documentation Issue" 3 | description: Report an issue in the API Reference documentation or Developer Guide 4 | title: "(short issue description)" 5 | labels: [documentation, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the issue 12 | description: A clear and concise description of the issue. 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: links 18 | attributes: 19 | label: Links 20 | description: | 21 | Include links to affected documentation page(s). 22 | validations: 23 | required: true 24 | -------------------------------------------------------------------------------- /docsrc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /source/checksums.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_CHECKSUMS_H 2 | #define AWS_CRT_PYTHON_CHECKSUMS_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | #include "module.h" 8 | 9 | PyObject *aws_py_checksums_crc32(PyObject *self, PyObject *args); 10 | PyObject *aws_py_checksums_crc32c(PyObject *self, PyObject *args); 11 | PyObject *aws_py_checksums_crc64nvme(PyObject *self, PyObject *args); 12 | PyObject *aws_py_checksums_crc32_combine(PyObject *self, PyObject *args); 13 | PyObject *aws_py_checksums_crc32c_combine(PyObject *self, PyObject *args); 14 | PyObject *aws_py_checksums_crc64nvme_combine(PyObject *self, PyObject *args); 15 | 16 | #endif /* AWS_CRT_PYTHON_CHECKSUMS_H */ 17 | -------------------------------------------------------------------------------- /continuous-delivery/build-wheels-win64.bat: -------------------------------------------------------------------------------- 1 | "C:\Program Files\Python39\python.exe" continuous-delivery\update-version.py || goto error 2 | 3 | "C:\Program Files\Python38\python.exe" -m build || goto error 4 | "C:\Program Files\Python39\python.exe" -m build || goto error 5 | "C:\Program Files\Python310\python.exe" -m build || goto error 6 | "C:\Program Files\Python311\python.exe" -m build || goto error 7 | 8 | :: Don't need to build wheels for Python 3.12, it works with the 3.11 stable ABI wheel 9 | 10 | :: We are using the 3.13 stable ABI from 3.13 onwards because of deprecated functions. 11 | "C:\Program Files\Python313\python.exe" -m build || goto error 12 | 13 | goto :EOF 14 | 15 | :error 16 | echo Failed with error #%errorlevel%. 17 | exit /b %errorlevel% 18 | -------------------------------------------------------------------------------- /continuous-delivery/build-wheels-manylinux2014-x86_64-jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #run build-wheels script in manylinux2014 docker image 3 | set -ex 4 | 5 | DOCKER_IMAGE=123124136734.dkr.ecr.us-east-1.amazonaws.com/aws-crt-manylinux2014-x64:latest 6 | 7 | $(aws --region us-east-1 ecr get-login --no-include-email) 8 | 9 | docker pull $DOCKER_IMAGE 10 | 11 | # NOTE: run as current user to avoid git "dubious ownership" error, 12 | # and so that output artifacts don't belong to "root" 13 | docker run --rm \ 14 | --mount type=bind,source=`pwd`,target=/aws-crt-python \ 15 | --user "$(id -u):$(id -g)" \ 16 | --workdir /aws-crt-python \ 17 | --entrypoint /bin/bash \ 18 | $DOCKER_IMAGE \ 19 | continuous-delivery/build-wheels-manylinux2014-x86_64.sh 20 | -------------------------------------------------------------------------------- /continuous-delivery/build-wheels-musllinux-1-1-x86_64-jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #run build-wheels script in musllinux_1_1 docker image 3 | set -ex 4 | 5 | DOCKER_IMAGE=123124136734.dkr.ecr.us-east-1.amazonaws.com/aws-crt-musllinux-1-1-x64:latest 6 | 7 | $(aws --region us-east-1 ecr get-login --no-include-email) 8 | 9 | docker pull $DOCKER_IMAGE 10 | 11 | # NOTE: run as current user to avoid git "dubious ownership" error, 12 | # and so that output artifacts don't belong to "root" 13 | docker run --rm \ 14 | --mount type=bind,source=`pwd`,target=/aws-crt-python \ 15 | --user "$(id -u):$(id -g)" \ 16 | --workdir /aws-crt-python \ 17 | --entrypoint /bin/bash \ 18 | $DOCKER_IMAGE \ 19 | continuous-delivery/build-wheels-musllinux-1-1-x86_64.sh 20 | -------------------------------------------------------------------------------- /continuous-delivery/build-wheels-manylinux2014-aarch64-jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #run build-wheels script in manylinux2014 docker image 3 | set -ex 4 | 5 | DOCKER_IMAGE=123124136734.dkr.ecr.us-east-1.amazonaws.com/aws-crt-manylinux2014-aarch64:latest 6 | 7 | $(aws --region us-east-1 ecr get-login --no-include-email) 8 | 9 | docker pull $DOCKER_IMAGE 10 | 11 | # NOTE: run as current user to avoid git "dubious ownership" error, 12 | # and so that output artifacts don't belong to "root" 13 | docker run --rm \ 14 | --mount type=bind,source=`pwd`,target=/aws-crt-python \ 15 | --user "$(id -u):$(id -g)" \ 16 | --workdir /aws-crt-python \ 17 | --entrypoint /bin/bash \ 18 | $DOCKER_IMAGE \ 19 | continuous-delivery/build-wheels-manylinux2014-aarch64.sh 20 | -------------------------------------------------------------------------------- /continuous-delivery/build-wheels-musllinux-1-1-aarch64-jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #run build-wheels script in musllinux_1_1 docker image 3 | set -ex 4 | 5 | DOCKER_IMAGE=123124136734.dkr.ecr.us-east-1.amazonaws.com/aws-crt-musllinux-1-1-aarch64:latest 6 | 7 | $(aws --region us-east-1 ecr get-login --no-include-email) 8 | 9 | docker pull $DOCKER_IMAGE 10 | 11 | # NOTE: run as current user to avoid git "dubious ownership" error, 12 | # and so that output artifacts don't belong to "root" 13 | docker run --rm \ 14 | --mount type=bind,source=`pwd`,target=/aws-crt-python \ 15 | --user "$(id -u):$(id -g)" \ 16 | --workdir /aws-crt-python \ 17 | --entrypoint /bin/bash \ 18 | $DOCKER_IMAGE \ 19 | continuous-delivery/build-wheels-musllinux-1-1-aarch64.sh 20 | -------------------------------------------------------------------------------- /codebuild/cd/test_prod_pypi.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | # this image assumes base Codebuild Ubuntu image 3 | phases: 4 | install: 5 | commands: 6 | - sudo apt-get update -y 7 | - sudo apt-get install python3 python3-pip -y 8 | - python3 -m pip install --upgrade pip 9 | pre_build: 10 | commands: 11 | - export CC=gcc 12 | build: 13 | commands: 14 | - echo Build started on `date` 15 | - cd aws-crt-python 16 | - CURRENT_TAG_VERSION=$(git describe --tags | cut -f2 -dv) 17 | - python3 continuous-delivery/pip-install-with-retry.py --no-cache-dir --user awscrt==$CURRENT_TAG_VERSION 18 | - python3 continuous-delivery/test-pip-install.py 19 | post_build: 20 | commands: 21 | - echo Build completed on `date` 22 | 23 | -------------------------------------------------------------------------------- /codebuild/cd/test_test_pypi.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | # this image assumes base Codebuild Ubuntu image 3 | phases: 4 | install: 5 | commands: 6 | - sudo apt-get update -y 7 | - sudo apt-get install python3 python3-pip -y 8 | - python3 -m pip install --upgrade pip 9 | pre_build: 10 | commands: 11 | - export CC=gcc 12 | build: 13 | commands: 14 | - echo Build started on `date` 15 | - cd aws-crt-python 16 | - CURRENT_TAG_VERSION=$(git describe --tags | cut -f2 -dv) 17 | - python3 continuous-delivery/pip-install-with-retry.py --no-cache-dir -i https://testpypi.python.org/simple --user awscrt==$CURRENT_TAG_VERSION 18 | - python3 continuous-delivery/test-pip-install.py 19 | post_build: 20 | commands: 21 | - echo Build completed on `date` 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/closed-issue-message.yml: -------------------------------------------------------------------------------- 1 | name: Closed Issue Message 2 | on: 3 | issues: 4 | types: [closed] 5 | jobs: 6 | auto_comment: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: aws-actions/closed-issue-message@v1 10 | with: 11 | # These inputs are both required 12 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 13 | message: | 14 | ### ⚠️COMMENT VISIBILITY WARNING⚠️ 15 | Comments on closed issues are hard for our team to see. 16 | If you need more assistance, please either tag a team member or open a new issue that references this one. 17 | If you wish to keep having a conversation with other community members under this issue feel free to do so. 18 | -------------------------------------------------------------------------------- /continuous-delivery/build-wheels-win32.bat: -------------------------------------------------------------------------------- 1 | 2 | "C:\Program Files (x86)\Python39-32\python.exe" .\continuous-delivery\update-version.py || goto error 3 | 4 | "C:\Program Files (x86)\Python38-32\python.exe" -m build || goto error 5 | "C:\Program Files (x86)\Python39-32\python.exe" -m build || goto error 6 | "C:\Program Files (x86)\Python310-32\python.exe" -m build || goto error 7 | "C:\Program Files (x86)\Python311-32\python.exe" -m build || goto error 8 | 9 | :: Don't need to build wheels for Python 3.12, it works with the 3.11 stable ABI wheel 10 | 11 | :: We are using the 3.13 stable ABI from 3.13 onwards because of deprecated functions. 12 | "C:\Program Files (x86)\Python313-32\python.exe" -m build || goto error 13 | 14 | goto :EOF 15 | 16 | :error 17 | echo Failed with error #%errorlevel%. 18 | exit /b %errorlevel% 19 | -------------------------------------------------------------------------------- /continuous-delivery/update-version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import re 5 | import subprocess 6 | 7 | tag = subprocess.run(['git', 'describe', '--tags'], 8 | capture_output=True, check=True, 9 | text=True).stdout.strip() 10 | # convert v0.2.12-2-g50254a9 to 0.2.12 11 | # test-version-exists will ensure to not include non-tagged commits 12 | version = tag.strip("v").split('-', 1)[0] 13 | 14 | init_path = os.path.join(os.path.dirname(__file__), '..', 'awscrt', '__init__.py') 15 | print("Updating awscrt.__version__ to version {}".format(version)) 16 | contents = None 17 | with open(init_path, 'r+') as init_py: 18 | contents = init_py.read() 19 | 20 | contents = re.sub(r"__version__ = '[^']+'", f"__version__ = '{version}'", contents) 21 | 22 | with open(init_path, 'w') as init_py: 23 | init_py.write(contents) 24 | -------------------------------------------------------------------------------- /codebuild/cd/manylinux1-tee.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | #this build spec assumes the manylinux1 image for pypi 3 | #additional packages we installed: cmake 3.5, libcrypto 1.1.0j, gcc 4.8.4 4 | phases: 5 | install: 6 | commands: 7 | pre_build: 8 | commands: 9 | - export CC=gcc 10 | build: 11 | commands: 12 | - echo Build started on `date` 13 | - mkdir $CODEBUILD_SRC_DIR/dist 14 | - cp -r $CODEBUILD_SRC_DIR_manylinux1_x86_64/dist/* $CODEBUILD_SRC_DIR/dist/ 15 | - cp -r $CODEBUILD_SRC_DIR_manylinux1_x86/dist/* $CODEBUILD_SRC_DIR/dist/ 16 | - cp -r $CODEBUILD_SRC_DIR_manylinux2014_arm/* $CODEBUILD_SRC_DIR/dist/ 17 | - cp -r $CODEBUILD_SRC_DIR_manylinux2014_x86_64/* $CODEBUILD_SRC_DIR/dist/ 18 | - ls $CODEBUILD_SRC_DIR/dist/ 19 | post_build: 20 | commands: 21 | - echo Build completed on `date` 22 | 23 | artifacts: 24 | files: 25 | - 'dist/*' 26 | -------------------------------------------------------------------------------- /test/test_exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | import awscrt.exceptions 5 | import unittest 6 | 7 | 8 | class AwsCrtErrorTest(unittest.TestCase): 9 | def test_error_code_conversion(self): 10 | err = awscrt.exceptions.from_code(0) 11 | self.assertEqual(0, err.code) 12 | self.assertEqual("AWS_ERROR_SUCCESS", err.name) 13 | self.assertTrue("success" in err.message.lower()) 14 | 15 | def test_error_code_remaps_to_corresponding_builtins(self): 16 | err = awscrt.exceptions.from_code(1) 17 | self.assertEqual(MemoryError, type(err)) 18 | 19 | def test_bad_error_code_wont_crash(self): 20 | err = awscrt.exceptions.from_code(999999999) 21 | self.assertEqual(999999999, err.code) 22 | self.assertIsNotNone(err.name) 23 | self.assertIsNotNone(err.message) 24 | -------------------------------------------------------------------------------- /docsrc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /codebuild/cd/manylinux-x86-build.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | #this build spec assumes the manylinux1 image for pypi 3 | #additional packages we installed: cmake 3.5, libcrypto 1.1.0j, gcc 4.8.4 4 | phases: 5 | install: 6 | commands: 7 | pre_build: 8 | commands: 9 | - export CC=gcc 10 | - cd aws-crt-python 11 | - /opt/python/cp38-cp38/bin/python ./continuous-delivery/update-version.py 12 | build: 13 | commands: 14 | - echo Build started on `date` 15 | - /opt/python/cp38-cp38/bin/python -m build 16 | - auditwheel repair --plat manylinux1_i686 dist/awscrt-*cp38-cp38-linux_i686.whl 17 | - /opt/python/cp39-cp39/bin/python -m build 18 | - auditwheel repair --plat manylinux1_i686 dist/awscrt-*cp39-cp39-linux_i686.whl 19 | # python 3.9 is the last version manylinux1 will ever receive 20 | - cp -r wheelhouse ../dist 21 | post_build: 22 | commands: 23 | - echo Build completed on `date` 24 | 25 | artifacts: 26 | files: 27 | - 'dist/*' 28 | -------------------------------------------------------------------------------- /continuous-delivery/build-wheels-osx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #before running this, you'll need cmake3 and a compiler. These python versions are just 3 | #using the default python installers from python.org. Each version needs updated: pip, build 4 | set -ex 5 | 6 | /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 ./continuous-delivery/update-version.py 7 | 8 | /Library/Frameworks/Python.framework/Versions/3.8/bin/python3 -m build 9 | /Library/Frameworks/Python.framework/Versions/3.9/bin/python3 -m build 10 | /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 -m build 11 | /Library/Frameworks/Python.framework/Versions/3.11/bin/python3 -m build 12 | 13 | # Don't need to build wheels for Python 3.12, it works with the 3.11 stable ABI wheel 14 | 15 | # We are using the Python 3.13 stable ABI from Python 3.13 onwards because of deprecated functions. 16 | /Library/Frameworks/Python.framework/Versions/3.13/bin/python3 -m build 17 | 18 | #now you just need to run twine (that's in a different script) 19 | -------------------------------------------------------------------------------- /guides/dev/vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "install", 8 | "type": "shell", 9 | "command": "${command:python.interpreterPath} scripts/install-dev.py", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | }, 14 | "presentation": { 15 | "panel": "shared", 16 | "clear": true 17 | }, 18 | "problemMatcher": [] 19 | }, 20 | { 21 | "label": "format", 22 | "type": "shell", 23 | "command": "${command:python.interpreterPath} scripts/format-all.py", 24 | "presentation": { 25 | "panel": "shared", 26 | "clear": true 27 | }, 28 | "problemMatcher": [] 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /codebuild/cd/manylinux-x64-build.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | #this build spec assumes the manylinux1 image for pypi 3 | #additional packages we installed: cmake 3.5, libcrypto 1.1.0j, gcc 4.8.4 4 | phases: 5 | install: 6 | commands: 7 | pre_build: 8 | commands: 9 | - export CC=gcc 10 | - cd aws-crt-python 11 | - /opt/python/cp38-cp38/bin/python ./continuous-delivery/update-version.py 12 | build: 13 | commands: 14 | - echo Build started on `date` 15 | - /opt/python/cp38-cp38/bin/python -m build 16 | - auditwheel repair --plat manylinux1_x86_64 dist/awscrt-*cp38-cp38-linux_x86_64.whl 17 | - /opt/python/cp39-cp39/bin/python -m build 18 | - auditwheel repair --plat manylinux1_x86_64 dist/awscrt-*cp39-cp39-linux_x86_64.whl 19 | # python 3.9 is the last version manylinux1 will ever receive 20 | - cp -r wheelhouse ../dist 21 | - cp dist/*.tar.gz ../dist/ 22 | post_build: 23 | commands: 24 | - echo Build completed on `date` 25 | 26 | artifacts: 27 | files: 28 | - 'dist/*' 29 | -------------------------------------------------------------------------------- /scripts/make-docs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import os.path 4 | from pathlib import Path 5 | import shutil 6 | import utils 7 | 8 | 9 | parser = argparse.ArgumentParser() 10 | parser.add_argument('--skip-clean', action='store_true', help="Iterate faster by skipping the clean step") 11 | args = parser.parse_args() 12 | 13 | utils.chdir_project_root() 14 | 15 | # clean 16 | if not args.skip_clean: 17 | docs = Path('docs') 18 | if docs.exists(): 19 | shutil.rmtree(docs) 20 | build = Path('docsrc/build') 21 | if build.exists(): 22 | shutil.rmtree(build) 23 | 24 | # build 25 | os.chdir('docsrc') 26 | utils.run('make html SPHINXOPTS="-W --keep-going"') 27 | utils.chdir_project_root() 28 | 29 | # copy 30 | docs = Path('docs') 31 | if docs.exists(): 32 | shutil.rmtree(docs) 33 | shutil.copytree('docsrc/build/html', docs) 34 | 35 | 36 | # The existence of this file tells GitHub Pages to just host the HTML as-is 37 | # https://github.blog/2009-12-29-bypassing-jekyll-on-github-pages/ 38 | Path('docs/.nojekyll').touch() 39 | -------------------------------------------------------------------------------- /codebuild/cd/publish_to_prod_pypi.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | # this image assumes base Codebuild Ubuntu image 3 | phases: 4 | install: 5 | commands: 6 | - sudo apt-get update -y 7 | - sudo apt-get install python3 python3-pip -y 8 | - python3 -m pip install --user --upgrade pip 9 | - python3 -m pip install --user --upgrade twine boto3 10 | pre_build: 11 | commands: 12 | - export CC=gcc 13 | build: 14 | commands: 15 | - echo Build started on `date` 16 | - mkdir dist 17 | - cp -rv $CODEBUILD_SRC_DIR_aws_crt_python_windows/dist/* dist/ 18 | - cp -rv $CODEBUILD_SRC_DIR_aws_crt_python_manylinux1/dist/* dist/ 19 | - cp -rv $CODEBUILD_SRC_DIR_aws_crt_python_musllinux1_1/dist/* dist/ 20 | - cp -rv $CODEBUILD_SRC_DIR_aws_crt_python_osx/* dist/ 21 | - ls -la dist/ 22 | - cd aws-crt-python 23 | - python3 continuous-delivery/pull-pypirc.py prod 24 | - python3 -m twine upload --repository pypi --verbose ../dist/* 25 | post_build: 26 | commands: 27 | - echo Build completed on `date` 28 | -------------------------------------------------------------------------------- /codebuild/cd/publish_to_test_pypi.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | # this image assumes base Codebuild Ubuntu image 3 | phases: 4 | install: 5 | commands: 6 | - sudo apt-get update -y 7 | - sudo apt-get install python3 python3-pip -y 8 | - python3 -m pip install --user --upgrade pip 9 | - python3 -m pip install --user --upgrade twine boto3 10 | pre_build: 11 | commands: 12 | - export CC=gcc 13 | build: 14 | commands: 15 | - echo Build started on `date` 16 | - mkdir dist 17 | - cp -rv $CODEBUILD_SRC_DIR_aws_crt_python_windows/dist/* dist/ 18 | - cp -rv $CODEBUILD_SRC_DIR_aws_crt_python_manylinux1/dist/* dist/ 19 | - cp -rv $CODEBUILD_SRC_DIR_aws_crt_python_musllinux1_1/dist/* dist/ 20 | - cp -rv $CODEBUILD_SRC_DIR_aws_crt_python_osx/* dist/ 21 | - ls -la dist/ 22 | - cd aws-crt-python 23 | - python3 continuous-delivery/pull-pypirc.py alpha 24 | - python3 -m twine upload --repository testpypi --verbose ../dist/* 25 | post_build: 26 | commands: 27 | - echo Build completed on `date` 28 | -------------------------------------------------------------------------------- /scripts/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shlex 3 | import subprocess 4 | import sys 5 | from typing import Sequence, Union 6 | 7 | 8 | def run(args: Union[str, Sequence[str]]): 9 | """ 10 | Run a program. 11 | 12 | args may be a string, or list of argument strings. 13 | 14 | If the program is "python3", this is replaced with the 15 | full path to the current python executable. 16 | """ 17 | 18 | # convert string to list 19 | # so that we don't need to pass shell=True to subprocess.run() 20 | # because turning on shell can mess things up (i.e. clang-format hangs forever for some reason) 21 | if isinstance(args, str): 22 | args = shlex.split(args) 23 | 24 | # ensure proper python executable is used 25 | if args[0] == 'python3': 26 | args[0] = sys.executable 27 | 28 | # run 29 | print(f'+ {subprocess.list2cmdline(args)}') 30 | result = subprocess.run(args) 31 | if result.returncode != 0: 32 | sys.exit('FAILED') 33 | 34 | 35 | def chdir_project_root(): 36 | root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 37 | os.chdir(root) 38 | -------------------------------------------------------------------------------- /codebuild/linux-integration-tests.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | #this build spec assumes the manylinux1 image for pypi 3 | #additional packages we installed: cmake 3.5, libcrypto 1.1.0j, gcc 4.8.4 4 | env: 5 | shell: bash 6 | variables: 7 | BUILDER_VERSION: v0.9.59 8 | BUILDER_SOURCE: releases 9 | BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net 10 | PACKAGE_NAME: aws-crt-python 11 | phases: 12 | install: 13 | commands: 14 | - add-apt-repository ppa:ubuntu-toolchain-r/test 15 | - apt-get update -y 16 | - apt-get install gcc-7 cmake ninja-build python3 python3-pip python3-venv -y 17 | pre_build: 18 | commands: 19 | - export CC=gcc-7 20 | build: 21 | commands: 22 | - echo Build started on `date` 23 | - git submodule update --init 24 | # Build library and test 25 | - python3 -c "from urllib.request import urlretrieve; urlretrieve('$BUILDER_HOST/$BUILDER_SOURCE/$BUILDER_VERSION/builder.pyz?run=$CODEBUILD_BUILD_ID', 'builder.pyz')" 26 | - python3 builder.pyz build --project aws-crt-python downstream 27 | post_build: 28 | commands: 29 | - echo Build completed on `date` 30 | -------------------------------------------------------------------------------- /continuous-delivery/test-version-exists: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | #force a failure if there's no tag 4 | GIT_TAG=$(git describe --tags) 5 | # Check if the git tag version starts with 'v' 6 | if [[ "$GIT_TAG" != v* ]]; then 7 | echo "Current tag version does not start with 'v', please ensure the tag is correctly formatted." 8 | exit 1 9 | fi 10 | # now get the tag without leading 'v' 11 | CURRENT_TAG=$(echo $GIT_TAG | cut -f2 -dv) 12 | # convert v0.2.12-2-g50254a9 to 0.2.12 13 | CURRENT_TAG_VERSION=$(git describe --tags | cut -f1 -d'-' | cut -f2 -dv) 14 | # if there's a hash on the tag, then this is not a release tagged commit 15 | if [ "$CURRENT_TAG" != "$CURRENT_TAG_VERSION" ]; then 16 | echo "Current tag version is not a release tag, cut a new release if you want to publish." 17 | exit 1 18 | fi 19 | 20 | if python3 -m pip install --no-cache-dir -vvv awscrt==$CURRENT_TAG_VERSION; then 21 | echo "$CURRENT_TAG_VERSION is already in pypi, cut a new tag if you want to upload another version." 22 | exit 1 23 | fi 24 | 25 | echo "$CURRENT_TAG_VERSION currently does not exist in pypi, allowing pipeline to continue." 26 | exit 0 27 | -------------------------------------------------------------------------------- /source/mqtt5_client.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_MQTT5_CLIENT_H 2 | #define AWS_CRT_PYTHON_MQTT5_CLIENT_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | 8 | #include "module.h" 9 | 10 | PyObject *aws_py_mqtt5_client_new(PyObject *self, PyObject *args); 11 | PyObject *aws_py_mqtt5_client_start(PyObject *self, PyObject *args); 12 | PyObject *aws_py_mqtt5_client_stop(PyObject *self, PyObject *args); 13 | PyObject *aws_py_mqtt5_client_publish(PyObject *self, PyObject *args); 14 | PyObject *aws_py_mqtt5_client_subscribe(PyObject *self, PyObject *args); 15 | PyObject *aws_py_mqtt5_client_unsubscribe(PyObject *self, PyObject *args); 16 | PyObject *aws_py_mqtt5_client_get_stats(PyObject *self, PyObject *args); 17 | 18 | PyObject *aws_py_mqtt5_ws_handshake_transform_complete(PyObject *self, PyObject *args); 19 | 20 | /* Given a python object, return a pointer to its underlying native type. 21 | * If NULL is returned, a python error has been set */ 22 | struct aws_mqtt5_client *aws_py_get_mqtt5_client(PyObject *mqtt5_client); 23 | 24 | #endif /* AWS_CRT_PYTHON_MQTT5_CLIENT_H */ 25 | -------------------------------------------------------------------------------- /continuous-delivery/build-wheels-musllinux-1-1-aarch64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #assumes image based on musllinux_1_1 3 | set -ex 4 | 5 | /opt/python/cp39-cp39/bin/python ./continuous-delivery/update-version.py 6 | 7 | /opt/python/cp38-cp38/bin/python -m build 8 | auditwheel repair --plat musllinux_1_1_aarch64 dist/awscrt-*cp38*.whl 9 | 10 | /opt/python/cp39-cp39/bin/python -m build 11 | auditwheel repair --plat musllinux_1_1_aarch64 dist/awscrt-*cp39*.whl 12 | 13 | /opt/python/cp310-cp310/bin/python -m build 14 | auditwheel repair --plat musllinux_1_1_aarch64 dist/awscrt-*cp310*.whl 15 | 16 | /opt/python/cp311-cp311/bin/python -m build 17 | auditwheel repair --plat musllinux_1_1_aarch64 dist/awscrt-*cp311*.whl 18 | 19 | # Don't need to build wheels for Python 3.12, it works with the 3.11 stable ABI wheel 20 | 21 | # We are using the Python 3.13 stable ABI from Python 3.13 onwards because of deprecated functions. 22 | /opt/python/cp313-cp313/bin/python -m build 23 | auditwheel repair --plat musllinux_1_1_aarch64 dist/awscrt-*cp313*.whl 24 | 25 | rm dist/*.whl 26 | cp -rv wheelhouse/* dist/ 27 | 28 | #now you just need to run twine (that's in a different script) 29 | -------------------------------------------------------------------------------- /continuous-delivery/build-wheels-musllinux-1-1-x86_64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #assumes image based on musllinux_1_1 + extras (pip) 3 | set -ex 4 | 5 | /opt/python/cp39-cp39/bin/python ./continuous-delivery/update-version.py 6 | 7 | /opt/python/cp38-cp38/bin/python -m build 8 | auditwheel repair --plat musllinux_1_1_x86_64 dist/awscrt-*cp38*.whl 9 | 10 | /opt/python/cp39-cp39/bin/python -m build 11 | auditwheel repair --plat musllinux_1_1_x86_64 dist/awscrt-*cp39*.whl 12 | 13 | /opt/python/cp310-cp310/bin/python -m build 14 | auditwheel repair --plat musllinux_1_1_x86_64 dist/awscrt-*cp310*.whl 15 | 16 | /opt/python/cp311-cp311/bin/python -m build 17 | auditwheel repair --plat musllinux_1_1_x86_64 dist/awscrt-*cp311*.whl 18 | 19 | # Don't need to build wheels for Python 3.12, it works with the 3.11 stable ABI wheel 20 | 21 | # We are using the Python 3.13 stable ABI from Python 3.13 onwards because of deprecated functions. 22 | /opt/python/cp313-cp313/bin/python -m build 23 | auditwheel repair --plat musllinux_1_1_x86_64 dist/awscrt-*cp313*.whl 24 | 25 | rm dist/*.whl 26 | cp -rv wheelhouse/* dist/ 27 | 28 | #now you just need to run twine (that's in a different script) 29 | -------------------------------------------------------------------------------- /test/test_appexit.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | import subprocess 5 | import sys 6 | import unittest 7 | 8 | 9 | class TestAppExit(unittest.TestCase): 10 | """Test that the application can exit with at any moment without native code somehow crashing python""" 11 | 12 | def _run_to_stage(self, module_name, stage): 13 | args = [sys.executable, '-m', module_name, stage.name] 14 | 15 | process = subprocess.Popen( 16 | args, 17 | stdout=subprocess.PIPE, 18 | stderr=subprocess.STDOUT) 19 | 20 | output = process.communicate()[0] 21 | 22 | # only print output if test failed 23 | if process.returncode != 0: 24 | print(subprocess.list2cmdline(args)) 25 | 26 | for line in output.splitlines(): 27 | print(line.decode()) 28 | 29 | self.assertEqual(0, process.returncode) 30 | 31 | def test_http(self): 32 | import test.appexit_http 33 | for stage in test.appexit_http.Stage: 34 | self._run_to_stage('test.appexit_http', stage) 35 | -------------------------------------------------------------------------------- /awscrt/common.py: -------------------------------------------------------------------------------- 1 | """ 2 | Cross-platform library for `awscrt`. 3 | """ 4 | import _awscrt 5 | 6 | 7 | def get_cpu_group_count() -> int: 8 | """ 9 | Returns number of processor groups on the system. 10 | 11 | Useful for working with non-uniform memory access (NUMA) nodes. 12 | """ 13 | return _awscrt.get_cpu_group_count() 14 | 15 | 16 | def get_cpu_count_for_group(group_idx: int) -> int: 17 | """ 18 | Returns number of processors in a given group. 19 | """ 20 | return _awscrt.get_cpu_count_for_group(group_idx) 21 | 22 | 23 | def join_all_native_threads(*, timeout_sec: float = -1.0) -> bool: 24 | """ 25 | Waits for all native threads to complete their join call. 26 | 27 | This can only be safely called from the main thread. 28 | This call may be required for native memory usage to reach zero. 29 | 30 | Args: 31 | timeout_sec (float): Number of seconds to wait before a timeout exception is raised. 32 | By default the wait is unbounded. 33 | 34 | Returns: 35 | bool: Returns whether threads could be joined before the timeout. 36 | """ 37 | return _awscrt.thread_join_all_managed(timeout_sec) 38 | -------------------------------------------------------------------------------- /continuous-delivery/build-wheels-manylinux2014-x86_64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #assumes image based on manylinux2014 + extras (cmake3, libcrypto, etc) 3 | set -ex 4 | 5 | /opt/python/cp39-cp39/bin/python ./continuous-delivery/update-version.py 6 | 7 | /opt/python/cp38-cp38/bin/python -m build 8 | auditwheel repair --plat manylinux2014_x86_64 dist/awscrt-*cp38*.whl 9 | 10 | /opt/python/cp39-cp39/bin/python -m build 11 | auditwheel repair --plat manylinux2014_x86_64 dist/awscrt-*cp39*.whl 12 | 13 | /opt/python/cp310-cp310/bin/python -m build 14 | auditwheel repair --plat manylinux2014_x86_64 dist/awscrt-*cp310*.whl 15 | 16 | /opt/python/cp311-cp311/bin/python -m build 17 | auditwheel repair --plat manylinux2014_x86_64 dist/awscrt-*cp311*.whl 18 | 19 | # Don't need to build wheels for Python 3.12, it works with the 3.11 stable ABI wheel 20 | 21 | # We are using the Python 3.13 stable ABI from Python 3.13 onwards because of deprecated functions. 22 | /opt/python/cp313-cp313/bin/python -m build 23 | auditwheel repair --plat manylinux2014_x86_64 dist/awscrt-*cp313*.whl 24 | 25 | rm dist/*.whl 26 | cp -rv wheelhouse/* dist/ 27 | 28 | #now you just need to run twine (that's in a different script) 29 | -------------------------------------------------------------------------------- /continuous-delivery/build-wheels-manylinux2014-aarch64.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #assumes image based on manylinux2014 + extras (cmake3, libcrypto, etc) 3 | set -ex 4 | 5 | /opt/python/cp39-cp39/bin/python ./continuous-delivery/update-version.py 6 | 7 | /opt/python/cp38-cp38/bin/python -m build 8 | auditwheel repair --plat manylinux2014_aarch64 dist/awscrt-*cp38*.whl 9 | 10 | /opt/python/cp39-cp39/bin/python -m build 11 | auditwheel repair --plat manylinux2014_aarch64 dist/awscrt-*cp39*.whl 12 | 13 | /opt/python/cp310-cp310/bin/python -m build 14 | auditwheel repair --plat manylinux2014_aarch64 dist/awscrt-*cp310*.whl 15 | 16 | /opt/python/cp311-cp311/bin/python -m build 17 | auditwheel repair --plat manylinux2014_aarch64 dist/awscrt-*cp311*.whl 18 | 19 | # Don't need to build wheels for Python 3.12, it works with the 3.11 stable ABI wheel 20 | 21 | # We are using the Python 3.13 stable ABI from Python 3.13 onwards because of deprecated functions. 22 | /opt/python/cp313-cp313/bin/python -m build 23 | auditwheel repair --plat manylinux2014_aarch64 dist/awscrt-*cp313*.whl 24 | 25 | rm dist/*.whl 26 | cp -rv wheelhouse/* dist/ 27 | 28 | #now you just need to run twine (that's in a different script) 29 | -------------------------------------------------------------------------------- /source/mqtt_request_response.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_MQTT_REQUEST_RESPONSE_H 2 | #define AWS_CRT_PYTHON_MQTT_REQUEST_RESPONSE_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | 8 | #include "module.h" 9 | 10 | struct aws_mqtt_request_response_client; 11 | 12 | PyObject *aws_py_mqtt_request_response_client_new_from_5(PyObject *self, PyObject *args); 13 | PyObject *aws_py_mqtt_request_response_client_new_from_311(PyObject *self, PyObject *args); 14 | PyObject *aws_py_mqtt_request_response_client_make_request(PyObject *self, PyObject *args); 15 | PyObject *aws_py_mqtt_request_response_client_create_stream(PyObject *self, PyObject *args); 16 | PyObject *aws_py_mqtt_streaming_operation_open(PyObject *self, PyObject *args); 17 | 18 | /* Given a python object, return a pointer to its underlying native type. 19 | * If NULL is returned, a python error has been set */ 20 | struct aws_mqtt_request_response_client *aws_py_get_mqtt_request_response_client( 21 | PyObject *mqtt_request_response_client); 22 | 23 | struct aws_mqtt_rr_client_operation *aws_py_get_mqtt_streaming_operation(PyObject *mqtt_streaming_operation); 24 | 25 | #endif /* AWS_CRT_PYTHON_MQTT_REQUEST_RESPONSE_H */ -------------------------------------------------------------------------------- /source/s3.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_S3_H 2 | #define AWS_CRT_PYTHON_S3_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | 8 | #include "module.h" 9 | 10 | PyObject *aws_py_s3_get_ec2_instance_type(PyObject *self, PyObject *args); 11 | PyObject *aws_py_s3_is_crt_s3_optimized_for_system(PyObject *self, PyObject *args); 12 | PyObject *aws_py_s3_get_recommended_throughput_target_gbps(PyObject *self, PyObject *args); 13 | PyObject *aws_py_s3_get_optimized_platforms(PyObject *self, PyObject *args); 14 | 15 | PyObject *aws_py_s3_client_new(PyObject *self, PyObject *args); 16 | PyObject *aws_py_s3_client_make_meta_request(PyObject *self, PyObject *args); 17 | 18 | PyObject *aws_py_s3_meta_request_cancel(PyObject *self, PyObject *args); 19 | 20 | PyObject *aws_py_s3_cross_process_lock_new(PyObject *self, PyObject *args); 21 | PyObject *aws_py_s3_cross_process_lock_acquire(PyObject *self, PyObject *args); 22 | PyObject *aws_py_s3_cross_process_lock_release(PyObject *self, PyObject *args); 23 | 24 | struct aws_s3_client *aws_py_get_s3_client(PyObject *s3_client); 25 | struct aws_s3_meta_request *aws_py_get_s3_meta_request(PyObject *s3_client); 26 | 27 | #endif /* AWS_CRT_PYTHON_S3_H */ 28 | -------------------------------------------------------------------------------- /scripts/clean.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import glob 3 | import os 4 | import shutil 5 | import utils 6 | 7 | # apply these patterns without recursing through subfolders 8 | NONRECURSIVE_PATTERNS = [ 9 | 'build/', 10 | '_awscrt*.*', # compiled _awscrt shared lib 11 | '*.egg-info/', 12 | 'dist/', 13 | 'wheelhouse/', 14 | 'docsrc/build/', 15 | ] 16 | 17 | # recurse through subfolders and apply these patterns 18 | RECURSIVE_PATTERNS = [ 19 | '*.pyc', 20 | '__pycache__' 21 | ] 22 | 23 | # approved list of folders 24 | # because we don't want to clean the virtual environment folder 25 | APPROVED_RECURSIVE_FOLDERS = [ 26 | 'awscrt', 27 | 'scripts', 28 | 'test', 29 | '.builder', 30 | ] 31 | 32 | utils.chdir_project_root() 33 | 34 | utils.run('python3 -m pip uninstall -y awscrt') 35 | 36 | paths = [] 37 | for pattern in NONRECURSIVE_PATTERNS: 38 | paths.extend(glob.glob(pattern)) 39 | 40 | for pattern in RECURSIVE_PATTERNS: 41 | for folder in APPROVED_RECURSIVE_FOLDERS: 42 | paths.extend(glob.glob(f'{folder}/**/{pattern}', recursive=True)) 43 | 44 | for path in paths: 45 | print(f'delete: {path}') 46 | if os.path.isfile(path): 47 | os.remove(path) 48 | else: 49 | shutil.rmtree(path) 50 | -------------------------------------------------------------------------------- /awscrt/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | from weakref import WeakSet 5 | 6 | __all__ = [ 7 | 'aio', 8 | 'auth', 9 | 'crypto', 10 | 'http', 11 | 'io', 12 | 'mqtt', 13 | 'mqtt5', 14 | 'mqtt_request_response', 15 | 's3', 16 | 'websocket', 17 | ] 18 | 19 | __version__ = '1.0.0.dev0' 20 | 21 | 22 | class NativeResource: 23 | """ 24 | Base for classes that bind to a native type. 25 | _binding is a python capsule referencing the native object. 26 | 27 | Note to developers: If NativeResource B depends on the existence of NativeResource A, 28 | have B's native code Py_INCREF/DECREF A's python class. This ensures that A will not be destroyed before B. 29 | If we simply had python class B referencing A, and the GC decided to clean up both, it might destroy A before B. 30 | """ 31 | 32 | # For tracking live NativeResources in tests/debug. 33 | # Note that WeakSet can accurately report if 0 objects exist, but iteration isn't 100% thread-safe. 34 | _track_lifetime = False 35 | _living = WeakSet() 36 | 37 | __slots__ = ('_binding', '__weakref__') 38 | 39 | def __init__(self): 40 | if NativeResource._track_lifetime: 41 | NativeResource._living.add(self) 42 | -------------------------------------------------------------------------------- /continuous-delivery/pip-install-with-retry.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import subprocess 4 | 5 | DOCS = """Given cmdline args, executes: python3 -m pip install [args...] 6 | Keeps retrying until the new version becomes available in pypi (or we time out)""" 7 | if len(sys.argv) < 2: 8 | sys.exit(DOCS) 9 | 10 | RETRY_INTERVAL_SECS = 10 11 | GIVE_UP_AFTER_SECS = 60 * 15 12 | 13 | pip_install_args = [sys.executable, '-m', 'pip', 'install'] + sys.argv[1:] 14 | 15 | start_time = time.time() 16 | while True: 17 | print(subprocess.list2cmdline(pip_install_args)) 18 | result = subprocess.run(pip_install_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 19 | 20 | stdout = result.stdout.decode().strip() 21 | if stdout: 22 | print(stdout) 23 | 24 | if result.returncode == 0: 25 | # success 26 | sys.exit(0) 27 | 28 | if "could not find a version" in stdout.lower(): 29 | elapsed_secs = time.time() - start_time 30 | if elapsed_secs < GIVE_UP_AFTER_SECS: 31 | # try again 32 | print("Retrying in", RETRY_INTERVAL_SECS, "secs...") 33 | time.sleep(RETRY_INTERVAL_SECS) 34 | continue 35 | else: 36 | print("Giving up on retries after", int(elapsed_secs), "total secs.") 37 | 38 | # fail 39 | sys.exit(result.returncode) 40 | -------------------------------------------------------------------------------- /.github/workflows/issue-regression-labeler.yml: -------------------------------------------------------------------------------- 1 | # Apply potential regression label on issues 2 | name: issue-regression-label 3 | on: 4 | issues: 5 | types: [opened, edited] 6 | jobs: 7 | add-regression-label: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | steps: 12 | - name: Fetch template body 13 | id: check_regression 14 | uses: actions/github-script@v7 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | TEMPLATE_BODY: ${{ github.event.issue.body }} 18 | with: 19 | script: | 20 | const regressionPattern = /\[x\] Select this option if this issue appears to be a regression\./i; 21 | const template = `${process.env.TEMPLATE_BODY}` 22 | const match = regressionPattern.test(template); 23 | core.setOutput('is_regression', match); 24 | - name: Manage regression label 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | run: | 28 | if [ "${{ steps.check_regression.outputs.is_regression }}" == "true" ]; then 29 | gh issue edit ${{ github.event.issue.number }} --add-label "potential-regression" -R ${{ github.repository }} 30 | else 31 | gh issue edit ${{ github.event.issue.number }} --remove-label "potential-regression" -R ${{ github.repository }} 32 | fi 33 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # Update the API documentation whenever the `main` branch changes. 2 | # This documentation lives in its own `docs` branch. 3 | name: docs 4 | 5 | on: 6 | push: 7 | branches: 8 | - 'main' 9 | 10 | jobs: 11 | update-docs-branch: 12 | runs-on: ubuntu-22.04 # getting errors from sphinx using ubuntu-24.04, so stuck on 22.04 till we diagnose it 13 | permissions: 14 | contents: write # allow push 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | with: 19 | submodules: true 20 | 21 | - name: Update docs branch 22 | run: | 23 | python3 -m pip install --upgrade pip 24 | python3 -m pip install --verbose ".[dev]" 25 | ./scripts/make-docs.py 26 | 27 | - name: Commit 28 | run: | 29 | git config --local user.email "action@github.com" 30 | git config --local user.name "GitHub Action" 31 | git add --force docs 32 | git commit --message="update docs" 33 | 34 | - name: Push to docs branch 35 | uses: ad-m/github-push-action@v0.6.0 36 | with: 37 | github_token: ${{ github.token }} 38 | branch: docs 39 | # Force push so that `docs` branch always looks like `main`, 40 | # but with 1 additional "update docs" commit. 41 | # This seems simpler than trying to cleanly merge `main` into 42 | # `docs` each time. 43 | force: true 44 | -------------------------------------------------------------------------------- /codebuild/mqtt5-python-canary-test.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | #this build spec assumes the manylinux1 image for pypi 3 | #additional packages we installed: cmake 3.5, libcrypto 1.1.0j, gcc 4.8.4 4 | env: 5 | shell: bash 6 | variables: 7 | CANARY_DURATION: 25200 8 | CANARY_THREADS: 3 9 | CANARY_TPS: 50 10 | CANARY_CLIENT_COUNT: 10 11 | CANARY_LOG_FILE: 'canary_log.txt' 12 | CANARY_LOG_LEVEL: 'ERROR' 13 | PACKAGE_NAME: aws-crt-python 14 | CANARY_TEST_EXE: 'python -m unittest --failfast --verbose 2>&1 | tee /tmp/tests.log test.test_mqtt5_canary' 15 | CANARY_SERVER_ARN: Mqtt5MosquittoSever 16 | phases: 17 | install: 18 | commands: 19 | - add-apt-repository ppa:ubuntu-toolchain-r/test 20 | - apt-get update -y 21 | - apt-get install gcc-7 cmake ninja-build python3 -y 22 | - python3 -m pip install psutil 23 | - python3 -m pip install boto3 24 | pre_build: 25 | commands: 26 | - export CC=gcc-7 27 | build: 28 | commands: 29 | - echo Build started on `date` 30 | - source ./codebuild/mqtt5_test_setup.sh s3://aws-crt-test-stuff/TestIotProdMQTT5EnvironmentVariables.txt us-east-1 31 | - export ENDPOINT=$(aws secretsmanager get-secret-value --secret-id "$CANARY_SERVER_ARN" --query "SecretString" | cut -f2 -d":" | sed -e 's/[\\\"\}]//g') 32 | - export GIT_HASH=$(git rev-parse HEAD) 33 | - $CODEBUILD_SRC_DIR/codebuild/mqtt5-python-canary-test.sh 34 | post_build: 35 | commands: 36 | - echo Build completed on `date` 37 | -------------------------------------------------------------------------------- /test/resources/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEADCCAugCCQCtEgLxN71/TDANBgkqhkiG9w0BAQsFADCBwTELMAkGA1UEBhMC 3 | VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxITAfBgNV 4 | BAoMGEFtYXpvbiBXZWIgU2VydmljZXMsIEluYzEbMBkGA1UECwwSQXdzIGNvbW1v 5 | biBydW50aW1lMRkwFwYDVQQDDBBwa2kuaXMudGhlLndvcnN0MTAwLgYJKoZIhvcN 6 | AQkBFiFhd3Mtc2RrLWNvbW1vbi1ydW50aW1lQGFtYXpvbi5jb20wHhcNMjAwMzI4 7 | MDMwNTI4WhcNNDcwODE0MDMwNTI4WjCBwTELMAkGA1UEBhMCVVMxEzARBgNVBAgM 8 | Cldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxITAfBgNVBAoMGEFtYXpvbiBX 9 | ZWIgU2VydmljZXMsIEluYzEbMBkGA1UECwwSQXdzIGNvbW1vbiBydW50aW1lMRkw 10 | FwYDVQQDDBBwa2kuaXMudGhlLndvcnN0MTAwLgYJKoZIhvcNAQkBFiFhd3Mtc2Rr 11 | LWNvbW1vbi1ydW50aW1lQGFtYXpvbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB 12 | DwAwggEKAoIBAQDCUFDcwnUkyK/7M2RFXmWGryCfUM/Y4sPV5dm8Q52wZcPATaAO 13 | Ucnh03qhPbxHyiF56EYNxF6tIXqMifO6CyiC1Dcvkq4yYVAYBH2GpwpscBraVrNX 14 | KUeBZVv3tAFIFWMgGSz4SYB+6pdTEHxFA3iZjH4WIMgMMAZWiz7ktqz3b+OqOSHT 15 | i89rShb0QVrAdHzSeKXwyRA5X4vTMLQEFwNU/6D57+1IdSlo0svdrF70GYuxpR7G 16 | 2VOIZqbVSyoL0dkuFCh1XjbuIyvNqshWXV644BtEwoP5P36tSONUiLbhLaR++pO0 17 | RB2WgniJSBGUe0gSfl9djaaAg7KdbfIoIosRAgMBAAEwDQYJKoZIhvcNAQELBQAD 18 | ggEBAKdg2Z2DL2sow8ckf90uqrwNpuCKlydg1XGS2hBSWyhiFd3Ztj9pRKdpsjA8 19 | mF9x4U+WwmekzV6CZKXPlO5qlCvU1h7dFVRHZtG4O1bIRQNcgycoiL7Q9Sp+crP1 20 | Y8pF1vjCuoMlGFSSMXazRVXcJ/FVeBD4rS8AmyR6B4W8fykVQAYNcH4gXwQ0WP7O 21 | bjJrI1kTB+3JQU1KHHDTi3/gMvCUanzDc8HJ0CEpch8AY9S4suAYJmM6HpWzczoy 22 | jUH/yQT0ehur9wRy566BwUdTVfi+2cQQfYNPqs9iJdYZ7loSBDRH7vO7ivE4Y/Zz 23 | ilbJgpP/vnoxwEVSYD0W+1ctaEg= 24 | -----END CERTIFICATE----- 25 | -------------------------------------------------------------------------------- /continuous-delivery/pull-pypirc.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | import boto3 5 | import base64 6 | import os 7 | import argparse 8 | 9 | 10 | def get_secret(stage): 11 | 12 | secret_name = '{}/aws-crt-python/.pypirc'.format(stage) 13 | region_name = 'us-east-1' 14 | 15 | # Create a Secrets Manager client 16 | session = boto3.session.Session() 17 | client = session.client( 18 | service_name='secretsmanager', 19 | region_name=region_name 20 | ) 21 | 22 | secret = None 23 | get_secret_value_response = client.get_secret_value(SecretId=secret_name) 24 | # Decrypts secret using the associated KMS CMK. 25 | # Depending on whether the secret is a string or binary, one of these fields will be populated. 26 | if 'SecretString' in get_secret_value_response: 27 | secret = get_secret_value_response['SecretString'] 28 | else: 29 | decoded_binary_secret = base64.b64decode(get_secret_value_response['SecretBinary']) 30 | secret = decoded_binary_secret 31 | 32 | with open(os.path.join(os.path.expanduser('~/'), '.pypirc'), 'w') as f: 33 | f.write(secret) 34 | 35 | print('.pypirc written to {}'.format(os.path.join(os.path.expanduser('~/'), '.pypirc'))) 36 | 37 | parser = argparse.ArgumentParser() 38 | parser.add_argument('stage', help='Stage to deploy the pypi package to (e.g. alpha, prod, etc...)', type=str) 39 | args = parser.parse_args() 40 | get_secret(args.stage) 41 | 42 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=75.3.1", 4 | ] 5 | build-backend = "setuptools.build_meta" 6 | 7 | [project] 8 | name = "awscrt" 9 | license = { text = "Apache-2.0", files = ["LICENSE"] } 10 | authors = [ 11 | { name = "Amazon Web Services, Inc", email = "aws-sdk-common-runtime@amazon.com" }, 12 | ] 13 | description = "A common runtime for AWS Python projects" 14 | readme = "README.md" 15 | requires-python = ">=3.8" 16 | classifiers = [ 17 | "Programming Language :: Python :: 3", 18 | "Operating System :: Microsoft :: Windows", 19 | "Operating System :: POSIX", 20 | "Operating System :: Unix", 21 | "Operating System :: MacOS", 22 | ] 23 | dynamic = ["version"] 24 | 25 | [project.urls] 26 | github = "https://github.com/awslabs/aws-crt-python" 27 | documentation = "https://awslabs.github.io/aws-crt-python" 28 | issues = "https://github.com/awslabs/aws-crt-python/issues" 29 | releasenotes = "https://github.com/awslabs/aws-crt-python/releases" 30 | 31 | [project.optional-dependencies] 32 | dev = [ 33 | "autopep8>=2.3.1", # for code formatting 34 | "build>=1.2.2", # for building wheels 35 | "sphinx>=7.2.6,<7.3; python_version >= '3.9'", # for building docs 36 | "websockets>=13.1", # for tests 37 | # for tests, restrict to exact version for test_http_client/TestClientMockServer/_on_remote_settings_changed that relies on the implementation details. Also, this is no needs to update this package. 38 | "h2==4.1.0", 39 | ] 40 | -------------------------------------------------------------------------------- /source/pkcs11_lib.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | #include "io.h" 6 | 7 | #include 8 | 9 | static const char *s_capsule_name = "aws_pkcs11_lib"; 10 | 11 | static void s_pkcs11_lib_capsule_destructor(PyObject *capsule) { 12 | struct aws_pkcs11_lib *pkcs11_lib = PyCapsule_GetPointer(capsule, s_capsule_name); 13 | aws_pkcs11_lib_release(pkcs11_lib); 14 | } 15 | 16 | struct aws_pkcs11_lib *aws_py_get_pkcs11_lib(PyObject *pkcs11_lib) { 17 | return aws_py_get_binding(pkcs11_lib, s_capsule_name, "Pkcs11Lib"); 18 | } 19 | 20 | PyObject *aws_py_pkcs11_lib_new(PyObject *self, PyObject *args) { 21 | (void)self; 22 | 23 | struct aws_byte_cursor filename; 24 | int behavior; 25 | if (!PyArg_ParseTuple(args, "s#i", &filename.ptr, &filename.len, &behavior)) { 26 | return NULL; 27 | } 28 | 29 | struct aws_pkcs11_lib_options options = { 30 | .filename = filename, 31 | .initialize_finalize_behavior = behavior, 32 | }; 33 | struct aws_pkcs11_lib *pkcs11_lib = aws_pkcs11_lib_new(aws_py_get_allocator(), &options); 34 | if (pkcs11_lib == NULL) { 35 | return PyErr_AwsLastError(); 36 | } 37 | 38 | PyObject *capsule = PyCapsule_New(pkcs11_lib, s_capsule_name, s_pkcs11_lib_capsule_destructor); 39 | if (capsule == NULL) { 40 | aws_pkcs11_lib_release(pkcs11_lib); /* cleanup due to error */ 41 | return NULL; 42 | } 43 | 44 | return capsule; 45 | } 46 | -------------------------------------------------------------------------------- /source/mqtt_client_connection.h: -------------------------------------------------------------------------------- 1 | #ifndef MQTT_CLIENT_CONNECTION_H 2 | #define MQTT_CLIENT_CONNECTION_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | 8 | /** 9 | * This file includes definitions for MQTT specific functions. 10 | */ 11 | 12 | #include "module.h" 13 | 14 | PyObject *aws_py_mqtt_client_connection_new(PyObject *self, PyObject *args); 15 | PyObject *aws_py_mqtt_client_connection_connect(PyObject *self, PyObject *args); 16 | PyObject *aws_py_mqtt_client_connection_reconnect(PyObject *self, PyObject *args); 17 | PyObject *aws_py_mqtt_client_connection_publish(PyObject *self, PyObject *args); 18 | PyObject *aws_py_mqtt_client_connection_subscribe(PyObject *self, PyObject *args); 19 | PyObject *aws_py_mqtt_client_connection_on_message(PyObject *self, PyObject *args); 20 | PyObject *aws_py_mqtt_client_connection_unsubscribe(PyObject *self, PyObject *args); 21 | PyObject *aws_py_mqtt_client_connection_resubscribe_existing_topics(PyObject *self, PyObject *args); 22 | PyObject *aws_py_mqtt_client_connection_disconnect(PyObject *self, PyObject *args); 23 | PyObject *aws_py_mqtt_client_connection_get_stats(PyObject *self, PyObject *args); 24 | 25 | PyObject *aws_py_mqtt_ws_handshake_transform_complete(PyObject *self, PyObject *args); 26 | 27 | /* Given a python object, return a pointer to its underlying native type. 28 | * If NULL is returned, a python error has been set */ 29 | struct aws_mqtt_client_connection *aws_py_get_mqtt_client_connection(PyObject *mqtt_client_connection); 30 | 31 | #endif /* MQTT_CLIENT_CONNECTION_H */ 32 | -------------------------------------------------------------------------------- /format-check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import os 4 | from pathlib import Path 5 | import re 6 | from subprocess import list2cmdline, run 7 | from tempfile import NamedTemporaryFile 8 | 9 | CLANG_FORMAT_VERSION = '18.1.6' 10 | 11 | INCLUDE_REGEX = re.compile(r'^source/.*\.(c|h)$') 12 | EXCLUDE_REGEX = re.compile(r'^$') 13 | 14 | arg_parser = argparse.ArgumentParser(description="Check with clang-format") 15 | arg_parser.add_argument('-i', '--inplace-edit', action='store_true', 16 | help="Edit files inplace") 17 | args = arg_parser.parse_args() 18 | 19 | os.chdir(Path(__file__).parent) 20 | 21 | # create file containing list of all files to format 22 | filepaths_file = NamedTemporaryFile(delete=False) 23 | for dirpath, dirnames, filenames in os.walk('.'): 24 | for filename in filenames: 25 | # our regexes expect filepath to use forward slash 26 | filepath = Path(dirpath, filename).as_posix() 27 | if not INCLUDE_REGEX.match(filepath): 28 | continue 29 | if EXCLUDE_REGEX.match(filepath): 30 | continue 31 | 32 | filepaths_file.write(f"{filepath}\n".encode()) 33 | filepaths_file.close() 34 | 35 | # use pipx to run clang-format from PyPI 36 | # this is a simple way to run the same clang-format version regardless of OS 37 | cmd = ['pipx', 'run', f'clang-format=={CLANG_FORMAT_VERSION}', 38 | f'--files={filepaths_file.name}'] 39 | if args.inplace_edit: 40 | cmd += ['-i'] 41 | else: 42 | cmd += ['--Werror', '--dry-run'] 43 | 44 | print(f"{Path.cwd()}$ {list2cmdline(cmd)}") 45 | if run(cmd).returncode: 46 | exit(1) 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature Request 3 | description: Suggest an idea for this project 4 | title: "(short issue description)" 5 | labels: [feature-request, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the feature 12 | description: A clear and concise description of the feature you are proposing. 13 | validations: 14 | required: true 15 | - type: textarea 16 | id: use-case 17 | attributes: 18 | label: Use Case 19 | description: | 20 | Why do you need this feature? For example: "I'm always frustrated when..." 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: solution 25 | attributes: 26 | label: Proposed Solution 27 | description: | 28 | Suggest how to implement the addition or change. Please include prototype/workaround/sketch/reference implementation. 29 | validations: 30 | required: false 31 | - type: textarea 32 | id: other 33 | attributes: 34 | label: Other Information 35 | description: | 36 | Any alternative solutions or features you considered, a more detailed explanation, stack traces, related issues, links for context, etc. 37 | validations: 38 | required: false 39 | - type: checkboxes 40 | id: ack 41 | attributes: 42 | label: Acknowledgements 43 | options: 44 | - label: I may be able to implement this feature request 45 | required: false 46 | - label: This feature might incur a breaking change 47 | required: false 48 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "aws-common-runtime/aws-c-common"] 2 | path = crt/aws-c-common 3 | url = https://github.com/awslabs/aws-c-common.git 4 | [submodule "aws-common-runtime/aws-c-io"] 5 | path = crt/aws-c-io 6 | url = https://github.com/awslabs/aws-c-io.git 7 | [submodule "aws-common-runtime/aws-c-mqtt"] 8 | path = crt/aws-c-mqtt 9 | url = https://github.com/awslabs/aws-c-mqtt.git 10 | [submodule "aws-common-runtime/s2n"] 11 | path = crt/s2n 12 | url = https://github.com/awslabs/s2n.git 13 | [submodule "aws-common-runtime/aws-c-cal"] 14 | path = crt/aws-c-cal 15 | url = https://github.com/awslabs/aws-c-cal.git 16 | [submodule "aws-common-runtime/aws-c-http"] 17 | path = crt/aws-c-http 18 | url = https://github.com/awslabs/aws-c-http.git 19 | [submodule "aws-common-runtime/aws-c-compression"] 20 | path = crt/aws-c-compression 21 | url = https://github.com/awslabs/aws-c-compression.git 22 | [submodule "aws-common-runtime/aws-c-auth"] 23 | path = crt/aws-c-auth 24 | url = https://github.com/awslabs/aws-c-auth.git 25 | [submodule "aws-common-runtime/aws-c-s3"] 26 | path = crt/aws-c-s3 27 | url = https://github.com/awslabs/aws-c-s3.git 28 | [submodule "aws-common-runtime/aws-c-event-stream"] 29 | path = crt/aws-c-event-stream 30 | url = https://github.com/awslabs/aws-c-event-stream.git 31 | [submodule "aws-common-runtime/aws-checksums"] 32 | path = crt/aws-checksums 33 | url = https://github.com/awslabs/aws-checksums.git 34 | [submodule "crt/aws-lc"] 35 | path = crt/aws-lc 36 | url = https://github.com/awslabs/aws-lc.git 37 | [submodule "crt/aws-c-sdkutils"] 38 | path = crt/aws-c-sdkutils 39 | url = https://github.com/awslabs/aws-c-sdkutils.git 40 | -------------------------------------------------------------------------------- /awscrt/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | import _awscrt 5 | 6 | 7 | def from_code(code): 8 | """Given an AWS Common Runtime error code, return an exception. 9 | 10 | Returns a common Python exception type, if it's appropriate. 11 | For example, `code=1` aka `AWS_ERROR_OOM` will result in `MemoryError`. 12 | Otherwise, an :class:`AwsCrtError` is returned. 13 | 14 | Args: 15 | code (int): error code. 16 | 17 | Returns: 18 | BaseException: 19 | """ 20 | builtin = _awscrt.get_corresponding_builtin_exception(code) 21 | if builtin: 22 | return builtin() 23 | 24 | name = _awscrt.get_error_name(code) 25 | msg = _awscrt.get_error_message(code) 26 | return AwsCrtError(code=code, name=name, message=msg) 27 | 28 | 29 | class AwsCrtError(Exception): 30 | """ 31 | Base exception class for AWS Common Runtime exceptions. 32 | 33 | Args: 34 | code (int): Int value of error. 35 | name (str): Name of error. 36 | message (str): Message about error. 37 | 38 | Attributes: 39 | code (int): Int value of error. 40 | name (str): Name of error. 41 | message (str): Message about error. 42 | """ 43 | 44 | def __init__(self, code, name, message): 45 | self.code = code 46 | self.name = name 47 | self.message = message 48 | 49 | def __repr__(self): 50 | return "{0}(name={1}, message={2}, code={3})".format( 51 | self.__class__.__name__, repr(self.name), repr(self.message), self.code) 52 | 53 | def __str__(self): 54 | return "{}: {}".format(self.name, self.message) 55 | -------------------------------------------------------------------------------- /test/resources/unittest.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAwrd1eMM//oEWSp7jeSnEH0FCJEzwMoHjZ8nliRJH2pRI1L3z 3 | AEbZvAaOpktxIAqp97nZRGThbF38s84RKcCQGavl/jNVHQq03fp5/+2CY0lakTq6 4 | VdkRYpTgulR6jyZ3fwEx7BPGTPqH+GCJtwN8xO4HXJDVZ+RLl6cw05AifV6/7fBg 5 | lUm2kC9lbzMHhn2GJzUTYu+CrK0bnJ1y/47ikK4IRyMG/XuWrP7gvkhVSr7RwlQK 6 | ZyDsxCBpEckj49hRkByF48vNjOQfkr8FWHf5b3lD56fdkyhIi3qJSKmRq6c/b3hI 7 | FmRsvyiLQZMTmwXw+0GVgik05GiX9CROyi7oBQIDAQABAoIBAQCmSgkfrheb4WBJ 8 | L/JySlH8oz26RJ6pc7XisFC/xAXrTNH6JZI+fUN7yVnP+QPOam0HbAd52nGNm6Kx 9 | Z93Oe25VDjHn6qzYlYkrPbss7wh4hG+9MZtqr2uidiSJ3JWiZSaOJ1YcGDM1EIxI 10 | eSGDU44dnG5bBjHKfPR1ukn/k8twppepc0oaN9GH5H9Oi3459MGVd/2Y1rdLeGy3 11 | F14LL/rH3mOWTf5dpvO8jbwxKe+89pA7KJ0Yukf1lEjsp2qtAvnuOLmXVVo4zJUV 12 | CsJpy3EFWiH5R7gYmdiLnie+/Hrf0dyglsENaJjs5SHu2bIbrRSPbbVgMRGyXOxw 13 | nC873ZANAoGBAPmVTVJDR2h8YpHtHTMjRVVVXg/NP56iTICiUWQjSyNITBOz+vNB 14 | 6DrkXlyjy2pRsNu+LW8KqrDfyYTcEPxYsafTkpAGegkCzQi6nazeYfTJs3ZKtQgL 15 | EZBuwiXBt+fAKrygVjHW+3drQbTVw61EiYkggqfmmbmbD10J5brCT/cfAoGBAMe5 16 | CbdI4mJsc9WVo85xpoHxQqhEMP6qKlbM7EwHr2U/5ayYhz6RyVWFBrtC5fftQcT1 17 | CgXldG7pGYw5tM80ZyNJ4Di59oPB+cn7BpX6/EbY8NxnQ/FDFhQClNodlO+9cQc9 18 | 02lu/pgKIMbXoRauPI1XH9G61c6Gqzb3GoNmS/BbAoGAMSVWMXGwGQINQuDnFA/I 19 | 8lG6EPCAq5MX72iHEGnmvM7atDKHoukCer5HWoSiZJArfEnJlokKLYQToYbYDWjF 20 | qt99h8DOvQGBMEvXCphLv7EMFqoFVc0gK0m0D01DEm6Du9ryviOwJsbHDH3fZzoM 21 | vyTYtYkyEhtXfiGZzBnBN08CgYEAkRvf80sBMIGbp5MRcO0iDUc9JwRklKMOIALD 22 | rHno9ad3N++aU9uYbIo6WdRBQeEf5/ohTidocSzrYvaKaAGq7fi+8Hgso45L7nR0 23 | VIQGx3LXjUt6EdCsOd2mSNM1YvpBJQHGmNQMJms1ZUNFxzTQyeCDPd6BeYxe2ZCv 24 | ssigZAECgYBBd7rXXTZM1QaJtQa86FirU8qkcwqToCCaZyMuKGuDqjJ87S8AXtmy 25 | jLTdZmtBUiLtP5pczriQqYb9UoUvl/EW3hWda5hbiXcw6YXLmeSDDaK/WWJnw3SS 26 | EzbRvExU54u35e4a7xM9nScUMERo6ACHo99FBu7tTp9MNpThKc+xKw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/resources/unittest.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEyjCCArICFGIciu+gXvSUQWZw2VxOvBRMNceBMA0GCSqGSIb3DQEBCwUAMIGh 3 | MQswCQYDVQQGEwJVUzELMAkGA1UECAwCV0ExEDAOBgNVBAcMB1NlYXR0bGUxDzAN 4 | BgNVBAoMBkFtYXpvbjEXMBUGA1UECwwOU0RLcyBhbmQgVG9vbHMxGzAZBgNVBAMM 5 | EmF3cy1jb21tb24tcnVudGltZTEsMCoGCSqGSIb3DQEJARYdYXdzLWNvbW1vbi1y 6 | dW50aW1lQGFtYXpvbi5jb20wHhcNMjEwNTA3MjAyMDQ5WhcNMzEwNTA1MjAyMDQ5 7 | WjCBoDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcM 8 | B1NlYXR0bGUxDzANBgNVBAoMBkFtYXpvbjEXMBUGA1UECwwOU0RLcyBhbmQgVG9v 9 | bHMxEjAQBgNVBAMMCWxvY2FsaG9zdDEsMCoGCSqGSIb3DQEJARYdYXdzLWNvbW1v 10 | bi1ydW50aW1lQGFtYXpvbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 11 | AoIBAQDCt3V4wz/+gRZKnuN5KcQfQUIkTPAygeNnyeWJEkfalEjUvfMARtm8Bo6m 12 | S3EgCqn3udlEZOFsXfyzzhEpwJAZq+X+M1UdCrTd+nn/7YJjSVqROrpV2RFilOC6 13 | VHqPJnd/ATHsE8ZM+of4YIm3A3zE7gdckNVn5EuXpzDTkCJ9Xr/t8GCVSbaQL2Vv 14 | MweGfYYnNRNi74KsrRucnXL/juKQrghHIwb9e5as/uC+SFVKvtHCVApnIOzEIGkR 15 | ySPj2FGQHIXjy82M5B+SvwVYd/lveUPnp92TKEiLeolIqZGrpz9veEgWZGy/KItB 16 | kxObBfD7QZWCKTTkaJf0JE7KLugFAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAGid 17 | mlpMSazmmx+pT9+OL/tjKlBed80oGVqS1nUdMj8oXu3OZPouVcnlYiiyQwrqp4vc 18 | YVH72tWyqRJoneGpZWrlMKES+b66DUyFgxNW6U7pokxNhgF/ku1xnbnNw2NdiYRY 19 | nKBu/oUZrANeUbspeQTU/BE6BIuFb/lT1A+706w9UeDpyE5gTCpMGgWI6Xrotcf7 20 | wSCb02HUhqeRUeL8IUnxIyCyr1s19HIOfvLvFKut58kaEfrLuTunDRg52lCUKNOX 21 | 8uItC0TuvGbmt9fEfrVr7ELYtWPocnSpgVqid1RUd+1MDc4U7jvjhCK+z2X90kcX 22 | 68zH6lCPyIDFsA/VrYeZBeUw1aeEtHtDwu7GRXdQ1EBqpNvru1ucxFPepH6DXO4/ 23 | oC1mQrsThGdH1CalYo0J4AShb9CqVZXtgcNnPCPXDGPuVgi6TPh0A9/aSIK3fF6W 24 | KFVfoTHnqb2dGfgF/GuEDi05T87LTA/h/Uteu24hjsKZhsUG5jyfQs8pEONl4Bsy 25 | 6UD87QXUoQoGOJyJtG+s/CBmlsNNzcd8YpJmetI6ZvnRWarIwst/FgbJdtmN0PdI 26 | wbKCnktmIiyjtWBtUGybH0CvBwDF/fgXHC1KlO/9jlfD9/YmpUJXxtfhwMURmAAK 27 | pOAFf+6ynCYBmTp/Rla31kEbkA/QAfHYUfr1UMWR 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Mozilla 4 | AlignAfterOpenBracket: AlwaysBreak 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlines: Right 8 | AlignOperands: true 9 | AlignTrailingComments: true 10 | AllowAllParametersOfDeclarationOnNextLine: false 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: Inline 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: false 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BreakBeforeBinaryOperators: None 21 | BreakBeforeBraces: Attach 22 | BreakBeforeTernaryOperators: true 23 | BreakStringLiterals: true 24 | ColumnLimit: 120 25 | ContinuationIndentWidth: 4 26 | DerivePointerAlignment: false 27 | IncludeBlocks: Preserve 28 | IndentCaseLabels: true 29 | IndentPPDirectives: AfterHash 30 | IndentWidth: 4 31 | IndentWrappedFunctionNames: true 32 | KeepEmptyLinesAtTheStartOfBlocks: true 33 | MacroBlockBegin: '' 34 | MacroBlockEnd: '' 35 | MaxEmptyLinesToKeep: 1 36 | PenaltyBreakAssignment: 2 37 | PenaltyBreakBeforeFirstCallParameter: 19 38 | PenaltyBreakComment: 300 39 | PenaltyBreakFirstLessLess: 120 40 | PenaltyBreakString: 1000 41 | PenaltyExcessCharacter: 1000000 42 | PenaltyReturnTypeOnItsOwnLine: 100000 43 | PointerAlignment: Right 44 | ReflowComments: true 45 | SortIncludes: true 46 | SpaceAfterCStyleCast: false 47 | SpaceBeforeAssignmentOperators: true 48 | SpaceBeforeParens: ControlStatements 49 | SpaceInEmptyParentheses: false 50 | SpacesInContainerLiterals: true 51 | SpacesInCStyleCastParentheses: false 52 | SpacesInParentheses: false 53 | SpacesInSquareBrackets: false 54 | Standard: Cpp11 55 | TabWidth: 4 56 | UseTab: Never 57 | ... 58 | 59 | -------------------------------------------------------------------------------- /source/common.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | #include "common.h" 6 | 7 | #include 8 | #include 9 | 10 | PyObject *aws_py_get_cpu_group_count(PyObject *self, PyObject *args) { 11 | (void)self; 12 | (void)args; 13 | uint16_t count = aws_get_cpu_group_count(); 14 | return PyLong_FromUnsignedLong(count); 15 | } 16 | 17 | PyObject *aws_py_get_cpu_count_for_group(PyObject *self, PyObject *args) { 18 | (void)self; 19 | 20 | uint16_t group_idx; 21 | if (!PyArg_ParseTuple(args, "H", &group_idx)) { 22 | return NULL; 23 | } 24 | 25 | size_t count = aws_get_cpu_count_for_group(group_idx); 26 | return PyLong_FromSize_t(count); 27 | } 28 | 29 | PyObject *aws_py_thread_join_all_managed(PyObject *self, PyObject *args) { 30 | (void)self; 31 | 32 | double timeout_sec = 0.0; 33 | if (!PyArg_ParseTuple(args, "d", &timeout_sec)) { 34 | PyErr_SetNone(PyExc_ValueError); 35 | return NULL; 36 | } 37 | 38 | /* Actual call uses 0 to denote "wait forever" */ 39 | uint64_t timeout_ns; 40 | if (timeout_sec < 0.0) { 41 | timeout_ns = 0; 42 | } else { 43 | timeout_ns = (uint64_t)(timeout_sec * 1000000000); 44 | if (timeout_ns == 0) { 45 | timeout_ns = 1; 46 | } 47 | } 48 | aws_thread_set_managed_join_timeout_ns(timeout_ns); 49 | int res = AWS_OP_SUCCESS; 50 | /* clang-format off */ 51 | Py_BEGIN_ALLOW_THREADS 52 | /* Drop GIL to allow other threads callback into python to finish joining. */ 53 | res = aws_thread_join_all_managed(); 54 | Py_END_ALLOW_THREADS 55 | 56 | if(res == AWS_OP_SUCCESS) { 57 | Py_RETURN_TRUE; 58 | } else { 59 | Py_RETURN_FALSE; 60 | } 61 | /* clang-format on */ 62 | } 63 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft source 2 | graft crt 3 | global-exclude .git* 4 | global-exclude .git*/** 5 | global-exclude .travis* 6 | global-exclude .travis/** 7 | global-exclude .builder/** 8 | global-exclude format-check.py 9 | global-exclude builder.json 10 | global-exclude codebuild/** 11 | global-exclude .clang-format 12 | global-exclude .clang-tidy 13 | global-exclude crt/*/verification/** 14 | global-exclude crt/*/docs/** 15 | global-exclude crt/*/bin/** 16 | global-exclude crt/*/scripts/** 17 | global-exclude docker-images/** 18 | global-exclude crt/aws-lc/**/test/** 19 | global-exclude crt/aws-lc/**/crypto_test_data* 20 | # aws-lc includes some files from tests during build 21 | global-include crt/aws-lc/**/trampoline-* 22 | global-exclude README* 23 | prune crt/**/AWSCRTAndroidTestRunner 24 | prune crt/aws-c-auth/tests 25 | prune crt/aws-c-cal/tests 26 | prune crt/aws-c-common/tests 27 | prune crt/aws-c-compression/tests 28 | prune crt/aws-c-io/tests 29 | prune crt/aws-c-mqtt/tests 30 | prune crt/aws-c-s3/tests 31 | prune crt/aws-c-sdkutils/tests 32 | prune crt/aws-c-cal/ecdsa-fuzz-corpus 33 | prune crt/aws-c-s3/benchmarks 34 | prune crt/s2n/tests 35 | prune crt/s2n/compliance/specs 36 | exclude crt/aws-lc/**/*test*.go 37 | exclude crt/aws-lc/**/*test*.json 38 | exclude crt/aws-lc/**/*test*.py 39 | exclude crt/aws-lc/**/*test*.txt 40 | prune crt/aws-lc/fuzz 41 | prune crt/aws-lc/ssl 42 | prune crt/aws-lc/tests 43 | graft crt/aws-lc/tests/compiler_features_tests 44 | prune crt/aws-lc/third_party 45 | graft crt/aws-lc/third_party/fiat 46 | graft crt/aws-lc/third_party/s2n-bignum 47 | graft crt/aws-lc/third_party/jitterentropy 48 | prune crt/aws-lc/third_party/jitterentropy/tests 49 | prune crt/aws-lc/third_party/jitterentropy/doc 50 | prune crt/aws-lc/tool 51 | prune crt/aws-lc/util 52 | include crt/aws-lc/util/fipstools/CMakeLists.txt 53 | include crt/aws-lc/util/fipstools/acvp/modulewrapper/CMakeLists.txt 54 | # by default only test/test*.py are included, include the entire test suite 55 | graft test 56 | -------------------------------------------------------------------------------- /codebuild/CanaryWrapper_MetricFunctions.py: -------------------------------------------------------------------------------- 1 | # Contains all of the metric reporting functions for the Canary Wrappers 2 | 3 | # Needs to be installed prior to running 4 | import psutil 5 | 6 | 7 | cache_cpu_psutil_process = None 8 | def get_metric_total_cpu_usage(psutil_process : psutil.Process): 9 | global cache_cpu_psutil_process 10 | 11 | try: 12 | if (psutil_process == None): 13 | print ("ERROR - No psutil.process passed! Cannot gather metric!", flush=True) 14 | return None 15 | # We always need to skip the first CPU poll 16 | if (cache_cpu_psutil_process != psutil_process): 17 | psutil.cpu_percent(interval=None) 18 | cache_cpu_psutil_process = psutil_process 19 | return None 20 | return psutil.cpu_percent(interval=None) 21 | except Exception as e: 22 | print ("ERROR - exception occurred gathering metrics!") 23 | print ("Exception: " + str(e), flush=True) 24 | return None 25 | 26 | # Note: This value is in BYTES. 27 | def get_metric_total_memory_usage_value(psutil_process : psutil.Process): 28 | try: 29 | if (psutil_process == None): 30 | print ("ERROR - No psutil.process passed! Cannot gather metric!", flush=True) 31 | return None 32 | return psutil.virtual_memory()[3] 33 | except Exception as e: 34 | print ("ERROR - exception occurred gathering metrics!") 35 | print ("Exception: " + str(e), flush=True) 36 | return None 37 | 38 | 39 | def get_metric_total_memory_usage_percent(psutil_process : psutil.Process): 40 | try: 41 | if (psutil_process == None): 42 | print ("ERROR - No psutil.process passed! Cannot gather metric!", flush=True) 43 | return None 44 | return psutil.virtual_memory()[2] 45 | except Exception as e: 46 | print ("ERROR - exception occurred gathering metrics!") 47 | print ("Exception: " + str(e), flush=True) 48 | return None 49 | -------------------------------------------------------------------------------- /test/resources/rootCA.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGJTCCBA2gAwIBAgIUVnRXX5WH+l+rPGGC9H/2qI8ikOswDQYJKoZIhvcNAQEL 3 | BQAwgaExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRs 4 | ZTEPMA0GA1UECgwGQW1hem9uMRcwFQYDVQQLDA5TREtzIGFuZCBUb29sczEbMBkG 5 | A1UEAwwSYXdzLWNvbW1vbi1ydW50aW1lMSwwKgYJKoZIhvcNAQkBFh1hd3MtY29t 6 | bW9uLXJ1bnRpbWVAYW1hem9uLmNvbTAeFw0yMTA1MDcyMDE3MDRaFw0zMTA1MDUy 7 | MDE3MDRaMIGhMQswCQYDVQQGEwJVUzELMAkGA1UECAwCV0ExEDAOBgNVBAcMB1Nl 8 | YXR0bGUxDzANBgNVBAoMBkFtYXpvbjEXMBUGA1UECwwOU0RLcyBhbmQgVG9vbHMx 9 | GzAZBgNVBAMMEmF3cy1jb21tb24tcnVudGltZTEsMCoGCSqGSIb3DQEJARYdYXdz 10 | LWNvbW1vbi1ydW50aW1lQGFtYXpvbi5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4IC 11 | DwAwggIKAoICAQC4tHQfElF5SZDRdOyvwc6qALXQGLVpx2RL29tCnl2aBaq3lzjn 12 | sdfF9h8bCjCH4xQv9FkfszBbOet70FN/E27RgOxYnQlhI1BnJAVnd/TqG9yPn8xB 13 | fCd1bkGg1s7ggjP2n4zHqsfe/Hmyd9AJSklMlF2JVm6eNynrFiUruLIQXJowFbi2 14 | dBIZEM4FL5rGPlOLVtLUhQasjx34Lae7x431ppEhpnA37F/NFdZVssRgV7Xw0ti3 15 | XhGa2+8mP2I693cqyiYoz6w1FOlkzzO8HYxHHeX41JYoo9SCb/JwwevmKLCLxL5A 16 | 0lBoF30BP0MNifx1aGvfxl9UsMijSfKXxaJouhF57lUP+fE5NhIPLhJaqmw7BRJk 17 | ipDTV5S7sOeKRBSBK+a94AtgA1O8lui00edyvUirA6SPhLID+dG/EgTemqa1iRjU 18 | PfTyssaDwdMPIGvMQNxlSxWjaU7HLVclPh797JxYee42JkzwxMnTCSIL5q92M00K 19 | TXG+G5PNI+TNsQu8JfkuDUH8vHkH2cXbzBZ267ursDHqvaZjZq/7hNIA5Q5Rizp/ 20 | 9G97yzrm4aUMFZJCXDtAUuj5wZctxMemb7qWQ+ExkTp/nQAMC5Lvy31Qj4h0c+9B 21 | qp9/1BBRfiJ46VhvLAVsWZ8IOJxcAk3Phoy3TYfGcE3NfPlQVMxpi+DjPQIDAQAB 22 | o1MwUTAdBgNVHQ4EFgQUyOT1h4YXjPuzVpGrPl6Vhs8yiiQwHwYDVR0jBBgwFoAU 23 | yOT1h4YXjPuzVpGrPl6Vhs8yiiQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B 24 | AQsFAAOCAgEAoluZ0rOsV7s48UvZXHOhtQ1naLWla3X4z/+t5bQYqO2OBvtn29Wp 25 | 80dm1KHj/AXgkwU3t5o7Okcn7QiUUbL4JxiSJThSIjgWfAhj+IhsSFFt/tEBtE14 26 | v1EsDx0BlA8bI/IF11yVIkrPodfd8HD69RGOU9+nI7ZBS6wvjwD0cy6GfZ9dkIGU 27 | 9DyCKyuOiw43lnQUFgkv87q10UeqiV+yB6a11V8EjcjvayZ5SerSK4AnihDiFUmi 28 | QlGB8Z40vqrkLzf4SlFGnTMBRDwRf8xdxZC3eBi2Q2HRNPKIuZ25zw12CNcHpSHN 29 | nwZlNaJc5TKhTTZ1yGGKx2Jebdfx7uPKUUnCXzi5v/b0L+2J3U/CHzyMCbw2rd8I 30 | vKnClvMG3wOXlXlQua3oKTJ7Yqn6QWqzXjT6ct/Y88ezkk6Ixjs8MRLkkcsCdMMN 31 | Imxyr6hoRwB8ZgTKbxn27EmyfmloZXptvkrg/aJ7joJnDc+EKaqXIzS1Ha+nNlEX 32 | wHdkL9RLlD8bxb+m4HSRi7o8Jd3ZEzse50TGOijX0hCM8oNaWWsL74Q+yxIPXEYW 33 | FVFUQL/zReZSyMaJ+Q4V1i42ZYN9aArPcbfFQLubGCMJ3hm2QU71C1Lx73tqu8jb 34 | kJ3sQTNmkGBHR7PYFsiQf0eyDa9gvPU5e2l5LQpH+mCUTba+QbnrwtU= 35 | -----END CERTIFICATE----- 36 | -------------------------------------------------------------------------------- /.builder/actions/aws_crt_python.py: -------------------------------------------------------------------------------- 1 | import Builder 2 | import argparse 3 | from pathlib import Path 4 | import sys 5 | 6 | 7 | class AWSCrtPython(Builder.Action): 8 | 9 | def run(self, env): 10 | # allow custom python to be used 11 | parser = argparse.ArgumentParser() 12 | parser.add_argument('--python') 13 | args = parser.parse_known_args(env.args.args)[0] 14 | if args.python: 15 | self.python = args.python 16 | else: 17 | # Fall back on using the "{python}" builder variable 18 | self.python = env.config['variables']['python'] 19 | 20 | # Create a virtual environment and use that. 21 | # Otherwise, in places like ubuntu 24.04, PEP 668 stops 22 | # you from globally installing/upgrading packages 23 | venv_dirpath = Path.cwd() / '.venv-builder' 24 | env.shell.exec(self.python, '-m', 'venv', str(venv_dirpath), check=True) 25 | if sys.platform == 'win32': 26 | self.python = str(venv_dirpath / 'Scripts/python') 27 | else: 28 | self.python = str(venv_dirpath / 'bin/python') 29 | 30 | # Enable S3 tests 31 | env.shell.setenv('AWS_TEST_S3', '1') 32 | env.shell.setenv('AWS_TEST_LOCALHOST', '1') 33 | 34 | actions = [ 35 | [self.python, '--version'], 36 | [self.python, '-m', 'pip', 'install', '--upgrade', 'pip'], 37 | Builder.SetupCrossCICrtEnvironment(), 38 | [self.python, '-m', 'pip', 'install', '--verbose', '.[dev]'], 39 | # "--failfast" because, given how our leak-detection in tests currently works, 40 | # once one test fails all the rest usually fail too. 41 | [self.python, '-m', 'unittest', 'discover', '--verbose', '--failfast'], 42 | # postman-echo.com in now requires TLS1.3, 43 | # but our Mac implementation doesn't support TLS1.3 yet. 44 | # The work has been planned to Dec. 2025 to support TLS1.3, 45 | # so disable the test for now. And reenable it afterward 46 | # # http_client_test.py launches external processes using the extra args 47 | # [self.python, 'crt/aws-c-http/integration-testing/http_client_test.py', 48 | # self.python, 'elasticurl.py'], 49 | ] 50 | 51 | return Builder.Script(actions, name='aws-crt-python') 52 | -------------------------------------------------------------------------------- /source/event_stream.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_EVENT_STREAM_H 2 | #define AWS_CRT_PYTHON_EVENT_STREAM_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | #include "module.h" 8 | 9 | struct aws_array_list; 10 | struct aws_event_stream_header_value_pair; 11 | 12 | PyObject *aws_py_event_stream_rpc_client_connection_connect(PyObject *self, PyObject *args); 13 | PyObject *aws_py_event_stream_rpc_client_connection_close(PyObject *self, PyObject *args); 14 | PyObject *aws_py_event_stream_rpc_client_connection_is_open(PyObject *self, PyObject *args); 15 | PyObject *aws_py_event_stream_rpc_client_connection_send_protocol_message(PyObject *self, PyObject *args); 16 | PyObject *aws_py_event_stream_rpc_client_connection_new_stream(PyObject *self, PyObject *args); 17 | PyObject *aws_py_event_stream_rpc_client_continuation_activate(PyObject *self, PyObject *args); 18 | PyObject *aws_py_event_stream_rpc_client_continuation_send_message(PyObject *self, PyObject *args); 19 | PyObject *aws_py_event_stream_rpc_client_continuation_is_closed(PyObject *self, PyObject *args); 20 | 21 | /** 22 | * Given a python list of Headers, init an aws_array_list of aws_event_stream_header_value_pairs. 23 | * All variable-length values are copied (owned) by the new headers. 24 | * Returns false and sets python exception if error occurred. 25 | */ 26 | bool aws_py_event_stream_native_headers_init(struct aws_array_list *native_headers, PyObject *headers_py); 27 | 28 | /** 29 | * Given an array of aws_event_stream_header_value_pair, create a python list containing (name, value, type) tuples. 30 | * Returns a new reference if successful. 31 | * Returns NULL and sets a python exception if error occurs. 32 | */ 33 | PyObject *aws_py_event_stream_python_headers_create( 34 | struct aws_event_stream_header_value_pair *native_headers, 35 | size_t count); 36 | 37 | /** 38 | * Common callback used by both connection and continuation messages. 39 | * user_data should be a python callable that takes an error_code and returns nothing. */ 40 | void aws_py_event_stream_rpc_client_on_message_flush(int error_code, void *user_data); 41 | 42 | /* Given a python object, return a pointer to its underlying native type. 43 | * If NULL is returned, a python error has been set */ 44 | 45 | struct aws_event_stream_rpc_client_connection *aws_py_get_event_stream_rpc_client_connection(PyObject *connection); 46 | struct aws_event_stream_rpc_client_continuation_token *aws_py_get_event_stream_rpc_client_continuation( 47 | PyObject *continuation); 48 | 49 | #endif /* AWS_CRT_PYTHON_EVENT_STREAM_H */ 50 | -------------------------------------------------------------------------------- /source/mqtt_client.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | #include "mqtt_client.h" 6 | 7 | #include "io.h" 8 | 9 | static const char *s_capsule_name_mqtt_client = "aws_mqtt_client"; 10 | 11 | struct mqtt_client_binding { 12 | struct aws_mqtt_client *native; 13 | 14 | /* Dependencies that must outlive this */ 15 | PyObject *bootstrap; 16 | PyObject *tls_ctx; 17 | }; 18 | 19 | static void s_mqtt_python_client_destructor(PyObject *client_capsule) { 20 | 21 | struct mqtt_client_binding *client = PyCapsule_GetPointer(client_capsule, s_capsule_name_mqtt_client); 22 | assert(client); 23 | 24 | aws_mqtt_client_release(client->native); 25 | Py_DECREF(client->bootstrap); 26 | Py_DECREF(client->tls_ctx); 27 | aws_mem_release(aws_py_get_allocator(), client); 28 | } 29 | 30 | PyObject *aws_py_mqtt_client_new(PyObject *self, PyObject *args) { 31 | (void)self; 32 | 33 | struct aws_allocator *allocator = aws_py_get_allocator(); 34 | 35 | PyObject *bootstrap_py; 36 | PyObject *tls_ctx_py; 37 | if (!PyArg_ParseTuple(args, "OO", &bootstrap_py, &tls_ctx_py)) { 38 | return NULL; 39 | } 40 | 41 | struct aws_client_bootstrap *bootstrap = aws_py_get_client_bootstrap(bootstrap_py); 42 | if (!bootstrap) { 43 | return NULL; 44 | } 45 | 46 | struct mqtt_client_binding *client = aws_mem_calloc(allocator, 1, sizeof(struct mqtt_client_binding)); 47 | if (!client) { 48 | PyErr_SetAwsLastError(); 49 | return NULL; 50 | } 51 | 52 | /* From hereon, we need to clean up if errors occur */ 53 | client->native = aws_mqtt_client_new(allocator, bootstrap); 54 | if (client->native == NULL) { 55 | PyErr_SetAwsLastError(); 56 | goto client_init_failed; 57 | } 58 | 59 | PyObject *capsule = PyCapsule_New(client, s_capsule_name_mqtt_client, s_mqtt_python_client_destructor); 60 | if (!capsule) { 61 | goto capsule_new_failed; 62 | } 63 | 64 | /* From hereon, nothing will fail */ 65 | 66 | client->bootstrap = bootstrap_py; 67 | Py_INCREF(client->bootstrap); 68 | client->tls_ctx = tls_ctx_py; 69 | Py_INCREF(client->tls_ctx); 70 | return capsule; 71 | 72 | capsule_new_failed: 73 | aws_mqtt_client_release(client->native); 74 | client_init_failed: 75 | aws_mem_release(allocator, client); 76 | return NULL; 77 | } 78 | 79 | struct aws_mqtt_client *aws_py_get_mqtt_client(PyObject *mqtt_client) { 80 | AWS_PY_RETURN_NATIVE_FROM_BINDING(mqtt_client, s_capsule_name_mqtt_client, "Client", mqtt_client_binding); 81 | } 82 | -------------------------------------------------------------------------------- /.github/workflows/stale_issue.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | 3 | # Controls when the action will run. 4 | on: 5 | schedule: 6 | - cron: '0 9 * * 1' # minute hour dom month dow 7 | 8 | jobs: 9 | cleanup: 10 | runs-on: ubuntu-latest 11 | name: Stale issue job 12 | permissions: 13 | issues: write 14 | pull-requests: write 15 | steps: 16 | - uses: aws-actions/stale-issue-cleanup@v3 17 | with: 18 | # Setting messages to an empty string will cause the automation to skip 19 | # that category 20 | ancient-issue-message: Greetings! Sorry to say but this is a very old issue that is probably not getting as much attention as it deservers. We encourage you to check if this is still an issue in the latest release and if you find that this is still a problem, please feel free to open a new one. 21 | stale-issue-message: Greetings! It looks like this issue hasn’t been active in longer than a week. We encourage you to check if this is still an issue in the latest release. Because it has been longer than a week since the last update on this, and in the absence of more information, we will be closing this issue soon. If you find that this is still a problem, please feel free to provide a comment or add an upvote to prevent automatic closure, or if the issue is already closed, please feel free to open a new one. 22 | stale-pr-message: Greetings! It looks like this PR hasn’t been active in longer than a week, add a comment or an upvote to prevent automatic closure, or if the issue is already closed, please feel free to open a new one. 23 | 24 | # These labels are required 25 | stale-issue-label: closing-soon 26 | exempt-issue-label: automation-exempt 27 | stale-pr-label: closing-soon 28 | exempt-pr-label: pr/needs-review 29 | response-requested-label: response-requested 30 | 31 | # Don't set closed-for-staleness label to skip closing very old issues 32 | # regardless of label 33 | closed-for-staleness-label: closed-for-staleness 34 | 35 | # Issue timing 36 | days-before-stale: 2 37 | days-before-close: 5 38 | days-before-ancient: 36500 39 | 40 | # If you don't want to mark a issue as being ancient based on a 41 | # threshold of "upvotes", you can set this here. An "upvote" is 42 | # the total number of +1, heart, hooray, and rocket reactions 43 | # on an issue. 44 | minimum-upvotes-to-exempt: 1 45 | 46 | repo-token: ${{ secrets.GITHUB_TOKEN }} 47 | loglevel: DEBUG 48 | # Set dry-run to true to not perform label or close actions. 49 | dry-run: false 50 | -------------------------------------------------------------------------------- /source/crypto.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_CRYPTO_H 2 | #define AWS_CRT_PYTHON_CRYPTO_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | #include "module.h" 8 | 9 | /** Name string for hash capsule. */ 10 | extern const char *s_capsule_name_hash; 11 | /** Name string for hmac capsule. */ 12 | extern const char *s_capsule_name_hmac; 13 | /** Name string for rsa capsule. */ 14 | extern const char *s_capsule_name_rsa; 15 | /** Name string for rsa capsule. */ 16 | extern const char *s_capsule_name_ed25519; 17 | 18 | PyObject *aws_py_sha1_new(PyObject *self, PyObject *args); 19 | PyObject *aws_py_sha256_new(PyObject *self, PyObject *args); 20 | PyObject *aws_py_md5_new(PyObject *self, PyObject *args); 21 | 22 | PyObject *aws_py_hash_update(PyObject *self, PyObject *args); 23 | PyObject *aws_py_hash_digest(PyObject *self, PyObject *args); 24 | 25 | PyObject *aws_py_sha256_hmac_new(PyObject *self, PyObject *args); 26 | 27 | PyObject *aws_py_hmac_update(PyObject *self, PyObject *args); 28 | PyObject *aws_py_hmac_digest(PyObject *self, PyObject *args); 29 | 30 | PyObject *aws_py_sha256_compute(PyObject *self, PyObject *args); 31 | PyObject *aws_py_md5_compute(PyObject *self, PyObject *args); 32 | PyObject *aws_py_sha256_hmac_compute(PyObject *self, PyObject *args); 33 | 34 | PyObject *aws_py_rsa_private_key_from_pem_data(PyObject *self, PyObject *args); 35 | PyObject *aws_py_rsa_public_key_from_pem_data(PyObject *self, PyObject *args); 36 | 37 | PyObject *aws_py_rsa_private_key_from_der_data(PyObject *self, PyObject *args); 38 | PyObject *aws_py_rsa_public_key_from_der_data(PyObject *self, PyObject *args); 39 | 40 | PyObject *aws_py_rsa_encrypt(PyObject *self, PyObject *args); 41 | PyObject *aws_py_rsa_decrypt(PyObject *self, PyObject *args); 42 | PyObject *aws_py_rsa_sign(PyObject *self, PyObject *args); 43 | PyObject *aws_py_rsa_verify(PyObject *self, PyObject *args); 44 | 45 | PyObject *aws_py_ed25519_new_generate(PyObject *self, PyObject *args); 46 | PyObject *aws_py_ed25519_export_public_key(PyObject *self, PyObject *args); 47 | PyObject *aws_py_ed25519_export_private_key(PyObject *self, PyObject *args); 48 | 49 | PyObject *aws_py_ec_new_generate(PyObject *self, PyObject *args); 50 | PyObject *aws_py_ec_key_from_der_data(PyObject *self, PyObject *args); 51 | PyObject *aws_py_ec_export_key(PyObject *self, PyObject *args); 52 | PyObject *aws_py_ec_sign(PyObject *self, PyObject *args); 53 | PyObject *aws_py_ec_verify(PyObject *self, PyObject *args); 54 | PyObject *aws_py_ec_encode_signature(PyObject *self, PyObject *args); 55 | PyObject *aws_py_ec_decode_signature(PyObject *self, PyObject *args); 56 | PyObject *aws_py_ec_decode_signature_to_padded_pair(PyObject *self, PyObject *args); 57 | PyObject *aws_py_ec_get_public_coords(PyObject *self, PyObject *args); 58 | 59 | #endif /* AWS_CRT_PYTHON_CRYPTO_H */ 60 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | # Enable native memory tracing so that tests can detect leaks. 5 | # This env-var MUST be set before awscrt is imported. 6 | # the "noqa" comment prevents the autoformatter from moving this line below other imports 7 | import os 8 | os.environ['AWS_CRT_MEMORY_TRACING'] = '2' # noqa 9 | os.environ['AWS_CRT_CRASH_HANDLER'] = '1' # noqa 10 | 11 | from awscrt import NativeResource 12 | from awscrt._test import check_for_leaks 13 | import time 14 | import unittest 15 | 16 | TIMEOUT = 30.0 17 | 18 | 19 | class NativeResourceTest(unittest.TestCase): 20 | """ 21 | Test fixture asserts there are no living NativeResources when a test completes. 22 | """ 23 | 24 | _previous_test_failed = False 25 | 26 | def setUp(self): 27 | NativeResource._track_lifetime = True 28 | # init_logging(LogLevel.Trace, 'stderr') 29 | 30 | def tearDown(self): 31 | # Stop checking for leaks if any test has failed. 32 | # It's likely that the failed test leaks data, which will make 33 | # all future tests look like they're leaking too. 34 | if NativeResourceTest._previous_test_failed: 35 | return 36 | 37 | # Determine whether the current test just failed. 38 | # This isn't possible with the public API, 39 | # and the technique to pull it off can vary by Python version. 40 | if hasattr(self._outcome, 'errors'): 41 | # Works in Python 3.10 and earlier 42 | result = self.defaultTestResult() 43 | self._feedErrorsToResult(result, self._outcome.errors) 44 | else: 45 | # Works in Python 3.11 and later 46 | result = self._outcome.result 47 | 48 | current_test_failed = any(failed_test == self for failed_test, _ in result.errors + result.failures) 49 | if current_test_failed: 50 | NativeResourceTest._previous_test_failed = True 51 | return 52 | 53 | # All tests have passed so far, check for leaks 54 | try: 55 | check_for_leaks(timeout_sec=TIMEOUT) 56 | except Exception: 57 | NativeResourceTest._previous_test_failed = True 58 | raise 59 | 60 | 61 | MAX_RETRIES = 5 62 | 63 | 64 | def _is_retryable_exception(e): 65 | exception_text = str(e) 66 | return "AWS_IO_TLS_NEGOTIATION_TIMEOUT" in exception_text or "AWS_IO_SOCKET_TIMEOUT" in exception_text 67 | 68 | 69 | def test_retry_wrapper(test_function): 70 | for i in range(MAX_RETRIES): 71 | try: 72 | test_function() 73 | return 74 | except Exception as e: 75 | if _is_retryable_exception(e) and i + 1 < MAX_RETRIES: 76 | check_for_leaks(timeout_sec=TIMEOUT) # Force cleanup between retries 77 | time.sleep(1) 78 | else: 79 | raise 80 | -------------------------------------------------------------------------------- /docsrc/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath(os.path.join('..', '..'))) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'awscrt' 21 | copyright = '2020, Amazon Web Services, Inc' 22 | author = 'Amazon Web Services, Inc' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = [ 31 | 'sphinx.ext.autodoc', 32 | 'sphinx.ext.napoleon', 33 | 'sphinx.ext.intersphinx', # for linking external docs (official Python API) 34 | ] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # List of patterns, relative to source directory, that match files and 40 | # directories to ignore when looking for source files. 41 | # This pattern also affects html_static_path and html_extra_path. 42 | exclude_patterns = [] 43 | 44 | # 'bysource' prevents enums from sorting in alphabetical order 45 | autodoc_member_order = 'bysource' 46 | 47 | # False puts type in the "Returns: " line, rather than its own "Return Type:" line. 48 | # without this, we ge a lot of empty "Returns:" lines because we only commented the type. 49 | napoleon_use_rtype = False 50 | 51 | # A string that determines how domain objects (e.g. functions, classes, 52 | # attributes, etc.) are displayed in their table of contents entry. 53 | toc_object_entries_show_parents = 'hide' 54 | 55 | # -- Options for HTML output ------------------------------------------------- 56 | 57 | # The theme to use for HTML and HTML Help pages. See the documentation for 58 | # a list of builtin themes. 59 | # 60 | html_theme = 'bizstyle' 61 | 62 | html_theme_options = { 63 | 'sidebarwidth': 300 64 | } 65 | 66 | # Add any paths that contain custom static files (such as style sheets) here, 67 | # relative to this directory. They are copied after the builtin static files, 68 | # so a file named "default.css" will overwrite the builtin "default.css". 69 | html_static_path = ['_static'] 70 | 71 | html_style = 'css/custom.css' 72 | 73 | # For cross-linking to types from other libraries 74 | intersphinx_mapping = { 75 | 'python': ('https://docs.python.org/3', None), 76 | } 77 | 78 | # Extra warnings 79 | nitpicky = True 80 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | description: Report a bug 4 | title: "(short issue description)" 5 | labels: [bug, needs-triage] 6 | assignees: [] 7 | body: 8 | - type: textarea 9 | id: description 10 | attributes: 11 | label: Describe the bug 12 | description: What is the problem? A clear and concise description of the bug. 13 | validations: 14 | required: true 15 | - type: checkboxes 16 | id: regression 17 | attributes: 18 | label: Regression Issue 19 | description: What is a regression? If it worked in a previous version but doesn't in the latest version, it's considered a regression. In this case, please provide specific version number in the report. 20 | options: 21 | - label: Select this option if this issue appears to be a regression. 22 | required: false 23 | - type: textarea 24 | id: expected 25 | attributes: 26 | label: Expected Behavior 27 | description: | 28 | What did you expect to happen? 29 | validations: 30 | required: true 31 | - type: textarea 32 | id: current 33 | attributes: 34 | label: Current Behavior 35 | description: | 36 | What actually happened? 37 | 38 | Please include full errors, uncaught exceptions, stack traces, and relevant logs. 39 | If service responses are relevant, please include wire logs. 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: reproduction 44 | attributes: 45 | label: Reproduction Steps 46 | description: | 47 | Provide a self-contained, concise snippet of code that can be used to reproduce the issue. 48 | For more complex issues provide a repo with the smallest sample that reproduces the bug. 49 | 50 | Avoid including business logic or unrelated code, it makes diagnosis more difficult. 51 | The code sample should be an SSCCE. See http://sscce.org/ for details. In short, please provide a code sample that we can copy/paste, run and reproduce. 52 | validations: 53 | required: true 54 | - type: textarea 55 | id: solution 56 | attributes: 57 | label: Possible Solution 58 | description: | 59 | Suggest a fix/reason for the bug 60 | validations: 61 | required: false 62 | - type: textarea 63 | id: context 64 | attributes: 65 | label: Additional Information/Context 66 | description: | 67 | Anything else that might be relevant for troubleshooting this bug. Providing context helps us come up with a solution that is most useful in the real world. 68 | validations: 69 | required: false 70 | 71 | - type: input 72 | id: aws-crt-python-version 73 | attributes: 74 | label: aws-crt-python version used 75 | validations: 76 | required: true 77 | 78 | - type: input 79 | id: compiler-version 80 | attributes: 81 | label: Python version used 82 | validations: 83 | required: true 84 | 85 | - type: input 86 | id: operating-system 87 | attributes: 88 | label: Operating System and version 89 | validations: 90 | required: true 91 | -------------------------------------------------------------------------------- /source/http_proxy.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | #include "http.h" 6 | 7 | #include "io.h" 8 | 9 | #include 10 | #include 11 | 12 | bool aws_py_http_proxy_options_init(struct aws_http_proxy_options *proxy_options, PyObject *py_proxy_options) { 13 | AWS_ZERO_STRUCT(*proxy_options); 14 | 15 | bool success = false; 16 | 17 | /* These references all need to be cleaned up before function returns */ 18 | PyObject *py_host_name = NULL; 19 | PyObject *py_tls_options = NULL; 20 | PyObject *py_username = NULL; 21 | PyObject *py_password = NULL; 22 | 23 | py_host_name = PyObject_GetAttrString(py_proxy_options, "host_name"); 24 | proxy_options->host = aws_byte_cursor_from_pyunicode(py_host_name); 25 | if (!proxy_options->host.ptr) { 26 | PyErr_SetString(PyExc_TypeError, "HttpProxyOptions.host_name is not a valid string"); 27 | goto done; 28 | } 29 | 30 | proxy_options->port = PyObject_GetAttrAsUint32(py_proxy_options, "HttpProxyOptions", "port"); 31 | if (PyErr_Occurred()) { 32 | goto done; 33 | } 34 | 35 | py_tls_options = PyObject_GetAttrString(py_proxy_options, "tls_connection_options"); 36 | if (py_tls_options != Py_None) { 37 | proxy_options->tls_options = aws_py_get_tls_connection_options(py_tls_options); 38 | if (!proxy_options->tls_options) { 39 | PyErr_SetString( 40 | PyExc_TypeError, "HttpProxyOptions.tls_connection_options is not a valid TlsConnectionOptions"); 41 | goto done; 42 | } 43 | } 44 | 45 | proxy_options->auth_type = PyObject_GetAttrAsIntEnum(py_proxy_options, "HttpProxyOptions", "auth_type"); 46 | if (PyErr_Occurred()) { 47 | goto done; 48 | } 49 | 50 | py_username = PyObject_GetAttrString(py_proxy_options, "auth_username"); 51 | if (py_username != Py_None) { 52 | proxy_options->auth_username = aws_byte_cursor_from_pyunicode(py_username); 53 | if (!proxy_options->auth_username.ptr) { 54 | PyErr_SetString(PyExc_TypeError, "HttpProxyOptions.auth_username is not a valid string"); 55 | goto done; 56 | } 57 | } 58 | 59 | py_password = PyObject_GetAttrString(py_proxy_options, "auth_password"); 60 | if (py_password != Py_None) { 61 | proxy_options->auth_password = aws_byte_cursor_from_pyunicode(py_password); 62 | if (!proxy_options->auth_password.ptr) { 63 | PyErr_SetString(PyExc_TypeError, "HttpProxyOptions.auth_password is not a valid string"); 64 | goto done; 65 | } 66 | } 67 | 68 | proxy_options->connection_type = PyObject_GetAttrAsIntEnum(py_proxy_options, "HttpProxyOptions", "connection_type"); 69 | if (PyErr_Occurred()) { 70 | goto done; 71 | } 72 | 73 | success = true; 74 | done: 75 | Py_XDECREF(py_host_name); 76 | Py_XDECREF(py_tls_options); 77 | Py_XDECREF(py_username); 78 | Py_XDECREF(py_password); 79 | if (!success) { 80 | AWS_ZERO_STRUCT(*proxy_options); 81 | } 82 | return success; 83 | } 84 | -------------------------------------------------------------------------------- /source/auth.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_AUTH_H 2 | #define AWS_CRT_PYTHON_AUTH_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | #include "module.h" 8 | 9 | struct aws_credentials; 10 | 11 | PyObject *aws_py_credentials_new(PyObject *self, PyObject *args); 12 | PyObject *aws_py_credentials_access_key_id(PyObject *self, PyObject *args); 13 | PyObject *aws_py_credentials_secret_access_key(PyObject *self, PyObject *args); 14 | PyObject *aws_py_credentials_session_token(PyObject *self, PyObject *args); 15 | PyObject *aws_py_credentials_expiration_timestamp_seconds(PyObject *self, PyObject *args); 16 | 17 | PyObject *aws_py_credentials_provider_get_credentials(PyObject *self, PyObject *args); 18 | PyObject *aws_py_credentials_provider_new_chain_default(PyObject *self, PyObject *args); 19 | PyObject *aws_py_credentials_provider_new_static(PyObject *self, PyObject *args); 20 | PyObject *aws_py_credentials_provider_new_profile(PyObject *self, PyObject *args); 21 | PyObject *aws_py_credentials_provider_new_process(PyObject *self, PyObject *args); 22 | PyObject *aws_py_credentials_provider_new_environment(PyObject *self, PyObject *args); 23 | PyObject *aws_py_credentials_provider_new_chain(PyObject *self, PyObject *args); 24 | PyObject *aws_py_credentials_provider_new_delegate(PyObject *self, PyObject *args); 25 | PyObject *aws_py_credentials_provider_new_cognito(PyObject *self, PyObject *args); 26 | PyObject *aws_py_credentials_provider_new_x509(PyObject *self, PyObject *args); 27 | 28 | PyObject *aws_py_signing_config_new(PyObject *self, PyObject *args); 29 | PyObject *aws_py_signing_config_get_algorithm(PyObject *self, PyObject *args); 30 | PyObject *aws_py_signing_config_get_signature_type(PyObject *self, PyObject *args); 31 | PyObject *aws_py_signing_config_get_credentials_provider(PyObject *self, PyObject *args); 32 | PyObject *aws_py_signing_config_get_region(PyObject *self, PyObject *args); 33 | PyObject *aws_py_signing_config_get_service(PyObject *self, PyObject *args); 34 | PyObject *aws_py_signing_config_get_date(PyObject *self, PyObject *args); 35 | PyObject *aws_py_signing_config_get_use_double_uri_encode(PyObject *self, PyObject *args); 36 | PyObject *aws_py_signing_config_get_should_normalize_uri_path(PyObject *self, PyObject *args); 37 | PyObject *aws_py_signing_config_get_signed_body_value(PyObject *self, PyObject *args); 38 | PyObject *aws_py_signing_config_get_signed_body_header_type(PyObject *self, PyObject *args); 39 | PyObject *aws_py_signing_config_get_expiration_in_seconds(PyObject *self, PyObject *args); 40 | PyObject *aws_py_signing_config_get_omit_session_token(PyObject *self, PyObject *args); 41 | 42 | PyObject *aws_py_sign_request_aws(PyObject *self, PyObject *args); 43 | 44 | /* Given a python object, return a pointer to its underlying native type. 45 | * If NULL is returned, a python error has been set */ 46 | 47 | struct aws_credentials *aws_py_get_credentials(PyObject *credentials); 48 | struct aws_credentials_provider *aws_py_get_credentials_provider(PyObject *credentials_provider); 49 | struct aws_signing_config_aws *aws_py_get_signing_config(PyObject *signing_config); 50 | PyObject *aws_py_credentials_new_request_from_native(struct aws_credentials *credentials); 51 | 52 | #endif // AWS_CRT_PYTHON_AUTH_H 53 | -------------------------------------------------------------------------------- /source/io.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_IO_H 2 | #define AWS_CRT_PYTHON_IO_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | 8 | /** 9 | * This file includes definitions for common aws-c-io functions. 10 | */ 11 | 12 | #include "module.h" 13 | 14 | struct aws_socket_options; 15 | 16 | /** 17 | * Init aws_socket_options from SocketOptions. Returns false and sets python exception if error occurred. 18 | */ 19 | bool aws_py_socket_options_init(struct aws_socket_options *socket_options, PyObject *py_socket_options); 20 | 21 | /** 22 | * Returns a capsule for logging and starts the logging sub-system 23 | */ 24 | PyObject *aws_py_init_logging(PyObject *self, PyObject *args); 25 | 26 | /** 27 | * Change logging level of the global logger. 28 | */ 29 | PyObject *aws_py_set_log_level(PyObject *self, PyObject *args); 30 | 31 | /** 32 | * Returns True if ALPN is available, False if it is not. 33 | */ 34 | PyObject *aws_py_is_alpn_available(PyObject *self, PyObject *args); 35 | 36 | /** 37 | * Returns True if the input TLS Cipher Preference Enum is supported on the current platform. False otherwise. 38 | */ 39 | PyObject *aws_py_is_tls_cipher_supported(PyObject *self, PyObject *args); 40 | 41 | /** 42 | * Create a new event_loop_group to be managed by a Python Capsule. 43 | */ 44 | PyObject *aws_py_event_loop_group_new(PyObject *self, PyObject *args); 45 | 46 | /** 47 | * Create a new default host_resolver to be managed by a Python Capsule. 48 | */ 49 | PyObject *aws_py_host_resolver_new_default(PyObject *self, PyObject *args); 50 | 51 | /** 52 | * Create a new client_bootstrap to be managed by a Python Capsule. 53 | */ 54 | PyObject *aws_py_client_bootstrap_new(PyObject *self, PyObject *args); 55 | 56 | /** 57 | * Create a new tls_ctx to be managed by a Python Capsule. 58 | */ 59 | PyObject *aws_py_client_tls_ctx_new(PyObject *self, PyObject *args); 60 | 61 | PyObject *aws_py_tls_connections_options_new_from_ctx(PyObject *self, PyObject *args); 62 | 63 | PyObject *aws_py_tls_connection_options_set_alpn_list(PyObject *self, PyObject *args); 64 | 65 | PyObject *aws_py_tls_connection_options_set_server_name(PyObject *self, PyObject *args); 66 | 67 | /** 68 | * Create a new aws_input_stream to be managed by a Python capsule. 69 | */ 70 | PyObject *aws_py_input_stream_new(PyObject *self, PyObject *args); 71 | 72 | /** 73 | * Create a new aws_pkcs11_lib to be managed by a Python capsule. 74 | */ 75 | PyObject *aws_py_pkcs11_lib_new(PyObject *self, PyObject *args); 76 | 77 | /* Given a python object, return a pointer to its underlying native type. 78 | * If NULL is returned, a python error has been set */ 79 | 80 | struct aws_event_loop_group *aws_py_get_event_loop_group(PyObject *event_loop_group); 81 | struct aws_host_resolver *aws_py_get_host_resolver(PyObject *host_resolver); 82 | struct aws_client_bootstrap *aws_py_get_client_bootstrap(PyObject *client_bootstrap); 83 | struct aws_tls_ctx *aws_py_get_tls_ctx(PyObject *tls_ctx); 84 | struct aws_tls_connection_options *aws_py_get_tls_connection_options(PyObject *tls_connection_options); 85 | struct aws_input_stream *aws_py_get_input_stream(PyObject *input_stream); 86 | struct aws_pkcs11_lib *aws_py_get_pkcs11_lib(PyObject *pkcs11_lib); 87 | 88 | #endif /* AWS_CRT_PYTHON_IO_H */ 89 | -------------------------------------------------------------------------------- /source/cbor.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_CBOR_H 2 | #define AWS_CRT_PYTHON_CBOR_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | #include "module.h" 8 | 9 | /******************************************************************************* 10 | * ENCODE 11 | ******************************************************************************/ 12 | 13 | PyObject *aws_py_cbor_encoder_new(PyObject *self, PyObject *args); 14 | PyObject *aws_py_cbor_encoder_get_encoded_data(PyObject *self, PyObject *args); 15 | 16 | PyObject *aws_py_cbor_encoder_write_uint(PyObject *self, PyObject *args); 17 | PyObject *aws_py_cbor_encoder_write_negint(PyObject *self, PyObject *args); 18 | PyObject *aws_py_cbor_encoder_write_float(PyObject *self, PyObject *args); 19 | PyObject *aws_py_cbor_encoder_write_bytes(PyObject *self, PyObject *args); 20 | PyObject *aws_py_cbor_encoder_write_text(PyObject *self, PyObject *args); 21 | PyObject *aws_py_cbor_encoder_write_array_start(PyObject *self, PyObject *args); 22 | PyObject *aws_py_cbor_encoder_write_map_start(PyObject *self, PyObject *args); 23 | PyObject *aws_py_cbor_encoder_write_tag(PyObject *self, PyObject *args); 24 | PyObject *aws_py_cbor_encoder_write_bool(PyObject *self, PyObject *args); 25 | 26 | /* Encode the types without value needed. The arg is the type to encode. */ 27 | PyObject *aws_py_cbor_encoder_write_simple_types(PyObject *self, PyObject *args); 28 | 29 | PyObject *aws_py_cbor_encoder_write_py_list(PyObject *self, PyObject *args); 30 | PyObject *aws_py_cbor_encoder_write_py_dict(PyObject *self, PyObject *args); 31 | PyObject *aws_py_cbor_encoder_write_data_item(PyObject *self, PyObject *args); 32 | 33 | /******************************************************************************* 34 | * DECODE 35 | ******************************************************************************/ 36 | 37 | PyObject *aws_py_cbor_decoder_new(PyObject *self, PyObject *args); 38 | PyObject *aws_py_cbor_decoder_peek_type(PyObject *self, PyObject *args); 39 | PyObject *aws_py_cbor_decoder_get_remaining_bytes_len(PyObject *self, PyObject *args); 40 | PyObject *aws_py_cbor_decoder_consume_next_element(PyObject *self, PyObject *args); 41 | PyObject *aws_py_cbor_decoder_consume_next_data_item(PyObject *self, PyObject *args); 42 | PyObject *aws_py_cbor_decoder_pop_next_unsigned_int(PyObject *self, PyObject *args); 43 | PyObject *aws_py_cbor_decoder_pop_next_negative_int(PyObject *self, PyObject *args); 44 | PyObject *aws_py_cbor_decoder_pop_next_float(PyObject *self, PyObject *args); 45 | PyObject *aws_py_cbor_decoder_pop_next_boolean(PyObject *self, PyObject *args); 46 | PyObject *aws_py_cbor_decoder_pop_next_bytes(PyObject *self, PyObject *args); 47 | PyObject *aws_py_cbor_decoder_pop_next_text(PyObject *self, PyObject *args); 48 | PyObject *aws_py_cbor_decoder_pop_next_array_start(PyObject *self, PyObject *args); 49 | PyObject *aws_py_cbor_decoder_pop_next_map_start(PyObject *self, PyObject *args); 50 | PyObject *aws_py_cbor_decoder_pop_next_tag(PyObject *self, PyObject *args); 51 | 52 | PyObject *aws_py_cbor_decoder_pop_next_py_list(PyObject *self, PyObject *args); 53 | PyObject *aws_py_cbor_decoder_pop_next_py_dict(PyObject *self, PyObject *args); 54 | PyObject *aws_py_cbor_decoder_pop_next_data_item(PyObject *self, PyObject *args); 55 | 56 | #endif /* AWS_CRT_PYTHON_CBOR_H */ 57 | -------------------------------------------------------------------------------- /source/http.h: -------------------------------------------------------------------------------- 1 | #ifndef AWS_CRT_PYTHON_HTTP_H 2 | #define AWS_CRT_PYTHON_HTTP_H 3 | /** 4 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 5 | * SPDX-License-Identifier: Apache-2.0. 6 | */ 7 | #include "module.h" 8 | 9 | struct aws_http_headers; 10 | struct aws_http_message; 11 | struct aws_http_proxy_options; 12 | 13 | /** 14 | * Init aws_http_proxy_options from HttpProxyOptions. 15 | * Returns false and sets python exception if error occurred. 16 | * 17 | * NOTE: The native struct must be used immediately because it's cursors 18 | * reference memory from strings in the PyObject. 19 | * If we need this struct to be a long-lived object, we'll need to do a full binding. 20 | */ 21 | bool aws_py_http_proxy_options_init(struct aws_http_proxy_options *proxy_options, PyObject *py_proxy_options); 22 | 23 | /** 24 | * Close the connection if it's open. 25 | */ 26 | PyObject *aws_py_http_connection_close(PyObject *self, PyObject *args); 27 | 28 | /** 29 | * Returns True if connection is open and usable, False otherwise. 30 | */ 31 | PyObject *aws_py_http_connection_is_open(PyObject *self, PyObject *args); 32 | 33 | /** 34 | * Create a new connection. returns void. The on_setup callback will be invoked 35 | * upon either success or failure of the connection. 36 | */ 37 | PyObject *aws_py_http_client_connection_new(PyObject *self, PyObject *args); 38 | 39 | PyObject *aws_py_http_client_stream_new(PyObject *self, PyObject *args); 40 | PyObject *aws_py_http_client_stream_activate(PyObject *self, PyObject *args); 41 | 42 | PyObject *aws_py_http2_client_stream_write_data(PyObject *self, PyObject *args); 43 | 44 | /* Create capsule around new request-style aws_http_message struct */ 45 | PyObject *aws_py_http_message_new_request(PyObject *self, PyObject *args); 46 | 47 | /* Create capsule to bind existing request-style aws_http_message struct. */ 48 | PyObject *aws_py_http_message_new_request_from_native(struct aws_http_message *request); 49 | 50 | PyObject *aws_py_http_message_get_request_method(PyObject *self, PyObject *args); 51 | PyObject *aws_py_http_message_set_request_method(PyObject *self, PyObject *args); 52 | PyObject *aws_py_http_message_get_request_path(PyObject *self, PyObject *args); 53 | PyObject *aws_py_http_message_set_request_path(PyObject *self, PyObject *args); 54 | PyObject *aws_py_http_message_set_body_stream(PyObject *self, PyObject *args); 55 | 56 | /* Create capsule to bind existing aws_http_headers struct. */ 57 | PyObject *aws_py_http_headers_new_from_native(struct aws_http_headers *headers); 58 | 59 | /* Create capsule around new aws_http_headers struct */ 60 | PyObject *aws_py_http_headers_new(PyObject *self, PyObject *args); 61 | 62 | PyObject *aws_py_http_headers_add(PyObject *self, PyObject *args); 63 | PyObject *aws_py_http_headers_add_pairs(PyObject *self, PyObject *args); 64 | PyObject *aws_py_http_headers_set(PyObject *self, PyObject *args); 65 | PyObject *aws_py_http_headers_get(PyObject *self, PyObject *args); 66 | PyObject *aws_py_http_headers_get_index(PyObject *self, PyObject *args); 67 | PyObject *aws_py_http_headers_count(PyObject *self, PyObject *args); 68 | PyObject *aws_py_http_headers_remove(PyObject *self, PyObject *args); 69 | PyObject *aws_py_http_headers_remove_value(PyObject *self, PyObject *args); 70 | PyObject *aws_py_http_headers_clear(PyObject *self, PyObject *args); 71 | 72 | /* Given a python object, return a pointer to its underlying native type. 73 | * If NULL is returned, a python error has been set */ 74 | 75 | struct aws_http_connection *aws_py_get_http_connection(PyObject *connection); 76 | struct aws_http_stream *aws_py_get_http_stream(PyObject *stream); 77 | struct aws_http_message *aws_py_get_http_message(PyObject *http_message); 78 | struct aws_http_headers *aws_py_get_http_headers(PyObject *http_headers); 79 | 80 | #endif /* AWS_CRT_PYTHON_HTTP_H */ 81 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/awslabs/aws-crt-python/issues), or [recently closed](https://github.com/awslabs/aws-crt-python/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Wait for a repository collaborator to look at your pull request, run the automated tests, and review. If additional changes or discussion is needed, a collaborator will get back to you, so please stay involved in the conversation. 38 | * Note: pull requests from forks will not run the automated tests without collaborator involvement for security reasons. If you make a pull request and see that the tests are pending, this is normal and expected. 39 | 40 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 41 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 42 | 43 | 44 | ## Finding contributions to work on 45 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-crt-python/labels/help%20wanted) issues is a great place to start. 46 | 47 | 48 | ## Code of Conduct 49 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 50 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 51 | opensource-codeofconduct@amazon.com with any additional questions or comments. 52 | 53 | 54 | ## Security issue notifications 55 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 56 | 57 | 58 | ## Licensing 59 | 60 | See the [LICENSE](https://github.com/awslabs/aws-crt-python/blob/main/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 61 | 62 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 63 | -------------------------------------------------------------------------------- /crt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | cmake_minimum_required(VERSION 3.9...3.31) 4 | 5 | # This CMakeLists.txt exists so we can build all the C libraries we depend on 6 | # simultaneously. This is much faster than building dependencies one at a time. 7 | # 8 | # This CMakeLists.txt does NOT build the Python extension itself. 9 | # We let setuptools handle that. 10 | project(aws-crt-dependencies) 11 | 12 | # Note: set() calls must use CACHE, and must be called before the option() they're overriding, 13 | # or they won't work right on CMake 3.12 and below. 14 | # see: https://cmake.org/cmake/help/v3.13/policy/CMP0077.html 15 | 16 | # This magic lets us build everything all at once 17 | set(IN_SOURCE_BUILD ON CACHE BOOL "") 18 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/aws-c-common/cmake) 19 | include(AwsFindPackage) 20 | 21 | # Build dependencies as static libs 22 | set(BUILD_SHARED_LIBS OFF CACHE BOOL "") 23 | set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE BOOL "") 24 | 25 | # Don't build the dependencies' tests 26 | set(BUILD_TESTING OFF CACHE BOOL "") 27 | include(CTest) 28 | 29 | option(AWS_USE_LIBCRYPTO_TO_SUPPORT_ED25519_EVERYWHERE "Set this if you want to use libcrypto to support ed25519 on Window/Apple" ON) 30 | 31 | # Use minimal debug info to reduce binary size. 32 | string(REPLACE "-g" "-g1" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") 33 | string(REPLACE "-g" "-g1" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") 34 | 35 | # On Unix we use S2N for TLS and AWS-LC crypto. 36 | # (On Windows and Apple we use the default OS libraries) 37 | if ((UNIX AND NOT APPLE) OR AWS_USE_LIBCRYPTO_TO_SUPPORT_ED25519_EVERYWHERE) 38 | option(USE_OPENSSL "Set this if you want to use your system's OpenSSL compatible libcrypto" OFF) 39 | include(AwsPrebuildDependency) 40 | 41 | if(NOT USE_OPENSSL) 42 | 43 | set(AWSLC_CMAKE_ARGUMENTS 44 | -DDISABLE_GO=ON # Build without using Go, we don't want the extra dependency 45 | -DDISABLE_PERL=ON # Build without using Perl, we don't want the extra dependency 46 | -DBUILD_LIBSSL=OFF # Don't need libssl, only need libcrypto 47 | -DBUILD_TESTING=OFF 48 | -DCMAKE_BUILD_TYPE=RelWithDebInfo # Use the same build type as the rest of the project 49 | ) 50 | 51 | if (APPLE OR WIN32) 52 | # Libcrypto implementations typically have several chunky pregenerated tables that add a lot 53 | # to artifact size. We dont really need them for ed25519 case on win/mac, so favor 54 | # smaller binary over perf here. 55 | # In future if there is more usage of lc on win/mac consider removing this 56 | list(APPEND AWSLC_CMAKE_ARGUMENTS -DOPENSSL_SMALL=1) 57 | endif() 58 | 59 | if(CMAKE_C_COMPILER_ID MATCHES "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS "5.0") 60 | # Disable AVX512 on old GCC that not supports it. 61 | list(APPEND AWSLC_CMAKE_ARGUMENTS -DMY_ASSEMBLER_IS_TOO_OLD_FOR_512AVX=ON) 62 | endif() 63 | 64 | # s2n-tls uses libcrypto during its configuration, so we need to prebuild aws-lc. 65 | aws_prebuild_dependency( 66 | DEPENDENCY_NAME AWSLC 67 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/aws-lc 68 | CMAKE_ARGUMENTS ${AWSLC_CMAKE_ARGUMENTS} 69 | ) 70 | endif() 71 | 72 | 73 | endif() 74 | 75 | if(UNIX AND NOT APPLE) 76 | # prebuild s2n-tls. 77 | aws_prebuild_dependency( 78 | DEPENDENCY_NAME S2N 79 | SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/s2n 80 | CMAKE_ARGUMENTS 81 | -DUNSAFE_TREAT_WARNINGS_AS_ERRORS=OFF 82 | -DBUILD_TESTING=OFF 83 | ) 84 | endif() 85 | 86 | add_subdirectory(aws-c-common) 87 | add_subdirectory(aws-c-sdkutils) 88 | add_subdirectory(aws-c-cal) 89 | add_subdirectory(aws-c-io) 90 | add_subdirectory(aws-checksums) 91 | add_subdirectory(aws-c-compression) 92 | add_subdirectory(aws-c-event-stream) 93 | add_subdirectory(aws-c-http) 94 | add_subdirectory(aws-c-auth) 95 | add_subdirectory(aws-c-mqtt) 96 | add_subdirectory(aws-c-s3) 97 | -------------------------------------------------------------------------------- /source/auth_signer.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #include "auth.h" 7 | 8 | #include "http.h" 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | /* Object that stays alive for duration async signing operation */ 15 | struct async_signing_data { 16 | PyObject *py_http_request; 17 | struct aws_http_message *http_request; /* owned by py_http_request, do not clean up. */ 18 | PyObject *py_signing_config; 19 | PyObject *py_on_complete; 20 | struct aws_signable *signable; 21 | }; 22 | 23 | static void s_async_signing_data_destroy(struct async_signing_data *async_data) { 24 | if (async_data) { 25 | Py_XDECREF(async_data->py_http_request); 26 | Py_XDECREF(async_data->py_signing_config); 27 | Py_XDECREF(async_data->py_on_complete); 28 | aws_signable_destroy(async_data->signable); 29 | aws_mem_release(aws_py_get_allocator(), async_data); 30 | } 31 | } 32 | 33 | static void s_signing_complete(struct aws_signing_result *signing_result, int error_code, void *userdata) { 34 | struct async_signing_data *async_data = userdata; 35 | 36 | if (!error_code) { 37 | struct aws_allocator *allocator = aws_py_get_allocator(); 38 | 39 | if (aws_apply_signing_result_to_http_request(async_data->http_request, allocator, signing_result)) { 40 | error_code = aws_last_error(); 41 | } 42 | } 43 | 44 | /*************** GIL ACQUIRE ***************/ 45 | PyGILState_STATE state; 46 | if (aws_py_gilstate_ensure(&state)) { 47 | return; /* Python has shut down. Nothing matters anymore, but don't crash */ 48 | } 49 | 50 | PyObject *py_result = PyObject_CallFunction(async_data->py_on_complete, "(i)", error_code); 51 | if (py_result) { 52 | Py_DECREF(py_result); 53 | } else { 54 | PyErr_WriteUnraisable(PyErr_Occurred()); 55 | } 56 | 57 | s_async_signing_data_destroy(async_data); 58 | 59 | PyGILState_Release(state); 60 | /*************** GIL RELEASE ***************/ 61 | } 62 | 63 | PyObject *aws_py_sign_request_aws(PyObject *self, PyObject *args) { 64 | (void)self; 65 | 66 | PyObject *py_http_request; 67 | PyObject *py_signing_config; 68 | PyObject *py_on_complete; 69 | if (!PyArg_ParseTuple(args, "OOO", &py_http_request, &py_signing_config, &py_on_complete)) { 70 | return NULL; 71 | } 72 | 73 | struct aws_http_message *http_request = aws_py_get_http_message(py_http_request); 74 | if (!http_request) { 75 | return NULL; 76 | } 77 | 78 | struct aws_signing_config_aws *signing_config = aws_py_get_signing_config(py_signing_config); 79 | if (!signing_config) { 80 | return NULL; 81 | } 82 | 83 | AWS_FATAL_ASSERT(py_on_complete != Py_None); 84 | 85 | struct aws_allocator *alloc = aws_py_get_allocator(); 86 | 87 | struct async_signing_data *async_data = aws_mem_calloc(alloc, 1, sizeof(struct async_signing_data)); 88 | if (!async_data) { 89 | return PyErr_AwsLastError(); 90 | } 91 | 92 | /* From hereon, we need to clean up if anything goes wrong. 93 | * Fortunately async_data's destroy fn will clean up anything stored inside of it. */ 94 | 95 | async_data->py_http_request = py_http_request; 96 | Py_INCREF(async_data->py_http_request); 97 | 98 | async_data->http_request = http_request; 99 | 100 | async_data->py_signing_config = py_signing_config; 101 | Py_INCREF(async_data->py_signing_config); 102 | 103 | async_data->py_on_complete = py_on_complete; 104 | Py_INCREF(async_data->py_on_complete); 105 | 106 | async_data->signable = aws_signable_new_http_request(aws_py_get_allocator(), http_request); 107 | if (!async_data->signable) { 108 | PyErr_SetAwsLastError(); 109 | goto error; 110 | } 111 | 112 | if (aws_sign_request_aws( 113 | alloc, 114 | async_data->signable, 115 | (struct aws_signing_config_base *)signing_config, 116 | s_signing_complete, 117 | async_data)) { 118 | PyErr_SetAwsLastError(); 119 | goto error; 120 | } 121 | 122 | Py_RETURN_NONE; 123 | 124 | error: 125 | s_async_signing_data_destroy(async_data); 126 | return NULL; 127 | } 128 | -------------------------------------------------------------------------------- /awscrt/checksums.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | import _awscrt 5 | 6 | 7 | def crc32(input: bytes, previous_crc32: int = 0) -> int: 8 | """ 9 | Perform a CRC32 (Ethernet, gzip) computation. 10 | 11 | If continuing to update a running CRC, pass its value into `previous_crc32`. 12 | Returns an unsigned 32-bit integer. 13 | """ 14 | return _awscrt.checksums_crc32(input, previous_crc32) 15 | 16 | 17 | def crc32c(input: bytes, previous_crc32c: int = 0) -> int: 18 | """ 19 | Perform a Castagnoli CRC32c (iSCSI) computation. 20 | If continuing to update a running CRC, pass its value into `previous_crc32c`. 21 | Returns an unsigned 32-bit integer. 22 | """ 23 | return _awscrt.checksums_crc32c(input, previous_crc32c) 24 | 25 | 26 | def crc64nvme(input: bytes, previous_crc64nvme: int = 0) -> int: 27 | """ 28 | Perform a CRC64 NVME computation. 29 | If continuing to update a running CRC, pass its value into `previous_crc64nvme`. 30 | Returns an unsigned 64-bit integer. 31 | """ 32 | return _awscrt.checksums_crc64nvme(input, previous_crc64nvme) 33 | 34 | 35 | def combine_crc32(crc32_result1: int, crc32_result2: int, data_length2: int) -> int: 36 | """ 37 | Combine two CRC32 (Ethernet, gzip) checksums computed over separate data blocks. 38 | 39 | This is equivalent to computing the CRC32 of the concatenated data blocks without 40 | having to re-scan the data. 41 | 42 | Given: 43 | crc1 = CRC32(data_block_A) 44 | crc2 = CRC32(data_block_B) 45 | 46 | This function computes: 47 | result = CRC32(data_block_A || data_block_B) 48 | 49 | Args: 50 | crc32_result1: The CRC32 checksum of the first data block 51 | crc32_result2: The CRC32 checksum of the second data block 52 | data_length2: The length (in bytes) of the original data that produced crc32_result2. 53 | This is NOT the size of the checksum (which is always 4 bytes), 54 | but rather the size of the data block that was checksummed. 55 | 56 | Returns: 57 | The combined CRC32 checksum as if computed over the concatenated data 58 | """ 59 | return _awscrt.checksums_crc32_combine(crc32_result1, crc32_result2, data_length2) 60 | 61 | 62 | def combine_crc32c(crc32c_result1: int, crc32c_result2: int, data_length2: int) -> int: 63 | """ 64 | Combine two CRC32C (Castagnoli, iSCSI) checksums computed over separate data blocks. 65 | 66 | This is equivalent to computing the CRC32C of the concatenated data blocks without 67 | having to re-scan the data. 68 | 69 | Given: 70 | crc1 = CRC32C(data_block_A) 71 | crc2 = CRC32C(data_block_B) 72 | 73 | This function computes: 74 | result = CRC32C(data_block_A || data_block_B) 75 | 76 | Args: 77 | crc32c_result1: The CRC32C checksum of the first data block 78 | crc32c_result2: The CRC32C checksum of the second data block 79 | data_length2: The length (in bytes) of the original data that produced crc32c_result2. 80 | This is NOT the size of the checksum (which is always 4 bytes), 81 | but rather the size of the data block that was checksummed. 82 | 83 | Returns: 84 | The combined CRC32C checksum as if computed over the concatenated data 85 | """ 86 | return _awscrt.checksums_crc32c_combine(crc32c_result1, crc32c_result2, data_length2) 87 | 88 | 89 | def combine_crc64nvme(crc64nvme_result1: int, crc64nvme_result2: int, data_length2: int) -> int: 90 | """ 91 | Combine two CRC64-NVME (CRC64-Rocksoft) checksums computed over separate data blocks. 92 | 93 | This is equivalent to computing the CRC64-NVME of the concatenated data blocks without 94 | having to re-scan the data. 95 | 96 | Given: 97 | crc1 = CRC64_NVME(data_block_A) 98 | crc2 = CRC64_NVME(data_block_B) 99 | 100 | This function computes: 101 | result = CRC64_NVME(data_block_A || data_block_B) 102 | 103 | Args: 104 | crc64nvme_result1: The CRC64-NVME checksum of the first data block 105 | crc64nvme_result2: The CRC64-NVME checksum of the second data block 106 | data_length2: The length (in bytes) of the original data that produced crc64nvme_result2. 107 | This is NOT the size of the checksum (which is always 8 bytes), 108 | but rather the size of the data block that was checksummed. 109 | 110 | Returns: 111 | The combined CRC64-NVME checksum as if computed over the concatenated data 112 | """ 113 | return _awscrt.checksums_crc64nvme_combine(crc64nvme_result1, crc64nvme_result2, data_length2) 114 | -------------------------------------------------------------------------------- /test/appexit_http.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | import awscrt.http 5 | import awscrt.io 6 | import enum 7 | import sys 8 | import threading 9 | 10 | # Application for use with the test_appexit unit tests. 11 | # Performs an HTTP request and shuts everything down, 12 | # with the opportunity to exit the application at any stage. 13 | 14 | Stage = enum.Enum('Stage', [ 15 | 'Start', 16 | 'EventLoopGroupStart', 17 | 'HostResolverStart', 18 | 'ClientBootstrapStart', 19 | 'TlsConnectionOptionsStart', 20 | 'HttpClientStart', 21 | 'HttpClientConnected', 22 | 'HttpStreamStart', 23 | 'HttpStreamReceivingBody', 24 | 'HttpStreamDone', 25 | 'HttpConnectionClose', 26 | 'HttpConnectionDone', 27 | 'TlsConnectionOptionsDone', 28 | 'ClientBootstrapDone', 29 | 'HostResolverDone', 30 | 'EventLoopGroupDone', 31 | 'Done', 32 | ]) 33 | 34 | 35 | EXIT_STAGE = Stage.Done 36 | TIMEOUT = 30.0 37 | REQUEST_HOST = 's3.amazonaws.com' 38 | REQUEST_PATH = '/code-sharing-aws-crt/elastigirl.png' 39 | REQUEST_PORT = 443 40 | 41 | 42 | def set_stage(stage): 43 | print("Stage:", stage) 44 | if stage.value >= EXIT_STAGE.value: 45 | print("Exiting now") 46 | sys.exit() 47 | 48 | 49 | if __name__ == '__main__': 50 | EXIT_STAGE = Stage[sys.argv[1]] 51 | 52 | awscrt.io.init_logging(awscrt.io.LogLevel.Trace, 'stdout') 53 | 54 | set_stage(Stage.Start) 55 | 56 | # EventLoopGroupStart 57 | elg = awscrt.io.EventLoopGroup() 58 | set_stage(Stage.EventLoopGroupStart) 59 | 60 | # HostResolverStart 61 | resolver = awscrt.io.DefaultHostResolver(elg) 62 | set_stage(Stage.HostResolverStart) 63 | 64 | # ClientBootstrapStart 65 | bootstrap = awscrt.io.ClientBootstrap(elg, resolver) 66 | set_stage(Stage.ClientBootstrapStart) 67 | 68 | # TlsConnectionOptionsStart 69 | tls_ctx_opt = awscrt.io.TlsContextOptions() 70 | tls_ctx = awscrt.io.ClientTlsContext(tls_ctx_opt) 71 | tls_conn_opt = tls_ctx.new_connection_options() 72 | tls_conn_opt.set_server_name(REQUEST_HOST) 73 | set_stage(Stage.TlsConnectionOptionsStart) 74 | 75 | # HttpClientStart 76 | http_connection = awscrt.http.HttpClientConnection.new( 77 | host_name=REQUEST_HOST, 78 | port=REQUEST_PORT, 79 | bootstrap=bootstrap, 80 | tls_connection_options=tls_conn_opt) 81 | set_stage(Stage.HttpClientStart) 82 | 83 | # HttpClientConnected 84 | try: 85 | http_connection = http_connection.result(TIMEOUT) 86 | except Exception: 87 | # the internet's a flaky place and this isn't a correctness test 88 | print("Connection failed. Exiting out early...") 89 | set_stage(Stage.Done) 90 | 91 | set_stage(Stage.HttpClientConnected) 92 | 93 | # HttpStreamStart 94 | request = awscrt.http.HttpRequest(path=REQUEST_PATH) 95 | request.headers.add('Host', REQUEST_HOST) 96 | 97 | receiving_body_event = threading.Event() 98 | 99 | def on_incoming_body(http_stream, chunk): 100 | receiving_body_event.set() 101 | 102 | http_stream = http_connection.request(request, on_body=on_incoming_body) 103 | http_stream.activate() 104 | set_stage(Stage.HttpStreamStart) 105 | 106 | # HttpStreamReceivingBody 107 | assert receiving_body_event.wait(TIMEOUT) 108 | set_stage(Stage.HttpStreamReceivingBody) 109 | 110 | # HttpStreamDone 111 | try: 112 | status_code = http_stream.completion_future.result(TIMEOUT) 113 | except Exception: 114 | # the internet's a flaky place and this isn't a correctness test 115 | print("Request failed. Continuing with cleanup...") 116 | 117 | del http_stream 118 | del request 119 | set_stage(Stage.HttpStreamDone) 120 | 121 | # HttpConnectionClose 122 | connection_shutdown_future = http_connection.close() 123 | set_stage(Stage.HttpConnectionClose) 124 | 125 | # HttpConnectionDone 126 | del http_connection 127 | connection_shutdown_future.result(TIMEOUT) 128 | set_stage(Stage.HttpConnectionDone) 129 | 130 | # TlsConnectionOptionsDone 131 | del tls_conn_opt 132 | del tls_ctx 133 | del tls_ctx_opt 134 | set_stage(Stage.TlsConnectionOptionsDone) 135 | 136 | # ClientBootstrapDone 137 | bootstrap_shutdown_event = bootstrap.shutdown_event 138 | del bootstrap 139 | assert bootstrap_shutdown_event.wait(TIMEOUT) 140 | set_stage(Stage.ClientBootstrapDone) 141 | 142 | # HostResolverDone 143 | del resolver 144 | set_stage(Stage.HostResolverDone) 145 | 146 | # EventLoopGroupDone 147 | elg_shutdown_event = elg.shutdown_event 148 | del elg 149 | assert elg_shutdown_event.wait(TIMEOUT) 150 | set_stage(Stage.EventLoopGroupDone) 151 | 152 | # Done 153 | set_stage(Stage.Done) 154 | -------------------------------------------------------------------------------- /test/test_eventstream.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | from awscrt.eventstream import * 5 | from test import NativeResourceTest 6 | import time 7 | from uuid import UUID, uuid4 8 | 9 | 10 | class TestHeaders(NativeResourceTest): 11 | def test_bool_true(self): 12 | name = 'truthy' 13 | value = True 14 | h = Header.from_bool(name, value) 15 | self.assertIs(HeaderType.BOOL_TRUE, h.type) 16 | self.assertEqual(value, h.value_as_bool()) 17 | self.assertEqual(value, h.value) 18 | self.assertEqual(name, h.name) 19 | 20 | def test_bool_false(self): 21 | name = 'falsey' 22 | value = False 23 | h = Header.from_bool(name, value) 24 | self.assertIs(HeaderType.BOOL_FALSE, h.type) 25 | self.assertEqual(value, h.value_as_bool()) 26 | self.assertEqual(value, h.value) 27 | self.assertEqual(name, h.name) 28 | 29 | def test_byte(self): 30 | name = 'bytey' 31 | value = 127 32 | h = Header.from_byte(name, value) 33 | self.assertIs(HeaderType.BYTE, h.type) 34 | self.assertEqual(value, h.value_as_byte()) 35 | self.assertEqual(value, h.value) 36 | self.assertEqual(name, h.name) 37 | 38 | self.assertRaises(ValueError, Header.from_byte, 'too-big', 128) 39 | self.assertRaises(ValueError, Header.from_byte, 'too-small', -129) 40 | 41 | def test_int16(self): 42 | name = 'sweet16' 43 | value = 32000 44 | h = Header.from_int16(name, value) 45 | self.assertIs(HeaderType.INT16, h.type) 46 | self.assertEqual(value, h.value_as_int16()) 47 | self.assertEqual(value, h.value) 48 | self.assertEqual(name, h.name) 49 | 50 | self.assertRaises(ValueError, Header.from_int16, 'too-big', 64000) 51 | self.assertRaises(ValueError, Header.from_int16, 'too-small', -64000) 52 | 53 | def test_int32(self): 54 | name = 'win32' 55 | value = 2000000000 56 | h = Header.from_int32(name, value) 57 | self.assertIs(HeaderType.INT32, h.type) 58 | self.assertEqual(value, h.value_as_int32()) 59 | self.assertEqual(value, h.value) 60 | self.assertEqual(name, h.name) 61 | 62 | self.assertRaises(ValueError, Header.from_int32, 'too-big', 4000000000) 63 | self.assertRaises(ValueError, Header.from_int32, 'too-small', -4000000000) 64 | 65 | def test_int64(self): 66 | name = 'N64' 67 | value = 9223372036854775807 68 | h = Header.from_int64(name, value) 69 | self.assertIs(HeaderType.INT64, h.type) 70 | self.assertEqual(value, h.value_as_int64()) 71 | self.assertEqual(value, h.value) 72 | self.assertEqual(name, h.name) 73 | 74 | self.assertRaises(ValueError, Header.from_int32, 'too-big', 18000000000000000000) 75 | self.assertRaises(ValueError, Header.from_int32, 'too-small', -18000000000000000000) 76 | 77 | def test_byte_buf(self): 78 | name = 'buffy' 79 | value = bytes(range(0, 256)) * 100 80 | h = Header.from_byte_buf(name, value) 81 | self.assertIs(HeaderType.BYTE_BUF, h.type) 82 | self.assertEqual(value, h.value_as_byte_buf()) 83 | self.assertEqual(value, h.value) 84 | self.assertEqual(name, h.name) 85 | 86 | def test_string(self): 87 | name = 'stringy' 88 | value = 'abcdefghijklmnopqrstuvwxyz' * 100 89 | h = Header.from_string(name, value) 90 | self.assertIs(HeaderType.STRING, h.type) 91 | self.assertEqual(value, h.value_as_string()) 92 | self.assertEqual(value, h.value) 93 | self.assertEqual(name, h.name) 94 | 95 | def test_timestamp(self): 96 | name = 'timeywimey' 97 | value = time.time() 98 | h = Header.from_timestamp(name, value) 99 | self.assertIs(HeaderType.TIMESTAMP, h.type) 100 | # compare with delta, since protocol uses int instead of float 101 | self.assertAlmostEqual(value, h.value_as_timestamp(), delta=1.0) 102 | self.assertAlmostEqual(value, h.value, delta=1.0) 103 | self.assertEqual(name, h.name) 104 | 105 | def test_uuid(self): 106 | name = 'davuuid' 107 | value = UUID('01234567-89ab-cdef-0123-456789abcdef') 108 | h = Header.from_uuid(name, value) 109 | self.assertIs(HeaderType.UUID, h.type) 110 | self.assertEqual(value, h.value_as_uuid()) 111 | self.assertEqual(value, h.value) 112 | self.assertEqual(name, h.name) 113 | 114 | def test_wrong_type(self): 115 | h = Header.from_bool('truthy', True) 116 | self.assertRaises(TypeError, h.value_as_byte) 117 | self.assertRaises(TypeError, h.value_as_int16) 118 | self.assertRaises(TypeError, h.value_as_int32) 119 | self.assertRaises(TypeError, h.value_as_int64) 120 | self.assertRaises(TypeError, h.value_as_byte_buf) 121 | self.assertRaises(TypeError, h.value_as_string) 122 | self.assertRaises(TypeError, h.value_as_timestamp) 123 | self.assertRaises(TypeError, h.value_as_uuid) 124 | 125 | h = Header.from_int32('32', 32) 126 | self.assertRaises(TypeError, h.value_as_bool) 127 | self.assertRaises(TypeError, h.value_as_int64) 128 | -------------------------------------------------------------------------------- /mqtt_test.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | import argparse 5 | from awscrt import io, mqtt 6 | from awscrt.io import LogLevel 7 | import threading 8 | import uuid 9 | 10 | TIMEOUT = 5 # seconds given to each step of the test before giving up 11 | UNIQUE_ID = str(uuid.uuid4()) # prevent simultaneously-running tests from interfering with each other 12 | CLIENT_ID = 'test_pubsub_' + UNIQUE_ID 13 | TOPIC = 'test/pubsub/' + UNIQUE_ID 14 | MESSAGE = 'test message ' + UNIQUE_ID 15 | 16 | parser = argparse.ArgumentParser() 17 | parser.add_argument('--endpoint', required=True, help="Connect to this endpoint (aka host-name)") 18 | parser.add_argument('--port', type=int, help="Override default connection port") 19 | parser.add_argument('--cert', help="File path to your client certificate, in PEM format") 20 | parser.add_argument('--key', help="File path to your private key, in PEM format") 21 | parser.add_argument('--root-ca', help="File path to root certificate authority, in PEM format") 22 | 23 | io.init_logging(LogLevel.Trace, 'stderr') 24 | 25 | def on_connection_interrupted(connection, error, **kwargs): 26 | print("Connection has been interrupted with error", error) 27 | 28 | def on_connection_resumed(connection, return_code, session_present, **kwargs): 29 | print("Connection has been resumed with return code", return_code, "and session present:", session_present) 30 | 31 | if not session_present: 32 | print("Resubscribing to existing topics") 33 | resubscribe_future, packet_id = connection.resubscribe_existing_topics() 34 | 35 | def on_resubscribe_complete(resubscribe_future): 36 | try: 37 | resubscribe_results = resubscribe_future.result() 38 | print("Resubscribe results:", resubscribe_results) 39 | assert(resubscribe_results['packet_id'] == packet_id) 40 | for (topic, qos) in resubscribe_results['topics']: 41 | assert(qos is not None) 42 | except Exception as e: 43 | print("Resubscribe failure:", e) 44 | exit(-1) 45 | 46 | resubscribe_future.add_done_callback(on_resubscribe_complete) 47 | 48 | receive_results = {} 49 | receive_event = threading.Event() 50 | def on_receive_message(topic, payload, dup, qos, retain, **kwargs): 51 | receive_results['topic'] = topic 52 | receive_results['payload'] = payload 53 | receive_results['dup'] = dup 54 | receive_results['qos'] = qos 55 | receive_results['retain'] = retain 56 | receive_event.set() 57 | 58 | # Run 59 | args = parser.parse_args() 60 | event_loop_group = io.EventLoopGroup(1) 61 | host_resolver = io.DefaultHostResolver(event_loop_group) 62 | client_bootstrap = io.ClientBootstrap(event_loop_group, host_resolver) 63 | 64 | tls_options = None 65 | if args.cert or args.key or args.root_ca: 66 | if args.cert: 67 | assert args.key 68 | tls_options = io.TlsContextOptions.create_client_with_mtls_from_path(args.cert, args.key) 69 | else: 70 | tls_options = io.TlsContextOptions() 71 | 72 | if args.root_ca: 73 | with open(args.root_ca, mode='rb') as ca: 74 | rootca = ca.read() 75 | tls_options.override_default_trust_store(rootca) 76 | 77 | if args.port: 78 | port = args.port 79 | elif io.is_alpn_available(): 80 | port = 443 81 | if tls_options: 82 | tls_options.alpn_list = ['x-amzn-mqtt-ca'] 83 | else: 84 | port = 8883 85 | 86 | tls_context = io.ClientTlsContext(tls_options) if tls_options else None 87 | mqtt_client = mqtt.Client(client_bootstrap, tls_context) 88 | 89 | # Connect 90 | print("Connecting to {}:{} with client-id:{}".format(args.endpoint, port, CLIENT_ID)) 91 | mqtt_connection = mqtt.Connection( 92 | client=mqtt_client, 93 | host_name=args.endpoint, 94 | port=port, 95 | client_id=CLIENT_ID, 96 | on_connection_interrupted=on_connection_interrupted, 97 | on_connection_resumed=on_connection_resumed) 98 | 99 | connect_results = mqtt_connection.connect().result(TIMEOUT) 100 | assert(connect_results['session_present'] == False) 101 | 102 | # Subscribe 103 | print("Subscribing to:", TOPIC) 104 | qos = mqtt.QoS.AT_LEAST_ONCE 105 | subscribe_future, subscribe_packet_id = mqtt_connection.subscribe( 106 | topic=TOPIC, 107 | qos=qos, 108 | callback=on_receive_message) 109 | subscribe_results = subscribe_future.result(TIMEOUT) 110 | assert(subscribe_results['packet_id'] == subscribe_packet_id) 111 | assert(subscribe_results['topic'] == TOPIC) 112 | print(subscribe_results) 113 | assert(subscribe_results['qos'] == qos) 114 | 115 | # Publish 116 | print("Publishing to '{}': {}".format(TOPIC, MESSAGE)) 117 | publish_future, publish_packet_id = mqtt_connection.publish( 118 | topic=TOPIC, 119 | payload=MESSAGE, 120 | qos=mqtt.QoS.AT_LEAST_ONCE) 121 | publish_results = publish_future.result(TIMEOUT) 122 | assert(publish_results['packet_id'] == publish_packet_id) 123 | 124 | # Receive Message 125 | print("Waiting to receive message") 126 | assert(receive_event.wait(TIMEOUT)) 127 | assert(receive_results['topic'] == TOPIC) 128 | assert(receive_results['payload'].decode() == MESSAGE) 129 | 130 | # Unsubscribe 131 | print("Unsubscribing from topic") 132 | unsubscribe_future, unsubscribe_packet_id = mqtt_connection.unsubscribe(TOPIC) 133 | unsubscribe_results = unsubscribe_future.result(TIMEOUT) 134 | assert(unsubscribe_results['packet_id'] == unsubscribe_packet_id) 135 | 136 | # Disconnect 137 | print("Disconnecting") 138 | mqtt_connection.disconnect().result(TIMEOUT) 139 | 140 | # Done 141 | print("Test Success") 142 | -------------------------------------------------------------------------------- /test/test_http_headers.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0. 3 | 4 | from awscrt.http import HttpHeaders, HttpRequest 5 | import awscrt.io 6 | from test import NativeResourceTest 7 | import unittest 8 | 9 | 10 | class TestHttpHeaders(NativeResourceTest): 11 | 12 | def test_add(self): 13 | h = HttpHeaders() 14 | h.add('Host', 'example.org') 15 | self.assertEqual('example.org', h.get('Host')) 16 | self.assertEqual(['example.org'], list(h.get_values('Host'))) 17 | 18 | def test_add_multi_values(self): 19 | h = HttpHeaders() 20 | h.add('Cookie', 'a=1') 21 | h.add('Cookie', 'b=2') 22 | self.assertEqual('a=1', h.get('Cookie')) 23 | self.assertEqual(['a=1', 'b=2'], list(h.get_values('Cookie'))) 24 | 25 | def test_add_pairs(self): 26 | h = HttpHeaders() 27 | h.add_pairs([ 28 | ('Host', 'example.org'), 29 | ('Cookie', 'a=1'), 30 | ('Cookie', 'b=2'), 31 | ]) 32 | self.assertEqual('example.org', h.get('Host')) 33 | self.assertEqual(['a=1', 'b=2'], list(h.get_values('Cookie'))) 34 | 35 | def test_set(self): 36 | h = HttpHeaders() 37 | 38 | # create 39 | h.set('Host', 'example.org') 40 | self.assertEqual(['example.org'], list(h.get_values('Host'))) 41 | 42 | # replace 43 | h.set('Host', 'example2.org') 44 | self.assertEqual(['example2.org'], list(h.get_values('Host'))) 45 | 46 | # replace many 47 | h.add('Host', 'example3.org') 48 | h.add('Host', 'example4.org') 49 | h.set('Host', 'example5.org') 50 | self.assertEqual(['example5.org'], list(h.get_values('Host'))) 51 | 52 | def test_unicode(self): 53 | # test adding unicode values in all the different ways 54 | h = HttpHeaders([('a', 'ሴ')]) 55 | self.assertEqual('ሴ', h.get('a')) 56 | 57 | h.set('b', '𦉘') 58 | self.assertEqual('𦉘', h.get('b')) 59 | 60 | h.add('c', '👁👄👁') 61 | self.assertEqual('👁👄👁', h.get('c')) 62 | 63 | h.add_pairs([('d', 'ⓤţḟ⁻❽')]) 64 | self.assertEqual('ⓤţḟ⁻❽', h.get('d')) 65 | 66 | def test_get_none(self): 67 | h = HttpHeaders() 68 | self.assertIsNone(h.get('Non-Existent')) 69 | self.assertEqual('Banana', h.get('Non-Existent', 'Banana')) 70 | self.assertEqual([], list(h.get_values('Non-Existent'))) 71 | 72 | def test_get_is_case_insensitive(self): 73 | h = HttpHeaders() 74 | h.set('Cookie', 'a=1') 75 | h.add_pairs([('cookie', 'b=2'), ('COOKIE', 'c=3')]) 76 | h.add(u'CoOkIe', 'd=4') # note: unicode 77 | self.assertEqual('a=1', h.get(u'COOKIE')) 78 | self.assertEqual(['a=1', 'b=2', 'c=3', 'd=4'], list(h.get_values('Cookie'))) 79 | 80 | def test_iter(self): 81 | # test that we iterate over everything we put in 82 | src = [('Host', 'example.org'), ('Cookie', 'a=1')] 83 | h = HttpHeaders(src) 84 | for pair in h: 85 | src.remove(pair) 86 | self.assertEqual(0, len(src)) 87 | 88 | def test_iter_order(self): 89 | # test that headers with multiple values are iterated in insertion order 90 | src = [('Cookie', 'a=1'), ('cookie', 'b=2')] 91 | h = HttpHeaders(src) 92 | gather = [pair for pair in h] 93 | 94 | # note this also compares that we preserved case of the names 95 | self.assertEqual(src, gather) 96 | 97 | def test_remove(self): 98 | h = HttpHeaders() 99 | 100 | self.assertRaises(KeyError, h.remove, 'Non-Existent') 101 | 102 | h.add('Host', 'example.org') 103 | h.remove('Host') 104 | self.assertIsNone(h.get('Host')) 105 | 106 | def test_remove_value(self): 107 | h = HttpHeaders() 108 | 109 | self.assertRaises(ValueError, h.remove_value, 'Non-Existent', 'Nope') 110 | 111 | # header with 1 value 112 | h.add('Host', 'example.org') 113 | self.assertRaises(ValueError, h.remove_value, 'Host', 'wrong-value') 114 | h.remove_value('Host', 'example.org') 115 | self.assertIsNone(h.get('Host')) 116 | 117 | # pluck out a duplicate value [1,2,2] -> [1,2] 118 | h.add_pairs([('Dupes', '1'), ('DUPES', '2'), ('dupes', '2')]) 119 | h.remove_value('Dupes', '2') 120 | self.assertEqual(['1', '2'], list(h.get_values('Dupes'))) 121 | 122 | def test_clear(self): 123 | h = HttpHeaders([('Host', 'example.org'), ('Cookie', 'a=1'), ('cookie', 'b=2')]) 124 | h.clear() 125 | self.assertEqual([], [pair for pair in h]) 126 | 127 | 128 | class TestHttpMessage(NativeResourceTest): 129 | def test_request_create_default(self): 130 | request = HttpRequest() 131 | self.assertEqual("GET", request.method) 132 | self.assertEqual("/", request.path) 133 | self.assertEqual([], list(request.headers)) 134 | self.assertIsNone(request.body_stream) 135 | 136 | def test_request_create_nondefault(self): 137 | src_headers = [('Cookie', 'a=1'), ('Cookie', 'b=2')] 138 | body_stream = open('test/test_http_headers.py', 'rb') 139 | request = HttpRequest(method="PUT", 140 | path="/upload", 141 | headers=HttpHeaders(src_headers), 142 | body_stream=body_stream) 143 | 144 | self.assertEqual("PUT", request.method) 145 | self.assertEqual("/upload", request.path) 146 | self.assertEqual(src_headers, list(request.headers)) 147 | self.assertIsNotNone(request.body_stream) 148 | body_stream.close() 149 | 150 | def test_headers_live_after_message_del(self): 151 | request = HttpRequest() 152 | headers = request.headers 153 | del request 154 | headers.add('Cookie', 'a=1') 155 | self.assertEqual([('Cookie', 'a=1')], list(headers)) 156 | 157 | def test_unicode(self): 158 | request = HttpRequest(path='/ሴ') 159 | self.assertEqual('/ሴ', request.path) 160 | 161 | 162 | if __name__ == '__main__': 163 | unittest.main() 164 | -------------------------------------------------------------------------------- /awscrt/_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | Private utilities for testing 3 | """ 4 | import _awscrt 5 | from awscrt import NativeResource 6 | from awscrt.common import join_all_native_threads 7 | import gc 8 | import inspect 9 | import os 10 | import sys 11 | import time 12 | import types 13 | 14 | from awscrt.io import ClientBootstrap, DefaultHostResolver, EventLoopGroup 15 | 16 | 17 | def native_memory_usage() -> int: 18 | """ 19 | Returns number of bytes currently allocated by awscrt's native code. 20 | 21 | `AWS_CRT_MEMORY_TRACING `environment variable must be set before module 22 | is loaded, or 0 will always be returned. Legal values are: 23 | 24 | * `AWS_CRT_MEMORY_TRACING=0`: No tracing 25 | 26 | * `AWS_CRT_MEMORY_TRACING=1`: Only track allocation sizes and total allocated 27 | 28 | * `AWS_CRT_MEMORY_TRACING=2`: Capture callstacks for each allocation 29 | """ 30 | return _awscrt.native_memory_usage() 31 | 32 | 33 | def dump_native_memory(): 34 | """ 35 | If there are outstanding allocations from awscrt's native code, dump them 36 | to log, along with any information gathered based on the tracing level. 37 | 38 | In order to see the dump, logging must initialized at `LogLevel.Trace` 39 | and the `AWS_CRT_MEMORY_TRACING` environment variable must be non-zero 40 | when module is loaded. Legal values are: 41 | 42 | * `AWS_CRT_MEMORY_TRACING=0`: No tracing 43 | 44 | * `AWS_CRT_MEMORY_TRACING=1`: Only track allocation sizes and total allocated 45 | 46 | * `AWS_CRT_MEMORY_TRACING=2`: Capture callstacks for each allocation 47 | """ 48 | return _awscrt.native_memory_dump() 49 | 50 | 51 | def check_for_leaks(*, timeout_sec=10.0): 52 | """ 53 | Checks that all awscrt resources have been freed after a test. 54 | 55 | If any resources still exist, debugging info is printed and an exception is raised. 56 | 57 | Requirements: 58 | * `awscrt.NativeResource._track_lifetime = True`: must be set before test begins 59 | to ensure accurate tracking. 60 | 61 | * `AWS_CRT_MEMORY_TRACING=2`: environment variable that must be set before 62 | any awscrt modules are imported, to ensure accurate native leak checks. 63 | 64 | * `AWS_CRT_MEMORY_PRINT_SECRETS_OK=1`: optional environment variable that 65 | will print the full contents of leaked python objects. DO NOT SET THIS 66 | if the test results will be made public as it may result in secrets 67 | being leaked. 68 | """ 69 | ClientBootstrap.release_static_default() 70 | EventLoopGroup.release_static_default() 71 | DefaultHostResolver.release_static_default() 72 | 73 | if os.getenv('AWS_CRT_MEMORY_TRACING') != '2': 74 | raise RuntimeError("environment variable AWS_CRT_MEMORY_TRACING=2 must be set for accurate leak checks") 75 | 76 | if not NativeResource._track_lifetime: 77 | raise RuntimeError("awscrt.NativeResource._track_lifetime=True must be set for accurate leak checks") 78 | 79 | # Native resources might need a few more ticks to finish cleaning themselves up. 80 | wait_until = time.time() + timeout_sec 81 | while time.time() < wait_until: 82 | if not NativeResource._living and not native_memory_usage() > 0: 83 | return 84 | gc.collect() 85 | # join_all_native_threads() is sometimes required to get mem usage to 0 86 | join_all_native_threads(timeout_sec=0.1) 87 | time.sleep(0.1) 88 | 89 | # Print out debugging info on leaking resources 90 | num_living_resources = len(NativeResource._living) 91 | if num_living_resources: 92 | 93 | leak_secrets_ok = os.getenv('AWS_CRT_MEMORY_PRINT_SECRETS_OK') == '1' 94 | if leak_secrets_ok: 95 | print("Leaking NativeResources:") 96 | else: 97 | print("Leaking NativeResources (set AWS_CRT_MEMORY_PRINT_SECRETS_OK=1 env var for more detailed report):") 98 | 99 | def _printobj(prefix, obj): 100 | # be sure not to accidentally print a dictionary with a password in it 101 | if leak_secrets_ok: 102 | s = str(obj) 103 | if len(s) > 1000: 104 | s = s[:1000] + '...TRUNCATED PRINT' 105 | print(prefix, s) 106 | else: 107 | print(prefix, type(obj)) 108 | 109 | for i in NativeResource._living: 110 | _printobj('-', i) 111 | 112 | # getrefcount(i) returns 4+ here, but 2 of those are due to debugging. 113 | # Don't show: 114 | # - 1 for WeakSet iterator due to this for-loop. 115 | # - 1 for getrefcount(i)'s reference. 116 | # But do show: 117 | # - 1 for item's self-reference. 118 | # - the rest are what's causing this leak. 119 | refcount = sys.getrefcount(i) - 2 120 | 121 | # Gather list of referrers, but don't show those created by the act of iterating the WeakSet 122 | referrers = [] 123 | for r in gc.get_referrers(i): 124 | if isinstance(r, types.FrameType): 125 | frameinfo = inspect.getframeinfo(r) 126 | our_fault = (frameinfo.filename.endswith('_weakrefset.py') or 127 | frameinfo.filename.endswith('awscrt/_test.py')) 128 | if our_fault: 129 | continue 130 | 131 | referrers.append(r) 132 | 133 | print(' sys.getrefcount():', refcount) 134 | print(' gc.referrers():', len(referrers)) 135 | for r in referrers: 136 | if isinstance(r, types.FrameType): 137 | _printobj(' -', inspect.getframeinfo(r)) 138 | else: 139 | _printobj(' -', r) 140 | 141 | mem_bytes = native_memory_usage() 142 | if mem_bytes > 0: 143 | print('Leaking {} bytes native memory (enable Trace logging to see more)'.format(mem_bytes)) 144 | dump_native_memory() 145 | 146 | raise RuntimeError("awscrt leak check failed. {} NativeResource objects. {} bytes native memory".format( 147 | num_living_resources, mem_bytes)) 148 | -------------------------------------------------------------------------------- /s3_benchmark.py: -------------------------------------------------------------------------------- 1 | from awscrt.s3 import S3Client, S3RequestType 2 | from awscrt.io import ClientBootstrap, DefaultHostResolver, EventLoopGroup 3 | from awscrt.auth import AwsCredentialsProvider 4 | from awscrt.http import HttpHeaders, HttpRequest 5 | import time 6 | # import os 7 | import sys 8 | import threading 9 | 10 | GBPS = 1000 * 1000 * 1000 11 | 12 | 13 | class CrtLazyReadStream(object): 14 | def __init__(self, filename, pattern, statistics, length=0): 15 | self._filename = filename 16 | self.length = length 17 | self._stream = None 18 | self._pattern = pattern 19 | self._statistic = statistics 20 | 21 | def _available_stream(self): 22 | if self._stream is None: 23 | self._stream = open(self._filename, self._pattern) 24 | return 25 | 26 | if self._stream.closed: 27 | self._stream = open(self._filename, self._pattern) 28 | 29 | def read(self, length): 30 | self._available_stream() 31 | data = self._stream.read(length) 32 | read_len = len(data) 33 | self._statistic.record_read(read_len) 34 | if read_len is 0: 35 | self._stream.close() 36 | return data 37 | 38 | def readinto1(self, m): 39 | # Read into memoryview m. 40 | self._available_stream() 41 | len = self._stream.readinto1(m) 42 | self._statistic.record_read(len) 43 | if len is 0: 44 | self._stream.close() 45 | return len 46 | 47 | def seek(self, offset, whence): 48 | self._available_stream() 49 | return self._stream.seek(offset, whence) 50 | 51 | def close(self): 52 | pass 53 | 54 | 55 | class Statistics(object): 56 | 57 | def __init__(self): 58 | self._lock = threading.Lock() 59 | self.end_time = 0 60 | self._bytes_peak = 0 61 | self._bytes_avg = 0 62 | self._bytes_read = 0 63 | self._bytes_sampled = 0 64 | self.sec_first_byte = 0 65 | self.star_time = time.time() 66 | self.last_sample_time = time.time() 67 | 68 | def record_read(self, size): 69 | with self._lock: 70 | self._bytes_read += size 71 | if self.sec_first_byte == 0: 72 | self.sec_first_byte = time.time() - self.star_time 73 | time_now = time.time() 74 | if time_now - self.last_sample_time > 1: 75 | bytes_this_second = (self._bytes_read - self._bytes_sampled) / (time_now - self.last_sample_time) 76 | self._bytes_sampled = self._bytes_read 77 | self._bytes_avg = (self._bytes_avg + bytes_this_second) * 0.5 78 | if self._bytes_peak < bytes_this_second: 79 | self._bytes_peak = bytes_this_second 80 | self.last_sample_time = time_now 81 | 82 | def bytes_peak(self): 83 | return (self._bytes_peak * 8) / GBPS 84 | 85 | def bytes_avg(self): 86 | return (self._bytes_avg * 8) / GBPS 87 | 88 | 89 | # Configurations 90 | region = "us-west-2" 91 | bucket_name = "aws-crt-python-s3-testing-bucket" 92 | object_name = "/0_10GB.txt" 93 | file_name = "." + object_name 94 | object_real_name = "/0_10GB" 95 | suffix = ".txt" 96 | repeat_times = 1 97 | bunch_size = 160 98 | 99 | writing_disk = True 100 | request_type = "download" 101 | 102 | # Initialization 103 | event_loop_group = EventLoopGroup(18) 104 | host_resolver = DefaultHostResolver(event_loop_group) 105 | bootstrap = ClientBootstrap(event_loop_group, host_resolver) 106 | credential_provider = AwsCredentialsProvider.new_default_chain(bootstrap) 107 | s3_client = S3Client( 108 | bootstrap=bootstrap, 109 | region="us-west-2", 110 | credential_provider=credential_provider, 111 | throughput_target_gbps=100) 112 | 113 | t_statistic = Statistics() 114 | 115 | headers = HttpHeaders([("host", bucket_name + ".s3." + region + ".amazonaws.com")]) 116 | request = HttpRequest("GET", object_name, headers) 117 | 118 | # file_stats = os.stat(file_name) 119 | # data_len = file_stats.st_size 120 | 121 | # data_stream = CrtLazyReadStream(file_name, "r+b", t_statistic, data_len) 122 | # upload_headers = HttpHeaders([("host", bucket_name + ".s3." + region + ".amazonaws.com"), 123 | # ("Content-Type", "text/plain"), ("Content-Length", str(data_len))]) 124 | # upload_request = HttpRequest("PUT", "/put_object_test_py_10MB.txt", upload_headers, data_stream) 125 | 126 | 127 | def on_body(offset, chunk, **kwargs): 128 | t_statistic.record_read(len(chunk)) 129 | # if writing_disk: 130 | # if not os.path.exists(file_name): 131 | # open(file_name, 'a').close() 132 | # with open(file_name, 'rb+') as f: 133 | # # seems like the seek here may screw up the file. 134 | # f.seek(offset) 135 | # f.write(chunk) 136 | 137 | 138 | completed_connections = 0 139 | 140 | 141 | def on_done(**kwargs): 142 | global completed_connections 143 | completed_connections += 1 144 | print("Finished connection {}".format(completed_connections)) 145 | sys.stdout.flush() 146 | 147 | 148 | def print_statistic(statistic): 149 | print("Gbps peak:", statistic.bytes_peak()) 150 | print("Gbps avg:", statistic.bytes_avg()) 151 | 152 | 153 | # init_logging(LogLevel.Trace, "trace_log.txt") 154 | start_time = time.time() 155 | completed = repeat_times * bunch_size 156 | 157 | for i in range(0, repeat_times): 158 | futures = [] 159 | s3_requests = [] 160 | for j in range(0, bunch_size): 161 | 162 | s3_requests.append(s3_client.make_request( 163 | request=request, 164 | type=S3RequestType.GET_OBJECT, 165 | on_body=on_body, 166 | on_done=on_done)) 167 | 168 | futures.append(s3_requests[j].finished_future) 169 | for j in futures: 170 | try: 171 | j.result(100000) 172 | except Exception as e: 173 | completed = completed - 1 174 | 175 | end_time = time.time() 176 | print_statistic(t_statistic) 177 | print("total time:", end_time - start_time) 178 | print("completed/all:", completed, repeat_times * bunch_size) 179 | print("latency:", t_statistic.sec_first_byte) 180 | -------------------------------------------------------------------------------- /codebuild/mqtt5_test_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Get the S3 URL containing all of the MQTT5 testing environment variables passed in to the bash script 4 | testing_env_bucket=$1 5 | region=$2 6 | 7 | # Make sure we have something: 8 | if [ "${testing_env_bucket}" != "" ] && [ "${region}" != "" ]; then 9 | echo "S3 bucket for environment variables found and region" 10 | else 11 | echo "Could not get S3 bucket for environment variables and/or region." 12 | echo "You need to run this script and pass the S3 URL of the file containing" 13 | echo "all of the environment variables to set, as well as the secrets for certificates and private keys" 14 | echo "" 15 | echo "Example: mqtt5_test_setup.sh s3:/// " 16 | echo "" 17 | echo "When finished, run 'cleanup' to remove the files downloaded:" 18 | echo "" 19 | echo "Example: mqtt5_test_setup.sh s3:/// cleanup" 20 | echo "" 21 | return 1 22 | fi 23 | 24 | # Is this just a request to clean up? 25 | # NOTE: This blindly assumes there is a environment_files.txt file 26 | if [ "${region}" != "cleanup" ]; then 27 | sleep 0.1 # we have to do something to do an else... 28 | else 29 | echo "Undoing environment variables" 30 | unset $(grep -v '^#' ${PWD}/environment_files.txt | xargs | cut -d "=" -f 1) 31 | unset AWS_TEST_MQTT5_CERTIFICATE_FILE 32 | unset AWS_TEST_MQTT5_KEY_FILE 33 | unset AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH 34 | unset AWS_TEST_MQTT5_IOT_KEY_PATH 35 | 36 | echo "Cleaning up resources..." 37 | rm "${PWD}/environment_files.txt" 38 | rm "${PWD}/crt_certificate.pem" 39 | rm "${PWD}/crt_privatekey.pem" 40 | rm "${PWD}/iot_certificate.pem" 41 | rm "${PWD}/iot_privatekey.pem" 42 | 43 | echo "Success!" 44 | return 0 45 | fi 46 | 47 | # Get the file from S3 48 | aws s3 cp ${testing_env_bucket} ${PWD}/environment_files.txt 49 | testing_env_file=$( cat environment_files.txt ) 50 | # Make sure we have data of some form 51 | if [ "${testing_env_file}" != "" ]; then 52 | echo "Environment variables secret found" 53 | else 54 | echo "Could not get environment variables from secrets!" 55 | return 1 56 | fi 57 | 58 | # Make all the variables in mqtt5_environment_variables.txt exported 59 | # so we can run MQTT5 tests 60 | export $(grep -v '^#' environment_files.txt | xargs) 61 | 62 | # CRT/non-builder certificate and key processing 63 | # Get the certificate and key secrets (dumps straight to a file) 64 | crt_cert_file=$(aws secretsmanager get-secret-value --secret-id "${AWS_TEST_MQTT5_CERTIFICATE_FILE_SECRET}" --query "SecretString" --region ${region} | cut -f2 -d":" | cut -f2 -d\") && echo -e "$crt_cert_file" > ${PWD}/crt_certificate.pem 65 | crt_key_file=$(aws secretsmanager get-secret-value --secret-id "${AWS_TEST_MQTT5_KEY_FILE_SECRET}" --query "SecretString" --region ${region} | cut -f2 -d":" | cut -f2 -d\") && echo -e "$crt_key_file" > ${PWD}/crt_privatekey.pem 66 | # Does the certificate file have data? If not, then abort! 67 | if [ "${crt_cert_file}" != "" ]; then 68 | echo "CRT Certificate secret found" 69 | else 70 | echo "Could not get CRT certificate from secrets!" 71 | 72 | # Clean up... 73 | unset $(grep -v '^#' environment_files.txt | xargs | cut -d "=" -f 1) 74 | rm "${PWD}/environment_files.txt" 75 | rm "${PWD}/crt_certificate.pem" 76 | rm "${PWD}/crt_privatekey.pem" 77 | 78 | return 1 79 | fi 80 | # Does the private key file have data? If not, then abort! 81 | if [ "${crt_key_file}" != "" ]; then 82 | echo "CRT Private key secret found" 83 | else 84 | echo "Could not get CRT private key from secrets!" 85 | 86 | # Clean up... 87 | unset $(grep -v '^#' environment_files.txt | xargs | cut -d "=" -f 1) 88 | rm "${PWD}/environment_files.txt" 89 | rm "${PWD}/crt_certificate.pem" 90 | rm "${PWD}/crt_privatekey.pem" 91 | 92 | return 1 93 | fi 94 | # Set the certificate and key paths (absolute paths for best compatibility) 95 | export AWS_TEST_MQTT5_CERTIFICATE_FILE="${PWD}/crt_certificate.pem" 96 | export AWS_TEST_MQTT5_KEY_FILE="${PWD}/crt_privatekey.pem" 97 | 98 | 99 | # IoT/Builder certificate and key processing 100 | # Get the certificate and key secrets (dumps straight to a file) 101 | iot_cert_file=$(aws secretsmanager get-secret-value --secret-id "${AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH_SECRET}" --query "SecretString" --region ${region} | cut -f2 -d":" | cut -f2 -d\") && echo -e "$iot_cert_file" > ./iot_certificate.pem 102 | iot_key_file=$(aws secretsmanager get-secret-value --secret-id "${AWS_TEST_MQTT5_IOT_KEY_PATH_SECRET}" --query "SecretString" --region ${region} | cut -f2 -d":" | cut -f2 -d\") && echo -e "$iot_key_file" > ./iot_privatekey.pem 103 | # Does the certificate file have data? If not, then abort! 104 | if [ "${iot_cert_file}" != "" ]; then 105 | echo "IoT Certificate secret found" 106 | else 107 | echo "Could not get IoT certificate from secrets!" 108 | 109 | # Clean up... 110 | unset $(grep -v '^#' environment_files.txt | xargs | cut -d "=" -f 1) 111 | unset AWS_TEST_MQTT5_CERTIFICATE_FILE 112 | unset AWS_TEST_MQTT5_KEY_FILE 113 | rm "${PWD}/environment_files.txt" 114 | rm "${PWD}/crt_certificate.pem" 115 | rm "${PWD}/crt_privatekey.pem" 116 | rm "${PWD}/iot_certificate.pem" 117 | rm "${PWD}/iot_privatekey.pem" 118 | 119 | return 1 120 | fi 121 | # Does the private key file have data? If not, then abort! 122 | if [ "${iot_key_file}" != "" ]; then 123 | echo "IoT Private key secret found" 124 | else 125 | echo "Could not get IoT private key from secrets!" 126 | 127 | # Clean up... 128 | unset $(grep -v '^#' environment_files.txt | xargs | cut -d "=" -f 1) 129 | unset AWS_TEST_MQTT5_CERTIFICATE_FILE 130 | unset AWS_TEST_MQTT5_KEY_FILE 131 | rm "${PWD}/environment_files.txt" 132 | rm "${PWD}/crt_certificate.pem" 133 | rm "${PWD}/crt_privatekey.pem" 134 | rm "${PWD}/iot_certificate.pem" 135 | rm "${PWD}/iot_privatekey.pem" 136 | 137 | return 1 138 | fi 139 | # Set IoT certificate and key paths 140 | export AWS_TEST_MQTT5_IOT_CERTIFICATE_PATH="${PWD}/iot_certificate.pem" 141 | export AWS_TEST_MQTT5_IOT_KEY_PATH="${PWD}/iot_privatekey.pem" 142 | 143 | # Everything is set 144 | echo "Success: Environment variables set!" 145 | return 0 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AWS CRT Python 2 | 3 | [![Version](https://img.shields.io/pypi/v/awscrt.svg?style=flat)](https://pypi.org/project/awscrt/) 4 | 5 | Python 3 bindings for the AWS Common Runtime. 6 | 7 | * [API documentation](https://awslabs.github.io/aws-crt-python) 8 | * [Development guide](guides/dev/README.md) for contributors to aws-crt-python's source code. 9 | 10 | ## License 11 | 12 | This library is licensed under the Apache 2.0 License. 13 | 14 | ## Minimum Requirements: 15 | 16 | * Python 3.8+ 17 | 18 | ## Installation 19 | 20 | To install from pip: 21 | 22 | ```bash 23 | python3 -m pip install awscrt 24 | ``` 25 | 26 | To install from Github: 27 | 28 | ```bash 29 | git clone https://github.com/awslabs/aws-crt-python.git 30 | cd aws-crt-python 31 | git submodule update --init 32 | python3 -m pip install . 33 | ``` 34 | 35 | See [Advanced Build Options](#advanced-build-options) for more info about building from source. 36 | 37 | ## Fork and Multiprocessing 38 | 39 | aws-crt-python uses background threads. This makes [os.fork()](https://docs.python.org/3/library/os.html#os.fork) unsafe. In a forked child process, all background threads vanish. The child will hang or crash when it tries to communicate with any of these (vanished) threads. 40 | 41 | Unfortunately, Python's [multiprocessing](https://docs.python.org/3/library/multiprocessing.html) module defaults to using fork when it creates child processes (on POSIX systems except macOS, in Python versions 3.13 and earlier). `multiprocessing` is used under the hood by many tools that do work in parallel, including [concurrent.futures.ProcessPoolExecutor](https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.ProcessPoolExecutor), and [pytorch.multiprocessing](https://pytorch.org/docs/stable/multiprocessing.html). 42 | 43 | If you need to use `multiprocessing` with aws-crt-python, set it to use "spawn" or "forkserver" instead of "fork" ([see docs](https://docs.python.org/3/library/multiprocessing.html#contexts-and-start-methods)). The Python community agrees, and `multiprocessing` will changes its default from "fork" to "spawn" in 3.14. It already uses "spawn" by default on macOS (because system libraries may start threads) and on Windows (because fork does not exist). 44 | 45 | If you **must** use fork with aws-crt-python, you may be able to avoid hangs and crashes if you manage your threads very carefully: 46 | 47 | 1. Release all CRT resources with background threads (e.g. clean up any `io.EventLoopGroup` instances). 48 | 2. Join all CRT threads before forking (use `common.join_all_native_threads()` ). 49 | 50 | For an example, see `test.test_s3.py.S3RequestTest.test_fork_workaround` . 51 | 52 | ## Mac-Only TLS Behavior 53 | 54 | Please note that on Mac, once a private key is used with a certificate, that certificate-key pair is imported into the Mac Keychain. All subsequent uses of that certificate will use the stored private key and ignore anything passed in programmatically. Beginning in v0.6.2, when a stored private key from the Keychain is used, the following will be logged at the "info" log level: 55 | 56 | ``` 57 | static: certificate has an existing certificate-key pair that was previously imported into the Keychain. Using key from Keychain instead of the one provided. 58 | ``` 59 | 60 | ## Crash Handler 61 | 62 | You can enable the crash handler by setting the environment variable `AWS_CRT_CRASH_HANDLER=1` . This will print the callstack to `stderr` in the event of a fatal error. 63 | 64 | ## Advanced Build Options 65 | 66 | ### OpenSSL and LibCrypto 67 | 68 | aws-crt-python does not use OpenSSL for TLS. 69 | On Apple and Windows devices, the OS's default TLS library is used. 70 | On Unix devices, [s2n-tls](https://github.com/aws/s2n-tls) is used. 71 | But s2n-tls uses libcrypto, the cryptography math library bundled with OpenSSL. 72 | 73 | To simplify installation, aws-crt-python has its own copy of libcrypto. 74 | This lets you install a wheel from PyPI without having OpenSSL installed. 75 | Unix wheels on PyPI come with libcrypto statically compiled in. 76 | Code to build libcrypto comes from [AWS-LC](https://github.com/aws/aws-lc). 77 | AWS-LC's code is included in the PyPI source package, 78 | and the git repository includes it as a submodule. 79 | 80 | If you need aws-crt-python to use the libcrypto included on your system, 81 | set environment variable `AWS_CRT_BUILD_USE_SYSTEM_LIBCRYPTO=1` while building from source: 82 | 83 | ```sh 84 | AWS_CRT_BUILD_USE_SYSTEM_LIBCRYPTO=1 python3 -m pip install --no-binary :all: --verbose awscrt 85 | ``` 86 | 87 | ( `--no-binary :all:` ensures you do not use the precompiled wheel from PyPI) 88 | 89 | aws-crt-python also exposes a number of cryptographic primitives. 90 | On Unix, those depend on libcrypto as described above. 91 | On Apple and Windows OS level crypto libraries are used whenever possible. 92 | One exception to above statement is that for ED25519 keygen on Windows and Apple, 93 | libcrypto is used as no viable OS level alternative exists. In that case Unix level notes 94 | about libcrypto apply to Apple and Windows as well. Libcrypto usage for ED25519 support is 95 | enabled on Windows and Apple by default and can be disabled by setting environment variable 96 | `AWS_CRT_BUILD_DISABLE_LIBCRYPTO_USE_FOR_ED25519_EVERYWHERE` as follows: 97 | (Note: ED25519 keygen functions will start returning not supported error in this case) 98 | 99 | ```sh 100 | AWS_CRT_BUILD_DISABLE_LIBCRYPTO_USE_FOR_ED25519_EVERYWHERE=1 python3 -m pip install --no-binary :all: --verbose awscrt 101 | ``` 102 | 103 | ( `--no-binary :all:` ensures you do not use the precompiled wheel from PyPI) 104 | 105 | ### AWS_CRT_BUILD_USE_SYSTEM_LIBS ### 106 | 107 | aws-crt-python depends on several C libraries that make up the AWS Common Runtime (libaws-c-common, libaws-c-s3, etc). 108 | By default, these libraries are built along with aws-crt-python and statically compiled in 109 | (their source code is under [crt/](crt/)). 110 | 111 | To skip building these dependencies, because they're already available on your system, 112 | set environment variable `AWS_CRT_BUILD_USE_SYSTEM_LIBS=1` while building from source: 113 | 114 | ```sh 115 | AWS_CRT_BUILD_USE_SYSTEM_LIBS=1 python3 -m pip install . 116 | ``` 117 | 118 | If these dependencies are available as both static and shared libs, you can force the static ones to be used by setting: `AWS_CRT_BUILD_FORCE_STATIC_LIBS=1` 119 | 120 | ### Windows SDK Version 121 | aws-crt-python builds against windows sdk version `10.0.17763.0`. This is the minimal version required for TLS 1.3 support on Windows. If you need a different Windows SDK version, you can set environment variable `AWS_CRT_WINDOWS_SDK_VERSION=` while building from source: -------------------------------------------------------------------------------- /source/http_message.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | #include "http.h" 6 | 7 | #include "io.h" 8 | #include 9 | 10 | static const char *s_capsule_name_http_message = "aws_http_message"; 11 | 12 | /** 13 | * Bind aws_http_message to HttpRequest/HttpResponse. 14 | * 15 | * This binding acts differently than most. Instead of keeping the native type in 16 | * sync with the Python type at all times, we lazily sync their contents only when we must. 17 | * We do this to reduce the impact of repeatedly crossing the language barrier. 18 | */ 19 | struct http_message_binding { 20 | struct aws_http_message *native; 21 | }; 22 | 23 | static void s_http_message_capsule_destructor(PyObject *capsule) { 24 | struct http_message_binding *message = PyCapsule_GetPointer(capsule, s_capsule_name_http_message); 25 | 26 | /* Note that destructor may be cleaning up a message that failed part-way through initialization */ 27 | aws_http_message_release(message->native); 28 | 29 | aws_mem_release(aws_py_get_allocator(), message); 30 | } 31 | 32 | static struct http_message_binding *s_binding_from_capsule(PyObject *capsule) { 33 | return PyCapsule_GetPointer(capsule, s_capsule_name_http_message); 34 | } 35 | 36 | struct aws_http_message *aws_py_get_http_message(PyObject *http_message) { 37 | AWS_PY_RETURN_NATIVE_FROM_BINDING( 38 | http_message, s_capsule_name_http_message, "HttpMessageBase", http_message_binding); 39 | } 40 | 41 | PyObject *aws_py_http_message_new_request_from_native(struct aws_http_message *request) { 42 | struct aws_allocator *alloc = aws_py_get_allocator(); 43 | struct http_message_binding *binding = aws_mem_calloc(alloc, 1, sizeof(struct http_message_binding)); 44 | if (!binding) { 45 | return PyErr_AwsLastError(); 46 | } 47 | 48 | /* From hereon, we need to clean up if errors occur. 49 | * Fortunately, the capsule destructors will clean up anything stored inside them. */ 50 | PyObject *py_capsule = PyCapsule_New(binding, s_capsule_name_http_message, s_http_message_capsule_destructor); 51 | if (!py_capsule) { 52 | goto error; 53 | } 54 | 55 | binding->native = request; 56 | aws_http_message_acquire(binding->native); 57 | 58 | return py_capsule; 59 | 60 | error: 61 | if (py_capsule) { 62 | Py_DECREF(py_capsule); 63 | } else { 64 | aws_mem_release(alloc, binding); 65 | } 66 | return NULL; 67 | } 68 | 69 | PyObject *aws_py_http_message_new_request(PyObject *self, PyObject *args) { 70 | (void)self; 71 | 72 | PyObject *py_headers; 73 | if (!PyArg_ParseTuple(args, "O", &py_headers)) { 74 | return NULL; 75 | } 76 | 77 | struct aws_http_headers *headers = aws_py_get_http_headers(py_headers); 78 | if (!headers) { 79 | return NULL; 80 | } 81 | 82 | struct aws_http_message *request = aws_http_message_new_request_with_headers(aws_py_get_allocator(), headers); 83 | if (!request) { 84 | return PyErr_AwsLastError(); 85 | } 86 | 87 | PyObject *py_capsule = aws_py_http_message_new_request_from_native(request); 88 | if (!py_capsule) { 89 | aws_http_message_release(request); 90 | return NULL; 91 | } 92 | 93 | /* The capsule has its own reference to the request now, 94 | * release the reference we got for creating it */ 95 | aws_http_message_release(request); 96 | 97 | return py_capsule; 98 | } 99 | 100 | static struct http_message_binding *s_get_binding_from_capsule_arg(PyObject *self, PyObject *args) { 101 | (void)self; 102 | PyObject *py_capsule; 103 | if (!PyArg_ParseTuple(args, "O", &py_capsule)) { 104 | return NULL; 105 | } 106 | return s_binding_from_capsule(py_capsule); 107 | } 108 | 109 | PyObject *aws_py_http_message_get_request_method(PyObject *self, PyObject *args) { 110 | struct http_message_binding *binding = s_get_binding_from_capsule_arg(self, args); 111 | if (!binding) { 112 | return NULL; 113 | } 114 | 115 | struct aws_byte_cursor method; 116 | if (aws_http_message_get_request_method(binding->native, &method)) { 117 | Py_RETURN_NONE; 118 | } 119 | 120 | return PyUnicode_FromAwsByteCursor(&method); 121 | } 122 | 123 | PyObject *aws_py_http_message_set_request_method(PyObject *self, PyObject *args) { 124 | (void)self; 125 | 126 | PyObject *py_capsule; 127 | struct aws_byte_cursor method; 128 | if (!PyArg_ParseTuple(args, "Os#", &py_capsule, &method.ptr, &method.len)) { 129 | return NULL; 130 | } 131 | 132 | struct http_message_binding *binding = s_binding_from_capsule(py_capsule); 133 | if (!binding) { 134 | return NULL; 135 | } 136 | 137 | if (aws_http_message_set_request_method(binding->native, method)) { 138 | return PyErr_AwsLastError(); 139 | } 140 | 141 | Py_RETURN_NONE; 142 | } 143 | 144 | PyObject *aws_py_http_message_get_request_path(PyObject *self, PyObject *args) { 145 | struct http_message_binding *binding = s_get_binding_from_capsule_arg(self, args); 146 | if (!binding) { 147 | return NULL; 148 | } 149 | 150 | struct aws_byte_cursor path; 151 | if (aws_http_message_get_request_path(binding->native, &path)) { 152 | Py_RETURN_NONE; 153 | } 154 | return PyUnicode_FromAwsByteCursor(&path); 155 | } 156 | 157 | PyObject *aws_py_http_message_set_request_path(PyObject *self, PyObject *args) { 158 | (void)self; 159 | 160 | PyObject *py_capsule; 161 | struct aws_byte_cursor path; 162 | if (!PyArg_ParseTuple(args, "Os#", &py_capsule, &path.ptr, &path.len)) { 163 | return NULL; 164 | } 165 | 166 | struct http_message_binding *binding = s_binding_from_capsule(py_capsule); 167 | if (!binding) { 168 | return NULL; 169 | } 170 | 171 | if (aws_http_message_set_request_path(binding->native, path)) { 172 | return PyErr_AwsLastError(); 173 | } 174 | 175 | Py_RETURN_NONE; 176 | } 177 | 178 | PyObject *aws_py_http_message_set_body_stream(PyObject *self, PyObject *args) { 179 | (void)self; 180 | 181 | PyObject *py_capsule; 182 | PyObject *py_stream; 183 | if (!PyArg_ParseTuple(args, "OO", &py_capsule, &py_stream)) { 184 | return NULL; 185 | } 186 | 187 | struct http_message_binding *binding = s_binding_from_capsule(py_capsule); 188 | if (!binding) { 189 | return NULL; 190 | } 191 | 192 | struct aws_input_stream *stream = NULL; 193 | if (py_stream != Py_None) { 194 | stream = aws_py_get_input_stream(py_stream); 195 | if (!stream) { 196 | return PyErr_AwsLastError(); 197 | } 198 | } 199 | 200 | aws_http_message_set_body_stream(binding->native, stream); 201 | 202 | Py_RETURN_NONE; 203 | } 204 | -------------------------------------------------------------------------------- /source/crc.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | * SPDX-License-Identifier: Apache-2.0. 4 | */ 5 | 6 | #include "checksums.h" 7 | 8 | #include "aws/checksums/crc.h" 9 | #include "aws/common/byte_buf.h" 10 | PyObject *checksums_crc32_common(PyObject *args, uint32_t (*checksum_fn)(const uint8_t *, size_t, uint32_t)) { 11 | Py_buffer input; 12 | PyObject *py_previousCrc; 13 | PyObject *py_result = NULL; 14 | 15 | if (!PyArg_ParseTuple(args, "s*O", &input, &py_previousCrc)) { 16 | return NULL; 17 | } 18 | 19 | /* Note: PyArg_ParseTuple() doesn't do overflow checking on unsigned values 20 | * so use PyLong_AsUnsignedLong() to get the value of the previousCrc arg */ 21 | uint32_t previousCrc = PyLong_AsUnsignedLong(py_previousCrc); 22 | 23 | if (previousCrc == (uint32_t)-1 && PyErr_Occurred()) { 24 | goto done; 25 | } 26 | 27 | if (!PyBuffer_IsContiguous(&input, 'C')) { 28 | PyErr_SetString(PyExc_ValueError, "input must be contiguous buffer"); 29 | goto done; 30 | } 31 | 32 | uint32_t val = previousCrc; 33 | 34 | /* Releasing the GIL for very small buffers is inefficient 35 | and may lower performance */ 36 | if (input.len > 1024 * 5) { 37 | unsigned char *buf = input.buf; 38 | Py_ssize_t len = input.len; 39 | 40 | /* clang-format off */ 41 | Py_BEGIN_ALLOW_THREADS 42 | val = checksum_fn(buf, (size_t)len, val); 43 | Py_END_ALLOW_THREADS 44 | /* clang-format on */ 45 | } else { 46 | val = checksum_fn(input.buf, (size_t)input.len, val); 47 | } 48 | py_result = PyLong_FromUnsignedLong(val); 49 | done: 50 | if (input.obj) { 51 | PyBuffer_Release(&input); 52 | } 53 | return py_result; 54 | } 55 | 56 | PyObject *aws_py_checksums_crc32(PyObject *self, PyObject *args) { 57 | (void)self; 58 | return checksums_crc32_common(args, aws_checksums_crc32_ex); 59 | } 60 | 61 | PyObject *aws_py_checksums_crc32c(PyObject *self, PyObject *args) { 62 | (void)self; 63 | return checksums_crc32_common(args, aws_checksums_crc32c_ex); 64 | } 65 | 66 | PyObject *aws_py_checksums_crc64nvme(PyObject *self, PyObject *args) { 67 | (void)self; 68 | Py_buffer input; 69 | PyObject *py_previousCrc64; 70 | PyObject *py_result = NULL; 71 | 72 | if (!PyArg_ParseTuple(args, "s*O", &input, &py_previousCrc64)) { 73 | return NULL; 74 | } 75 | 76 | /* Note: PyArg_ParseTuple() doesn't do overflow checking on unsigned values 77 | * so use PyLong_AsUnsignedLongLong() to get the value of the previousCrc arg */ 78 | uint64_t previousCrc = PyLong_AsUnsignedLongLong(py_previousCrc64); 79 | 80 | if (previousCrc == (uint64_t)-1 && PyErr_Occurred()) { 81 | goto done; 82 | } 83 | 84 | if (!PyBuffer_IsContiguous(&input, 'C')) { 85 | PyErr_SetString(PyExc_ValueError, "input must be contiguous buffer"); 86 | goto done; 87 | } 88 | 89 | /* Releasing the GIL for very small buffers is inefficient 90 | and may lower performance */ 91 | if (input.len > 1024 * 5) { 92 | /* clang-format off */ 93 | Py_BEGIN_ALLOW_THREADS 94 | previousCrc = aws_checksums_crc64nvme_ex(input.buf, (size_t)input.len, previousCrc); 95 | Py_END_ALLOW_THREADS 96 | /* clang-format on */ 97 | } else { 98 | previousCrc = aws_checksums_crc64nvme_ex(input.buf, (size_t)input.len, previousCrc); 99 | } 100 | py_result = PyLong_FromUnsignedLongLong(previousCrc); 101 | done: 102 | if (input.obj) { 103 | PyBuffer_Release(&input); 104 | } 105 | return py_result; 106 | } 107 | 108 | PyObject *aws_py_checksums_crc32_combine(PyObject *self, PyObject *args) { 109 | (void)self; 110 | PyObject *py_crc1; 111 | PyObject *py_crc2; 112 | PyObject *py_len2; 113 | 114 | if (!PyArg_ParseTuple(args, "OOO", &py_crc1, &py_crc2, &py_len2)) { 115 | return NULL; 116 | } 117 | 118 | uint32_t crc1 = PyLong_AsUnsignedLong(py_crc1); 119 | if (crc1 == (uint32_t)-1 && PyErr_Occurred()) { 120 | PyErr_Clear(); 121 | PyErr_SetString(PyExc_ValueError, "crc32_result1 is not a valid unsigned 32-bit integer"); 122 | return NULL; 123 | } 124 | 125 | uint32_t crc2 = PyLong_AsUnsignedLong(py_crc2); 126 | if (crc2 == (uint32_t)-1 && PyErr_Occurred()) { 127 | PyErr_Clear(); 128 | PyErr_SetString(PyExc_ValueError, "crc32_result2 is not a valid unsigned 32-bit integer"); 129 | return NULL; 130 | } 131 | 132 | uint64_t len2 = PyLong_AsUnsignedLongLong(py_len2); 133 | if (len2 == (uint64_t)-1 && PyErr_Occurred()) { 134 | PyErr_Clear(); 135 | PyErr_SetString(PyExc_ValueError, "data_length2 is not a valid unsigned 64-bit integer"); 136 | return NULL; 137 | } 138 | 139 | uint32_t result = aws_checksums_crc32_combine(crc1, crc2, len2); 140 | return PyLong_FromUnsignedLong(result); 141 | } 142 | 143 | PyObject *aws_py_checksums_crc32c_combine(PyObject *self, PyObject *args) { 144 | (void)self; 145 | PyObject *py_crc1; 146 | PyObject *py_crc2; 147 | PyObject *py_len2; 148 | 149 | if (!PyArg_ParseTuple(args, "OOO", &py_crc1, &py_crc2, &py_len2)) { 150 | return NULL; 151 | } 152 | 153 | uint32_t crc1 = PyLong_AsUnsignedLong(py_crc1); 154 | if (crc1 == (uint32_t)-1 && PyErr_Occurred()) { 155 | PyErr_Clear(); 156 | PyErr_SetString(PyExc_ValueError, "crc32c_result1 is not a valid unsigned 32-bit integer"); 157 | return NULL; 158 | } 159 | 160 | uint32_t crc2 = PyLong_AsUnsignedLong(py_crc2); 161 | if (crc2 == (uint32_t)-1 && PyErr_Occurred()) { 162 | PyErr_Clear(); 163 | PyErr_SetString(PyExc_ValueError, "crc32c_result2 is not a valid unsigned 32-bit integer"); 164 | return NULL; 165 | } 166 | 167 | uint64_t len2 = PyLong_AsUnsignedLongLong(py_len2); 168 | if (len2 == (uint64_t)-1 && PyErr_Occurred()) { 169 | PyErr_Clear(); 170 | PyErr_SetString(PyExc_ValueError, "data_length2 is not a valid unsigned 64-bit integer"); 171 | return NULL; 172 | } 173 | 174 | uint32_t result = aws_checksums_crc32c_combine(crc1, crc2, len2); 175 | return PyLong_FromUnsignedLong(result); 176 | } 177 | 178 | PyObject *aws_py_checksums_crc64nvme_combine(PyObject *self, PyObject *args) { 179 | (void)self; 180 | PyObject *py_crc1; 181 | PyObject *py_crc2; 182 | PyObject *py_len2; 183 | 184 | if (!PyArg_ParseTuple(args, "OOO", &py_crc1, &py_crc2, &py_len2)) { 185 | return NULL; 186 | } 187 | 188 | uint64_t crc1 = PyLong_AsUnsignedLongLong(py_crc1); 189 | if (crc1 == (uint64_t)-1 && PyErr_Occurred()) { 190 | PyErr_Clear(); 191 | PyErr_SetString(PyExc_ValueError, "crc64nvme_result1 is not a valid unsigned 64-bit integer"); 192 | return NULL; 193 | } 194 | 195 | uint64_t crc2 = PyLong_AsUnsignedLongLong(py_crc2); 196 | if (crc2 == (uint64_t)-1 && PyErr_Occurred()) { 197 | PyErr_Clear(); 198 | PyErr_SetString(PyExc_ValueError, "crc64nvme_result2 is not a valid unsigned 64-bit integer"); 199 | return NULL; 200 | } 201 | 202 | uint64_t len2 = PyLong_AsUnsignedLongLong(py_len2); 203 | if (len2 == (uint64_t)-1 && PyErr_Occurred()) { 204 | PyErr_Clear(); 205 | PyErr_SetString(PyExc_ValueError, "data_length2 is not a valid unsigned 64-bit integer"); 206 | return NULL; 207 | } 208 | 209 | uint64_t result = aws_checksums_crc64nvme_combine(crc1, crc2, len2); 210 | return PyLong_FromUnsignedLongLong(result); 211 | } 212 | -------------------------------------------------------------------------------- /guides/dev/binding-simple.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Python
Python
C
C
EventLoopGroup+ _binding: PyCapsulePyCapsule+ pointer: void*event_loop_group_binding+ native: aws_event_loop_group*aws_event_loop_group
--------------------------------------------------------------------------------