pax_global_header00006660000000000000000000000064141633514320014514gustar00rootroot0000000000000052 comment=6104b75ab53c412fc2c9f9d93e90c468708cc51a pylibsrtp-0.7.0/000077500000000000000000000000001416335143200135505ustar00rootroot00000000000000pylibsrtp-0.7.0/.github/000077500000000000000000000000001416335143200151105ustar00rootroot00000000000000pylibsrtp-0.7.0/.github/workflows/000077500000000000000000000000001416335143200171455ustar00rootroot00000000000000pylibsrtp-0.7.0/.github/workflows/tests.yml000066400000000000000000000067531416335143200210450ustar00rootroot00000000000000name: tests on: [push, pull_request] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-python@v1 with: python-version: 3.7 - name: Install packages run: pip install black flake8 isort - name: Run linters run: | flake8 src tests isort -c -df -rc src tests black --check --diff src tests test: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest] python: - '3.10' - '3.9' - '3.8' - '3.7' steps: - uses: actions/checkout@v1 - uses: actions/setup-python@v1 with: python-version: ${{ matrix.python }} - name: Install OS Packages if: matrix.os == 'macos-latest' run: | brew update brew install srtp - name: Install OS Packages if: matrix.os == 'ubuntu-latest' run: | sudo apt-get update sudo apt-get install libsrtp2-dev - name: Run tests run: | python -m pip install -U pip setuptools wheel pip install coverage pip install . coverage run -m unittest discover -v coverage xml - name: Upload coverage report uses: codecov/codecov-action@v1 package-source: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-python@v1 with: python-version: 3.7 - name: Build source package run: python setup.py sdist - name: Upload source package uses: actions/upload-artifact@v1 with: name: dist path: dist/ package-wheel: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v1 - uses: actions/setup-python@v1 with: python-version: 3.7 - name: Add msbuild to PATH if: matrix.os == 'windows-latest' uses: microsoft/setup-msbuild@v1.0.2 - name: Set up QEMU if: runner.os == 'Linux' uses: docker/setup-qemu-action@v1 with: platforms: all - name: Build wheels env: CIBW_ARCHS_LINUX: auto aarch64 CIBW_ARCHS_MACOS: x86_64 arm64 CIBW_BEFORE_BUILD: scripts/build-libsrtp /tmp/vendor CIBW_BEFORE_BUILD_WINDOWS: scripts\build-libsrtp.bat C:\cibw\vendor CIBW_ENVIRONMENT: CFLAGS=-I/tmp/vendor/include LDFLAGS=-L/tmp/vendor/lib CIBW_ENVIRONMENT_WINDOWS: INCLUDE=C:\\cibw\\vendor\\include LIB=C:\\cibw\\vendor\\lib CIBW_SKIP: cp36-* pp36-* *-musllinux* CIBW_TEST_COMMAND: python -m unittest discover -s {project}/tests shell: bash run: | pip install cibuildwheel cibuildwheel --output-dir dist - name: Upload wheels uses: actions/upload-artifact@v1 with: name: dist path: dist/ publish: runs-on: ubuntu-latest needs: [lint, test, package-source, package-wheel] steps: - uses: actions/checkout@v1 - uses: actions/download-artifact@v1 with: name: dist path: dist/ - name: Publish to PyPI if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') uses: pypa/gh-action-pypi-publish@master with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} pylibsrtp-0.7.0/.gitignore000066400000000000000000000001101416335143200155300ustar00rootroot00000000000000*.egg-info *.pyc *.so .coverage .eggs .vscode /build /dist /docs/_build pylibsrtp-0.7.0/LICENSE000066400000000000000000000027461416335143200145660ustar00rootroot00000000000000Copyright (c) 2018 Jeremy Lainé. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of pylibsrtp nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pylibsrtp-0.7.0/MANIFEST.in000066400000000000000000000001751416335143200153110ustar00rootroot00000000000000include LICENSE recursive-include docs *.py *.rst Makefile recursive-include src/_cffi_src *.py recursive-include tests *.py pylibsrtp-0.7.0/README.rst000066400000000000000000000052451416335143200152450ustar00rootroot00000000000000pylibsrtp ========= |rtd| |pypi-v| |pypi-pyversions| |pypi-l| |pypi-wheel| |tests| |codecov| .. |rtd| image:: https://readthedocs.org/projects/pylibsrtp/badge/?version=latest :target: https://pylibsrtp.readthedocs.io/ .. |pypi-v| image:: https://img.shields.io/pypi/v/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-pyversions| image:: https://img.shields.io/pypi/pyversions/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-l| image:: https://img.shields.io/pypi/l/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-wheel| image:: https://img.shields.io/pypi/wheel/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |tests| image:: https://github.com/aiortc/pylibsrtp/workflows/tests/badge.svg :target: https://github.com/aiortc/pylibsrtp/actions .. |codecov| image:: https://img.shields.io/codecov/c/github/aiortc/pylibsrtp.svg :target: https://codecov.io/gh/aiortc/pylibsrtp What is ``pylibsrtp``? ---------------------- ``pylibsrtp`` is a Python wrapper around `libsrtp`_, making it possible to encrypt and decrypt Secure Real-time Transport Protocol (SRTP) packets from Python code. SRTP is a profile of the Real-time Transport Protocol (RTP) which provides confidentiality, message authentication, and replay protection. It is defined by `RFC 3711`_. You can install ``pylibsrtp`` with ``pip``: .. code-block:: console $ pip install pylibsrtp To learn more about ``pylibsrtp`` please `read the documentation`_. .. _libsrtp: https://github.com/cisco/libsrtp .. _RFC 3711: https://tools.ietf.org/html/rfc3711 .. _read the documentation: https://pylibsrtp.readthedocs.io/en/stable/ Example ------- .. code:: python #!/usr/bin/env python from pylibsrtp import Policy, Session key = (b'\x00' * 30) rtp = b'\x80\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + (b'\xd4' * 160) # protect RTP tx_policy = Policy(key=key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) tx_session = Session(policy=tx_policy) srtp = tx_session.protect(rtp) # unprotect RTP rx_policy = Policy(key=key, ssrc_type=Policy.SSRC_ANY_INBOUND) rx_session = Session(policy=rx_policy) rtp2 = rx_session.unprotect(srtp) # check roundtrip worked! assert rtp2 == rtp Building pylibsrtp ------------------ If you wish to build pylibsrtp yourself, you will need libsrtp version 2.0 or better. On Debian/Ubuntu run: .. code-block:: console $ apt install libsrtp2-dev On OS X run: .. code-block:: console $ brew install srtp License ------- ``pylibsrtp`` is released under the `BSD license`_. .. _BSD license: https://pylibsrtp.readthedocs.io/en/stable/license.html pylibsrtp-0.7.0/docs/000077500000000000000000000000001416335143200145005ustar00rootroot00000000000000pylibsrtp-0.7.0/docs/Makefile000066400000000000000000000011361416335143200161410ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = pylibsrtp SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)pylibsrtp-0.7.0/docs/api.rst000066400000000000000000000002451416335143200160040ustar00rootroot00000000000000API Reference ============= .. automodule:: pylibsrtp .. autoclass:: Error .. autoclass:: Policy :members: .. autoclass:: Session :members: pylibsrtp-0.7.0/docs/conf.py000066400000000000000000000125441416335143200160050ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # pylibsrtp documentation build configuration file, created by # sphinx-quickstart on Thu Feb 8 17:22:14 2018. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os, sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) # Mock out binding class MockLib: ssrc_undefined = 0 ssrc_specific = 1 ssrc_any_inbound = 2 ssrc_any_outbound = 3 def srtp_init(self): pass class MockBinding: ffi = None lib = MockLib() sys.modules.update({'pylibsrtp._binding': MockBinding()}) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'pylibsrtp' copyright = u'2018, Jeremy Lainé' author = u'Jeremy Lainé' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '' # The full version, including alpha/beta/rc tags. release = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { 'github_button': True, 'github_user': 'aiortc', 'github_repo': 'pylibsrtp', } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { '**': [ 'relations.html', # needs 'show_related': True theme option to display 'searchbox.html', ] } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'pylibsrtpdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'pylibsrtp.tex', 'pylibsrtp Documentation', u'Jeremy Lainé', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'pylibsrtp', 'pylibsrtp Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'pylibsrtp', 'pylibsrtp Documentation', author, 'pylibsrtp', 'One line description of project.', 'Miscellaneous'), ] pylibsrtp-0.7.0/docs/index.rst000066400000000000000000000026351416335143200163470ustar00rootroot00000000000000pylibsrtp ========= |pypi-v| |pypi-pyversions| |pypi-l| |pypi-wheel| |tests| |codecov| .. |pypi-v| image:: https://img.shields.io/pypi/v/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-pyversions| image:: https://img.shields.io/pypi/pyversions/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-l| image:: https://img.shields.io/pypi/l/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-wheel| image:: https://img.shields.io/pypi/wheel/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |tests| image:: https://github.com/aiortc/pylibsrtp/workflows/tests/badge.svg :target: https://github.com/aiortc/pylibsrtp/actions .. |codecov| image:: https://img.shields.io/codecov/c/github/aiortc/pylibsrtp.svg :target: https://codecov.io/gh/aiortc/pylibsrtp ``pylibsrtp`` is a Python wrapper around `libsrtp`_, making it possible to encrypt and decrypt Secure Real-time Transport Protocol (SRTP) packets from Python code. SRTP is a profile of the Real-time Transport Protocol (RTP) which provides confidentiality, message authentication, and replay protection. It is defined by `RFC 3711`_. You can install ``pylibsrtp`` with ``pip``: .. code-block:: console $ pip install pylibsrtp .. _libsrtp: https://github.com/cisco/libsrtp .. _RFC 3711: https://tools.ietf.org/html/rfc3711 .. toctree:: :maxdepth: 2 api license pylibsrtp-0.7.0/docs/license.rst000066400000000000000000000000601416335143200166500ustar00rootroot00000000000000License ------- .. literalinclude:: ../LICENSE pylibsrtp-0.7.0/pyproject.toml000066400000000000000000000001011416335143200164540ustar00rootroot00000000000000[build-system] requires = ["cffi>=1.0.0", "setuptools", "wheel"] pylibsrtp-0.7.0/scripts/000077500000000000000000000000001416335143200152375ustar00rootroot00000000000000pylibsrtp-0.7.0/scripts/build-libsrtp000077500000000000000000000004421416335143200177410ustar00rootroot00000000000000#!/bin/sh set -e destdir=$1 for d in libsrtp $destdir; do if [ -e $d ]; then rm -rf $d fi done git clone https://github.com/cisco/libsrtp/ cd libsrtp git checkout -qf v2.4.2 cmake . -DCMAKE_INSTALL_PREFIX=$destdir -DCMAKE_POSITION_INDEPENDENT_CODE=ON make make install pylibsrtp-0.7.0/scripts/build-libsrtp.bat000066400000000000000000000012151416335143200205020ustar00rootroot00000000000000set destdir=%1 for %%d in (libsrtp %destdir%) do ( if exist %%d ( rmdir /s /q %%d ) ) git clone https://github.com/cisco/libsrtp/ cd libsrtp git checkout -qf v2.4.2 if "%PYTHON_ARCH%" == "64" ( set CMAKE_OPTIONS=-A x64 ) else ( set CMAKE_OPTIONS=-A Win32 ) cmake . -G "Visual Studio 16 2019" %CMAKE_OPTIONS% cmake --build . --config Release mkdir %destdir% mkdir %destdir%\include mkdir %destdir%\include\srtp2 mkdir %destdir%\lib for %%d in (include\srtp.h crypto\include\auth.h crypto\include\cipher.h crypto\include\crypto_types.h) do ( copy %%d %destdir%\include\srtp2 ) copy Release\srtp2.lib %destdir%\lib\srtp2.lib pylibsrtp-0.7.0/setup.cfg000066400000000000000000000001141416335143200153650ustar00rootroot00000000000000[coverage:run] source = pylibsrtp [flake8] ignore=E203 max-line-length=100 pylibsrtp-0.7.0/setup.py000066400000000000000000000031201416335143200152560ustar00rootroot00000000000000import os.path import sys import setuptools root_dir = os.path.abspath(os.path.dirname(__file__)) about = {} about_file = os.path.join(root_dir, "src", "pylibsrtp", "about.py") with open(about_file, encoding="utf-8") as fp: exec(fp.read(), about) readme_file = os.path.join(root_dir, "README.rst") with open(readme_file, encoding="utf-8") as f: long_description = f.read() if os.environ.get("READTHEDOCS") == "True": cffi_modules = [] else: cffi_modules = ["src/_cffi_src/build_srtp.py:ffibuilder"] setuptools.setup( name=about["__title__"], version=about["__version__"], description=about["__summary__"], long_description=long_description, url=about["__uri__"], author=about["__author__"], author_email=about["__email__"], license=about["__license__"], classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Topic :: Communications :: Telephony", "Topic :: Security :: Cryptography", ], cffi_modules=cffi_modules, package_dir={"": "src"}, package_data={"pylibsrtp": ["py.typed"]}, packages=["pylibsrtp"], install_requires=["cffi>=1.0.0"], setup_requires=["cffi>=1.0.0"], ) pylibsrtp-0.7.0/src/000077500000000000000000000000001416335143200143375ustar00rootroot00000000000000pylibsrtp-0.7.0/src/_cffi_src/000077500000000000000000000000001416335143200162545ustar00rootroot00000000000000pylibsrtp-0.7.0/src/_cffi_src/build_srtp.py000066400000000000000000000032631416335143200210010ustar00rootroot00000000000000import sys from cffi import FFI libraries = ["srtp2"] if sys.platform == "win32": libraries += ["ws2_32"] ffibuilder = FFI() ffibuilder.set_source( "pylibsrtp._binding", "#include ", libraries=libraries ) ffibuilder.cdef( """ typedef enum { srtp_err_status_ok = 0, ... } srtp_err_status_t; typedef enum { ssrc_undefined = 0, ssrc_specific = 1, ssrc_any_inbound = 2, ssrc_any_outbound = 3 } srtp_ssrc_type_t; typedef struct srtp_crypto_policy_t { ...; } srtp_crypto_policy_t; typedef struct { srtp_ssrc_type_t type; unsigned int value; } srtp_ssrc_t; typedef struct srtp_ctx_t_ srtp_ctx_t; typedef srtp_ctx_t *srtp_t; typedef struct srtp_policy_t { srtp_ssrc_t ssrc; srtp_crypto_policy_t rtp; srtp_crypto_policy_t rtcp; unsigned char *key; unsigned long window_size; int allow_repeat_tx; ...; } srtp_policy_t; srtp_err_status_t srtp_init(void); srtp_err_status_t srtp_create(srtp_t *session, const srtp_policy_t *policy); srtp_err_status_t srtp_dealloc(srtp_t s); void srtp_crypto_policy_set_rtp_default(srtp_crypto_policy_t *p); void srtp_crypto_policy_set_rtcp_default(srtp_crypto_policy_t *p); srtp_err_status_t srtp_add_stream(srtp_t session, const srtp_policy_t *policy); srtp_err_status_t srtp_remove_stream(srtp_t session, unsigned int ssrc); srtp_err_status_t srtp_protect(srtp_t ctx, void *rtp_hdr, int *len_ptr); srtp_err_status_t srtp_protect_rtcp(srtp_t ctx, void *rtcp_hdr, int *pkt_octet_len); srtp_err_status_t srtp_unprotect(srtp_t ctx, void *srtp_hdr, int *len_ptr); srtp_err_status_t srtp_unprotect_rtcp(srtp_t ctx, void *srtcp_hdr, int *pkt_octet_len); """ ) if __name__ == "__main__": ffibuilder.compile(verbose=True) pylibsrtp-0.7.0/src/pylibsrtp/000077500000000000000000000000001416335143200163675ustar00rootroot00000000000000pylibsrtp-0.7.0/src/pylibsrtp/__init__.py000066400000000000000000000150611416335143200205030ustar00rootroot00000000000000from socket import htonl from typing import Optional from ._binding import ffi, lib from .about import __version__ __all__ = ["Error", "Policy", "Session", "__version__"] ERRORS = [ "nothing to report", "unspecified failure", "unsupported parameter", "couldn't allocate memory", "couldn't deallocate properly", "couldn't initialize", "can't process as much data as requested", "authentication failure", "cipher failure", "replay check failed (bad index)", "replay check failed (index too old)", "algorithm failed test routine", "unsupported operation", "no appropriate context found", "unable to perform desired validation", "can't use key any more", "error in use of socket", "error in use POSIX signals", "nonce check failed", "couldn't read data", "couldn't write data", "error parsing data", "error encoding data", "error while using semaphores", "error while using pfkey", "error MKI present in packet is invalid", "packet index is too old to consider", "packet index advanced, reset needed", ] SRTP_MAX_TRAILER_LEN = 16 + 128 class Error(Exception): """ Error that occurred making a `libsrtp` API call. """ pass def _srtp_assert(rc): if rc != lib.srtp_err_status_ok: raise Error(ERRORS[rc]) class Policy: """ Policy for single SRTP stream. """ #: Indicates an undefined SSRC type SSRC_UNDEFINED = lib.ssrc_undefined #: Indicates a specific SSRC value SSRC_SPECIFIC = lib.ssrc_specific #: Indicates any inbound SSRC value SSRC_ANY_INBOUND = lib.ssrc_any_inbound #: Indicates any inbound SSRC value SSRC_ANY_OUTBOUND = lib.ssrc_any_outbound def __init__( self, key: Optional[bytes] = None, ssrc_type: int = SSRC_UNDEFINED, ssrc_value: int = 0, ) -> None: self._policy = ffi.new("srtp_policy_t *") lib.srtp_crypto_policy_set_rtp_default(ffi.addressof(self._policy.rtp)) lib.srtp_crypto_policy_set_rtcp_default(ffi.addressof(self._policy.rtcp)) self.key = key self.ssrc_type = ssrc_type self.ssrc_value = ssrc_value @property def allow_repeat_tx(self) -> bool: """ Whether retransmissions of packets with the same sequence number are allowed. """ return self._policy.allow_repeat_tx == 1 @allow_repeat_tx.setter def allow_repeat_tx(self, allow_repeat_tx: bool) -> None: self._policy.allow_repeat_tx = 1 if allow_repeat_tx else 0 @property def key(self) -> Optional[bytes]: """ The SRTP master key. """ if self.__cdata is None: return None return ffi.buffer(self.__cdata) @key.setter def key(self, key: Optional[bytes]) -> None: if key is None: self.__cdata = None self._policy.key = ffi.NULL return if not isinstance(key, bytes): raise TypeError("key must be bytes") self.__cdata = ffi.new("unsigned char[]", len(key)) self.__cdata[0 : len(key)] = key self._policy.key = self.__cdata @property def ssrc_type(self) -> int: """ The SSRC type. """ return self._policy.ssrc.type @ssrc_type.setter def ssrc_type(self, ssrc_type: int) -> None: self._policy.ssrc.type = ssrc_type @property def ssrc_value(self) -> int: """ The SSRC value, if it is not a wildcard. """ return self._policy.ssrc.value @ssrc_value.setter def ssrc_value(self, ssrc_value: int) -> None: self._policy.ssrc.value = ssrc_value @property def window_size(self) -> int: """ The window size to use for replay protection. """ return self._policy.window_size @window_size.setter def window_size(self, window_size: int) -> None: self._policy.window_size = window_size class Session: """ SRTP session, which may comprise several streams. If `policy` is not specified, streams should be added later using the :func:`add_stream` method. """ def __init__(self, policy: Optional[Policy] = None) -> None: srtp = ffi.new("srtp_t *") if policy is None: _policy = ffi.NULL else: _policy = policy._policy _srtp_assert(lib.srtp_create(srtp, _policy)) self._cdata = ffi.new("char[]", 1500) self._buffer = ffi.buffer(self._cdata) self._srtp = ffi.gc(srtp, lambda x: lib.srtp_dealloc(x[0])) def add_stream(self, policy: Policy) -> None: """ Add a stream to the SRTP session, applying the given `policy` to the stream. :param policy: :class:`Policy` """ _srtp_assert(lib.srtp_add_stream(self._srtp[0], policy._policy)) def remove_stream(self, ssrc: int) -> None: """ Remove the stream with the given `ssrc` from the SRTP session. :param ssrc: :class:`int` """ _srtp_assert(lib.srtp_remove_stream(self._srtp[0], htonl(ssrc))) def protect(self, packet: bytes) -> bytes: """ Apply SRTP protection to the RTP `packet`. :param packet: :class:`bytes` :rtype: :class:`bytes` """ return self.__process(packet, lib.srtp_protect, SRTP_MAX_TRAILER_LEN) def protect_rtcp(self, packet: bytes) -> bytes: """ Apply SRTCP protection to the RTCP `packet`. :param packet: :class:`bytes` :rtype: :class:`bytes` """ return self.__process(packet, lib.srtp_protect_rtcp, SRTP_MAX_TRAILER_LEN) def unprotect(self, packet: bytes) -> bytes: """ Verify SRTP protection of the SRTP packet. :param packet: :class:`bytes` :rtype: :class:`bytes` """ return self.__process(packet, lib.srtp_unprotect) def unprotect_rtcp(self, packet: bytes) -> bytes: """ Verify SRTCP protection of the SRTCP packet. :param packet: :class:`bytes` :rtype: :class:`bytes` """ return self.__process(packet, lib.srtp_unprotect_rtcp) def __process(self, data, func, trailer=0): if not isinstance(data, bytes): raise TypeError("packet must be bytes") if len(data) > len(self._cdata) - trailer: raise ValueError("packet is too long") len_p = ffi.new("int *") len_p[0] = len(data) self._buffer[0 : len(data)] = data _srtp_assert(func(self._srtp[0], self._cdata, len_p)) return self._buffer[0 : len_p[0]] lib.srtp_init() pylibsrtp-0.7.0/src/pylibsrtp/about.py000066400000000000000000000003541416335143200200550ustar00rootroot00000000000000__author__ = "Jeremy Lainé" __email__ = "jeremy.laine@m4x.org" __license__ = "BSD" __summary__ = "Python wrapper around the libsrtp library" __title__ = "pylibsrtp" __uri__ = "https://github.com/aiortc/pylibsrtp" __version__ = "0.7.0" pylibsrtp-0.7.0/src/pylibsrtp/py.typed000066400000000000000000000000071416335143200200630ustar00rootroot00000000000000Marker pylibsrtp-0.7.0/tests/000077500000000000000000000000001416335143200147125ustar00rootroot00000000000000pylibsrtp-0.7.0/tests/__init__.py000066400000000000000000000000001416335143200170110ustar00rootroot00000000000000pylibsrtp-0.7.0/tests/test_session.py000066400000000000000000000127231416335143200200130ustar00rootroot00000000000000from unittest import TestCase from pylibsrtp import Error, Policy, Session RTP = ( b"\x80\x08\x00\x00" # version, packet type, sequence number b"\x00\x00\x00\x00" # timestamp b"\x00\x00\x30\x39" # ssrc: 12345 ) + (b"\xd4" * 160) RTCP = ( b"\x80\xc8\x00\x06\xf3\xcb\x20\x01\x83\xab\x03\xa1\xeb\x02\x0b\x3a" b"\x00\x00\x94\x20\x00\x00\x00\x9e\x00\x00\x9b\x88" ) # Set key to predetermined value KEY = ( b"\x00\x01\x02\x03\x04\x05\x06\x07" b"\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" b"\x10\x11\x12\x13\x14\x15\x16\x17" b"\x18\x19\x1a\x1b\x1c\x1d" ) class PolicyTest(TestCase): def test_allow_repeat_tx(self): policy = Policy() self.assertEqual(policy.allow_repeat_tx, False) policy.allow_repeat_tx = True self.assertEqual(policy.allow_repeat_tx, True) policy.allow_repeat_tx = False self.assertEqual(policy.allow_repeat_tx, False) policy.allow_repeat_tx = 1 self.assertEqual(policy.allow_repeat_tx, True) policy.allow_repeat_tx = 0 self.assertEqual(policy.allow_repeat_tx, False) def test_key(self): policy = Policy() self.assertEqual(policy.key, None) policy.key = KEY self.assertEqual(policy.key, KEY) policy.key = None self.assertEqual(policy.key, None) with self.assertRaises(TypeError) as cm: policy.key = 1234 self.assertEqual(policy.key, None) self.assertEqual(str(cm.exception), "key must be bytes") def test_ssrc_type(self): policy = Policy() self.assertEqual(policy.ssrc_type, Policy.SSRC_UNDEFINED) policy.ssrc_type = Policy.SSRC_ANY_INBOUND self.assertEqual(policy.ssrc_type, Policy.SSRC_ANY_INBOUND) def test_ssrc_value(self): policy = Policy() self.assertEqual(policy.ssrc_value, 0) policy.ssrc_value = 12345 self.assertEqual(policy.ssrc_value, 12345) def test_window_size(self): policy = Policy() self.assertEqual(policy.window_size, 0) policy.window_size = 1024 self.assertEqual(policy.window_size, 1024) class SessionTest(TestCase): def test_no_key(self): policy = Policy(ssrc_type=Policy.SSRC_ANY_OUTBOUND) with self.assertRaises(Error) as cm: Session(policy=policy) self.assertEqual(str(cm.exception), "unsupported parameter") def test_add_remove_stream(self): # protect RTP tx_session = Session( policy=Policy(key=KEY, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345) ) protected = tx_session.protect(RTP) self.assertEqual(len(protected), 182) # add stream and unprotect RTP rx_session = Session() rx_session.add_stream( Policy(key=KEY, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345) ) unprotected = rx_session.unprotect(protected) self.assertEqual(len(unprotected), 172) self.assertEqual(unprotected, RTP) # remove stream rx_session.remove_stream(12345) # try removing stream again with self.assertRaises(Error) as cm: rx_session.remove_stream(12345) self.assertEqual(str(cm.exception), "no appropriate context found") def test_rtp_any_ssrc(self): # protect RTP tx_session = Session(policy=Policy(key=KEY, ssrc_type=Policy.SSRC_ANY_OUTBOUND)) protected = tx_session.protect(RTP) self.assertEqual(len(protected), 182) # bad type with self.assertRaises(TypeError) as cm: tx_session.protect(4567) self.assertEqual(str(cm.exception), "packet must be bytes") # bad length with self.assertRaises(ValueError) as cm: tx_session.protect(b"0" * 1500) self.assertEqual(str(cm.exception), "packet is too long") # unprotect RTP rx_session = Session(policy=Policy(key=KEY, ssrc_type=Policy.SSRC_ANY_INBOUND)) unprotected = rx_session.unprotect(protected) self.assertEqual(len(unprotected), 172) self.assertEqual(unprotected, RTP) def test_rtcp_any_ssrc(self): # protect RCTP tx_session = Session(policy=Policy(key=KEY, ssrc_type=Policy.SSRC_ANY_OUTBOUND)) protected = tx_session.protect_rtcp(RTCP) self.assertEqual(len(protected), 42) # bad type with self.assertRaises(TypeError) as cm: tx_session.protect_rtcp(4567) self.assertEqual(str(cm.exception), "packet must be bytes") # bad length with self.assertRaises(ValueError) as cm: tx_session.protect_rtcp(b"0" * 1500) self.assertEqual(str(cm.exception), "packet is too long") # unprotect RTCP rx_session = Session(policy=Policy(key=KEY, ssrc_type=Policy.SSRC_ANY_INBOUND)) unprotected = rx_session.unprotect_rtcp(protected) self.assertEqual(len(unprotected), 28) self.assertEqual(unprotected, RTCP) def test_rtp_specific_ssrc(self): # protect RTP tx_session = Session( policy=Policy(key=KEY, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345) ) protected = tx_session.protect(RTP) self.assertEqual(len(protected), 182) # unprotect RTP rx_session = Session( policy=Policy(key=KEY, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345) ) unprotected = rx_session.unprotect(protected) self.assertEqual(len(unprotected), 172) self.assertEqual(unprotected, RTP)