ソースを参照

Vendor the full chattolib dependency tree under vendor/

vendor_chattolib.sh now materialises every transitive dependency
(anyio, httpx, idna, typing_extensions, ...) next to chattolib itself,
with vendor/.lock pinning the resolution, so the plugin no longer
relies on anything being installed in the host environment.
Paul Klumpp 1 週間 前
コミット
fa84af16cf
100 ファイル変更24680 行追加1 行削除
  1. 0 0
      vendor/.lock
  2. 0 0
      vendor/__init__.py
  3. 1 0
      vendor/anyio-4.14.2.dist-info/INSTALLER
  4. 107 0
      vendor/anyio-4.14.2.dist-info/METADATA
  5. 54 0
      vendor/anyio-4.14.2.dist-info/RECORD
  6. 0 0
      vendor/anyio-4.14.2.dist-info/REQUESTED
  7. 5 0
      vendor/anyio-4.14.2.dist-info/WHEEL
  8. 2 0
      vendor/anyio-4.14.2.dist-info/entry_points.txt
  9. 20 0
      vendor/anyio-4.14.2.dist-info/licenses/LICENSE
  10. 119 0
      vendor/anyio-4.14.2.dist-info/scm_file_list.json
  11. 8 0
      vendor/anyio-4.14.2.dist-info/scm_version.json
  12. 1 0
      vendor/anyio-4.14.2.dist-info/top_level.txt
  13. 115 0
      vendor/anyio/__init__.py
  14. 0 0
      vendor/anyio/_backends/__init__.py
  15. 3136 0
      vendor/anyio/_backends/_asyncio.py
  16. 1468 0
      vendor/anyio/_backends/_trio.py
  17. 0 0
      vendor/anyio/_core/__init__.py
  18. 167 0
      vendor/anyio/_core/_asyncio_selector_thread.py
  19. 200 0
      vendor/anyio/_core/_contextmanagers.py
  20. 240 0
      vendor/anyio/_core/_eventloop.py
  21. 177 0
      vendor/anyio/_core/_exceptions.py
  22. 960 0
      vendor/anyio/_core/_fileio.py
  23. 18 0
      vendor/anyio/_core/_resources.py
  24. 29 0
      vendor/anyio/_core/_signals.py
  25. 1011 0
      vendor/anyio/_core/_sockets.py
  26. 52 0
      vendor/anyio/_core/_streams.py
  27. 196 0
      vendor/anyio/_core/_subprocesses.py
  28. 772 0
      vendor/anyio/_core/_synchronization.py
  29. 412 0
      vendor/anyio/_core/_tasks.py
  30. 613 0
      vendor/anyio/_core/_tempfile.py
  31. 82 0
      vendor/anyio/_core/_testing.py
  32. 81 0
      vendor/anyio/_core/_typedattr.py
  33. 58 0
      vendor/anyio/abc/__init__.py
  34. 410 0
      vendor/anyio/abc/_eventloop.py
  35. 33 0
      vendor/anyio/abc/_resources.py
  36. 399 0
      vendor/anyio/abc/_sockets.py
  37. 235 0
      vendor/anyio/abc/_streams.py
  38. 79 0
      vendor/anyio/abc/_subprocesses.py
  39. 209 0
      vendor/anyio/abc/_tasks.py
  40. 73 0
      vendor/anyio/abc/_testing.py
  41. 582 0
      vendor/anyio/from_thread.py
  42. 400 0
      vendor/anyio/functools.py
  43. 626 0
      vendor/anyio/itertools.py
  44. 228 0
      vendor/anyio/lowlevel.py
  45. 0 0
      vendor/anyio/py.typed
  46. 375 0
      vendor/anyio/pytest_plugin.py
  47. 0 0
      vendor/anyio/streams/__init__.py
  48. 201 0
      vendor/anyio/streams/buffered.py
  49. 157 0
      vendor/anyio/streams/file.py
  50. 326 0
      vendor/anyio/streams/memory.py
  51. 150 0
      vendor/anyio/streams/stapled.py
  52. 176 0
      vendor/anyio/streams/text.py
  53. 436 0
      vendor/anyio/streams/tls.py
  54. 246 0
      vendor/anyio/to_interpreter.py
  55. 269 0
      vendor/anyio/to_process.py
  56. 80 0
      vendor/anyio/to_thread.py
  57. 10 0
      vendor/bin/httpx
  58. 10 0
      vendor/bin/idna
  59. 1 0
      vendor/certifi-2026.7.22.dist-info/INSTALLER
  60. 78 0
      vendor/certifi-2026.7.22.dist-info/METADATA
  61. 14 0
      vendor/certifi-2026.7.22.dist-info/RECORD
  62. 0 0
      vendor/certifi-2026.7.22.dist-info/REQUESTED
  63. 5 0
      vendor/certifi-2026.7.22.dist-info/WHEEL
  64. 20 0
      vendor/certifi-2026.7.22.dist-info/licenses/LICENSE
  65. 1 0
      vendor/certifi-2026.7.22.dist-info/top_level.txt
  66. 4 0
      vendor/certifi/__init__.py
  67. 12 0
      vendor/certifi/__main__.py
  68. 3959 0
      vendor/certifi/cacert.pem
  69. 83 0
      vendor/certifi/core.py
  70. 0 0
      vendor/certifi/py.typed
  71. 0 0
      vendor/certifi/tests/__init__.py
  72. 18 0
      vendor/certifi/tests/test_certify.py
  73. 131 1
      vendor/chattolib/__init__.py
  74. 1 0
      vendor/connectrpc-0.11.1.dist-info/INSTALLER
  75. 188 0
      vendor/connectrpc-0.11.1.dist-info/METADATA
  76. 46 0
      vendor/connectrpc-0.11.1.dist-info/RECORD
  77. 0 0
      vendor/connectrpc-0.11.1.dist-info/REQUESTED
  78. 4 0
      vendor/connectrpc-0.11.1.dist-info/WHEEL
  79. 219 0
      vendor/connectrpc-0.11.1.dist-info/licenses/LICENSE
  80. 6 0
      vendor/connectrpc/__init__.py
  81. 166 0
      vendor/connectrpc/_asyncio_timeout.py
  82. 467 0
      vendor/connectrpc/_client_async.py
  83. 69 0
      vendor/connectrpc/_client_shared.py
  84. 472 0
      vendor/connectrpc/_client_sync.py
  85. 118 0
      vendor/connectrpc/_codec.py
  86. 50 0
      vendor/connectrpc/_compression.py
  87. 121 0
      vendor/connectrpc/_envelope.py
  88. 1 0
      vendor/connectrpc/_gen/__init__.py
  89. 1 0
      vendor/connectrpc/_gen/google/__init__.py
  90. 1 0
      vendor/connectrpc/_gen/google/rpc/__init__.py
  91. 93 0
      vendor/connectrpc/_gen/google/rpc/status_pb.py
  92. 148 0
      vendor/connectrpc/_headers.py
  93. 242 0
      vendor/connectrpc/_interceptor_async.py
  94. 240 0
      vendor/connectrpc/_interceptor_sync.py
  95. 304 0
      vendor/connectrpc/_protocol.py
  96. 332 0
      vendor/connectrpc/_protocol_connect.py
  97. 443 0
      vendor/connectrpc/_protocol_grpc.py
  98. 33 0
      vendor/connectrpc/_protocol_server.py
  99. 110 0
      vendor/connectrpc/_response_metadata.py
  100. 665 0
      vendor/connectrpc/_server_async.py

+ 0 - 0
vendor/.lock


+ 0 - 0
vendor/__init__.py


+ 1 - 0
vendor/anyio-4.14.2.dist-info/INSTALLER

@@ -0,0 +1 @@
+uv

+ 107 - 0
vendor/anyio-4.14.2.dist-info/METADATA

@@ -0,0 +1,107 @@
+Metadata-Version: 2.4
+Name: anyio
+Version: 4.14.2
+Summary: High-level concurrency and networking framework on top of asyncio or Trio
+Author-email: Alex Grönholm <alex.gronholm@nextday.fi>
+License-Expression: MIT
+Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/
+Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html
+Project-URL: Source code, https://github.com/agronholm/anyio
+Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Framework :: AnyIO
+Classifier: Typing :: Typed
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Classifier: Programming Language :: Python :: 3.15
+Requires-Python: >=3.10
+Description-Content-Type: text/x-rst
+License-File: LICENSE
+Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11"
+Requires-Dist: idna>=2.8
+Requires-Dist: typing_extensions>=4.5; python_version < "3.13"
+Provides-Extra: trio
+Requires-Dist: trio>=0.32.0; extra == "trio"
+Dynamic: license-file
+
+.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg
+  :target: https://github.com/agronholm/anyio/actions/workflows/test.yml
+  :alt: Build Status
+.. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master
+  :target: https://coveralls.io/github/agronholm/anyio?branch=master
+  :alt: Code Coverage
+.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest
+  :target: https://anyio.readthedocs.io/en/latest/?badge=latest
+  :alt: Documentation
+.. image:: https://badges.gitter.im/gitterHQ/gitter.svg
+  :target: https://gitter.im/python-trio/AnyIO
+  :alt: Gitter chat
+.. image:: https://tidelift.com/badges/package/pypi/anyio
+  :target: https://tidelift.com/subscription/pkg/pypi-anyio
+  :alt: Tidelift
+
+AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or
+Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony
+with the native SC of Trio itself.
+
+Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or
+Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full
+refactoring necessary. It will blend in with the native libraries of your chosen backend.
+
+To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it
+`here <https://anyio.readthedocs.io/en/stable/why.html>`_.
+
+Documentation
+-------------
+
+View full documentation at: https://anyio.readthedocs.io/
+
+Features
+--------
+
+AnyIO offers the following functionality:
+
+* Task groups (nurseries_ in trio terminology)
+* High-level networking (TCP, UDP and UNIX sockets)
+
+  * `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python
+    3.8)
+  * async/await style UDP sockets (unlike asyncio where you still have to use Transports and
+    Protocols)
+
+* A versatile API for byte streams and object streams
+* Inter-task synchronization and communication (locks, conditions, events, semaphores, object
+  streams)
+* Worker threads
+* Subprocesses
+* Subinterpreter support for code parallelization (on Python 3.13 and later)
+* Asynchronous file I/O (using worker threads)
+* Signal handling
+* Asynchronous versions of the functools_ and itertools_ modules
+
+AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures.
+It even works with the popular Hypothesis_ library.
+
+.. _asyncio: https://docs.python.org/3/library/asyncio.html
+.. _Trio: https://github.com/python-trio/trio
+.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency
+.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning
+.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs
+.. _pytest: https://docs.pytest.org/en/latest/
+.. _functools: https://docs.python.org/3/library/functools.html
+.. _itertools: https://docs.python.org/3/library/itertools.html
+.. _Hypothesis: https://hypothesis.works/
+
+Security contact information
+----------------------------
+
+To report a security vulnerability, please use the `Tidelift security contact`_.
+Tidelift will coordinate the fix and disclosure.
+
+.. _Tidelift security contact: https://tidelift.com/security

+ 54 - 0
vendor/anyio-4.14.2.dist-info/RECORD

@@ -0,0 +1,54 @@
+anyio-4.14.2.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
+anyio-4.14.2.dist-info/METADATA,sha256=xeb8Tf2DMxmROyh756MNmTXpIIx--RdjBiGFWqrE1Bk,4645
+anyio-4.14.2.dist-info/RECORD,,
+anyio-4.14.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+anyio-4.14.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
+anyio-4.14.2.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39
+anyio-4.14.2.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081
+anyio-4.14.2.dist-info/scm_file_list.json,sha256=wDSXGv8Ehn5ZW5BhB-RlaAc16zY_OfO27qrlMfMMZy8,3654
+anyio-4.14.2.dist-info/scm_version.json,sha256=KgaUx31SyaqGFQFKpG3FO9kCk-ygOKS7Uf0yk56OzaY,161
+anyio-4.14.2.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6
+anyio/__init__.py,sha256=HitUIfzvAojSeaHVmJ9rFn8k_yI63G6s_jUL2QChf4U,6405
+anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+anyio/_backends/_asyncio.py,sha256=eK0j8PI0O7y3bX8cUBsafoWP8wsXR-szP6ciyP4Mkkk,104400
+anyio/_backends/_trio.py,sha256=hoTXL8zI81v4UE-YT_cr2fzh_zY1mKdmZD3oQHmeR0E,45583
+anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626
+anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215
+anyio/_core/_eventloop.py,sha256=ByZUeJD9alMfcyTseRo5IzTO0IltEul_Gyq9iqSjqDk,6658
+anyio/_core/_exceptions.py,sha256=OfzLO4Z3Hog1TnipbIn72YNtkoYxS4lHW9MqKDeGc88,4936
+anyio/_core/_fileio.py,sha256=hHfyV0bXDL-R2ZNnInwse3nmTAd36AIz1cBxgmAwzAQ,31358
+anyio/_core/_resources.py,sha256=NbmU5O5UX3xEyACnkmYX28Fmwdl-f-ny0tHym26e0w0,435
+anyio/_core/_signals.py,sha256=mjTBB2hTKNPRlU0IhnijeQedpWOGERDiMjSlJQsFrug,1016
+anyio/_core/_sockets.py,sha256=HtjiSH-yzehlqh_LpD3PGIafDuUCwd8gueUuk5MFeNk,35288
+anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806
+anyio/_core/_subprocesses.py,sha256=M2GCc4NKXCbB_GtEskJgndiM2b0VS0NK_ohmFxri7O8,7923
+anyio/_core/_synchronization.py,sha256=kgPk88-eVOmY-pDNs-ReRbcEelY1a7YczuVBPsZwc8A,21591
+anyio/_core/_tasks.py,sha256=y99vRi-AFzEv6kyvIwqrzuskE2NMiNItWnuWGk7eOr4,13126
+anyio/_core/_tempfile.py,sha256=jE2w59FRF3yRo4vjkjfZF2YcqsBZvc66VWRwrJGDYGk,19624
+anyio/_core/_testing.py,sha256=u7MPqGXwpTxqI7hclSdNA30z2GH1Nw258uwKvy_RfBg,2340
+anyio/_core/_typedattr.py,sha256=P4ozZikn3-DbpoYcvyghS_FOYAgbmUxeoU8-L_07pZM,2508
+anyio/abc/__init__.py,sha256=6mWhcl_pGXhrgZVHP_TCfMvIXIOp9mroEFM90fYCU_U,2869
+anyio/abc/_eventloop.py,sha256=OqWYSEj0TmwL_xniCJt3_jHFWsuMk9THk8tCTGsKapI,10681
+anyio/abc/_resources.py,sha256=DrYvkNN1hH6Uvv5_5uKySvDsnknGVDe8FCKfko0VtN8,783
+anyio/abc/_sockets.py,sha256=OmVDrfemVvF9c5K1tpBgQyV6fn5v0XyCExLAqBOGz9o,13124
+anyio/abc/_streams.py,sha256=rnSwRy-6y80TPQtIXit5LjMMiU1CCWS1oMNsmJQHRTg,7582
+anyio/abc/_subprocesses.py,sha256=cumAPJTktOQtw63IqG0lDpyZqu_l1EElvQHMiwJgL08,2067
+anyio/abc/_tasks.py,sha256=m-FtE4phxeNIELSG7A3H7VUz3jA2Ib5J2JIew8-PS6o,6642
+anyio/abc/_testing.py,sha256=9YYM2AXsYFvf4PLjUEr6yRxDiUeB5QbY_gOg0X_C6lY,2034
+anyio/from_thread.py,sha256=JYsbaCaIB_Iit6kNhtXSteJGt4PcQ7ncq0nIpcelIrg,19265
+anyio/functools.py,sha256=T4JS8IXq-x1S0Lbo2owF8l9fza2KypO147QLeyz4cjs,11797
+anyio/itertools.py,sha256=QV-9mnRCr2yBph8g01QFvN-bQ_Yle-8Sl13YSydBlMI,16168
+anyio/lowlevel.py,sha256=-59484Z6K5jF7XJJhHxYHIKnvFH_uAeMhl9a_AfaSHI,6280
+anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+anyio/pytest_plugin.py,sha256=paMpI_VMNQf2bir0LfvgMpXSiYJoHDzWdKUVTyoHmvQ,13609
+anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+anyio/streams/buffered.py,sha256=u7hCD8SNrYHcutG6K5wEiy1F88-pizgjvEFM22Kq2Cw,6746
+anyio/streams/file.py,sha256=6jujI2m-QJITqqKFamrupX_DNsU7y2Fz3omLZxOLuY0,4524
+anyio/streams/memory.py,sha256=ZmKWCLpyItOCXmvCQT-L8IyJHNFaB-OVJbrn88CaMo0,10776
+anyio/streams/stapled.py,sha256=mDNF9Gj4deXfOuKSZmgkEG-QExYAKAGjYBHbrs-rJaQ,4486
+anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765
+anyio/streams/tls.py,sha256=Gvs--YOoFxcyn-hakXOiPM8H-aEp8nsU6k9rTrKqJA4,15801
+anyio/to_interpreter.py,sha256=_mLngrMy97TMR6VbW4Y6YzDUk9ZuPcQMPlkuyRh3C9k,7100
+anyio/to_process.py,sha256=jHw7v6XHBNIfYa6DqnqKY0gsRo8ScOCh8fo19fqFvjU,9848
+anyio/to_thread.py,sha256=bYszW0lCDfTmLnXbAYic05HEe_Di_0UWKabgL2vU0T0,2750

+ 0 - 0
vendor/anyio-4.14.2.dist-info/REQUESTED


+ 5 - 0
vendor/anyio-4.14.2.dist-info/WHEEL

@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: setuptools (83.0.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+

+ 2 - 0
vendor/anyio-4.14.2.dist-info/entry_points.txt

@@ -0,0 +1,2 @@
+[pytest11]
+anyio = anyio.pytest_plugin

+ 20 - 0
vendor/anyio-4.14.2.dist-info/licenses/LICENSE

@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Alex Grönholm
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 119 - 0
vendor/anyio-4.14.2.dist-info/scm_file_list.json

@@ -0,0 +1,119 @@
+{
+  "files": [
+    ".pre-commit-config.yaml",
+    "LICENSE",
+    "pyproject.toml",
+    "AGENTS.md",
+    "README.rst",
+    "CLAUDE.md",
+    ".readthedocs.yml",
+    ".gitignore",
+    "docs/tempfile.rst",
+    "docs/signals.rst",
+    "docs/synchronization.rst",
+    "docs/contextmanagers.rst",
+    "docs/testing.rst",
+    "docs/networking.rst",
+    "docs/contributing.rst",
+    "docs/index.rst",
+    "docs/versionhistory.rst",
+    "docs/threads.rst",
+    "docs/api.rst",
+    "docs/typedattrs.rst",
+    "docs/basics.rst",
+    "docs/fileio.rst",
+    "docs/cancellation.rst",
+    "docs/support.rst",
+    "docs/streams.rst",
+    "docs/why.rst",
+    "docs/tasks.rst",
+    "docs/migration.rst",
+    "docs/conf.py",
+    "docs/subprocesses.rst",
+    "docs/faq.rst",
+    "docs/subinterpreters.rst",
+    "src/anyio/functools.py",
+    "src/anyio/py.typed",
+    "src/anyio/__init__.py",
+    "src/anyio/pytest_plugin.py",
+    "src/anyio/itertools.py",
+    "src/anyio/to_interpreter.py",
+    "src/anyio/from_thread.py",
+    "src/anyio/to_process.py",
+    "src/anyio/to_thread.py",
+    "src/anyio/lowlevel.py",
+    "src/anyio/_backends/_trio.py",
+    "src/anyio/_backends/__init__.py",
+    "src/anyio/_backends/_asyncio.py",
+    "src/anyio/streams/memory.py",
+    "src/anyio/streams/__init__.py",
+    "src/anyio/streams/tls.py",
+    "src/anyio/streams/file.py",
+    "src/anyio/streams/text.py",
+    "src/anyio/streams/stapled.py",
+    "src/anyio/streams/buffered.py",
+    "src/anyio/abc/_eventloop.py",
+    "src/anyio/abc/__init__.py",
+    "src/anyio/abc/_sockets.py",
+    "src/anyio/abc/_tasks.py",
+    "src/anyio/abc/_subprocesses.py",
+    "src/anyio/abc/_resources.py",
+    "src/anyio/abc/_streams.py",
+    "src/anyio/abc/_testing.py",
+    "src/anyio/_core/_typedattr.py",
+    "src/anyio/_core/_eventloop.py",
+    "src/anyio/_core/__init__.py",
+    "src/anyio/_core/_tempfile.py",
+    "src/anyio/_core/_sockets.py",
+    "src/anyio/_core/_tasks.py",
+    "src/anyio/_core/_fileio.py",
+    "src/anyio/_core/_synchronization.py",
+    "src/anyio/_core/_subprocesses.py",
+    "src/anyio/_core/_resources.py",
+    "src/anyio/_core/_contextmanagers.py",
+    "src/anyio/_core/_exceptions.py",
+    "src/anyio/_core/_streams.py",
+    "src/anyio/_core/_signals.py",
+    "src/anyio/_core/_asyncio_selector_thread.py",
+    "src/anyio/_core/_testing.py",
+    "tests/test_itertools.py",
+    "tests/test_functools.py",
+    "tests/test_eventloop.py",
+    "tests/__init__.py",
+    "tests/test_to_thread.py",
+    "tests/test_from_thread.py",
+    "tests/test_lowlevel.py",
+    "tests/test_to_interpreter.py",
+    "tests/test_sockets.py",
+    "tests/test_typedattr.py",
+    "tests/test_to_process.py",
+    "tests/test_all_attributes.py",
+    "tests/test_synchronization.py",
+    "tests/test_debugging.py",
+    "tests/test_contextmanagers.py",
+    "tests/test_fileio.py",
+    "tests/conftest.py",
+    "tests/test_signals.py",
+    "tests/test_deprecations.py",
+    "tests/test_tempfile.py",
+    "tests/test_taskgroups.py",
+    "tests/test_pytest_plugin.py",
+    "tests/test_subprocesses.py",
+    "tests/streams/test_text.py",
+    "tests/streams/test_memory.py",
+    "tests/streams/__init__.py",
+    "tests/streams/test_file.py",
+    "tests/streams/test_stapled.py",
+    "tests/streams/test_tls.py",
+    "tests/streams/test_buffered.py",
+    ".github/pull_request_template.md",
+    ".github/dependabot.yml",
+    ".github/FUNDING.yml",
+    ".github/ISSUE_TEMPLATE/features_request.yaml",
+    ".github/ISSUE_TEMPLATE/bug_report.yaml",
+    ".github/ISSUE_TEMPLATE/config.yml",
+    ".github/workflows/test.yml",
+    ".github/workflows/test-downstream.yml",
+    ".github/workflows/publish.yml"
+  ]
+}

+ 8 - 0
vendor/anyio-4.14.2.dist-info/scm_version.json

@@ -0,0 +1,8 @@
+{
+  "tag": "4.14.2",
+  "distance": 0,
+  "node": "gc384f99687c64c59ed8a11c3a0f11a2d57daff71",
+  "dirty": false,
+  "branch": "HEAD",
+  "node_date": "2026-07-12"
+}

+ 1 - 0
vendor/anyio-4.14.2.dist-info/top_level.txt

@@ -0,0 +1 @@
+anyio

+ 115 - 0
vendor/anyio/__init__.py

@@ -0,0 +1,115 @@
+from __future__ import annotations
+
+from ._core._contextmanagers import AsyncContextManagerMixin as AsyncContextManagerMixin
+from ._core._contextmanagers import ContextManagerMixin as ContextManagerMixin
+from ._core._eventloop import current_time as current_time
+from ._core._eventloop import get_all_backends as get_all_backends
+from ._core._eventloop import get_available_backends as get_available_backends
+from ._core._eventloop import get_cancelled_exc_class as get_cancelled_exc_class
+from ._core._eventloop import run as run
+from ._core._eventloop import sleep as sleep
+from ._core._eventloop import sleep_forever as sleep_forever
+from ._core._eventloop import sleep_until as sleep_until
+from ._core._exceptions import BrokenResourceError as BrokenResourceError
+from ._core._exceptions import BrokenWorkerInterpreter as BrokenWorkerInterpreter
+from ._core._exceptions import BrokenWorkerProcess as BrokenWorkerProcess
+from ._core._exceptions import BusyResourceError as BusyResourceError
+from ._core._exceptions import ClosedResourceError as ClosedResourceError
+from ._core._exceptions import ConnectionFailed as ConnectionFailed
+from ._core._exceptions import DelimiterNotFound as DelimiterNotFound
+from ._core._exceptions import EndOfStream as EndOfStream
+from ._core._exceptions import IncompleteRead as IncompleteRead
+from ._core._exceptions import NoEventLoopError as NoEventLoopError
+from ._core._exceptions import RunFinishedError as RunFinishedError
+from ._core._exceptions import TaskCancelled as TaskCancelled
+from ._core._exceptions import TaskFailed as TaskFailed
+from ._core._exceptions import TaskNotFinished as TaskNotFinished
+from ._core._exceptions import TypedAttributeLookupError as TypedAttributeLookupError
+from ._core._exceptions import WouldBlock as WouldBlock
+from ._core._fileio import AsyncFile as AsyncFile
+from ._core._fileio import Path as Path
+from ._core._fileio import open_file as open_file
+from ._core._fileio import wrap_file as wrap_file
+from ._core._resources import aclose_forcefully as aclose_forcefully
+from ._core._signals import open_signal_receiver as open_signal_receiver
+from ._core._sockets import TCPConnectable as TCPConnectable
+from ._core._sockets import UNIXConnectable as UNIXConnectable
+from ._core._sockets import as_connectable as as_connectable
+from ._core._sockets import connect_tcp as connect_tcp
+from ._core._sockets import connect_unix as connect_unix
+from ._core._sockets import create_connected_udp_socket as create_connected_udp_socket
+from ._core._sockets import (
+    create_connected_unix_datagram_socket as create_connected_unix_datagram_socket,
+)
+from ._core._sockets import create_tcp_listener as create_tcp_listener
+from ._core._sockets import create_udp_socket as create_udp_socket
+from ._core._sockets import create_unix_datagram_socket as create_unix_datagram_socket
+from ._core._sockets import create_unix_listener as create_unix_listener
+from ._core._sockets import getaddrinfo as getaddrinfo
+from ._core._sockets import getnameinfo as getnameinfo
+from ._core._sockets import notify_closing as notify_closing
+from ._core._sockets import wait_readable as wait_readable
+from ._core._sockets import wait_socket_readable as wait_socket_readable
+from ._core._sockets import wait_socket_writable as wait_socket_writable
+from ._core._sockets import wait_writable as wait_writable
+from ._core._streams import create_memory_object_stream as create_memory_object_stream
+from ._core._subprocesses import open_process as open_process
+from ._core._subprocesses import run_process as run_process
+from ._core._synchronization import CapacityLimiter as CapacityLimiter
+from ._core._synchronization import (
+    CapacityLimiterStatistics as CapacityLimiterStatistics,
+)
+from ._core._synchronization import Condition as Condition
+from ._core._synchronization import ConditionStatistics as ConditionStatistics
+from ._core._synchronization import Event as Event
+from ._core._synchronization import EventStatistics as EventStatistics
+from ._core._synchronization import Lock as Lock
+from ._core._synchronization import LockStatistics as LockStatistics
+from ._core._synchronization import ResourceGuard as ResourceGuard
+from ._core._synchronization import Semaphore as Semaphore
+from ._core._synchronization import SemaphoreStatistics as SemaphoreStatistics
+from ._core._tasks import TASK_STATUS_IGNORED as TASK_STATUS_IGNORED
+from ._core._tasks import CancelScope as CancelScope
+from ._core._tasks import TaskHandle as TaskHandle
+from ._core._tasks import create_task_group as create_task_group
+from ._core._tasks import current_effective_deadline as current_effective_deadline
+from ._core._tasks import fail_after as fail_after
+from ._core._tasks import move_on_after as move_on_after
+from ._core._tempfile import NamedTemporaryFile as NamedTemporaryFile
+from ._core._tempfile import SpooledTemporaryFile as SpooledTemporaryFile
+from ._core._tempfile import TemporaryDirectory as TemporaryDirectory
+from ._core._tempfile import TemporaryFile as TemporaryFile
+from ._core._tempfile import gettempdir as gettempdir
+from ._core._tempfile import gettempdirb as gettempdirb
+from ._core._tempfile import mkdtemp as mkdtemp
+from ._core._tempfile import mkstemp as mkstemp
+from ._core._testing import TaskInfo as TaskInfo
+from ._core._testing import get_current_task as get_current_task
+from ._core._testing import get_running_tasks as get_running_tasks
+from ._core._testing import wait_all_tasks_blocked as wait_all_tasks_blocked
+from ._core._typedattr import TypedAttributeProvider as TypedAttributeProvider
+from ._core._typedattr import TypedAttributeSet as TypedAttributeSet
+from ._core._typedattr import typed_attribute as typed_attribute
+
+# Re-export imports so they look like they live directly in this package
+for __value in list(locals().values()):
+    if getattr(__value, "__module__", "").startswith("anyio."):
+        __value.__module__ = __name__
+
+
+del __value
+
+
+def __getattr__(attr: str) -> type[BrokenWorkerInterpreter]:
+    """Support deprecated aliases."""
+    if attr == "BrokenWorkerIntepreter":
+        import warnings
+
+        warnings.warn(
+            "The 'BrokenWorkerIntepreter' alias is deprecated, use 'BrokenWorkerInterpreter' instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return BrokenWorkerInterpreter
+
+    raise AttributeError(f"module {__name__!r} has no attribute {attr!r}")

+ 0 - 0
vendor/anyio/_backends/__init__.py


+ 3136 - 0
vendor/anyio/_backends/_asyncio.py

@@ -0,0 +1,3136 @@
+from __future__ import annotations
+
+import array
+import asyncio
+import concurrent.futures
+import contextvars
+import math
+import os
+import socket
+import sys
+import threading
+import weakref
+from asyncio import (
+    AbstractEventLoop,
+    CancelledError,
+    all_tasks,
+    create_task,
+    current_task,
+    get_running_loop,
+    sleep,
+)
+from asyncio.base_events import _run_until_complete_cb  # type: ignore[attr-defined]
+from collections import OrderedDict, deque
+from collections.abc import (
+    AsyncGenerator,
+    AsyncIterator,
+    Awaitable,
+    Callable,
+    Collection,
+    Coroutine,
+    Iterable,
+    Sequence,
+)
+from concurrent.futures import Future
+from contextlib import AbstractContextManager
+from contextvars import Context, copy_context
+from dataclasses import dataclass, field
+from functools import partial, wraps
+from inspect import (
+    CORO_RUNNING,
+    CORO_SUSPENDED,
+    getcoroutinestate,
+)
+from io import IOBase
+from os import PathLike
+from queue import Queue
+from signal import Signals
+from socket import AddressFamily, SocketKind
+from threading import Thread
+from types import CodeType, TracebackType
+from typing import (
+    IO,
+    TYPE_CHECKING,
+    Any,
+    Literal,
+    ParamSpec,
+    TypeVar,
+    cast,
+)
+from weakref import WeakKeyDictionary
+
+from .. import (
+    CapacityLimiterStatistics,
+    EventStatistics,
+    LockStatistics,
+    TaskInfo,
+    abc,
+)
+from .._core._eventloop import (
+    claim_worker_thread,
+    set_current_async_library,
+    threadlocals,
+)
+from .._core._exceptions import (
+    BrokenResourceError,
+    BusyResourceError,
+    ClosedResourceError,
+    EndOfStream,
+    RunFinishedError,
+    WouldBlock,
+)
+from .._core._sockets import convert_ipv6_sockaddr
+from .._core._streams import create_memory_object_stream
+from .._core._synchronization import (
+    CapacityLimiter as BaseCapacityLimiter,
+)
+from .._core._synchronization import Event as BaseEvent
+from .._core._synchronization import Lock as BaseLock
+from .._core._synchronization import (
+    ResourceGuard,
+    SemaphoreStatistics,
+)
+from .._core._synchronization import Semaphore as BaseSemaphore
+from .._core._tasks import CancelScope as BaseCancelScope
+from .._core._tasks import TaskHandle
+from ..abc import (
+    AsyncBackend,
+    IPSockAddrType,
+    SocketListener,
+    UDPPacketType,
+    UNIXDatagramPacketType,
+)
+from ..abc._eventloop import StrOrBytesPath
+from ..abc._tasks import call_for_coroutine, get_callable_name
+from ..lowlevel import RunVar
+from ..streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
+
+if TYPE_CHECKING:
+    from _typeshed import FileDescriptorLike
+else:
+    FileDescriptorLike = object
+
+if sys.version_info >= (3, 11):
+    from asyncio import Runner
+    from typing import TypeVarTuple, Unpack
+else:
+    import contextvars
+    import enum
+    import signal
+    from asyncio import coroutines, events, exceptions, tasks
+
+    from exceptiongroup import BaseExceptionGroup
+    from typing_extensions import TypeVarTuple, Unpack
+
+    class _State(enum.Enum):
+        CREATED = "created"
+        INITIALIZED = "initialized"
+        CLOSED = "closed"
+
+    class Runner:
+        # Copied from CPython 3.11
+        def __init__(
+            self,
+            *,
+            debug: bool | None = None,
+            loop_factory: Callable[[], AbstractEventLoop] | None = None,
+        ):
+            self._state = _State.CREATED
+            self._debug = debug
+            self._loop_factory = loop_factory
+            self._loop: AbstractEventLoop | None = None
+            self._context = None
+            self._interrupt_count = 0
+            self._set_event_loop = False
+
+        def __enter__(self) -> Runner:
+            self._lazy_init()
+            return self
+
+        def __exit__(
+            self,
+            exc_type: type[BaseException] | None,
+            exc_val: BaseException | None,
+            exc_tb: TracebackType | None,
+        ) -> None:
+            self.close()
+
+        def close(self) -> None:
+            """Shutdown and close event loop."""
+            loop = self._loop
+            if self._state is not _State.INITIALIZED or loop is None:
+                return
+            try:
+                _cancel_all_tasks(loop)
+                loop.run_until_complete(loop.shutdown_asyncgens())
+                if hasattr(loop, "shutdown_default_executor"):
+                    loop.run_until_complete(loop.shutdown_default_executor())
+                else:
+                    loop.run_until_complete(_shutdown_default_executor(loop))
+            finally:
+                if self._set_event_loop:
+                    events.set_event_loop(None)
+                loop.close()
+                self._loop = None
+                self._state = _State.CLOSED
+
+        def get_loop(self) -> AbstractEventLoop:
+            """Return embedded event loop."""
+            self._lazy_init()
+            return self._loop
+
+        def run(self, coro: Coroutine[T_Retval], *, context=None) -> T_Retval:
+            """Run a coroutine inside the embedded event loop."""
+            if not coroutines.iscoroutine(coro):
+                raise ValueError(f"a coroutine was expected, got {coro!r}")
+
+            if events._get_running_loop() is not None:
+                # fail fast with short traceback
+                raise RuntimeError(
+                    "Runner.run() cannot be called from a running event loop"
+                )
+
+            self._lazy_init()
+
+            if context is None:
+                context = self._context
+            task = context.run(self._loop.create_task, coro)
+
+            if (
+                threading.current_thread() is threading.main_thread()
+                and signal.getsignal(signal.SIGINT) is signal.default_int_handler
+            ):
+                sigint_handler = partial(self._on_sigint, main_task=task)
+                try:
+                    signal.signal(signal.SIGINT, sigint_handler)
+                except ValueError:
+                    # `signal.signal` may throw if `threading.main_thread` does
+                    # not support signals (e.g. embedded interpreter with signals
+                    # not registered - see gh-91880)
+                    sigint_handler = None
+            else:
+                sigint_handler = None
+
+            self._interrupt_count = 0
+            try:
+                return self._loop.run_until_complete(task)
+            except exceptions.CancelledError:
+                if self._interrupt_count > 0:
+                    uncancel = getattr(task, "uncancel", None)
+                    if uncancel is not None and uncancel() == 0:
+                        raise KeyboardInterrupt  # noqa: B904
+                raise  # CancelledError
+            finally:
+                if (
+                    sigint_handler is not None
+                    and signal.getsignal(signal.SIGINT) is sigint_handler
+                ):
+                    signal.signal(signal.SIGINT, signal.default_int_handler)
+
+        def _lazy_init(self) -> None:
+            if self._state is _State.CLOSED:
+                raise RuntimeError("Runner is closed")
+            if self._state is _State.INITIALIZED:
+                return
+            if self._loop_factory is None:
+                self._loop = events.new_event_loop()
+                if not self._set_event_loop:
+                    # Call set_event_loop only once to avoid calling
+                    # attach_loop multiple times on child watchers
+                    events.set_event_loop(self._loop)
+                    self._set_event_loop = True
+            else:
+                self._loop = self._loop_factory()
+            if self._debug is not None:
+                self._loop.set_debug(self._debug)
+            self._context = contextvars.copy_context()
+            self._state = _State.INITIALIZED
+
+        def _on_sigint(self, signum, frame, main_task: asyncio.Task) -> None:
+            self._interrupt_count += 1
+            if self._interrupt_count == 1 and not main_task.done():
+                main_task.cancel()
+                # wakeup loop if it is blocked by select() with long timeout
+                self._loop.call_soon_threadsafe(lambda: None)
+                return
+            raise KeyboardInterrupt()
+
+    def _cancel_all_tasks(loop: AbstractEventLoop) -> None:
+        to_cancel = tasks.all_tasks(loop)
+        if not to_cancel:
+            return
+
+        for task in to_cancel:
+            task.cancel()
+
+        loop.run_until_complete(tasks.gather(*to_cancel, return_exceptions=True))
+
+        for task in to_cancel:
+            if task.cancelled():
+                continue
+            if task.exception() is not None:
+                loop.call_exception_handler(
+                    {
+                        "message": "unhandled exception during asyncio.run() shutdown",
+                        "exception": task.exception(),
+                        "task": task,
+                    }
+                )
+
+    async def _shutdown_default_executor(loop: AbstractEventLoop) -> None:
+        """Schedule the shutdown of the default executor."""
+
+        def _do_shutdown(future: asyncio.futures.Future) -> None:
+            try:
+                loop._default_executor.shutdown(wait=True)  # type: ignore[attr-defined]
+                loop.call_soon_threadsafe(future.set_result, None)
+            except Exception as ex:
+                loop.call_soon_threadsafe(future.set_exception, ex)
+
+        loop._executor_shutdown_called = True
+        if loop._default_executor is None:
+            return
+        future = loop.create_future()
+        thread = threading.Thread(target=_do_shutdown, args=(future,))
+        thread.start()
+        try:
+            await future
+        finally:
+            thread.join()
+
+
+T_Retval = TypeVar("T_Retval")
+T_co = TypeVar("T_co", covariant=True)
+T_contra = TypeVar("T_contra", contravariant=True)
+PosArgsT = TypeVarTuple("PosArgsT")
+P = ParamSpec("P")
+
+_root_task: RunVar[asyncio.Task | None] = RunVar("_root_task")
+
+
+def find_root_task() -> asyncio.Task:
+    root_task = _root_task.get(None)
+    if root_task is not None and not root_task.done():
+        return root_task
+
+    # Look for a task that has been started via run_until_complete()
+    for task in all_tasks():
+        if task._callbacks and not task.done():
+            callbacks = [cb for cb, context in task._callbacks]
+            for cb in callbacks:
+                if (
+                    cb is _run_until_complete_cb
+                    or getattr(cb, "__module__", None) == "uvloop.loop"
+                ):
+                    _root_task.set(task)
+                    return task
+
+    # Look up the topmost task in the AnyIO task tree, if possible
+    task = cast(asyncio.Task, current_task())
+    state = _task_states.get(task)
+    if state:
+        cancel_scope = state.cancel_scope
+        while cancel_scope and cancel_scope._parent_scope is not None:
+            cancel_scope = cancel_scope._parent_scope
+
+        if cancel_scope is not None:
+            return cast(asyncio.Task, cancel_scope._host_task)
+
+    return task
+
+
+#
+# Event loop
+#
+
+_run_vars: WeakKeyDictionary[asyncio.AbstractEventLoop, Any] = WeakKeyDictionary()
+
+
+def _task_started(task: asyncio.Task) -> bool:
+    """Return ``True`` if the task has been started and has not finished."""
+    # The task coro should never be None here, as we never add finished tasks to the
+    # task list
+    coro = task.get_coro()
+    assert coro is not None
+    return getcoroutinestate(coro) in (CORO_RUNNING, CORO_SUSPENDED)
+
+
+#
+# Timeouts and cancellation
+#
+
+
+def is_anyio_cancellation(exc: CancelledError) -> bool:
+    # Sometimes third party frameworks catch a CancelledError and raise a new one, so as
+    # a workaround we have to look at the previous ones in __context__ too for a
+    # matching cancel message
+    while True:
+        if (
+            exc.args
+            and isinstance(exc.args[0], str)
+            and exc.args[0].startswith("Cancelled via cancel scope ")
+        ):
+            return True
+
+        if isinstance(exc.__context__, CancelledError):
+            exc = exc.__context__
+            continue
+
+        return False
+
+
+class CancelScope(BaseCancelScope):
+    __slots__ = (
+        "_active",
+        "_cancel_called",
+        "_cancel_handle",
+        "_cancel_reason",
+        "_cancelled_caught",
+        "_child_scopes",
+        "_deadline",
+        "_host_task",
+        "_parent_scope",
+        "_pending_uncancellations",
+        "_shield",
+        "_tasks",
+        "_timeout_handle",
+    )
+
+    def __new__(
+        cls, *, deadline: float = math.inf, shield: bool = False
+    ) -> CancelScope:
+        return object.__new__(cls)
+
+    def __init__(self, deadline: float = math.inf, shield: bool = False):
+        self._deadline = deadline
+        self._shield = shield
+        self._parent_scope: CancelScope | None = None
+        self._child_scopes: set[CancelScope] = set()
+        self._cancel_called = False
+        self._cancel_reason: str | None = None
+        self._cancelled_caught = False
+        self._active = False
+        self._timeout_handle: asyncio.TimerHandle | None = None
+        self._cancel_handle: asyncio.Handle | None = None
+        self._tasks: set[asyncio.Task] = set()
+        self._host_task: asyncio.Task | None = None
+        if sys.version_info >= (3, 11):
+            self._pending_uncancellations: int | None = 0
+        else:
+            self._pending_uncancellations = None
+
+    def __enter__(self) -> CancelScope:
+        if self._active:
+            raise RuntimeError(
+                "Each CancelScope may only be used for a single 'with' block"
+            )
+
+        self._host_task = host_task = cast(asyncio.Task, current_task())
+        self._tasks.add(host_task)
+        try:
+            task_state = _task_states[host_task]
+        except KeyError:
+            task_state = TaskState(None, self)
+            _task_states[host_task] = task_state
+        else:
+            self._parent_scope = task_state.cancel_scope
+            task_state.cancel_scope = self
+            if self._parent_scope is not None:
+                # If using an eager task factory, the parent scope may not even contain
+                # the host task
+                self._parent_scope._child_scopes.add(self)
+                self._parent_scope._tasks.discard(host_task)
+
+        self._timeout()
+        self._active = True
+
+        # Start cancelling the host task if the scope was cancelled before entering
+        if self._cancel_called:
+            self._deliver_cancellation(self)
+
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> bool:
+        del exc_tb
+
+        if not self._active:
+            raise RuntimeError("This cancel scope is not active")
+        if current_task() is not self._host_task:
+            raise RuntimeError(
+                "Attempted to exit cancel scope in a different task than it was "
+                "entered in"
+            )
+
+        assert self._host_task is not None
+        host_task_state = _task_states.get(self._host_task)
+        if host_task_state is None or host_task_state.cancel_scope is not self:
+            raise RuntimeError(
+                "Attempted to exit a cancel scope that isn't the current tasks's "
+                "current cancel scope"
+            )
+
+        try:
+            self._active = False
+            if self._timeout_handle:
+                self._timeout_handle.cancel()
+                self._timeout_handle = None
+
+            self._tasks.remove(self._host_task)
+            if self._parent_scope is not None:
+                self._parent_scope._child_scopes.remove(self)
+                self._parent_scope._tasks.add(self._host_task)
+
+            host_task_state.cancel_scope = self._parent_scope
+
+            # Restart the cancellation effort in the closest visible, cancelled parent
+            # scope if necessary
+            self._restart_cancellation_in_parent()
+
+            # We only swallow the exception iff it was an AnyIO CancelledError, either
+            # directly as exc_val or inside an exception group and there are no cancelled
+            # parent cancel scopes visible to us here
+            if self._cancel_called and not self._parent_cancellation_is_visible_to_us:
+                # For each level-cancel() call made on the host task, call uncancel()
+                while self._pending_uncancellations:
+                    self._host_task.uncancel()
+                    self._pending_uncancellations -= 1
+
+                # Update cancelled_caught and check for exceptions we must not swallow
+                if isinstance(exc_val, BaseExceptionGroup):
+                    cancelleds_caught, remaining = exc_val.split(
+                        lambda exc: (
+                            isinstance(exc, CancelledError)
+                            and is_anyio_cancellation(exc)
+                        )
+                    )
+
+                    if cancelleds_caught is None:
+                        return False
+
+                    self._cancelled_caught = True
+
+                    if remaining is None:
+                        return True
+
+                    context = remaining.__context__
+                    try:
+                        # Preserve __cause__ and __suppress_context__ by avoiding `raise
+                        # ... from ...`
+                        raise remaining
+                    finally:
+                        # Preserve __context__
+                        remaining.__context__ = context
+                        del context
+                else:
+                    if isinstance(exc_val, CancelledError) and is_anyio_cancellation(
+                        exc_val
+                    ):
+                        self._cancelled_caught = True
+                        return True
+                    else:
+                        return False
+            else:
+                if self._pending_uncancellations:
+                    assert self._parent_scope is not None
+                    assert self._parent_scope._pending_uncancellations is not None
+                    self._parent_scope._pending_uncancellations += (
+                        self._pending_uncancellations
+                    )
+                    self._pending_uncancellations = 0
+
+                return False
+        finally:
+            self._host_task = None
+            del exc_val
+
+    @property
+    def _effectively_cancelled(self) -> bool:
+        cancel_scope: CancelScope | None = self
+        while cancel_scope is not None:
+            if cancel_scope._cancel_called:
+                return True
+
+            if cancel_scope.shield:
+                return False
+
+            cancel_scope = cancel_scope._parent_scope
+
+        return False
+
+    @property
+    def _parent_cancellation_is_visible_to_us(self) -> bool:
+        return (
+            self._parent_scope is not None
+            and not self.shield
+            and self._parent_scope._effectively_cancelled
+        )
+
+    def _timeout(self) -> None:
+        if self._deadline != math.inf:
+            loop = get_running_loop()
+            if loop.time() >= self._deadline:
+                self.cancel("deadline exceeded")
+            else:
+                self._timeout_handle = loop.call_at(self._deadline, self._timeout)
+
+    def _deliver_cancellation(self, origin: CancelScope) -> bool:
+        """
+        Deliver cancellation to directly contained tasks and nested cancel scopes.
+
+        Schedule another run at the end if we still have tasks eligible for
+        cancellation.
+
+        :param origin: the cancel scope that originated the cancellation
+        :return: ``True`` if the delivery needs to be retried on the next cycle
+
+        """
+        should_retry = False
+        current = current_task()
+        for task in self._tasks:
+            # Always skip tasks that are already done (see issue #1111)
+            if task.done():
+                continue
+
+            should_retry = True
+            if task._must_cancel:  # type: ignore[attr-defined]
+                continue
+
+            # The task is eligible for cancellation if it has started
+            if task is not current and (task is self._host_task or _task_started(task)):
+                waiter = task._fut_waiter  # type: ignore[attr-defined]
+                if not isinstance(waiter, asyncio.Future) or not waiter.done():
+                    task.cancel(origin._cancel_reason)
+                    if (
+                        task is origin._host_task
+                        and origin._pending_uncancellations is not None
+                    ):
+                        origin._pending_uncancellations += 1
+
+        # Deliver cancellation to child scopes that aren't shielded or running their own
+        # cancellation callbacks
+        for scope in self._child_scopes:
+            if not scope._shield and not scope.cancel_called:
+                should_retry = scope._deliver_cancellation(origin) or should_retry
+
+        # Schedule another callback if there are still tasks left
+        if origin is self:
+            if should_retry:
+                self._cancel_handle = get_running_loop().call_soon(
+                    self._deliver_cancellation, origin
+                )
+            else:
+                self._cancel_handle = None
+
+        return should_retry
+
+    def _restart_cancellation_in_parent(self) -> None:
+        """
+        Restart the cancellation effort in the closest directly cancelled parent scope.
+
+        """
+        scope = self._parent_scope
+        while scope is not None:
+            if scope._cancel_called:
+                if scope._cancel_handle is None:
+                    scope._deliver_cancellation(scope)
+
+                break
+
+            # No point in looking beyond any shielded scope
+            if scope._shield:
+                break
+
+            scope = scope._parent_scope
+
+    def cancel(self, reason: str | None = None) -> None:
+        if not self._cancel_called:
+            if self._timeout_handle:
+                self._timeout_handle.cancel()
+                self._timeout_handle = None
+
+            self._cancel_called = True
+            self._cancel_reason = f"Cancelled via cancel scope {id(self):x}"
+            if task := current_task():
+                self._cancel_reason += f" by {task}"
+
+            if reason:
+                self._cancel_reason += f"; reason: {reason}"
+
+            if self._host_task is not None:
+                self._deliver_cancellation(self)
+
+    @property
+    def deadline(self) -> float:
+        return self._deadline
+
+    @deadline.setter
+    def deadline(self, value: float) -> None:
+        self._deadline = float(value)
+        if self._timeout_handle is not None:
+            self._timeout_handle.cancel()
+            self._timeout_handle = None
+
+        if self._active and not self._cancel_called:
+            self._timeout()
+
+    @property
+    def cancel_called(self) -> bool:
+        return self._cancel_called
+
+    @property
+    def cancelled_caught(self) -> bool:
+        return self._cancelled_caught
+
+    @property
+    def shield(self) -> bool:
+        return self._shield
+
+    @shield.setter
+    def shield(self, value: bool) -> None:
+        if self._shield != value:
+            self._shield = value
+            if not value:
+                self._restart_cancellation_in_parent()
+
+
+#
+# Task states
+#
+
+
+class TaskState:
+    """
+    Encapsulates auxiliary task information that cannot be added to the Task instance
+    itself because there are no guarantees about its implementation.
+    """
+
+    __slots__ = "parent_id", "cancel_scope", "__weakref__"
+
+    def __init__(self, parent_id: int | None, cancel_scope: CancelScope | None):
+        self.parent_id = parent_id
+        self.cancel_scope = cancel_scope
+
+
+_task_states: WeakKeyDictionary[asyncio.Task, TaskState] = WeakKeyDictionary()
+
+
+#
+# Task groups
+#
+
+
+class _AsyncioTaskStatus(abc.TaskStatus):
+    def __init__(self, future: asyncio.Future, parent_id: int):
+        self._future = future
+        self._parent_id = parent_id
+
+    def started(self, value: T_contra | None = None) -> None:
+        try:
+            self._future.set_result(value)
+        except asyncio.InvalidStateError:
+            if not self._future.cancelled():
+                raise RuntimeError(
+                    "called 'started' twice on the same task status"
+                ) from None
+
+        task = cast(asyncio.Task, current_task())
+        _task_states[task].parent_id = self._parent_id
+
+
+if sys.version_info >= (3, 12):
+    _eager_task_factory_code: CodeType | None = asyncio.eager_task_factory.__code__
+else:
+    _eager_task_factory_code = None
+
+
+class TaskGroup(abc.TaskGroup):
+    def __init__(self) -> None:
+        self.cancel_scope: CancelScope = CancelScope()
+        self._entered = False
+        self._exceptions: list[BaseException] = []
+        self._tasks: set[asyncio.Task] = set()
+        self._on_completed_fut: asyncio.Future[None] | None = None
+
+    async def __aenter__(self) -> TaskGroup:
+        if self._entered:
+            raise RuntimeError("TaskGroup cannot be entered more than once")
+
+        self._entered = True
+
+        self.cancel_scope.__enter__()
+        return self
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> bool:
+        try:
+            if exc_val is not None:
+                self.cancel_scope.cancel()
+                if not isinstance(exc_val, CancelledError):
+                    self._exceptions.append(exc_val)
+
+            loop = get_running_loop()
+            try:
+                if self._tasks:
+                    with CancelScope() as wait_scope:
+                        while self._tasks:
+                            self._on_completed_fut = loop.create_future()
+
+                            try:
+                                await self._on_completed_fut
+                            except CancelledError as exc:
+                                # Shield the scope against further cancellation attempts,
+                                # as they're not productive (#695)
+                                wait_scope.shield = True
+                                self.cancel_scope.cancel()
+
+                                # Set exc_val from the cancellation exception if it was
+                                # previously unset. However, we should not replace a native
+                                # cancellation exception with one raise by a cancel scope.
+                                if exc_val is None or (
+                                    isinstance(exc_val, CancelledError)
+                                    and not is_anyio_cancellation(exc)
+                                ):
+                                    exc_val = exc
+
+                            self._on_completed_fut = None
+                else:
+                    # If there are no child tasks to wait on, run at least one checkpoint
+                    # anyway
+                    await AsyncIOBackend.cancel_shielded_checkpoint()
+
+                if self._exceptions:
+                    # The exception that got us here should already have been
+                    # added to self._exceptions so it's ok to break exception
+                    # chaining and avoid adding a "During handling of above..."
+                    # for each nesting level.
+                    raise BaseExceptionGroup(
+                        "unhandled errors in a TaskGroup", self._exceptions
+                    ) from None
+                elif exc_val:
+                    raise exc_val
+            except BaseException as exc:
+                if self.cancel_scope.__exit__(type(exc), exc, exc.__traceback__):
+                    return True
+
+                raise
+
+            return self.cancel_scope.__exit__(exc_type, exc_val, exc_tb)
+        finally:
+            del exc_val, exc_tb, self._exceptions
+
+    def _spawn(
+        self,
+        coro: Coroutine[Any, Any, T_co],
+        name: object,
+        task_status_future: asyncio.Future | None = None,
+    ) -> TaskHandle[T_co]:
+        def task_done(_task: asyncio.Task) -> None:
+            if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None:
+                asyncio.future_discard_from_awaited_by(
+                    _task, self.cancel_scope._host_task
+                )
+
+            task_state = _task_states[_task]
+            assert task_state.cancel_scope is not None
+            assert _task in task_state.cancel_scope._tasks
+            task_state.cancel_scope._tasks.remove(_task)
+            self._tasks.remove(task)
+            del _task_states[_task]
+
+            if self._on_completed_fut is not None and not self._tasks:
+                try:
+                    self._on_completed_fut.set_result(None)
+                except asyncio.InvalidStateError:
+                    pass
+
+            try:
+                exc = _task.exception()
+            except CancelledError as e:
+                while isinstance(e.__context__, CancelledError):
+                    e = e.__context__
+
+                exc = e
+
+            if exc is not None:
+                # The future can only be in the cancelled state if the host task was
+                # cancelled, so return immediately instead of adding one more
+                # CancelledError to the exceptions list
+                if task_status_future is not None and task_status_future.cancelled():
+                    return
+
+                if task_status_future is None or task_status_future.done():
+                    if not isinstance(exc, CancelledError):
+                        self._exceptions.append(exc)
+
+                    if not self.cancel_scope._effectively_cancelled:
+                        self.cancel_scope.cancel()
+                else:
+                    task_status_future.set_exception(exc)
+            elif task_status_future is not None and not task_status_future.done():
+                task_status_future.set_exception(
+                    RuntimeError("Child exited without calling task_status.started()")
+                )
+
+        if task_status_future:
+            parent_id = id(current_task())
+        else:
+            parent_id = id(self.cancel_scope._host_task)
+
+        handle = TaskHandle(coro, name)
+        loop = asyncio.get_running_loop()
+        wrapper_coro = handle._run_coro()
+        if (
+            (factory := loop.get_task_factory())
+            and getattr(factory, "__code__", None) is _eager_task_factory_code
+            and (closure := getattr(factory, "__closure__", None))
+        ):
+            custom_task_constructor = closure[0].cell_contents
+            task = custom_task_constructor(wrapper_coro, loop=loop, name=handle.name)
+        else:
+            task = loop.create_task(wrapper_coro, name=handle.name)
+
+        # Make the spawned task inherit the task group's cancel scope
+        _task_states[task] = TaskState(
+            parent_id=parent_id, cancel_scope=self.cancel_scope
+        )
+        self.cancel_scope._tasks.add(task)
+        self._tasks.add(task)
+        if sys.version_info >= (3, 14) and self.cancel_scope._host_task is not None:
+            asyncio.future_add_to_awaited_by(task, self.cancel_scope._host_task)
+
+        task.add_done_callback(task_done)
+        return handle
+
+    def create_task(
+        self,
+        coro: Coroutine[Any, Any, T_co],
+        *,
+        name: object = None,
+        context: Context | None = None,
+    ) -> TaskHandle[T_co]:
+        if not isinstance(coro, Coroutine):
+            raise TypeError(f"expected a coroutine, got {coro.__class__.__qualname__}")
+
+        if not self._entered or not self.cancel_scope._active:
+            coro.close()
+            raise RuntimeError(
+                "This task group is not active; no new tasks can be started."
+            )
+
+        if context is not None:
+            return context.run(self._spawn, coro, name=name)
+        else:
+            return self._spawn(coro, name=name)
+
+    async def start(
+        self,
+        func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
+        *args: Unpack[PosArgsT],
+        name: object = None,
+        return_handle: Literal[False] | Literal[True] = False,
+    ) -> Any:
+        if not self._entered or not self.cancel_scope._active:
+            raise RuntimeError(
+                "This task group is not active; no new tasks can be started."
+            )
+
+        future: asyncio.Future = asyncio.Future()
+        final_name = get_callable_name(func, name)
+        task_status = _AsyncioTaskStatus(future, id(self.cancel_scope._host_task))
+        coro = call_for_coroutine(func, args, task_status=task_status)
+        handle = self._spawn(coro, final_name, future)
+
+        # If the task raises an exception after sending a start value without a switch
+        # point between, the task group is cancelled and this method never proceeds to
+        # process the completed future. That's why we have to have a shielded cancel
+        # scope here.
+        try:
+            await future
+        except BaseException:
+            if handle.status is TaskHandle.Status.PENDING:
+                # Cancel the task and wait for it to exit before returning
+                handle.cancel()
+                with CancelScope(shield=True):
+                    await handle.wait()
+
+            raise
+
+        if return_handle:
+            handle._start_value = future.result()
+            return handle
+        else:
+            return future.result()
+
+
+#
+# Threads
+#
+
+_Retval_Queue_Type = tuple[T_Retval | None, BaseException | None]
+
+
+class WorkerThread(Thread):
+    MAX_IDLE_TIME = 10  # seconds
+
+    def __init__(
+        self,
+        root_task: asyncio.Task,
+        workers: set[WorkerThread],
+        idle_workers: deque[WorkerThread],
+    ):
+        super().__init__(name="AnyIO worker thread")
+        self.root_task = root_task
+        self.workers = workers
+        self.idle_workers = idle_workers
+        self.loop = root_task._loop
+        self.queue: Queue[
+            tuple[Context, Callable, tuple, asyncio.Future, CancelScope] | None
+        ] = Queue(2)
+        self.idle_since = AsyncIOBackend.current_time()
+        self.stopping = False
+
+    def _report_result(
+        self, future: asyncio.Future, result: Any, exc: BaseException | None
+    ) -> None:
+        self.idle_since = AsyncIOBackend.current_time()
+        if not self.stopping:
+            self.idle_workers.append(self)
+
+        if not future.cancelled():
+            if exc is not None:
+                if isinstance(exc, StopIteration):
+                    new_exc = RuntimeError("coroutine raised StopIteration")
+                    new_exc.__cause__ = exc
+                    exc = new_exc
+
+                future.set_exception(exc)
+            else:
+                future.set_result(result)
+
+    def run(self) -> None:
+        with claim_worker_thread(AsyncIOBackend, self.loop):
+            while True:
+                item = self.queue.get()
+                if item is None:
+                    # Shutdown command received
+                    return
+
+                context, func, args, future, cancel_scope = item
+                if not future.cancelled():
+                    result = None
+                    exception: BaseException | None = None
+                    threadlocals.current_cancel_scope = cancel_scope
+                    try:
+                        result = context.run(func, *args)
+                    except BaseException as exc:
+                        exception = exc
+                    finally:
+                        del threadlocals.current_cancel_scope
+
+                    if not self.loop.is_closed():
+                        self.loop.call_soon_threadsafe(
+                            self._report_result, future, result, exception
+                        )
+
+                    del result, exception
+
+                self.queue.task_done()
+                del item, context, func, args, future, cancel_scope
+
+    def stop(self, f: asyncio.Task | None = None) -> None:
+        self.stopping = True
+        self.queue.put_nowait(None)
+        self.workers.discard(self)
+        try:
+            self.idle_workers.remove(self)
+        except ValueError:
+            pass
+
+
+_threadpool_idle_workers: RunVar[deque[WorkerThread]] = RunVar(
+    "_threadpool_idle_workers"
+)
+_threadpool_workers: RunVar[set[WorkerThread]] = RunVar("_threadpool_workers")
+
+
+#
+# Subprocesses
+#
+
+
+@dataclass(eq=False)
+class StreamReaderWrapper(abc.ByteReceiveStream):
+    _stream: asyncio.StreamReader
+
+    async def receive(self, max_bytes: int = 65536) -> bytes:
+        if max_bytes < 1:
+            raise ValueError("max_bytes must be a positive integer")
+
+        data = await self._stream.read(max_bytes)
+        if data:
+            return data
+        else:
+            raise EndOfStream
+
+    async def aclose(self) -> None:
+        self._stream.set_exception(ClosedResourceError())
+        await AsyncIOBackend.checkpoint()
+
+
+@dataclass(eq=False)
+class StreamWriterWrapper(abc.ByteSendStream):
+    _stream: asyncio.StreamWriter
+    _closed: bool = field(init=False, default=False)
+
+    async def send(self, item: bytes) -> None:
+        await AsyncIOBackend.checkpoint_if_cancelled()
+        stream_paused = self._stream._protocol._paused  # type: ignore[attr-defined]
+        try:
+            self._stream.write(item)
+            await self._stream.drain()
+        except (ConnectionResetError, BrokenPipeError, RuntimeError) as exc:
+            # If closed by us and/or the peer:
+            # * on stdlib, drain() raises ConnectionResetError or BrokenPipeError
+            # * on uvloop and Winloop, write() eventually starts raising RuntimeError
+            if self._closed:
+                raise ClosedResourceError from exc
+            elif self._stream.is_closing():
+                raise BrokenResourceError from exc
+
+            raise
+
+        if not stream_paused:
+            await AsyncIOBackend.cancel_shielded_checkpoint()
+
+    async def aclose(self) -> None:
+        self._closed = True
+        self._stream.close()
+        await AsyncIOBackend.checkpoint()
+
+
+@dataclass(eq=False)
+class Process(abc.Process):
+    _process: asyncio.subprocess.Process
+    _stdin: StreamWriterWrapper | None
+    _stdout: StreamReaderWrapper | None
+    _stderr: StreamReaderWrapper | None
+    _exited: asyncio.Event
+    _transport: asyncio.SubprocessTransport
+
+    async def aclose(self) -> None:
+        with CancelScope(shield=True) as scope:
+            # We need to close the underlying pipe_transports as well to allow a
+            # process blocking on full buffers to receive SIGPIPE and exit.
+            if self._stdin:
+                await self._stdin.aclose()
+                if pipe := self._transport.get_pipe_transport(0):
+                    pipe.close()
+            if self._stdout:
+                await self._stdout.aclose()
+                if pipe := self._transport.get_pipe_transport(1):
+                    pipe.close()
+            if self._stderr:
+                await self._stderr.aclose()
+                if pipe := self._transport.get_pipe_transport(2):
+                    pipe.close()
+
+            scope.shield = False
+            try:
+                await self.wait()
+            except BaseException:
+                scope.shield = True
+                # Closing the transport on asyncio also handles sending kill
+                self._transport.close()
+                await self.wait()
+                raise
+
+    async def wait(self) -> int:
+        await self._exited.wait()
+        assert self._process.returncode is not None
+        return self._process.returncode
+
+    def terminate(self) -> None:
+        self._process.terminate()
+
+    def kill(self) -> None:
+        self._process.kill()
+
+    def send_signal(self, signal: int) -> None:
+        self._process.send_signal(signal)
+
+    @property
+    def pid(self) -> int:
+        return self._process.pid
+
+    @property
+    def returncode(self) -> int | None:
+        return self._process.returncode
+
+    @property
+    def stdin(self) -> abc.ByteSendStream | None:
+        return self._stdin
+
+    @property
+    def stdout(self) -> abc.ByteReceiveStream | None:
+        return self._stdout
+
+    @property
+    def stderr(self) -> abc.ByteReceiveStream | None:
+        return self._stderr
+
+
+def _forcibly_shutdown_process_pool_on_exit(
+    workers: set[Process], _task: object
+) -> None:
+    """
+    Forcibly shuts down worker processes belonging to this event loop."""
+    child_watcher: asyncio.AbstractChildWatcher | None = None  # type: ignore[name-defined]
+    if sys.version_info < (3, 12):
+        try:
+            child_watcher = asyncio.get_event_loop_policy().get_child_watcher()
+        except NotImplementedError:
+            pass
+
+    # Close as much as possible (w/o async/await) to avoid warnings
+    for process in workers.copy():
+        if process.returncode is not None:
+            continue
+
+        process._stdin._stream._transport.close()  # type: ignore[union-attr]
+        process._stdout._stream._transport.close()  # type: ignore[union-attr]
+        process._stderr._stream._transport.close()  # type: ignore[union-attr]
+        process.kill()
+        if child_watcher:
+            child_watcher.remove_child_handler(process.pid)
+
+
+async def _shutdown_process_pool_on_exit(workers: set[abc.Process]) -> None:
+    """
+    Shuts down worker processes belonging to this event loop.
+
+    NOTE: this only works when the event loop was started using asyncio.run() or
+    anyio.run().
+
+    """
+    process: abc.Process
+    try:
+        await sleep(math.inf)
+    except asyncio.CancelledError:
+        workers = workers.copy()
+        for process in workers:
+            if process.returncode is None:
+                process.kill()
+
+        for process in workers:
+            await process.aclose()
+
+
+#
+# Sockets and networking
+#
+
+
+class StreamProtocol(asyncio.Protocol):
+    read_queue: deque[bytes]
+    read_event: asyncio.Event
+    write_event: asyncio.Event
+    exception: Exception | None = None
+    is_at_eof: bool = False
+
+    def connection_made(self, transport: asyncio.BaseTransport) -> None:
+        self.read_queue = deque()
+        self.read_event = asyncio.Event()
+        self.write_event = asyncio.Event()
+        self.write_event.set()
+        cast(asyncio.Transport, transport).set_write_buffer_limits(0)
+
+    def connection_lost(self, exc: Exception | None) -> None:
+        if exc:
+            self.exception = exc
+
+        self.read_event.set()
+        self.write_event.set()
+
+    def data_received(self, data: bytes) -> None:
+        # ProactorEventloop sometimes sends bytearray instead of bytes
+        self.read_queue.append(bytes(data))
+        self.read_event.set()
+
+    def eof_received(self) -> bool | None:
+        self.is_at_eof = True
+        self.read_event.set()
+        return True
+
+    def pause_writing(self) -> None:
+        self.write_event = asyncio.Event()
+
+    def resume_writing(self) -> None:
+        self.write_event.set()
+
+
+class DatagramProtocol(asyncio.DatagramProtocol):
+    read_queue: deque[tuple[bytes, IPSockAddrType]]
+    read_event: asyncio.Event
+    write_event: asyncio.Event
+    closed_event: asyncio.Event
+    exception: Exception | None = None
+
+    def connection_made(self, transport: asyncio.BaseTransport) -> None:
+        self.read_queue = deque(maxlen=100)  # arbitrary value
+        self.read_event = asyncio.Event()
+        self.write_event = asyncio.Event()
+        self.closed_event = asyncio.Event()
+        self.write_event.set()
+
+    def connection_lost(self, exc: Exception | None) -> None:
+        self.read_event.set()
+        self.write_event.set()
+        self.closed_event.set()
+
+    def datagram_received(self, data: bytes, addr: IPSockAddrType) -> None:
+        addr = convert_ipv6_sockaddr(addr)
+        self.read_queue.append((data, addr))
+        self.read_event.set()
+
+    def error_received(self, exc: Exception) -> None:
+        self.exception = exc
+
+    def pause_writing(self) -> None:
+        self.write_event.clear()
+
+    def resume_writing(self) -> None:
+        self.write_event.set()
+
+
+class SocketStream(abc.SocketStream):
+    def __init__(self, transport: asyncio.Transport, protocol: StreamProtocol):
+        self._transport = transport
+        self._protocol = protocol
+        self._receive_guard = ResourceGuard("reading from")
+        self._send_guard = ResourceGuard("writing to")
+        self._closed = False
+
+    @property
+    def _raw_socket(self) -> socket.socket:
+        return self._transport.get_extra_info("socket")
+
+    async def receive(self, max_bytes: int = 65536) -> bytes:
+        if max_bytes < 1:
+            raise ValueError("max_bytes must be a positive integer")
+
+        with self._receive_guard:
+            if (
+                not self._protocol.read_event.is_set()
+                and not self._transport.is_closing()
+                and not self._protocol.is_at_eof
+            ):
+                self._transport.resume_reading()
+                await self._protocol.read_event.wait()
+                self._transport.pause_reading()
+            else:
+                await AsyncIOBackend.checkpoint()
+
+            try:
+                chunk = self._protocol.read_queue.popleft()
+            except IndexError:
+                if self._closed:
+                    raise ClosedResourceError from None
+                elif self._protocol.exception:
+                    raise BrokenResourceError from self._protocol.exception
+                else:
+                    raise EndOfStream from None
+
+            if len(chunk) > max_bytes:
+                # Split the oversized chunk
+                chunk, leftover = chunk[:max_bytes], chunk[max_bytes:]
+                self._protocol.read_queue.appendleft(leftover)
+
+            # If the read queue is empty, clear the flag so that the next call will
+            # block until data is available
+            if not self._protocol.read_queue:
+                self._protocol.read_event.clear()
+
+        return chunk
+
+    async def send(self, item: bytes) -> None:
+        with self._send_guard:
+            await AsyncIOBackend.checkpoint()
+
+            if self._closed:
+                raise ClosedResourceError
+            elif self._protocol.exception is not None:
+                raise BrokenResourceError from self._protocol.exception
+
+            try:
+                self._transport.write(item)
+            except RuntimeError as exc:
+                if self._transport.is_closing():
+                    raise BrokenResourceError from exc
+                else:
+                    raise
+
+            await self._protocol.write_event.wait()
+
+    async def send_eof(self) -> None:
+        try:
+            self._transport.write_eof()
+        except OSError:
+            pass
+
+    async def aclose(self) -> None:
+        self._closed = True
+        if not self._transport.is_closing():
+            try:
+                self._transport.write_eof()
+            except OSError:
+                pass
+
+            self._transport.close()
+            await sleep(0)
+            self._transport.abort()
+
+
+class _RawSocketMixin:
+    _receive_future: asyncio.Future | None = None
+    _send_future: asyncio.Future | None = None
+    _closing = False
+
+    def __init__(self, raw_socket: socket.socket):
+        self.__raw_socket = raw_socket
+        self._receive_guard = ResourceGuard("reading from")
+        self._send_guard = ResourceGuard("writing to")
+
+    @property
+    def _raw_socket(self) -> socket.socket:
+        return self.__raw_socket
+
+    def _wait_until_readable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future:
+        def callback(f: object) -> None:
+            del self._receive_future
+            loop.remove_reader(self.__raw_socket)
+
+        f = self._receive_future = asyncio.Future()
+        loop.add_reader(self.__raw_socket, f.set_result, None)
+        f.add_done_callback(callback)
+        return f
+
+    def _wait_until_writable(self, loop: asyncio.AbstractEventLoop) -> asyncio.Future:
+        def callback(f: object) -> None:
+            del self._send_future
+            loop.remove_writer(self.__raw_socket)
+
+        f = self._send_future = asyncio.Future()
+        loop.add_writer(self.__raw_socket, f.set_result, None)
+        f.add_done_callback(callback)
+        return f
+
+    async def aclose(self) -> None:
+        if not self._closing:
+            self._closing = True
+            if self.__raw_socket.fileno() != -1:
+                self.__raw_socket.close()
+
+            if self._receive_future:
+                self._receive_future.set_result(None)
+            if self._send_future:
+                self._send_future.set_result(None)
+
+
+class UNIXSocketStream(_RawSocketMixin, abc.UNIXSocketStream):
+    async def send_eof(self) -> None:
+        with self._send_guard:
+            self._raw_socket.shutdown(socket.SHUT_WR)
+
+    async def receive(self, max_bytes: int = 65536) -> bytes:
+        if max_bytes < 1:
+            raise ValueError("max_bytes must be a positive integer")
+
+        loop = get_running_loop()
+        await AsyncIOBackend.checkpoint()
+        with self._receive_guard:
+            while True:
+                try:
+                    data = self._raw_socket.recv(max_bytes)
+                except BlockingIOError:
+                    await self._wait_until_readable(loop)
+                except OSError as exc:
+                    if self._closing:
+                        raise ClosedResourceError from None
+                    else:
+                        raise BrokenResourceError from exc
+                else:
+                    if not data:
+                        raise EndOfStream
+
+                    return data
+
+    async def send(self, item: bytes) -> None:
+        loop = get_running_loop()
+        await AsyncIOBackend.checkpoint()
+        with self._send_guard:
+            view = memoryview(item)
+            while view:
+                try:
+                    bytes_sent = self._raw_socket.send(view)
+                except BlockingIOError:
+                    await self._wait_until_writable(loop)
+                except OSError as exc:
+                    if self._closing:
+                        raise ClosedResourceError from None
+                    else:
+                        raise BrokenResourceError from exc
+                else:
+                    view = view[bytes_sent:]
+
+    async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]:
+        if not isinstance(msglen, int) or msglen < 0:
+            raise ValueError("msglen must be a non-negative integer")
+        if not isinstance(maxfds, int) or maxfds < 1:
+            raise ValueError("maxfds must be a positive integer")
+
+        loop = get_running_loop()
+        fds = array.array("i")
+        await AsyncIOBackend.checkpoint()
+        with self._receive_guard:
+            while True:
+                try:
+                    message, ancdata, flags, addr = self._raw_socket.recvmsg(
+                        msglen, socket.CMSG_LEN(maxfds * fds.itemsize)
+                    )
+                except BlockingIOError:
+                    await self._wait_until_readable(loop)
+                except OSError as exc:
+                    if self._closing:
+                        raise ClosedResourceError from None
+                    else:
+                        raise BrokenResourceError from exc
+                else:
+                    if not message and not ancdata:
+                        raise EndOfStream
+
+                    break
+
+        for cmsg_level, cmsg_type, cmsg_data in ancdata:
+            if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS:
+                raise RuntimeError(
+                    f"Received unexpected ancillary data; message = {message!r}, "
+                    f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}"
+                )
+
+            fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
+
+        return message, list(fds)
+
+    async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None:
+        if not message:
+            raise ValueError("message must not be empty")
+        if not fds:
+            raise ValueError("fds must not be empty")
+
+        loop = get_running_loop()
+        filenos: list[int] = []
+        for fd in fds:
+            if isinstance(fd, int):
+                filenos.append(fd)
+            elif isinstance(fd, IOBase):
+                filenos.append(fd.fileno())
+
+        fdarray = array.array("i", filenos)
+        await AsyncIOBackend.checkpoint()
+        with self._send_guard:
+            while True:
+                try:
+                    # The ignore can be removed after mypy picks up
+                    # https://github.com/python/typeshed/pull/5545
+                    self._raw_socket.sendmsg(
+                        [message], [(socket.SOL_SOCKET, socket.SCM_RIGHTS, fdarray)]
+                    )
+                    break
+                except BlockingIOError:
+                    await self._wait_until_writable(loop)
+                except OSError as exc:
+                    if self._closing:
+                        raise ClosedResourceError from None
+                    else:
+                        raise BrokenResourceError from exc
+
+
+class TCPSocketListener(abc.SocketListener):
+    _accept_scope: CancelScope | None = None
+    _closed = False
+
+    def __init__(self, raw_socket: socket.socket):
+        self.__raw_socket = raw_socket
+        self._loop = cast(asyncio.BaseEventLoop, get_running_loop())
+        self._accept_guard = ResourceGuard("accepting connections from")
+
+    @property
+    def _raw_socket(self) -> socket.socket:
+        return self.__raw_socket
+
+    async def accept(self) -> abc.SocketStream:
+        if self._closed:
+            raise ClosedResourceError
+
+        with self._accept_guard:
+            await AsyncIOBackend.checkpoint()
+            with CancelScope() as self._accept_scope:
+                try:
+                    client_sock, _addr = await self._loop.sock_accept(self._raw_socket)
+                except asyncio.CancelledError:
+                    # Workaround for https://bugs.python.org/issue41317
+                    try:
+                        self._loop.remove_reader(self._raw_socket)
+                    except (ValueError, NotImplementedError):
+                        pass
+
+                    if self._closed:
+                        raise ClosedResourceError from None
+
+                    raise
+                finally:
+                    self._accept_scope = None
+
+        client_sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+        transport, protocol = await self._loop.connect_accepted_socket(
+            StreamProtocol, client_sock
+        )
+        return SocketStream(transport, protocol)
+
+    async def aclose(self) -> None:
+        if self._closed:
+            return
+
+        self._closed = True
+        if self._accept_scope:
+            # Workaround for https://bugs.python.org/issue41317
+            try:
+                self._loop.remove_reader(self._raw_socket)
+            except (ValueError, NotImplementedError):
+                pass
+
+            self._accept_scope.cancel()
+            await sleep(0)
+
+        self._raw_socket.close()
+
+
+class UNIXSocketListener(abc.SocketListener):
+    def __init__(self, raw_socket: socket.socket):
+        self.__raw_socket = raw_socket
+        self._loop = get_running_loop()
+        self._accept_guard = ResourceGuard("accepting connections from")
+        self._closed = False
+
+    async def accept(self) -> abc.SocketStream:
+        await AsyncIOBackend.checkpoint()
+        with self._accept_guard:
+            while True:
+                try:
+                    client_sock, _ = self.__raw_socket.accept()
+                    client_sock.setblocking(False)
+                    return UNIXSocketStream(client_sock)
+                except BlockingIOError:
+                    f: asyncio.Future = asyncio.Future()
+                    self._loop.add_reader(self.__raw_socket, f.set_result, None)
+                    f.add_done_callback(
+                        lambda _: self._loop.remove_reader(self.__raw_socket)
+                    )
+                    await f
+                except OSError as exc:
+                    if self._closed:
+                        raise ClosedResourceError from None
+                    else:
+                        raise BrokenResourceError from exc
+
+    async def aclose(self) -> None:
+        self._closed = True
+        self.__raw_socket.close()
+
+    @property
+    def _raw_socket(self) -> socket.socket:
+        return self.__raw_socket
+
+
+class UDPSocket(abc.UDPSocket):
+    def __init__(
+        self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol
+    ):
+        self._transport = transport
+        self._protocol = protocol
+        self._receive_guard = ResourceGuard("reading from")
+        self._send_guard = ResourceGuard("writing to")
+        self._closed = False
+
+    @property
+    def _raw_socket(self) -> socket.socket:
+        return self._transport.get_extra_info("socket")
+
+    async def aclose(self) -> None:
+        self._closed = True
+        if not self._transport.is_closing():
+            self._transport.close()
+
+        await self._protocol.closed_event.wait()
+
+    async def receive(self) -> tuple[bytes, IPSockAddrType]:
+        with self._receive_guard:
+            await AsyncIOBackend.checkpoint()
+
+            # If the buffer is empty, ask for more data
+            if not self._protocol.read_queue and not self._transport.is_closing():
+                self._protocol.read_event.clear()
+                await self._protocol.read_event.wait()
+
+            try:
+                return self._protocol.read_queue.popleft()
+            except IndexError:
+                if self._closed:
+                    raise ClosedResourceError from None
+                else:
+                    raise BrokenResourceError from None
+
+    async def send(self, item: UDPPacketType) -> None:
+        with self._send_guard:
+            await AsyncIOBackend.checkpoint()
+            await self._protocol.write_event.wait()
+            if self._closed:
+                raise ClosedResourceError
+            elif self._transport.is_closing():
+                raise BrokenResourceError
+            else:
+                self._transport.sendto(*item)
+
+
+class ConnectedUDPSocket(abc.ConnectedUDPSocket):
+    def __init__(
+        self, transport: asyncio.DatagramTransport, protocol: DatagramProtocol
+    ):
+        self._transport = transport
+        self._protocol = protocol
+        self._receive_guard = ResourceGuard("reading from")
+        self._send_guard = ResourceGuard("writing to")
+        self._closed = False
+
+    @property
+    def _raw_socket(self) -> socket.socket:
+        return self._transport.get_extra_info("socket")
+
+    async def aclose(self) -> None:
+        self._closed = True
+        if not self._transport.is_closing():
+            self._transport.close()
+
+        await self._protocol.closed_event.wait()
+
+    async def receive(self) -> bytes:
+        with self._receive_guard:
+            await AsyncIOBackend.checkpoint()
+
+            # If the buffer is empty, ask for more data
+            if not self._protocol.read_queue and not self._transport.is_closing():
+                self._protocol.read_event.clear()
+                await self._protocol.read_event.wait()
+
+            try:
+                packet = self._protocol.read_queue.popleft()
+            except IndexError:
+                if self._closed:
+                    raise ClosedResourceError from None
+                else:
+                    raise BrokenResourceError from None
+
+            return packet[0]
+
+    async def send(self, item: bytes) -> None:
+        with self._send_guard:
+            await AsyncIOBackend.checkpoint()
+            await self._protocol.write_event.wait()
+            if self._closed:
+                raise ClosedResourceError
+            elif self._transport.is_closing():
+                raise BrokenResourceError
+            else:
+                self._transport.sendto(item)
+
+
+class UNIXDatagramSocket(_RawSocketMixin, abc.UNIXDatagramSocket):
+    async def receive(self) -> UNIXDatagramPacketType:
+        loop = get_running_loop()
+        await AsyncIOBackend.checkpoint()
+        with self._receive_guard:
+            while True:
+                try:
+                    data = self._raw_socket.recvfrom(65536)
+                except BlockingIOError:
+                    await self._wait_until_readable(loop)
+                except OSError as exc:
+                    if self._closing:
+                        raise ClosedResourceError from None
+                    else:
+                        raise BrokenResourceError from exc
+                else:
+                    return data
+
+    async def send(self, item: UNIXDatagramPacketType) -> None:
+        loop = get_running_loop()
+        await AsyncIOBackend.checkpoint()
+        with self._send_guard:
+            while True:
+                try:
+                    self._raw_socket.sendto(*item)
+                except BlockingIOError:
+                    await self._wait_until_writable(loop)
+                except OSError as exc:
+                    if self._closing:
+                        raise ClosedResourceError from None
+                    else:
+                        raise BrokenResourceError from exc
+                else:
+                    return
+
+
+class ConnectedUNIXDatagramSocket(_RawSocketMixin, abc.ConnectedUNIXDatagramSocket):
+    async def receive(self) -> bytes:
+        loop = get_running_loop()
+        await AsyncIOBackend.checkpoint()
+        with self._receive_guard:
+            while True:
+                try:
+                    data = self._raw_socket.recv(65536)
+                except BlockingIOError:
+                    await self._wait_until_readable(loop)
+                except OSError as exc:
+                    if self._closing:
+                        raise ClosedResourceError from None
+                    else:
+                        raise BrokenResourceError from exc
+                else:
+                    return data
+
+    async def send(self, item: bytes) -> None:
+        loop = get_running_loop()
+        await AsyncIOBackend.checkpoint()
+        with self._send_guard:
+            while True:
+                try:
+                    self._raw_socket.send(item)
+                except BlockingIOError:
+                    await self._wait_until_writable(loop)
+                except OSError as exc:
+                    if self._closing:
+                        raise ClosedResourceError from None
+                    else:
+                        raise BrokenResourceError from exc
+                else:
+                    return
+
+
+_read_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("read_events")
+_write_events: RunVar[dict[int, asyncio.Future[bool]]] = RunVar("write_events")
+
+
+#
+# Synchronization
+#
+
+
+class Event(BaseEvent):
+    __slots__ = ("_event",)
+
+    def __new__(cls) -> Event:
+        return object.__new__(cls)
+
+    def __init__(self) -> None:
+        self._event = asyncio.Event()
+
+    def set(self) -> None:
+        self._event.set()
+
+    def is_set(self) -> bool:
+        return self._event.is_set()
+
+    async def wait(self) -> None:
+        if self.is_set():
+            await AsyncIOBackend.checkpoint()
+        else:
+            await self._event.wait()
+
+    def statistics(self) -> EventStatistics:
+        return EventStatistics(len(self._event._waiters))
+
+
+class Lock(BaseLock):
+    __slots__ = "_fast_acquire", "_owner_task", "_waiters"
+
+    def __new__(cls, *, fast_acquire: bool = False) -> Lock:
+        return object.__new__(cls)
+
+    def __init__(self, *, fast_acquire: bool = False) -> None:
+        self._fast_acquire = fast_acquire
+        self._owner_task: asyncio.Task | None = None
+        self._waiters: deque[tuple[asyncio.Task, asyncio.Future]] = deque()
+
+    async def acquire(self) -> None:
+        task = cast(asyncio.Task, current_task())
+        if self._owner_task is None and not self._waiters:
+            await AsyncIOBackend.checkpoint_if_cancelled()
+            self._owner_task = task
+
+            # Unless on the "fast path", yield control of the event loop so that other
+            # tasks can run too
+            if not self._fast_acquire:
+                try:
+                    await AsyncIOBackend.cancel_shielded_checkpoint()
+                except CancelledError:
+                    self.release()
+                    raise
+
+            return
+
+        if self._owner_task == task:
+            raise RuntimeError("Attempted to acquire an already held Lock")
+
+        fut: asyncio.Future[None] = asyncio.Future()
+        item = task, fut
+        self._waiters.append(item)
+        try:
+            await fut
+        except CancelledError:
+            if fut.cancelled():
+                try:
+                    self._waiters.remove(item)
+                except ValueError:
+                    pass
+            else:
+                self.release()
+
+            raise
+
+    def acquire_nowait(self) -> None:
+        task = cast(asyncio.Task, current_task())
+        if self._owner_task is None and not self._waiters:
+            self._owner_task = task
+            return
+
+        if self._owner_task is task:
+            raise RuntimeError("Attempted to acquire an already held Lock")
+
+        raise WouldBlock
+
+    def locked(self) -> bool:
+        return self._owner_task is not None
+
+    def release(self) -> None:
+        if self._owner_task != current_task():
+            raise RuntimeError("The current task is not holding this lock")
+
+        # A cancelled waiter that already received ownership removes itself from
+        # _waiters before calling release(); any cancelled waiter still queued here
+        # was cancelled before being woken, so drop it.
+        while self._waiters:
+            task, fut = self._waiters.popleft()
+            if fut.cancelled():
+                continue
+
+            self._owner_task = task
+            fut.set_result(None)
+            return
+
+        self._owner_task = None
+
+    def statistics(self) -> LockStatistics:
+        task_info = AsyncIOTaskInfo(self._owner_task) if self._owner_task else None
+        return LockStatistics(self.locked(), task_info, len(self._waiters))
+
+
+class Semaphore(BaseSemaphore):
+    __slots__ = "_value", "_max_value", "_fast_acquire", "_waiters"
+
+    def __new__(
+        cls,
+        initial_value: int,
+        *,
+        max_value: int | None = None,
+        fast_acquire: bool = False,
+    ) -> Semaphore:
+        return object.__new__(cls)
+
+    def __init__(
+        self,
+        initial_value: int,
+        *,
+        max_value: int | None = None,
+        fast_acquire: bool = False,
+    ):
+        super().__init__(initial_value, max_value=max_value)
+        self._value = initial_value
+        self._max_value = max_value
+        self._fast_acquire = fast_acquire
+        self._waiters: deque[asyncio.Future[None]] = deque()
+
+    async def acquire(self) -> None:
+        if self._value > 0 and not self._waiters:
+            await AsyncIOBackend.checkpoint_if_cancelled()
+            self._value -= 1
+
+            # Unless on the "fast path", yield control of the event loop so that other
+            # tasks can run too
+            if not self._fast_acquire:
+                try:
+                    await AsyncIOBackend.cancel_shielded_checkpoint()
+                except CancelledError:
+                    self.release()
+                    raise
+
+            return
+
+        fut: asyncio.Future[None] = asyncio.Future()
+        self._waiters.append(fut)
+        try:
+            await fut
+        except CancelledError:
+            if fut.cancelled():
+                try:
+                    self._waiters.remove(fut)
+                except ValueError:
+                    pass
+            else:
+                self.release()
+
+            raise
+
+    def acquire_nowait(self) -> None:
+        if self._value == 0:
+            raise WouldBlock
+
+        self._value -= 1
+
+    def release(self) -> None:
+        if self._max_value is not None and self._value == self._max_value:
+            raise ValueError("semaphore released too many times")
+
+        while self._waiters:
+            fut = self._waiters.popleft()
+            if fut.cancelled():
+                continue
+
+            fut.set_result(None)
+            return
+
+        self._value += 1
+
+    @property
+    def value(self) -> int:
+        return self._value
+
+    @property
+    def max_value(self) -> int | None:
+        return self._max_value
+
+    def statistics(self) -> SemaphoreStatistics:
+        return SemaphoreStatistics(len(self._waiters))
+
+
+class CapacityLimiter(BaseCapacityLimiter):
+    __slots__ = "_total_tokens", "_borrowers", "_wait_queue"
+
+    def __new__(cls, total_tokens: float) -> CapacityLimiter:
+        return object.__new__(cls)
+
+    def __init__(self, total_tokens: float):
+        self._total_tokens: float = 0
+        self._borrowers: set[Any] = set()
+        self._wait_queue: OrderedDict[Any, asyncio.Event] = OrderedDict()
+        self.total_tokens = total_tokens
+
+    async def __aenter__(self) -> None:
+        await self.acquire()
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self.release()
+
+    @property
+    def total_tokens(self) -> float:
+        return self._total_tokens
+
+    @total_tokens.setter
+    def total_tokens(self, value: float) -> None:
+        if not isinstance(value, int) and not math.isinf(value):
+            raise TypeError("total_tokens must be an int or math.inf")
+
+        if value < 0:
+            raise ValueError("total_tokens must be >= 0")
+
+        waiters_to_notify = max(value - self._total_tokens, 0)
+        self._total_tokens = value
+
+        # Notify waiting tasks that they have acquired the limiter
+        while self._wait_queue and waiters_to_notify:
+            borrower, event = self._wait_queue.popitem(last=False)
+            self._borrowers.add(borrower)
+            event.set()
+            waiters_to_notify -= 1
+
+    @property
+    def borrowed_tokens(self) -> int:
+        return len(self._borrowers)
+
+    @property
+    def available_tokens(self) -> float:
+        return self._total_tokens - len(self._borrowers)
+
+    def _notify_next_waiter(self) -> None:
+        """Hand a free token to the next task in line, if any."""
+        if self._wait_queue and len(self._borrowers) < self._total_tokens:
+            borrower, event = self._wait_queue.popitem(last=False)
+            self._borrowers.add(borrower)
+            event.set()
+
+    def acquire_nowait(self) -> None:
+        self.acquire_on_behalf_of_nowait(current_task())
+
+    def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
+        if borrower in self._borrowers:
+            raise RuntimeError(
+                "this borrower is already holding one of this CapacityLimiter's tokens"
+            )
+
+        if self._wait_queue or len(self._borrowers) >= self._total_tokens:
+            raise WouldBlock
+
+        self._borrowers.add(borrower)
+
+    async def acquire(self) -> None:
+        return await self.acquire_on_behalf_of(current_task())
+
+    async def acquire_on_behalf_of(self, borrower: object) -> None:
+        await AsyncIOBackend.checkpoint_if_cancelled()
+        try:
+            self.acquire_on_behalf_of_nowait(borrower)
+        except WouldBlock:
+            event = asyncio.Event()
+            self._wait_queue[borrower] = event
+            try:
+                await event.wait()
+            except BaseException:
+                self._wait_queue.pop(borrower, None)
+                if event.is_set():
+                    self._borrowers.discard(borrower)
+                    self._notify_next_waiter()
+
+                raise
+        else:
+            try:
+                await AsyncIOBackend.cancel_shielded_checkpoint()
+            except BaseException:
+                self.release()
+                raise
+
+    def release(self) -> None:
+        self.release_on_behalf_of(current_task())
+
+    def release_on_behalf_of(self, borrower: object) -> None:
+        try:
+            self._borrowers.remove(borrower)
+        except KeyError:
+            raise RuntimeError(
+                "this borrower isn't holding any of this CapacityLimiter's tokens"
+            ) from None
+
+        self._notify_next_waiter()
+
+    def statistics(self) -> CapacityLimiterStatistics:
+        return CapacityLimiterStatistics(
+            self.borrowed_tokens,
+            self.total_tokens,
+            tuple(self._borrowers),
+            len(self._wait_queue),
+        )
+
+
+_default_thread_limiter: RunVar[CapacityLimiter] = RunVar("_default_thread_limiter")
+
+
+#
+# Operating system signals
+#
+
+
+class _SignalReceiver:
+    def __init__(self, signals: tuple[Signals, ...]):
+        self._signals = signals
+        self._loop = get_running_loop()
+        self._signal_queue: deque[Signals] = deque()
+        self._future: asyncio.Future = asyncio.Future()
+        self._handled_signals: set[Signals] = set()
+
+    def _deliver(self, signum: Signals) -> None:
+        self._signal_queue.append(signum)
+        if not self._future.done():
+            self._future.set_result(None)
+
+    def __enter__(self) -> _SignalReceiver:
+        for sig in set(self._signals):
+            self._loop.add_signal_handler(sig, self._deliver, sig)
+            self._handled_signals.add(sig)
+
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        for sig in self._handled_signals:
+            self._loop.remove_signal_handler(sig)
+
+    def __aiter__(self) -> _SignalReceiver:
+        return self
+
+    async def __anext__(self) -> Signals:
+        await AsyncIOBackend.checkpoint()
+        if not self._signal_queue:
+            self._future = asyncio.Future()
+            await self._future
+
+        return self._signal_queue.popleft()
+
+
+#
+# Testing and debugging
+#
+
+
+class AsyncIOTaskInfo(TaskInfo):
+    def __init__(self, task: asyncio.Task):
+        task_state = _task_states.get(task)
+        if task_state is None:
+            parent_id = None
+        else:
+            parent_id = task_state.parent_id
+
+        coro = task.get_coro()
+        assert coro is not None, "created TaskInfo from a completed Task"
+        super().__init__(id(task), parent_id, task.get_name(), coro)
+        self._task = weakref.ref(task)
+
+    def has_pending_cancellation(self) -> bool:
+        if not (task := self._task()):
+            # If the task isn't around anymore, it won't have a pending cancellation
+            return False
+
+        if task._must_cancel:  # type: ignore[attr-defined]
+            return True
+        elif (
+            isinstance(task._fut_waiter, asyncio.Future)  # type: ignore[attr-defined]
+            and task._fut_waiter.cancelled()  # type: ignore[attr-defined]
+        ):
+            return True
+
+        if task_state := _task_states.get(task):
+            if cancel_scope := task_state.cancel_scope:
+                return cancel_scope._effectively_cancelled
+
+        return False
+
+
+class TestRunner(abc.TestRunner):
+    _send_stream: MemoryObjectSendStream[tuple[Awaitable[Any], asyncio.Future[Any]]]
+
+    def __init__(
+        self,
+        *,
+        debug: bool | None = None,
+        use_uvloop: bool = False,
+        loop_factory: Callable[[], AbstractEventLoop] | None = None,
+    ) -> None:
+        if use_uvloop and loop_factory is None:
+            if sys.platform != "win32":
+                import uvloop
+
+                loop_factory = uvloop.new_event_loop
+            else:
+                import winloop
+
+                loop_factory = winloop.new_event_loop
+
+        self._runner = Runner(debug=debug, loop_factory=loop_factory)
+        self._exceptions: list[BaseException] = []
+        self._runner_task: asyncio.Task | None = None
+
+    def __enter__(self) -> TestRunner:
+        self._runner.__enter__()
+        self.get_loop().set_exception_handler(self._exception_handler)
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self._runner.__exit__(exc_type, exc_val, exc_tb)
+
+    def get_loop(self) -> AbstractEventLoop:
+        return self._runner.get_loop()
+
+    def is_running(self) -> bool:
+        try:
+            asyncio.get_running_loop()
+            return True
+        except RuntimeError:
+            return False
+
+    def _exception_handler(
+        self, loop: asyncio.AbstractEventLoop, context: dict[str, Any]
+    ) -> None:
+        if isinstance(context.get("exception"), Exception):
+            self._exceptions.append(context["exception"])
+        else:
+            loop.default_exception_handler(context)
+
+    def _raise_async_exceptions(self) -> None:
+        # Re-raise any exceptions raised in asynchronous callbacks
+        if self._exceptions:
+            exceptions, self._exceptions = self._exceptions, []
+            if len(exceptions) == 1:
+                raise exceptions[0]
+            elif exceptions:
+                raise BaseExceptionGroup(
+                    "Multiple exceptions occurred in asynchronous callbacks", exceptions
+                )
+
+    async def _run_tests_and_fixtures(
+        self,
+        receive_stream: MemoryObjectReceiveStream[
+            tuple[Awaitable[T_Retval], asyncio.Future[T_Retval]]
+        ],
+    ) -> None:
+        from _pytest.outcomes import OutcomeException
+
+        with receive_stream, self._send_stream:
+            async for coro, future in receive_stream:
+                try:
+                    retval = await coro
+                except CancelledError as exc:
+                    if not future.cancelled():
+                        future.cancel(*exc.args)
+
+                    raise
+                except BaseException as exc:
+                    if not future.cancelled():
+                        future.set_exception(exc)
+
+                    if not isinstance(exc, (Exception, OutcomeException)):
+                        raise
+                else:
+                    if not future.cancelled():
+                        future.set_result(retval)
+
+    async def _call_in_runner_task(
+        self,
+        func: Callable[P, Awaitable[T_Retval]],
+        /,
+        *args: P.args,
+        **kwargs: P.kwargs,
+    ) -> T_Retval:
+        if not self._runner_task:
+            self._send_stream, receive_stream = create_memory_object_stream[
+                tuple[Awaitable[Any], asyncio.Future]
+            ](1)
+            self._runner_task = self.get_loop().create_task(
+                self._run_tests_and_fixtures(receive_stream)
+            )
+
+        coro = func(*args, **kwargs)
+        future: asyncio.Future[T_Retval] = self.get_loop().create_future()
+        self._send_stream.send_nowait((coro, future))
+        return await future
+
+    def run_asyncgen_fixture(
+        self,
+        fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]],
+        kwargs: dict[str, Any],
+    ) -> Iterable[T_Retval]:
+        asyncgen = fixture_func(**kwargs)
+        fixturevalue: T_Retval = self.get_loop().run_until_complete(
+            self._call_in_runner_task(asyncgen.asend, None)
+        )
+        self._raise_async_exceptions()
+
+        yield fixturevalue
+
+        try:
+            self.get_loop().run_until_complete(
+                self._call_in_runner_task(asyncgen.asend, None)
+            )
+        except StopAsyncIteration:
+            self._raise_async_exceptions()
+        else:
+            self.get_loop().run_until_complete(asyncgen.aclose())
+            raise RuntimeError("Async generator fixture did not stop")
+
+    def run_fixture(
+        self,
+        fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]],
+        kwargs: dict[str, Any],
+    ) -> T_Retval:
+        retval = self.get_loop().run_until_complete(
+            self._call_in_runner_task(fixture_func, **kwargs)
+        )
+        self._raise_async_exceptions()
+        return retval
+
+    def run_test(
+        self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any]
+    ) -> None:
+        from _pytest.outcomes import OutcomeException
+
+        try:
+            self.get_loop().run_until_complete(
+                self._call_in_runner_task(test_func, **kwargs)
+            )
+        except Exception as exc:
+            self._exceptions.append(exc)
+        except OutcomeException:
+            raise
+        except BaseException:
+            # A BaseException (e.g. KeyboardInterrupt, SystemExit) interrupted the event loop before
+            # the test completed. Cancel _runner_task so it does not resume when the event
+            # loop is re-entered during async generator fixture teardown.
+            if self._runner_task is not None and not self._runner_task.done():
+                self._runner_task.cancel()
+                self._send_stream.close()
+                try:
+                    self.get_loop().run_until_complete(self._runner_task)
+                except CancelledError:
+                    pass
+                finally:
+                    self._runner_task = None
+            raise
+        self._raise_async_exceptions()
+
+
+class _ProcessStreamProtocol(asyncio.subprocess.SubprocessStreamProtocol):
+    """
+    A subprocess protocol that allows us to be notified of ``process_exited``
+
+    asyncio's own ``Process.wait()`` only resolves once every pipe transport has
+    disconnected so to get same semantics as on trio and uvloop we need this.
+    """
+
+    def __init__(self) -> None:
+        # Match the standard factory for asyncio.create_process
+        super().__init__(limit=2**16, loop=asyncio.get_running_loop())
+        self.exited = asyncio.Event()
+
+    def process_exited(self) -> None:
+        super().process_exited()
+        self.exited.set()
+
+
+class AsyncIOBackend(AsyncBackend):
+    @classmethod
+    def run(
+        cls,
+        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
+        args: tuple[Unpack[PosArgsT]],
+        kwargs: dict[str, Any],
+        options: dict[str, Any],
+    ) -> T_Retval:
+        @wraps(func)
+        async def wrapper() -> T_Retval:
+            task = cast(asyncio.Task, current_task())
+            task.set_name(get_callable_name(func))
+            _task_states[task] = TaskState(None, None)
+
+            try:
+                return await func(*args)
+            finally:
+                del _task_states[task]
+
+        debug = options.get("debug", None)
+        loop_factory = options.get("loop_factory", None)
+        if loop_factory is None and options.get("use_uvloop", False):
+            if sys.platform != "win32":
+                import uvloop
+
+                loop_factory = uvloop.new_event_loop
+            else:
+                import winloop
+
+                loop_factory = winloop.new_event_loop
+
+        with Runner(debug=debug, loop_factory=loop_factory) as runner:
+            return runner.run(wrapper())
+
+    @classmethod
+    def current_token(cls) -> object:
+        return get_running_loop()
+
+    @classmethod
+    def current_time(cls) -> float:
+        return get_running_loop().time()
+
+    @classmethod
+    def cancelled_exception_class(cls) -> type[BaseException]:
+        return CancelledError
+
+    @classmethod
+    async def checkpoint(cls) -> None:
+        await sleep(0)
+
+    @classmethod
+    async def checkpoint_if_cancelled(cls) -> None:
+        task = current_task()
+        if task is None:
+            return
+
+        try:
+            cancel_scope = _task_states[task].cancel_scope
+        except KeyError:
+            return
+
+        while cancel_scope:
+            if cancel_scope.cancel_called:
+                await sleep(0)
+            elif cancel_scope.shield:
+                break
+            else:
+                cancel_scope = cancel_scope._parent_scope
+
+    @classmethod
+    async def cancel_shielded_checkpoint(cls) -> None:
+        with CancelScope(shield=True):
+            await sleep(0)
+
+    @classmethod
+    async def sleep(cls, delay: float) -> None:
+        await sleep(delay)
+
+    @classmethod
+    def create_cancel_scope(
+        cls, *, deadline: float = math.inf, shield: bool = False
+    ) -> CancelScope:
+        return CancelScope(deadline=deadline, shield=shield)
+
+    @classmethod
+    def current_effective_deadline(cls) -> float:
+        if (task := current_task()) is None:
+            return math.inf
+
+        try:
+            cancel_scope = _task_states[task].cancel_scope
+        except KeyError:
+            return math.inf
+
+        deadline = math.inf
+        while cancel_scope:
+            deadline = min(deadline, cancel_scope.deadline)
+            if cancel_scope._cancel_called:
+                deadline = -math.inf
+                break
+            elif cancel_scope.shield:
+                break
+            else:
+                cancel_scope = cancel_scope._parent_scope
+
+        return deadline
+
+    @classmethod
+    def create_task_group(cls) -> abc.TaskGroup:
+        return TaskGroup()
+
+    @classmethod
+    def create_event(cls) -> abc.Event:
+        return Event()
+
+    @classmethod
+    def create_lock(cls, *, fast_acquire: bool) -> abc.Lock:
+        return Lock(fast_acquire=fast_acquire)
+
+    @classmethod
+    def create_semaphore(
+        cls,
+        initial_value: int,
+        *,
+        max_value: int | None = None,
+        fast_acquire: bool = False,
+    ) -> abc.Semaphore:
+        return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire)
+
+    @classmethod
+    def create_capacity_limiter(cls, total_tokens: float) -> abc.CapacityLimiter:
+        return CapacityLimiter(total_tokens)
+
+    @classmethod
+    async def run_sync_in_worker_thread(  # type: ignore[return]
+        cls,
+        func: Callable[[Unpack[PosArgsT]], T_Retval],
+        args: tuple[Unpack[PosArgsT]],
+        abandon_on_cancel: bool = False,
+        limiter: abc.CapacityLimiter | None = None,
+    ) -> T_Retval:
+        await cls.checkpoint()
+
+        # If this is the first run in this event loop thread, set up the necessary
+        # variables
+        try:
+            idle_workers = _threadpool_idle_workers.get()
+            workers = _threadpool_workers.get()
+        except LookupError:
+            idle_workers = deque()
+            workers = set()
+            _threadpool_idle_workers.set(idle_workers)
+            _threadpool_workers.set(workers)
+
+        async with limiter or cls.current_default_thread_limiter():
+            with CancelScope(shield=not abandon_on_cancel) as scope:
+                future = asyncio.Future[T_Retval]()
+                root_task = find_root_task()
+                if not idle_workers:
+                    worker = WorkerThread(root_task, workers, idle_workers)
+                    worker.start()
+                    workers.add(worker)
+                    root_task.add_done_callback(
+                        worker.stop, context=contextvars.Context()
+                    )
+                else:
+                    worker = idle_workers.pop()
+
+                    # Prune any other workers that have been idle for MAX_IDLE_TIME
+                    # seconds or longer
+                    now = cls.current_time()
+                    while idle_workers:
+                        if (
+                            now - idle_workers[0].idle_since
+                            < WorkerThread.MAX_IDLE_TIME
+                        ):
+                            break
+
+                        expired_worker = idle_workers.popleft()
+                        expired_worker.root_task.remove_done_callback(
+                            expired_worker.stop
+                        )
+                        expired_worker.stop()
+
+                context = copy_context()
+                context.run(set_current_async_library, None)
+                if abandon_on_cancel or scope._parent_scope is None:
+                    worker_scope = scope
+                else:
+                    worker_scope = scope._parent_scope
+
+                worker.queue.put_nowait((context, func, args, future, worker_scope))
+                return await future
+
+    @classmethod
+    def check_cancelled(cls) -> None:
+        scope: CancelScope | None = threadlocals.current_cancel_scope
+        while scope is not None:
+            if scope.cancel_called:
+                raise CancelledError(f"Cancelled via cancel scope {id(scope):x}")
+
+            if scope.shield:
+                return
+
+            scope = scope._parent_scope
+
+    @classmethod
+    def run_async_from_thread(
+        cls,
+        func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
+        args: tuple[Unpack[PosArgsT]],
+        token: object,
+    ) -> T_co:
+        async def task_wrapper() -> T_co:
+            __tracebackhide__ = True
+            if scope is not None:
+                task = cast(asyncio.Task, current_task())
+                _task_states[task] = TaskState(None, scope)
+                scope._tasks.add(task)
+            try:
+                return await func(*args)
+            except CancelledError as exc:
+                raise concurrent.futures.CancelledError(str(exc)) from None
+            finally:
+                if scope is not None:
+                    scope._tasks.discard(task)
+
+        loop = cast(
+            "AbstractEventLoop", token or threadlocals.current_token.native_token
+        )
+        if loop.is_closed():
+            raise RunFinishedError
+
+        context = copy_context()
+        context.run(set_current_async_library, "asyncio")
+        scope = getattr(threadlocals, "current_cancel_scope", None)
+        f: concurrent.futures.Future[T_co] = context.run(
+            asyncio.run_coroutine_threadsafe, task_wrapper(), loop=loop
+        )
+        return f.result()
+
+    @classmethod
+    def run_sync_from_thread(
+        cls,
+        func: Callable[[Unpack[PosArgsT]], T_Retval],
+        args: tuple[Unpack[PosArgsT]],
+        token: object,
+    ) -> T_Retval:
+        @wraps(func)
+        def wrapper() -> None:
+            try:
+                set_current_async_library("asyncio")
+                f.set_result(func(*args))
+            except BaseException as exc:
+                f.set_exception(exc)
+                if not isinstance(exc, Exception):
+                    raise
+
+        loop = cast(
+            "AbstractEventLoop", token or threadlocals.current_token.native_token
+        )
+        if loop.is_closed():
+            raise RunFinishedError
+
+        f: concurrent.futures.Future[T_Retval] = Future()
+        loop.call_soon_threadsafe(wrapper)
+        return f.result()
+
+    @classmethod
+    async def open_process(
+        cls,
+        command: StrOrBytesPath | Sequence[StrOrBytesPath],
+        *,
+        stdin: int | IO[Any] | None,
+        stdout: int | IO[Any] | None,
+        stderr: int | IO[Any] | None,
+        **kwargs: Any,
+    ) -> Process:
+        await cls.checkpoint()
+        if isinstance(command, PathLike):
+            command = os.fspath(command)
+
+        # Use loop.subprocess_shell()/subprocess_exec() rather than their
+        # asyncio.create_subprocess_*() counterparts to get access to
+        # transport/protocol.
+        loop = asyncio.get_running_loop()
+        if isinstance(command, (str, bytes)):
+            transport, protocol = await loop.subprocess_shell(
+                _ProcessStreamProtocol,
+                command,
+                stdin=stdin,
+                stdout=stdout,
+                stderr=stderr,
+                **kwargs,
+            )
+        else:
+            transport, protocol = await loop.subprocess_exec(
+                _ProcessStreamProtocol,
+                *command,
+                stdin=stdin,
+                stdout=stdout,
+                stderr=stderr,
+                **kwargs,
+            )
+
+        process = asyncio.subprocess.Process(transport, protocol, loop)
+        stdin_stream = StreamWriterWrapper(process.stdin) if process.stdin else None
+        stdout_stream = StreamReaderWrapper(process.stdout) if process.stdout else None
+        stderr_stream = StreamReaderWrapper(process.stderr) if process.stderr else None
+        return Process(
+            process,
+            stdin_stream,
+            stdout_stream,
+            stderr_stream,
+            protocol.exited,
+            transport,
+        )
+
+    @classmethod
+    def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None:
+        create_task(
+            _shutdown_process_pool_on_exit(workers),
+            name="AnyIO process pool shutdown task",
+        )
+        find_root_task().add_done_callback(
+            partial(_forcibly_shutdown_process_pool_on_exit, workers)  # type:ignore[arg-type]
+        )
+
+    @classmethod
+    async def connect_tcp(
+        cls, host: str, port: int, local_address: IPSockAddrType | None = None
+    ) -> abc.SocketStream:
+        transport, protocol = cast(
+            tuple[asyncio.Transport, StreamProtocol],
+            await get_running_loop().create_connection(
+                StreamProtocol, host, port, local_addr=local_address
+            ),
+        )
+        transport.pause_reading()
+        return SocketStream(transport, protocol)
+
+    @classmethod
+    async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream:
+        await cls.checkpoint()
+        loop = get_running_loop()
+        raw_socket = socket.socket(socket.AF_UNIX)
+        raw_socket.setblocking(False)
+        while True:
+            try:
+                raw_socket.connect(path)
+            except BlockingIOError:
+                f: asyncio.Future = asyncio.Future()
+                loop.add_writer(raw_socket, f.set_result, None)
+                f.add_done_callback(lambda _: loop.remove_writer(raw_socket))
+                await f
+            except BaseException:
+                raw_socket.close()
+                raise
+            else:
+                return UNIXSocketStream(raw_socket)
+
+    @classmethod
+    def create_tcp_listener(cls, sock: socket.socket) -> SocketListener:
+        return TCPSocketListener(sock)
+
+    @classmethod
+    def create_unix_listener(cls, sock: socket.socket) -> SocketListener:
+        return UNIXSocketListener(sock)
+
+    @classmethod
+    async def create_udp_socket(
+        cls,
+        family: AddressFamily,
+        local_address: IPSockAddrType | None,
+        remote_address: IPSockAddrType | None,
+        reuse_port: bool,
+    ) -> UDPSocket | ConnectedUDPSocket:
+        transport, protocol = await get_running_loop().create_datagram_endpoint(
+            DatagramProtocol,
+            local_addr=local_address,
+            remote_addr=remote_address,
+            family=family,
+            reuse_port=reuse_port,
+        )
+        if protocol.exception:
+            transport.close()
+            raise protocol.exception
+
+        if not remote_address:
+            return UDPSocket(transport, protocol)
+        else:
+            return ConnectedUDPSocket(transport, protocol)
+
+    @classmethod
+    async def create_unix_datagram_socket(  # type: ignore[override]
+        cls, raw_socket: socket.socket, remote_path: str | bytes | None
+    ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket:
+        await cls.checkpoint()
+        loop = get_running_loop()
+
+        if remote_path:
+            while True:
+                try:
+                    raw_socket.connect(remote_path)
+                except BlockingIOError:
+                    f: asyncio.Future = asyncio.Future()
+                    loop.add_writer(raw_socket, f.set_result, None)
+                    f.add_done_callback(lambda _: loop.remove_writer(raw_socket))
+                    await f
+                except BaseException:
+                    raw_socket.close()
+                    raise
+                else:
+                    return ConnectedUNIXDatagramSocket(raw_socket)
+        else:
+            return UNIXDatagramSocket(raw_socket)
+
+    @classmethod
+    async def getaddrinfo(
+        cls,
+        host: bytes | str | None,
+        port: str | int | None,
+        *,
+        family: int | AddressFamily = 0,
+        type: int | SocketKind = 0,
+        proto: int = 0,
+        flags: int = 0,
+    ) -> Sequence[
+        tuple[
+            AddressFamily,
+            SocketKind,
+            int,
+            str,
+            tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes],
+        ]
+    ]:
+        return await get_running_loop().getaddrinfo(
+            host, port, family=family, type=type, proto=proto, flags=flags
+        )
+
+    @classmethod
+    async def getnameinfo(
+        cls, sockaddr: IPSockAddrType, flags: int = 0
+    ) -> tuple[str, str]:
+        return await get_running_loop().getnameinfo(sockaddr, flags)
+
+    @classmethod
+    async def wait_readable(cls, obj: FileDescriptorLike) -> None:
+        try:
+            read_events = _read_events.get()
+        except LookupError:
+            read_events = {}
+            _read_events.set(read_events)
+
+        fd = obj if isinstance(obj, int) else obj.fileno()
+        if read_events.get(fd):
+            raise BusyResourceError("reading from")
+
+        loop = get_running_loop()
+        fut: asyncio.Future[bool] = loop.create_future()
+
+        def cb() -> None:
+            try:
+                del read_events[fd]
+            except KeyError:
+                pass
+            else:
+                remove_reader(fd)
+
+            try:
+                fut.set_result(True)
+            except asyncio.InvalidStateError:
+                pass
+
+        try:
+            loop.add_reader(fd, cb)
+        except NotImplementedError:
+            from anyio._core._asyncio_selector_thread import get_selector
+
+            selector = get_selector()
+            selector.add_reader(fd, cb)
+            remove_reader = selector.remove_reader
+        else:
+            remove_reader = loop.remove_reader
+
+        read_events[fd] = fut
+        try:
+            success = await fut
+        finally:
+            try:
+                del read_events[fd]
+            except KeyError:
+                pass
+            else:
+                remove_reader(fd)
+
+        if not success:
+            raise ClosedResourceError
+
+    @classmethod
+    async def wait_writable(cls, obj: FileDescriptorLike) -> None:
+        try:
+            write_events = _write_events.get()
+        except LookupError:
+            write_events = {}
+            _write_events.set(write_events)
+
+        fd = obj if isinstance(obj, int) else obj.fileno()
+        if write_events.get(fd):
+            raise BusyResourceError("writing to")
+
+        loop = get_running_loop()
+        fut: asyncio.Future[bool] = loop.create_future()
+
+        def cb() -> None:
+            try:
+                del write_events[fd]
+            except KeyError:
+                pass
+            else:
+                remove_writer(fd)
+
+            try:
+                fut.set_result(True)
+            except asyncio.InvalidStateError:
+                pass
+
+        try:
+            loop.add_writer(fd, cb)
+        except NotImplementedError:
+            from anyio._core._asyncio_selector_thread import get_selector
+
+            selector = get_selector()
+            selector.add_writer(fd, cb)
+            remove_writer = selector.remove_writer
+        else:
+            remove_writer = loop.remove_writer
+
+        write_events[fd] = fut
+        try:
+            success = await fut
+        finally:
+            try:
+                del write_events[fd]
+            except KeyError:
+                pass
+            else:
+                remove_writer(fd)
+
+        if not success:
+            raise ClosedResourceError
+
+    @classmethod
+    def notify_closing(cls, obj: FileDescriptorLike) -> None:
+        fd = obj if isinstance(obj, int) else obj.fileno()
+        loop = get_running_loop()
+
+        try:
+            write_events = _write_events.get()
+        except LookupError:
+            pass
+        else:
+            try:
+                fut = write_events.pop(fd)
+            except KeyError:
+                pass
+            else:
+                try:
+                    fut.set_result(False)
+                except asyncio.InvalidStateError:
+                    pass
+
+                try:
+                    loop.remove_writer(fd)
+                except NotImplementedError:
+                    from anyio._core._asyncio_selector_thread import get_selector
+
+                    get_selector().remove_writer(fd)
+
+        try:
+            read_events = _read_events.get()
+        except LookupError:
+            pass
+        else:
+            try:
+                fut = read_events.pop(fd)
+            except KeyError:
+                pass
+            else:
+                try:
+                    fut.set_result(False)
+                except asyncio.InvalidStateError:
+                    pass
+
+                try:
+                    loop.remove_reader(fd)
+                except NotImplementedError:
+                    from anyio._core._asyncio_selector_thread import get_selector
+
+                    get_selector().remove_reader(fd)
+
+    @classmethod
+    async def wrap_listener_socket(cls, sock: socket.socket) -> SocketListener:
+        if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX:
+            return UNIXSocketListener(sock)
+
+        return TCPSocketListener(sock)
+
+    @classmethod
+    async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream:
+        transport, protocol = await get_running_loop().create_connection(
+            StreamProtocol, sock=sock
+        )
+        return SocketStream(transport, protocol)
+
+    @classmethod
+    async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream:
+        return UNIXSocketStream(sock)
+
+    @classmethod
+    async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket:
+        transport, protocol = await get_running_loop().create_datagram_endpoint(
+            DatagramProtocol, sock=sock
+        )
+        return UDPSocket(transport, protocol)
+
+    @classmethod
+    async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket:
+        transport, protocol = await get_running_loop().create_datagram_endpoint(
+            DatagramProtocol, sock=sock
+        )
+        return ConnectedUDPSocket(transport, protocol)
+
+    @classmethod
+    async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket:
+        return UNIXDatagramSocket(sock)
+
+    @classmethod
+    async def wrap_connected_unix_datagram_socket(
+        cls, sock: socket.socket
+    ) -> ConnectedUNIXDatagramSocket:
+        return ConnectedUNIXDatagramSocket(sock)
+
+    @classmethod
+    def current_default_thread_limiter(cls) -> CapacityLimiter:
+        try:
+            return _default_thread_limiter.get()
+        except LookupError:
+            limiter = CapacityLimiter(40)
+            _default_thread_limiter.set(limiter)
+            return limiter
+
+    @classmethod
+    def open_signal_receiver(
+        cls, *signals: Signals
+    ) -> AbstractContextManager[AsyncIterator[Signals]]:
+        return _SignalReceiver(signals)
+
+    @classmethod
+    def get_current_task(cls) -> TaskInfo:
+        return AsyncIOTaskInfo(current_task())  # type: ignore[arg-type]
+
+    @classmethod
+    def get_running_tasks(cls) -> Sequence[TaskInfo]:
+        return [AsyncIOTaskInfo(task) for task in all_tasks() if not task.done()]
+
+    @classmethod
+    async def wait_all_tasks_blocked(cls) -> None:
+        await cls.checkpoint()
+        this_task = current_task()
+        while True:
+            for task in all_tasks():
+                if task is this_task:
+                    continue
+
+                waiter = task._fut_waiter  # type: ignore[attr-defined]
+                if waiter is None or waiter.done():
+                    await sleep(0.1)
+                    break
+            else:
+                return
+
+    @classmethod
+    def create_test_runner(cls, options: dict[str, Any]) -> TestRunner:
+        return TestRunner(**options)
+
+
+backend_class = AsyncIOBackend

+ 1468 - 0
vendor/anyio/_backends/_trio.py

@@ -0,0 +1,1468 @@
+from __future__ import annotations
+
+import array
+import math
+import os
+import socket
+import sys
+import types
+import weakref
+from collections.abc import (
+    AsyncGenerator,
+    AsyncIterator,
+    Awaitable,
+    Callable,
+    Collection,
+    Coroutine,
+    Iterable,
+    Sequence,
+)
+from contextlib import AbstractContextManager
+from contextvars import Context
+from dataclasses import dataclass
+from functools import partial, wraps
+from io import IOBase
+from os import PathLike
+from signal import Signals
+from socket import AddressFamily, SocketKind
+from types import TracebackType
+from typing import (
+    IO,
+    TYPE_CHECKING,
+    Any,
+    Generic,
+    Literal,
+    NoReturn,
+    ParamSpec,
+    TypeVar,
+    cast,
+    overload,
+)
+
+import trio.from_thread
+import trio.lowlevel
+from outcome import Error, Outcome, Value
+from trio.lowlevel import (
+    current_root_task,
+    current_task,
+    notify_closing,
+    wait_readable,
+    wait_writable,
+)
+from trio.socket import SocketType as TrioSocketType
+from trio.to_thread import run_sync
+
+from .. import (
+    CapacityLimiterStatistics,
+    EventStatistics,
+    LockStatistics,
+    RunFinishedError,
+    TaskInfo,
+    WouldBlock,
+    abc,
+)
+from .._core._eventloop import claim_worker_thread
+from .._core._exceptions import (
+    BrokenResourceError,
+    BusyResourceError,
+    ClosedResourceError,
+    EndOfStream,
+)
+from .._core._sockets import convert_ipv6_sockaddr
+from .._core._streams import create_memory_object_stream
+from .._core._synchronization import (
+    CapacityLimiter as BaseCapacityLimiter,
+)
+from .._core._synchronization import Event as BaseEvent
+from .._core._synchronization import Lock as BaseLock
+from .._core._synchronization import (
+    ResourceGuard,
+    SemaphoreStatistics,
+)
+from .._core._synchronization import Semaphore as BaseSemaphore
+from .._core._tasks import CancelScope as BaseCancelScope
+from .._core._tasks import TaskHandle
+from ..abc import IPSockAddrType, UDPPacketType, UNIXDatagramPacketType
+from ..abc._eventloop import AsyncBackend, StrOrBytesPath
+from ..abc._tasks import T_contra, call_for_coroutine, get_callable_name
+from ..streams.memory import MemoryObjectSendStream
+
+if TYPE_CHECKING:
+    from _typeshed import FileDescriptorLike
+
+if sys.version_info >= (3, 11):
+    from typing import TypeVarTuple, Unpack
+else:
+    from exceptiongroup import BaseExceptionGroup
+    from typing_extensions import TypeVarTuple, Unpack
+
+T = TypeVar("T")
+T_Retval = TypeVar("T_Retval")
+T_co = TypeVar("T_co", covariant=True)
+T_SockAddr = TypeVar("T_SockAddr", str, IPSockAddrType)
+PosArgsT = TypeVarTuple("PosArgsT")
+P = ParamSpec("P")
+
+
+def ensure_returns_coro(
+    func: Callable[P, Awaitable[T_Retval]],
+) -> Callable[P, Coroutine[Any, Any, T_Retval]]:
+    @wraps(func)
+    def wrapper(*args: P.args, **kwargs: P.kwargs) -> Coroutine[Any, Any, T_Retval]:
+        awaitable = func(*args, **kwargs)
+        # Check the common case first.
+        if isinstance(awaitable, Coroutine):
+            return awaitable
+        elif not isinstance(awaitable, Awaitable):
+            # The user violated the type annotations. Still, we should pass this on to
+            # Trio so it can raise with an appropriate message.
+            return awaitable
+        else:
+
+            @wraps(func)
+            async def inner_wrapper() -> T_Retval:
+                return await awaitable
+
+            return inner_wrapper()
+
+    return wrapper
+
+
+#
+# Event loop
+#
+
+RunVar = trio.lowlevel.RunVar
+
+
+#
+# Timeouts and cancellation
+#
+
+
+class CancelScope(BaseCancelScope):
+    __slots__ = ("__original",)
+
+    def __new__(
+        cls, original: trio.CancelScope | None = None, **kwargs: object
+    ) -> CancelScope:
+        return object.__new__(cls)
+
+    def __init__(self, original: trio.CancelScope | None = None, **kwargs: Any) -> None:
+        self.__original = original or trio.CancelScope(**kwargs)
+
+    def __enter__(self) -> CancelScope:
+        self.__original.__enter__()
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> bool:
+        return self.__original.__exit__(exc_type, exc_val, exc_tb)
+
+    def cancel(self, reason: str | None = None) -> None:
+        self.__original.cancel(reason)
+
+    @property
+    def deadline(self) -> float:
+        return self.__original.deadline
+
+    @deadline.setter
+    def deadline(self, value: float) -> None:
+        self.__original.deadline = value
+
+    @property
+    def cancel_called(self) -> bool:
+        return self.__original.cancel_called
+
+    @property
+    def cancelled_caught(self) -> bool:
+        return self.__original.cancelled_caught
+
+    @property
+    def shield(self) -> bool:
+        return self.__original.shield
+
+    @shield.setter
+    def shield(self, value: bool) -> None:
+        self.__original.shield = value
+
+
+#
+# Task groups
+#
+
+empty_start_value = object()
+
+
+class _TrioTaskStatus(Generic[T_contra], abc.TaskStatus[T_contra]):
+    early_start_value: T_contra | object = empty_start_value
+    real_task_status: trio.TaskStatus[T_contra | None] | None = None
+
+    def started(self, value: T_contra | None = None) -> None:
+        if self.real_task_status is None:
+            if self.early_start_value is not empty_start_value:
+                raise RuntimeError("called 'started' twice on the same task status")
+
+            self.early_start_value = value
+        else:
+            self.real_task_status.started(value)
+
+
+class TaskGroup(abc.TaskGroup):
+    def __init__(self) -> None:
+        self._entered = False
+        self._active = False
+        self._nursery_manager = trio.open_nursery(strict_exception_groups=True)
+        self.cancel_scope = None  # type: ignore[assignment]
+
+    async def __aenter__(self) -> TaskGroup:
+        if self._entered:
+            raise RuntimeError("TaskGroup cannot be entered more than once")
+
+        self._entered = True
+        self._active = True
+        self._nursery = await self._nursery_manager.__aenter__()
+        self.cancel_scope = CancelScope(self._nursery.cancel_scope)
+        return self
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> bool:
+        try:
+            # trio.Nursery.__exit__ returns bool; .open_nursery has wrong type
+            return await self._nursery_manager.__aexit__(exc_type, exc_val, exc_tb)  # type: ignore[return-value]
+        except BaseExceptionGroup as exc:
+            if not exc.split(trio.Cancelled)[1]:
+                raise trio.Cancelled._create() from exc
+
+            raise
+        finally:
+            del exc_val, exc_tb
+            self._active = False
+
+    def _check_active(self, coro: Coroutine | None = None) -> None:
+        if not self._active:
+            if coro is not None:
+                coro.close()
+
+            raise RuntimeError(
+                "This task group is not active; no new tasks can be started."
+            )
+
+    def create_task(
+        self,
+        coro: Coroutine[Any, Any, T_co],
+        *,
+        name: object = None,
+        context: Context | None = None,
+    ) -> TaskHandle[T_co]:
+        if not isinstance(coro, Coroutine):
+            raise TypeError(f"expected a coroutine, got {coro.__class__.__qualname__}")
+
+        self._check_active(coro)
+        handle = TaskHandle(coro, name)
+        if context is not None:
+            context.run(
+                partial(self._nursery.start_soon, handle._run_coro, name=handle.name)
+            )
+        else:
+            self._nursery.start_soon(handle._run_coro, name=handle.name)
+
+        return handle
+
+    async def start(
+        self,
+        func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
+        *args: Unpack[PosArgsT],
+        name: object = None,
+        return_handle: Literal[False] | Literal[True] = False,
+    ) -> Any:
+        handle: TaskHandle[T_co]
+
+        async def run_coro_with_task_status(
+            *, task_status: trio.TaskStatus[Any]
+        ) -> None:
+            nonlocal handle
+            wrapper_task_status = _TrioTaskStatus()
+            coro = call_for_coroutine(func, args, task_status=wrapper_task_status)
+            if wrapper_task_status.early_start_value is not empty_start_value:
+                task_status.started(wrapper_task_status.early_start_value)
+            else:
+                wrapper_task_status.real_task_status = task_status
+
+            handle = TaskHandle(coro, name)
+            await handle._run_coro()
+
+        self._check_active()
+        final_name = get_callable_name(func, name)
+        start_value = await self._nursery.start(
+            run_coro_with_task_status, name=final_name
+        )
+        if return_handle:
+            handle._start_value = start_value
+            return handle
+        else:
+            return start_value
+
+
+#
+# Subprocesses
+#
+
+
+@dataclass(eq=False)
+class ReceiveStreamWrapper(abc.ByteReceiveStream):
+    _stream: trio.abc.ReceiveStream
+
+    async def receive(self, max_bytes: int | None = None) -> bytes:
+        if max_bytes is not None and max_bytes < 1:
+            raise ValueError("max_bytes must be a positive integer")
+
+        try:
+            data = await self._stream.receive_some(max_bytes)
+        except trio.ClosedResourceError as exc:
+            raise ClosedResourceError from exc.__cause__
+        except trio.BrokenResourceError as exc:
+            raise BrokenResourceError from exc.__cause__
+
+        if data:
+            return bytes(data)
+        else:
+            raise EndOfStream
+
+    async def aclose(self) -> None:
+        await self._stream.aclose()
+
+
+@dataclass(eq=False)
+class SendStreamWrapper(abc.ByteSendStream):
+    _stream: trio.abc.SendStream
+
+    async def send(self, item: bytes) -> None:
+        try:
+            await self._stream.send_all(item)
+        except trio.ClosedResourceError as exc:
+            raise ClosedResourceError from exc.__cause__
+        except trio.BrokenResourceError as exc:
+            raise BrokenResourceError from exc.__cause__
+
+    async def aclose(self) -> None:
+        await self._stream.aclose()
+
+
+@dataclass(eq=False)
+class Process(abc.Process):
+    _process: trio.Process
+    _stdin: abc.ByteSendStream | None
+    _stdout: abc.ByteReceiveStream | None
+    _stderr: abc.ByteReceiveStream | None
+
+    async def aclose(self) -> None:
+        with CancelScope(shield=True):
+            if self._stdin:
+                await self._stdin.aclose()
+            if self._stdout:
+                await self._stdout.aclose()
+            if self._stderr:
+                await self._stderr.aclose()
+
+        try:
+            await self.wait()
+        except BaseException:
+            self.kill()
+            with CancelScope(shield=True):
+                await self.wait()
+            raise
+
+    async def wait(self) -> int:
+        return await self._process.wait()
+
+    def terminate(self) -> None:
+        self._process.terminate()
+
+    def kill(self) -> None:
+        self._process.kill()
+
+    def send_signal(self, signal: Signals) -> None:
+        self._process.send_signal(signal)
+
+    @property
+    def pid(self) -> int:
+        return self._process.pid
+
+    @property
+    def returncode(self) -> int | None:
+        return self._process.returncode
+
+    @property
+    def stdin(self) -> abc.ByteSendStream | None:
+        return self._stdin
+
+    @property
+    def stdout(self) -> abc.ByteReceiveStream | None:
+        return self._stdout
+
+    @property
+    def stderr(self) -> abc.ByteReceiveStream | None:
+        return self._stderr
+
+
+class _ProcessPoolShutdownInstrument(trio.abc.Instrument):
+    def after_run(self) -> None:
+        super().after_run()
+
+
+current_default_worker_process_limiter: trio.lowlevel.RunVar = RunVar(
+    "current_default_worker_process_limiter"
+)
+
+
+async def _shutdown_process_pool(workers: set[abc.Process]) -> None:
+    try:
+        await trio.sleep(math.inf)
+    except trio.Cancelled:
+        for process in workers:
+            if process.returncode is None:
+                process.kill()
+
+        with CancelScope(shield=True):
+            for process in workers:
+                await process.aclose()
+
+
+#
+# Sockets and networking
+#
+
+
+class _TrioSocketMixin(Generic[T_SockAddr]):
+    def __init__(self, trio_socket: TrioSocketType) -> None:
+        self._trio_socket = trio_socket
+        self._closed = False
+
+    def _check_closed(self) -> None:
+        if self._closed:
+            raise ClosedResourceError
+        if self._trio_socket.fileno() < 0:
+            raise BrokenResourceError
+
+    @property
+    def _raw_socket(self) -> socket.socket:
+        return self._trio_socket._sock  # type: ignore[attr-defined]
+
+    async def aclose(self) -> None:
+        if self._trio_socket.fileno() >= 0:
+            self._closed = True
+            self._trio_socket.close()
+
+    def _convert_socket_error(self, exc: BaseException) -> NoReturn:
+        if isinstance(exc, trio.ClosedResourceError):
+            raise ClosedResourceError from exc
+        elif self._trio_socket.fileno() < 0 and self._closed:
+            raise ClosedResourceError from None
+        elif isinstance(exc, OSError):
+            raise BrokenResourceError from exc
+        else:
+            raise exc
+
+
+class SocketStream(_TrioSocketMixin, abc.SocketStream):
+    def __init__(self, trio_socket: TrioSocketType) -> None:
+        super().__init__(trio_socket)
+        self._receive_guard = ResourceGuard("reading from")
+        self._send_guard = ResourceGuard("writing to")
+
+    async def receive(self, max_bytes: int = 65536) -> bytes:
+        if max_bytes < 1:
+            raise ValueError("max_bytes must be a positive integer")
+
+        with self._receive_guard:
+            try:
+                data = await self._trio_socket.recv(max_bytes)
+            except BaseException as exc:
+                self._convert_socket_error(exc)
+
+            if data:
+                return data
+            else:
+                raise EndOfStream
+
+    async def send(self, item: bytes) -> None:
+        with self._send_guard:
+            view = memoryview(item)
+            while view:
+                try:
+                    bytes_sent = await self._trio_socket.send(view)
+                except BaseException as exc:
+                    self._convert_socket_error(exc)
+
+                view = view[bytes_sent:]
+
+    async def send_eof(self) -> None:
+        self._trio_socket.shutdown(socket.SHUT_WR)
+
+
+class UNIXSocketStream(SocketStream, abc.UNIXSocketStream):
+    async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]:
+        if not isinstance(msglen, int) or msglen < 0:
+            raise ValueError("msglen must be a non-negative integer")
+        if not isinstance(maxfds, int) or maxfds < 1:
+            raise ValueError("maxfds must be a positive integer")
+
+        fds = array.array("i")
+        await trio.lowlevel.checkpoint()
+        with self._receive_guard:
+            while True:
+                try:
+                    message, ancdata, flags, addr = await self._trio_socket.recvmsg(
+                        msglen, socket.CMSG_LEN(maxfds * fds.itemsize)
+                    )
+                except BaseException as exc:
+                    self._convert_socket_error(exc)
+                else:
+                    if not message and not ancdata:
+                        raise EndOfStream
+
+                    break
+
+        for cmsg_level, cmsg_type, cmsg_data in ancdata:
+            if cmsg_level != socket.SOL_SOCKET or cmsg_type != socket.SCM_RIGHTS:
+                raise RuntimeError(
+                    f"Received unexpected ancillary data; message = {message!r}, "
+                    f"cmsg_level = {cmsg_level}, cmsg_type = {cmsg_type}"
+                )
+
+            fds.frombytes(cmsg_data[: len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
+
+        return message, list(fds)
+
+    async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None:
+        if not message:
+            raise ValueError("message must not be empty")
+        if not fds:
+            raise ValueError("fds must not be empty")
+
+        filenos: list[int] = []
+        for fd in fds:
+            if isinstance(fd, int):
+                filenos.append(fd)
+            elif isinstance(fd, IOBase):
+                filenos.append(fd.fileno())
+
+        fdarray = array.array("i", filenos)
+        await trio.lowlevel.checkpoint()
+        with self._send_guard:
+            while True:
+                try:
+                    await self._trio_socket.sendmsg(
+                        [message],
+                        [
+                            (
+                                socket.SOL_SOCKET,
+                                socket.SCM_RIGHTS,
+                                fdarray,
+                            )
+                        ],
+                    )
+                    break
+                except BaseException as exc:
+                    self._convert_socket_error(exc)
+
+
+class TCPSocketListener(_TrioSocketMixin, abc.SocketListener):
+    def __init__(self, raw_socket: socket.socket):
+        super().__init__(trio.socket.from_stdlib_socket(raw_socket))
+        self._accept_guard = ResourceGuard("accepting connections from")
+
+    async def accept(self) -> SocketStream:
+        with self._accept_guard:
+            try:
+                trio_socket, _addr = await self._trio_socket.accept()
+            except BaseException as exc:
+                self._convert_socket_error(exc)
+
+        trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+        return SocketStream(trio_socket)
+
+
+class UNIXSocketListener(_TrioSocketMixin, abc.SocketListener):
+    def __init__(self, raw_socket: socket.socket):
+        super().__init__(trio.socket.from_stdlib_socket(raw_socket))
+        self._accept_guard = ResourceGuard("accepting connections from")
+
+    async def accept(self) -> UNIXSocketStream:
+        with self._accept_guard:
+            try:
+                trio_socket, _addr = await self._trio_socket.accept()
+            except BaseException as exc:
+                self._convert_socket_error(exc)
+
+        return UNIXSocketStream(trio_socket)
+
+
+class UDPSocket(_TrioSocketMixin[IPSockAddrType], abc.UDPSocket):
+    def __init__(self, trio_socket: TrioSocketType) -> None:
+        super().__init__(trio_socket)
+        self._receive_guard = ResourceGuard("reading from")
+        self._send_guard = ResourceGuard("writing to")
+
+    async def receive(self) -> tuple[bytes, IPSockAddrType]:
+        with self._receive_guard:
+            try:
+                data, addr = await self._trio_socket.recvfrom(65536)
+                return data, convert_ipv6_sockaddr(addr)
+            except BaseException as exc:
+                self._convert_socket_error(exc)
+
+    async def send(self, item: UDPPacketType) -> None:
+        with self._send_guard:
+            try:
+                await self._trio_socket.sendto(*item)
+            except BaseException as exc:
+                self._convert_socket_error(exc)
+
+
+class ConnectedUDPSocket(_TrioSocketMixin[IPSockAddrType], abc.ConnectedUDPSocket):
+    def __init__(self, trio_socket: TrioSocketType) -> None:
+        super().__init__(trio_socket)
+        self._receive_guard = ResourceGuard("reading from")
+        self._send_guard = ResourceGuard("writing to")
+
+    async def receive(self) -> bytes:
+        with self._receive_guard:
+            try:
+                return await self._trio_socket.recv(65536)
+            except BaseException as exc:
+                self._convert_socket_error(exc)
+
+    async def send(self, item: bytes) -> None:
+        with self._send_guard:
+            try:
+                await self._trio_socket.send(item)
+            except BaseException as exc:
+                self._convert_socket_error(exc)
+
+
+class UNIXDatagramSocket(_TrioSocketMixin[str], abc.UNIXDatagramSocket):
+    def __init__(self, trio_socket: TrioSocketType) -> None:
+        super().__init__(trio_socket)
+        self._receive_guard = ResourceGuard("reading from")
+        self._send_guard = ResourceGuard("writing to")
+
+    async def receive(self) -> UNIXDatagramPacketType:
+        with self._receive_guard:
+            try:
+                data, addr = await self._trio_socket.recvfrom(65536)
+                return data, addr
+            except BaseException as exc:
+                self._convert_socket_error(exc)
+
+    async def send(self, item: UNIXDatagramPacketType) -> None:
+        with self._send_guard:
+            try:
+                await self._trio_socket.sendto(*item)
+            except BaseException as exc:
+                self._convert_socket_error(exc)
+
+
+class ConnectedUNIXDatagramSocket(
+    _TrioSocketMixin[str], abc.ConnectedUNIXDatagramSocket
+):
+    def __init__(self, trio_socket: TrioSocketType) -> None:
+        super().__init__(trio_socket)
+        self._receive_guard = ResourceGuard("reading from")
+        self._send_guard = ResourceGuard("writing to")
+
+    async def receive(self) -> bytes:
+        with self._receive_guard:
+            try:
+                return await self._trio_socket.recv(65536)
+            except BaseException as exc:
+                self._convert_socket_error(exc)
+
+    async def send(self, item: bytes) -> None:
+        with self._send_guard:
+            try:
+                await self._trio_socket.send(item)
+            except BaseException as exc:
+                self._convert_socket_error(exc)
+
+
+#
+# Synchronization
+#
+
+
+class Event(BaseEvent):
+    __slots__ = ("__original",)
+
+    def __new__(cls) -> Event:
+        return object.__new__(cls)
+
+    def __init__(self) -> None:
+        self.__original = trio.Event()
+
+    def is_set(self) -> bool:
+        return self.__original.is_set()
+
+    async def wait(self) -> None:
+        return await self.__original.wait()
+
+    def statistics(self) -> EventStatistics:
+        orig_statistics = self.__original.statistics()
+        return EventStatistics(tasks_waiting=orig_statistics.tasks_waiting)
+
+    def set(self) -> None:
+        self.__original.set()
+
+
+class Lock(BaseLock):
+    __slots__ = "_fast_acquire", "__original"
+
+    def __new__(cls, *, fast_acquire: bool = False) -> Lock:
+        return object.__new__(cls)
+
+    def __init__(self, *, fast_acquire: bool = False) -> None:
+        self._fast_acquire = fast_acquire
+        self.__original = trio.Lock()
+
+    @staticmethod
+    def _convert_runtime_error_msg(exc: RuntimeError) -> None:
+        if exc.args == ("attempt to re-acquire an already held Lock",):
+            exc.args = ("Attempted to acquire an already held Lock",)
+
+    async def acquire(self) -> None:
+        if not self._fast_acquire:
+            try:
+                await self.__original.acquire()
+            except RuntimeError as exc:
+                self._convert_runtime_error_msg(exc)
+                raise
+
+            return
+
+        # This is the "fast path" where we don't let other tasks run
+        await trio.lowlevel.checkpoint_if_cancelled()
+        try:
+            self.__original.acquire_nowait()
+        except trio.WouldBlock:
+            await self.__original._lot.park()
+        except RuntimeError as exc:
+            self._convert_runtime_error_msg(exc)
+            raise
+
+    def acquire_nowait(self) -> None:
+        try:
+            self.__original.acquire_nowait()
+        except trio.WouldBlock:
+            raise WouldBlock from None
+        except RuntimeError as exc:
+            self._convert_runtime_error_msg(exc)
+            raise
+
+    def locked(self) -> bool:
+        return self.__original.locked()
+
+    def release(self) -> None:
+        self.__original.release()
+
+    def statistics(self) -> LockStatistics:
+        orig_statistics = self.__original.statistics()
+        owner = TrioTaskInfo(orig_statistics.owner) if orig_statistics.owner else None
+        return LockStatistics(
+            orig_statistics.locked, owner, orig_statistics.tasks_waiting
+        )
+
+
+class Semaphore(BaseSemaphore):
+    __slots__ = ("__original",)
+
+    def __new__(
+        cls,
+        initial_value: int,
+        *,
+        max_value: int | None = None,
+        fast_acquire: bool = False,
+    ) -> Semaphore:
+        return object.__new__(cls)
+
+    def __init__(
+        self,
+        initial_value: int,
+        *,
+        max_value: int | None = None,
+        fast_acquire: bool = False,
+    ) -> None:
+        super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire)
+        self.__original = trio.Semaphore(initial_value, max_value=max_value)
+
+    async def acquire(self) -> None:
+        if not self._fast_acquire:
+            await self.__original.acquire()
+            return
+
+        # This is the "fast path" where we don't let other tasks run
+        await trio.lowlevel.checkpoint_if_cancelled()
+        try:
+            self.__original.acquire_nowait()
+        except trio.WouldBlock:
+            await self.__original._lot.park()
+
+    def acquire_nowait(self) -> None:
+        try:
+            self.__original.acquire_nowait()
+        except trio.WouldBlock:
+            raise WouldBlock from None
+
+    @property
+    def max_value(self) -> int | None:
+        return self.__original.max_value
+
+    @property
+    def value(self) -> int:
+        return self.__original.value
+
+    def release(self) -> None:
+        self.__original.release()
+
+    def statistics(self) -> SemaphoreStatistics:
+        orig_statistics = self.__original.statistics()
+        return SemaphoreStatistics(orig_statistics.tasks_waiting)
+
+
+class CapacityLimiter(BaseCapacityLimiter):
+    __slots__ = ("__original",)
+
+    def __new__(
+        cls,
+        total_tokens: float | None = None,
+        *,
+        original: trio.CapacityLimiter | None = None,
+    ) -> CapacityLimiter:
+        return object.__new__(cls)
+
+    def __init__(
+        self,
+        total_tokens: float | None = None,
+        *,
+        original: trio.CapacityLimiter | None = None,
+    ) -> None:
+        if original is not None:
+            self.__original = original
+        else:
+            assert total_tokens is not None
+            self.__original = trio.CapacityLimiter(total_tokens)
+
+    async def __aenter__(self) -> None:
+        return await self.__original.__aenter__()
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        await self.__original.__aexit__(exc_type, exc_val, exc_tb)
+
+    @property
+    def total_tokens(self) -> float:
+        return self.__original.total_tokens
+
+    @total_tokens.setter
+    def total_tokens(self, value: float) -> None:
+        self.__original.total_tokens = value
+
+    @property
+    def borrowed_tokens(self) -> int:
+        return self.__original.borrowed_tokens
+
+    @property
+    def available_tokens(self) -> float:
+        return self.__original.available_tokens
+
+    def acquire_nowait(self) -> None:
+        try:
+            self.__original.acquire_nowait()
+        except trio.WouldBlock:
+            raise WouldBlock from None
+
+    def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
+        try:
+            self.__original.acquire_on_behalf_of_nowait(borrower)
+        except trio.WouldBlock:
+            raise WouldBlock from None
+
+    async def acquire(self) -> None:
+        await self.__original.acquire()
+
+    async def acquire_on_behalf_of(self, borrower: object) -> None:
+        await self.__original.acquire_on_behalf_of(borrower)
+
+    def release(self) -> None:
+        return self.__original.release()
+
+    def release_on_behalf_of(self, borrower: object) -> None:
+        return self.__original.release_on_behalf_of(borrower)
+
+    def statistics(self) -> CapacityLimiterStatistics:
+        orig = self.__original.statistics()
+        return CapacityLimiterStatistics(
+            borrowed_tokens=orig.borrowed_tokens,
+            total_tokens=orig.total_tokens,
+            borrowers=tuple(orig.borrowers),
+            tasks_waiting=orig.tasks_waiting,
+        )
+
+
+_capacity_limiter_wrapper: trio.lowlevel.RunVar = RunVar("_capacity_limiter_wrapper")
+
+
+#
+# Signal handling
+#
+
+
+class _SignalReceiver:
+    _iterator: AsyncIterator[int]
+
+    def __init__(self, signals: tuple[Signals, ...]):
+        self._signals = signals
+
+    def __enter__(self) -> _SignalReceiver:
+        self._cm = trio.open_signal_receiver(*self._signals)
+        self._iterator = self._cm.__enter__()
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> bool | None:
+        return self._cm.__exit__(exc_type, exc_val, exc_tb)
+
+    def __aiter__(self) -> _SignalReceiver:
+        return self
+
+    async def __anext__(self) -> Signals:
+        signum = await self._iterator.__anext__()
+        return Signals(signum)
+
+
+#
+# Testing and debugging
+#
+
+
+class TestRunner(abc.TestRunner):
+    def __init__(self, **options: Any) -> None:
+        from queue import Queue
+
+        self._call_queue: Queue[Callable[[], object]] = Queue()
+        self._send_stream: (
+            MemoryObjectSendStream[tuple[Awaitable[Any], list[Outcome]]] | None
+        ) = None
+        self._options = options
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: types.TracebackType | None,
+    ) -> None:
+        if self._send_stream:
+            self._send_stream.close()
+            while self._send_stream is not None:
+                self._call_queue.get()()
+
+    def is_running(self) -> bool:
+        return trio.lowlevel.in_trio_task()
+
+    async def _run_tests_and_fixtures(self) -> None:
+        self._send_stream, receive_stream = create_memory_object_stream[
+            tuple[Awaitable[Any], list[Outcome]]
+        ](1)
+        with receive_stream:
+            async for awaitable, outcome_holder in receive_stream:
+                try:
+                    retval = await awaitable
+                except BaseException as exc:
+                    outcome_holder.append(Error(exc))
+                else:
+                    outcome_holder.append(Value(retval))
+
+    def _main_task_finished(self, outcome: object) -> None:
+        self._send_stream = None
+
+    def _call_in_runner_task(
+        self,
+        func: Callable[P, Awaitable[T_Retval]],
+        /,
+        *args: P.args,
+        **kwargs: P.kwargs,
+    ) -> T_Retval:
+        if self._send_stream is None:
+            trio.lowlevel.start_guest_run(
+                self._run_tests_and_fixtures,
+                run_sync_soon_threadsafe=self._call_queue.put,
+                done_callback=self._main_task_finished,
+                **self._options,
+            )
+            while self._send_stream is None:
+                self._call_queue.get()()
+
+        outcome_holder: list[Outcome] = []
+        self._send_stream.send_nowait((func(*args, **kwargs), outcome_holder))
+        while not outcome_holder:
+            self._call_queue.get()()
+
+        return outcome_holder[0].unwrap()
+
+    def run_asyncgen_fixture(
+        self,
+        fixture_func: Callable[..., AsyncGenerator[T_Retval, Any]],
+        kwargs: dict[str, Any],
+    ) -> Iterable[T_Retval]:
+        asyncgen = fixture_func(**kwargs)
+        fixturevalue: T_Retval = self._call_in_runner_task(asyncgen.asend, None)
+
+        yield fixturevalue
+
+        try:
+            self._call_in_runner_task(asyncgen.asend, None)
+        except StopAsyncIteration:
+            pass
+        else:
+            self._call_in_runner_task(asyncgen.aclose)
+            raise RuntimeError("Async generator fixture did not stop")
+
+    def run_fixture(
+        self,
+        fixture_func: Callable[..., Coroutine[Any, Any, T_Retval]],
+        kwargs: dict[str, Any],
+    ) -> T_Retval:
+        return self._call_in_runner_task(fixture_func, **kwargs)
+
+    def run_test(
+        self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any]
+    ) -> None:
+        self._call_in_runner_task(test_func, **kwargs)
+
+
+class TrioTaskInfo(TaskInfo):
+    def __init__(self, task: trio.lowlevel.Task):
+        parent_id = None
+        if task.parent_nursery and task.parent_nursery.parent_task:
+            parent_id = id(task.parent_nursery.parent_task)
+
+        super().__init__(id(task), parent_id, task.name, task.coro)
+        self._task = weakref.proxy(task)
+
+    def has_pending_cancellation(self) -> bool:
+        try:
+            return self._task._cancel_status.effectively_cancelled
+        except ReferenceError:
+            # If the task is no longer around, it surely doesn't have a cancellation
+            # pending
+            return False
+
+
+class TrioBackend(AsyncBackend):
+    @classmethod
+    def run(
+        cls,
+        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
+        args: tuple[Unpack[PosArgsT]],
+        kwargs: dict[str, Any],
+        options: dict[str, Any],
+    ) -> T_Retval:
+        assert not kwargs, "unreachable, and not supported by Trio"
+        return trio.run(ensure_returns_coro(func), *args, **options)
+
+    @classmethod
+    def current_token(cls) -> object:
+        return trio.lowlevel.current_trio_token()
+
+    @classmethod
+    def current_time(cls) -> float:
+        return trio.current_time()
+
+    @classmethod
+    def cancelled_exception_class(cls) -> type[BaseException]:
+        return trio.Cancelled
+
+    @classmethod
+    async def checkpoint(cls) -> None:
+        await trio.lowlevel.checkpoint()
+
+    @classmethod
+    async def checkpoint_if_cancelled(cls) -> None:
+        await trio.lowlevel.checkpoint_if_cancelled()
+
+    @classmethod
+    async def cancel_shielded_checkpoint(cls) -> None:
+        await trio.lowlevel.cancel_shielded_checkpoint()
+
+    @classmethod
+    async def sleep(cls, delay: float) -> None:
+        await trio.sleep(delay)
+
+    @classmethod
+    def create_cancel_scope(
+        cls, *, deadline: float = math.inf, shield: bool = False
+    ) -> abc.CancelScope:
+        return CancelScope(deadline=deadline, shield=shield)
+
+    @classmethod
+    def current_effective_deadline(cls) -> float:
+        return trio.current_effective_deadline()
+
+    @classmethod
+    def create_task_group(cls) -> abc.TaskGroup:
+        return TaskGroup()
+
+    @classmethod
+    def create_event(cls) -> abc.Event:
+        return Event()
+
+    @classmethod
+    def create_lock(cls, *, fast_acquire: bool) -> Lock:
+        return Lock(fast_acquire=fast_acquire)
+
+    @classmethod
+    def create_semaphore(
+        cls,
+        initial_value: int,
+        *,
+        max_value: int | None = None,
+        fast_acquire: bool = False,
+    ) -> abc.Semaphore:
+        return Semaphore(initial_value, max_value=max_value, fast_acquire=fast_acquire)
+
+    @classmethod
+    def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter:
+        return CapacityLimiter(total_tokens)
+
+    @classmethod
+    async def run_sync_in_worker_thread(
+        cls,
+        func: Callable[[Unpack[PosArgsT]], T_Retval],
+        args: tuple[Unpack[PosArgsT]],
+        abandon_on_cancel: bool = False,
+        limiter: abc.CapacityLimiter | None = None,
+    ) -> T_Retval:
+        def wrapper() -> T_Retval:
+            with claim_worker_thread(TrioBackend, token):
+                return func(*args)
+
+        token = TrioBackend.current_token()
+        return await run_sync(
+            wrapper,
+            abandon_on_cancel=abandon_on_cancel,
+            limiter=cast(trio.CapacityLimiter, limiter),
+        )
+
+    @classmethod
+    def check_cancelled(cls) -> None:
+        trio.from_thread.check_cancelled()
+
+    @classmethod
+    def run_async_from_thread(
+        cls,
+        func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
+        args: tuple[Unpack[PosArgsT]],
+        token: object,
+    ) -> T_co:
+        trio_token = cast("trio.lowlevel.TrioToken | None", token)
+        try:
+            return trio.from_thread.run(func, *args, trio_token=trio_token)
+        except trio.RunFinishedError:
+            raise RunFinishedError from None
+
+    @classmethod
+    def run_sync_from_thread(
+        cls,
+        func: Callable[[Unpack[PosArgsT]], T_Retval],
+        args: tuple[Unpack[PosArgsT]],
+        token: object,
+    ) -> T_Retval:
+        trio_token = cast("trio.lowlevel.TrioToken | None", token)
+        try:
+            return trio.from_thread.run_sync(func, *args, trio_token=trio_token)
+        except trio.RunFinishedError:
+            raise RunFinishedError from None
+
+    @classmethod
+    async def open_process(
+        cls,
+        command: StrOrBytesPath | Sequence[StrOrBytesPath],
+        *,
+        stdin: int | IO[Any] | None,
+        stdout: int | IO[Any] | None,
+        stderr: int | IO[Any] | None,
+        **kwargs: Any,
+    ) -> Process:
+        def convert_item(item: StrOrBytesPath) -> str:
+            str_or_bytes = os.fspath(item)
+            if isinstance(str_or_bytes, str):
+                return str_or_bytes
+            else:
+                return os.fsdecode(str_or_bytes)
+
+        if isinstance(command, (str, bytes, PathLike)):
+            process = await trio.lowlevel.open_process(
+                convert_item(command),
+                stdin=stdin,
+                stdout=stdout,
+                stderr=stderr,
+                shell=True,
+                **kwargs,
+            )
+        else:
+            process = await trio.lowlevel.open_process(
+                [convert_item(item) for item in command],
+                stdin=stdin,
+                stdout=stdout,
+                stderr=stderr,
+                shell=False,
+                **kwargs,
+            )
+
+        stdin_stream = SendStreamWrapper(process.stdin) if process.stdin else None
+        stdout_stream = ReceiveStreamWrapper(process.stdout) if process.stdout else None
+        stderr_stream = ReceiveStreamWrapper(process.stderr) if process.stderr else None
+        return Process(process, stdin_stream, stdout_stream, stderr_stream)
+
+    @classmethod
+    def setup_process_pool_exit_at_shutdown(cls, workers: set[abc.Process]) -> None:
+        trio.lowlevel.spawn_system_task(_shutdown_process_pool, workers)
+
+    @classmethod
+    async def connect_tcp(
+        cls, host: str, port: int, local_address: IPSockAddrType | None = None
+    ) -> SocketStream:
+        family = socket.AF_INET6 if ":" in host else socket.AF_INET
+        trio_socket = trio.socket.socket(family)
+        trio_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+        if local_address:
+            await trio_socket.bind(local_address)
+
+        try:
+            await trio_socket.connect((host, port))
+        except BaseException:
+            trio_socket.close()
+            raise
+
+        return SocketStream(trio_socket)
+
+    @classmethod
+    async def connect_unix(cls, path: str | bytes) -> abc.UNIXSocketStream:
+        trio_socket = trio.socket.socket(socket.AF_UNIX)
+        try:
+            await trio_socket.connect(path)
+        except BaseException:
+            trio_socket.close()
+            raise
+
+        return UNIXSocketStream(trio_socket)
+
+    @classmethod
+    def create_tcp_listener(cls, sock: socket.socket) -> abc.SocketListener:
+        return TCPSocketListener(sock)
+
+    @classmethod
+    def create_unix_listener(cls, sock: socket.socket) -> abc.SocketListener:
+        return UNIXSocketListener(sock)
+
+    @classmethod
+    async def create_udp_socket(
+        cls,
+        family: socket.AddressFamily,
+        local_address: IPSockAddrType | None,
+        remote_address: IPSockAddrType | None,
+        reuse_port: bool,
+    ) -> UDPSocket | ConnectedUDPSocket:
+        trio_socket = trio.socket.socket(family=family, type=socket.SOCK_DGRAM)
+
+        if reuse_port:
+            trio_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+
+        if local_address:
+            await trio_socket.bind(local_address)
+
+        if remote_address:
+            await trio_socket.connect(remote_address)
+            return ConnectedUDPSocket(trio_socket)
+        else:
+            return UDPSocket(trio_socket)
+
+    @classmethod
+    @overload
+    async def create_unix_datagram_socket(
+        cls, raw_socket: socket.socket, remote_path: None
+    ) -> abc.UNIXDatagramSocket: ...
+
+    @classmethod
+    @overload
+    async def create_unix_datagram_socket(
+        cls, raw_socket: socket.socket, remote_path: str | bytes
+    ) -> abc.ConnectedUNIXDatagramSocket: ...
+
+    @classmethod
+    async def create_unix_datagram_socket(
+        cls, raw_socket: socket.socket, remote_path: str | bytes | None
+    ) -> abc.UNIXDatagramSocket | abc.ConnectedUNIXDatagramSocket:
+        trio_socket = trio.socket.from_stdlib_socket(raw_socket)
+
+        if remote_path:
+            await trio_socket.connect(remote_path)
+            return ConnectedUNIXDatagramSocket(trio_socket)
+        else:
+            return UNIXDatagramSocket(trio_socket)
+
+    @classmethod
+    async def getaddrinfo(
+        cls,
+        host: bytes | str | None,
+        port: str | int | None,
+        *,
+        family: int | AddressFamily = 0,
+        type: int | SocketKind = 0,
+        proto: int = 0,
+        flags: int = 0,
+    ) -> Sequence[
+        tuple[
+            AddressFamily,
+            SocketKind,
+            int,
+            str,
+            tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes],
+        ]
+    ]:
+        return await trio.socket.getaddrinfo(host, port, family, type, proto, flags)
+
+    @classmethod
+    async def getnameinfo(
+        cls, sockaddr: IPSockAddrType, flags: int = 0
+    ) -> tuple[str, str]:
+        return await trio.socket.getnameinfo(sockaddr, flags)
+
+    @classmethod
+    async def wait_readable(cls, obj: FileDescriptorLike) -> None:
+        try:
+            await wait_readable(obj)
+        except trio.ClosedResourceError as exc:
+            raise ClosedResourceError().with_traceback(exc.__traceback__) from None
+        except trio.BusyResourceError:
+            raise BusyResourceError("reading from") from None
+
+    @classmethod
+    async def wait_writable(cls, obj: FileDescriptorLike) -> None:
+        try:
+            await wait_writable(obj)
+        except trio.ClosedResourceError as exc:
+            raise ClosedResourceError().with_traceback(exc.__traceback__) from None
+        except trio.BusyResourceError:
+            raise BusyResourceError("writing to") from None
+
+    @classmethod
+    def notify_closing(cls, obj: FileDescriptorLike) -> None:
+        notify_closing(obj)
+
+    @classmethod
+    async def wrap_listener_socket(cls, sock: socket.socket) -> abc.SocketListener:
+        if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX:
+            return UNIXSocketListener(sock)
+
+        return TCPSocketListener(sock)
+
+    @classmethod
+    async def wrap_stream_socket(cls, sock: socket.socket) -> SocketStream:
+        trio_sock = trio.socket.from_stdlib_socket(sock)
+        return SocketStream(trio_sock)
+
+    @classmethod
+    async def wrap_unix_stream_socket(cls, sock: socket.socket) -> UNIXSocketStream:
+        trio_sock = trio.socket.from_stdlib_socket(sock)
+        return UNIXSocketStream(trio_sock)
+
+    @classmethod
+    async def wrap_udp_socket(cls, sock: socket.socket) -> UDPSocket:
+        trio_sock = trio.socket.from_stdlib_socket(sock)
+        return UDPSocket(trio_sock)
+
+    @classmethod
+    async def wrap_connected_udp_socket(cls, sock: socket.socket) -> ConnectedUDPSocket:
+        trio_sock = trio.socket.from_stdlib_socket(sock)
+        return ConnectedUDPSocket(trio_sock)
+
+    @classmethod
+    async def wrap_unix_datagram_socket(cls, sock: socket.socket) -> UNIXDatagramSocket:
+        trio_sock = trio.socket.from_stdlib_socket(sock)
+        return UNIXDatagramSocket(trio_sock)
+
+    @classmethod
+    async def wrap_connected_unix_datagram_socket(
+        cls, sock: socket.socket
+    ) -> ConnectedUNIXDatagramSocket:
+        trio_sock = trio.socket.from_stdlib_socket(sock)
+        return ConnectedUNIXDatagramSocket(trio_sock)
+
+    @classmethod
+    def current_default_thread_limiter(cls) -> CapacityLimiter:
+        try:
+            return _capacity_limiter_wrapper.get()
+        except LookupError:
+            limiter = CapacityLimiter(
+                original=trio.to_thread.current_default_thread_limiter()
+            )
+            _capacity_limiter_wrapper.set(limiter)
+            return limiter
+
+    @classmethod
+    def open_signal_receiver(
+        cls, *signals: Signals
+    ) -> AbstractContextManager[AsyncIterator[Signals]]:
+        return _SignalReceiver(signals)
+
+    @classmethod
+    def get_current_task(cls) -> TaskInfo:
+        task = current_task()
+        return TrioTaskInfo(task)
+
+    @classmethod
+    def get_running_tasks(cls) -> Sequence[TaskInfo]:
+        root_task = current_root_task()
+        assert root_task
+        task_infos = [TrioTaskInfo(root_task)]
+        nurseries = root_task.child_nurseries
+        while nurseries:
+            new_nurseries: list[trio.Nursery] = []
+            for nursery in nurseries:
+                for task in nursery.child_tasks:
+                    task_infos.append(TrioTaskInfo(task))
+                    new_nurseries.extend(task.child_nurseries)
+
+            nurseries = new_nurseries
+
+        return task_infos
+
+    @classmethod
+    async def wait_all_tasks_blocked(cls) -> None:
+        from trio.testing import wait_all_tasks_blocked
+
+        await wait_all_tasks_blocked()
+
+    @classmethod
+    def create_test_runner(cls, options: dict[str, Any]) -> TestRunner:
+        return TestRunner(**options)
+
+
+backend_class = TrioBackend

+ 0 - 0
vendor/anyio/_core/__init__.py


+ 167 - 0
vendor/anyio/_core/_asyncio_selector_thread.py

@@ -0,0 +1,167 @@
+from __future__ import annotations
+
+import asyncio
+import socket
+import threading
+from collections.abc import Callable
+from selectors import EVENT_READ, EVENT_WRITE, DefaultSelector
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+    from _typeshed import FileDescriptorLike
+
+_selector_lock = threading.Lock()
+_selector: Selector | None = None
+
+
+class Selector:
+    def __init__(self) -> None:
+        self._thread = threading.Thread(target=self.run, name="AnyIO socket selector")
+        self._selector = DefaultSelector()
+        self._send, self._receive = socket.socketpair()
+        self._send.setblocking(False)
+        self._receive.setblocking(False)
+        # This somewhat reduces the amount of memory wasted queueing up data
+        # for wakeups. With these settings, maximum number of 1-byte sends
+        # before getting BlockingIOError:
+        #   Linux 4.8: 6
+        #   macOS (darwin 15.5): 1
+        #   Windows 10: 525347
+        # Windows you're weird. (And on Windows setting SNDBUF to 0 makes send
+        # blocking, even on non-blocking sockets, so don't do that.)
+        self._receive.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1)
+        self._send.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 1)
+        # On Windows this is a TCP socket so this might matter. On other
+        # platforms this fails b/c AF_UNIX sockets aren't actually TCP.
+        try:
+            self._send.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+        except OSError:
+            pass
+
+        self._selector.register(self._receive, EVENT_READ)
+        self._closed = False
+
+    def start(self) -> None:
+        self._thread.start()
+        threading._register_atexit(self._stop)  # type: ignore[attr-defined]
+
+    def _stop(self) -> None:
+        global _selector
+        self._closed = True
+        self._notify_self()
+        self._send.close()
+        self._thread.join()
+        self._selector.unregister(self._receive)
+        self._receive.close()
+        self._selector.close()
+        _selector = None
+        assert not self._selector.get_map(), (
+            "selector still has registered file descriptors after shutdown"
+        )
+
+    def _notify_self(self) -> None:
+        try:
+            self._send.send(b"\x00")
+        except BlockingIOError:
+            pass
+
+    def add_reader(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None:
+        loop = asyncio.get_running_loop()
+        try:
+            key = self._selector.get_key(fd)
+        except KeyError:
+            self._selector.register(fd, EVENT_READ, {EVENT_READ: (loop, callback)})
+        else:
+            if EVENT_READ in key.data:
+                raise ValueError(
+                    "this file descriptor is already registered for reading"
+                )
+
+            key.data[EVENT_READ] = loop, callback
+            self._selector.modify(fd, key.events | EVENT_READ, key.data)
+
+        self._notify_self()
+
+    def add_writer(self, fd: FileDescriptorLike, callback: Callable[[], Any]) -> None:
+        loop = asyncio.get_running_loop()
+        try:
+            key = self._selector.get_key(fd)
+        except KeyError:
+            self._selector.register(fd, EVENT_WRITE, {EVENT_WRITE: (loop, callback)})
+        else:
+            if EVENT_WRITE in key.data:
+                raise ValueError(
+                    "this file descriptor is already registered for writing"
+                )
+
+            key.data[EVENT_WRITE] = loop, callback
+            self._selector.modify(fd, key.events | EVENT_WRITE, key.data)
+
+        self._notify_self()
+
+    def remove_reader(self, fd: FileDescriptorLike) -> bool:
+        try:
+            key = self._selector.get_key(fd)
+        except KeyError:
+            return False
+
+        if new_events := key.events ^ EVENT_READ:
+            del key.data[EVENT_READ]
+            self._selector.modify(fd, new_events, key.data)
+        else:
+            self._selector.unregister(fd)
+
+        return True
+
+    def remove_writer(self, fd: FileDescriptorLike) -> bool:
+        try:
+            key = self._selector.get_key(fd)
+        except KeyError:
+            return False
+
+        if new_events := key.events ^ EVENT_WRITE:
+            del key.data[EVENT_WRITE]
+            self._selector.modify(fd, new_events, key.data)
+        else:
+            self._selector.unregister(fd)
+
+        return True
+
+    def run(self) -> None:
+        while not self._closed:
+            for key, events in self._selector.select():
+                if key.fileobj is self._receive:
+                    try:
+                        while self._receive.recv(4096):
+                            pass
+                    except BlockingIOError:
+                        pass
+
+                    continue
+
+                if events & EVENT_READ:
+                    loop, callback = key.data[EVENT_READ]
+                    self.remove_reader(key.fd)
+                    try:
+                        loop.call_soon_threadsafe(callback)
+                    except RuntimeError:
+                        pass  # the loop was already closed
+
+                if events & EVENT_WRITE:
+                    loop, callback = key.data[EVENT_WRITE]
+                    self.remove_writer(key.fd)
+                    try:
+                        loop.call_soon_threadsafe(callback)
+                    except RuntimeError:
+                        pass  # the loop was already closed
+
+
+def get_selector() -> Selector:
+    global _selector
+
+    with _selector_lock:
+        if _selector is None:
+            _selector = Selector()
+            _selector.start()
+
+        return _selector

+ 200 - 0
vendor/anyio/_core/_contextmanagers.py

@@ -0,0 +1,200 @@
+from __future__ import annotations
+
+from abc import abstractmethod
+from contextlib import AbstractAsyncContextManager, AbstractContextManager
+from inspect import isasyncgen, iscoroutine, isgenerator
+from types import TracebackType
+from typing import Protocol, TypeVar, cast, final
+
+_T_co = TypeVar("_T_co", covariant=True)
+_ExitT_co = TypeVar("_ExitT_co", covariant=True, bound="bool | None")
+
+
+class _SupportsCtxMgr(Protocol[_T_co, _ExitT_co]):
+    def __contextmanager__(self) -> AbstractContextManager[_T_co, _ExitT_co]: ...
+
+
+class _SupportsAsyncCtxMgr(Protocol[_T_co, _ExitT_co]):
+    def __asynccontextmanager__(
+        self,
+    ) -> AbstractAsyncContextManager[_T_co, _ExitT_co]: ...
+
+
+class ContextManagerMixin:
+    """
+    Mixin class providing context manager functionality via a generator-based
+    implementation.
+
+    This class allows you to implement a context manager via :meth:`__contextmanager__`
+    which should return a generator. The mechanics are meant to mirror those of
+    :func:`@contextmanager <contextlib.contextmanager>`.
+
+    .. note:: Classes using this mix-in are not reentrant as context managers, meaning
+        that once you enter it, you can't re-enter before first exiting it.
+
+    .. seealso:: :doc:`contextmanagers`
+    """
+
+    __cm: AbstractContextManager[object, bool | None] | None = None
+
+    @final
+    def __enter__(self: _SupportsCtxMgr[_T_co, bool | None]) -> _T_co:
+        # Needed for mypy to assume self still has the __cm member
+        assert isinstance(self, ContextManagerMixin)
+        if self.__cm is not None:
+            raise RuntimeError(
+                f"this {self.__class__.__qualname__} has already been entered"
+            )
+
+        cm = self.__contextmanager__()
+        if not isinstance(cm, AbstractContextManager):
+            if isgenerator(cm):
+                raise TypeError(
+                    "__contextmanager__() returned a generator object instead of "
+                    "a context manager. Did you forget to add the @contextmanager "
+                    "decorator?"
+                )
+
+            raise TypeError(
+                f"__contextmanager__() did not return a context manager object, "
+                f"but {cm.__class__!r}"
+            )
+
+        if cm is self:
+            raise TypeError(
+                f"{self.__class__.__qualname__}.__contextmanager__() returned "
+                f"self. Did you forget to add the @contextmanager decorator and a "
+                f"'yield' statement?"
+            )
+
+        value = cm.__enter__()
+        self.__cm = cm
+        return value
+
+    @final
+    def __exit__(
+        self: _SupportsCtxMgr[object, _ExitT_co],
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> _ExitT_co:
+        # Needed for mypy to assume self still has the __cm member
+        assert isinstance(self, ContextManagerMixin)
+        if self.__cm is None:
+            raise RuntimeError(
+                f"this {self.__class__.__qualname__} has not been entered yet"
+            )
+
+        # Prevent circular references
+        cm = self.__cm
+        del self.__cm
+
+        return cast(_ExitT_co, cm.__exit__(exc_type, exc_val, exc_tb))
+
+    @abstractmethod
+    def __contextmanager__(self) -> AbstractContextManager[object, bool | None]:
+        """
+        Implement your context manager logic here.
+
+        This method **must** be decorated with
+        :func:`@contextmanager <contextlib.contextmanager>`.
+
+        .. note:: Remember that the ``yield`` will raise any exception raised in the
+            enclosed context block, so use a ``finally:`` block to clean up resources!
+
+        :return: a context manager object
+        """
+
+
+class AsyncContextManagerMixin:
+    """
+    Mixin class providing async context manager functionality via a generator-based
+    implementation.
+
+    This class allows you to implement a context manager via
+    :meth:`__asynccontextmanager__`. The mechanics are meant to mirror those of
+    :func:`@asynccontextmanager <contextlib.asynccontextmanager>`.
+
+    .. note:: Classes using this mix-in are not reentrant as context managers, meaning
+        that once you enter it, you can't re-enter before first exiting it.
+
+    .. seealso:: :doc:`contextmanagers`
+    """
+
+    __cm: AbstractAsyncContextManager[object, bool | None] | None = None
+
+    @final
+    async def __aenter__(self: _SupportsAsyncCtxMgr[_T_co, bool | None]) -> _T_co:
+        # Needed for mypy to assume self still has the __cm member
+        assert isinstance(self, AsyncContextManagerMixin)
+        if self.__cm is not None:
+            raise RuntimeError(
+                f"this {self.__class__.__qualname__} has already been entered"
+            )
+
+        cm = self.__asynccontextmanager__()
+        if not isinstance(cm, AbstractAsyncContextManager):
+            if isasyncgen(cm):
+                raise TypeError(
+                    "__asynccontextmanager__() returned an async generator instead of "
+                    "an async context manager. Did you forget to add the "
+                    "@asynccontextmanager decorator?"
+                )
+            elif iscoroutine(cm):
+                cm.close()
+                raise TypeError(
+                    "__asynccontextmanager__() returned a coroutine object instead of "
+                    "an async context manager. Did you forget to add the "
+                    "@asynccontextmanager decorator and a 'yield' statement?"
+                )
+
+            raise TypeError(
+                f"__asynccontextmanager__() did not return an async context manager, "
+                f"but {cm.__class__!r}"
+            )
+
+        if cm is self:
+            raise TypeError(
+                f"{self.__class__.__qualname__}.__asynccontextmanager__() returned "
+                f"self. Did you forget to add the @asynccontextmanager decorator and a "
+                f"'yield' statement?"
+            )
+
+        value = await cm.__aenter__()
+        self.__cm = cm
+        return value
+
+    @final
+    async def __aexit__(
+        self: _SupportsAsyncCtxMgr[object, _ExitT_co],
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> _ExitT_co:
+        assert isinstance(self, AsyncContextManagerMixin)
+        if self.__cm is None:
+            raise RuntimeError(
+                f"this {self.__class__.__qualname__} has not been entered yet"
+            )
+
+        # Prevent circular references
+        cm = self.__cm
+        del self.__cm
+
+        return cast(_ExitT_co, await cm.__aexit__(exc_type, exc_val, exc_tb))
+
+    @abstractmethod
+    def __asynccontextmanager__(
+        self,
+    ) -> AbstractAsyncContextManager[object, bool | None]:
+        """
+        Implement your async context manager logic here.
+
+        This method **must** be decorated with
+        :func:`@asynccontextmanager <contextlib.asynccontextmanager>`.
+
+        .. note:: Remember that the ``yield`` will raise any exception raised in the
+            enclosed context block, so use a ``finally:`` block to clean up resources!
+
+        :return: an async context manager object
+        """

+ 240 - 0
vendor/anyio/_core/_eventloop.py

@@ -0,0 +1,240 @@
+from __future__ import annotations
+
+import math
+import sys
+import threading
+from collections.abc import Awaitable, Callable, Generator
+from contextlib import contextmanager
+from contextvars import Token
+from importlib import import_module
+from typing import TYPE_CHECKING, Any, TypeVar
+
+from ._exceptions import NoEventLoopError
+
+if sys.version_info >= (3, 11):
+    from typing import TypeVarTuple, Unpack
+else:
+    from typing_extensions import TypeVarTuple, Unpack
+
+sniffio: Any
+try:
+    import sniffio
+except ModuleNotFoundError:
+    sniffio = None
+
+if TYPE_CHECKING:
+    from ..abc import AsyncBackend
+
+# This must be updated when new backends are introduced
+BACKENDS = "asyncio", "trio"
+
+T_Retval = TypeVar("T_Retval")
+PosArgsT = TypeVarTuple("PosArgsT")
+
+threadlocals = threading.local()
+loaded_backends: dict[str, type[AsyncBackend]] = {}
+
+
+def run(
+    func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
+    *args: Unpack[PosArgsT],
+    backend: str = "asyncio",
+    backend_options: dict[str, Any] | None = None,
+) -> T_Retval:
+    """
+    Run the given coroutine function in an asynchronous event loop.
+
+    The current thread must not be already running an event loop.
+
+    :param func: a coroutine function
+    :param args: positional arguments to ``func``
+    :param backend: name of the asynchronous event loop implementation – currently
+        either ``asyncio`` or ``trio``
+    :param backend_options: keyword arguments to call the backend ``run()``
+        implementation with (documented :ref:`here <backend options>`)
+    :return: the return value of the coroutine function
+    :raises RuntimeError: if an asynchronous event loop is already running in this
+        thread
+    :raises LookupError: if the named backend is not found
+
+    """
+    if asynclib_name := current_async_library():
+        raise RuntimeError(f"Already running {asynclib_name} in this thread")
+
+    try:
+        async_backend = get_async_backend(backend)
+    except ImportError as exc:
+        if backend in BACKENDS:
+            raise LookupError(
+                f"Backend {backend!r} is not available. "
+                f"Install it with: pip install anyio[{backend}]"
+            ) from exc
+
+        raise LookupError(f"No such backend: {backend}") from exc
+
+    token = None
+    if asynclib_name is None:
+        # Since we're in control of the event loop, we can cache the name of the async
+        # library
+        token = set_current_async_library(backend)
+
+    try:
+        backend_options = backend_options or {}
+        return async_backend.run(func, args, {}, backend_options)
+    finally:
+        reset_current_async_library(token)
+
+
+async def sleep(delay: float) -> None:
+    """
+    Pause the current task for the specified duration.
+
+    :param delay: the duration, in seconds
+
+    """
+    return await get_async_backend().sleep(delay)
+
+
+async def sleep_forever() -> None:
+    """
+    Pause the current task until it's cancelled.
+
+    This is a shortcut for ``sleep(math.inf)``.
+
+    .. versionadded:: 3.1
+
+    """
+    await sleep(math.inf)
+
+
+async def sleep_until(deadline: float) -> None:
+    """
+    Pause the current task until the given time.
+
+    :param deadline: the absolute time to wake up at (according to the internal
+        monotonic clock of the event loop)
+
+    .. versionadded:: 3.1
+
+    """
+    now = current_time()
+    await sleep(max(deadline - now, 0))
+
+
+def current_time() -> float:
+    """
+    Return the current value of the event loop's internal clock.
+
+    :return: the clock value (seconds)
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    return get_async_backend().current_time()
+
+
+def get_all_backends() -> tuple[str, ...]:
+    """Return a tuple of the names of all built-in backends."""
+    return BACKENDS
+
+
+def get_available_backends() -> tuple[str, ...]:
+    """
+    Test for the availability of built-in backends.
+
+    :return a tuple of the built-in backend names that were successfully imported
+
+    .. versionadded:: 4.12
+
+    """
+    available_backends: list[str] = []
+    for backend_name in get_all_backends():
+        try:
+            get_async_backend(backend_name)
+        except ImportError:
+            continue
+
+        available_backends.append(backend_name)
+
+    return tuple(available_backends)
+
+
+def get_cancelled_exc_class() -> type[BaseException]:
+    """
+    Return the current async library's cancellation exception class.
+
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    return get_async_backend().cancelled_exception_class()
+
+
+#
+# Private API
+#
+
+
+@contextmanager
+def claim_worker_thread(
+    backend_class: type[AsyncBackend], token: object
+) -> Generator[Any, None, None]:
+    from ..lowlevel import EventLoopToken
+
+    threadlocals.current_token = EventLoopToken(backend_class, token)
+    try:
+        yield
+    finally:
+        del threadlocals.current_token
+
+
+def get_async_backend(asynclib_name: str | None = None) -> type[AsyncBackend]:
+    if asynclib_name is None:
+        asynclib_name = current_async_library()
+        if not asynclib_name:
+            raise NoEventLoopError(
+                f"Not currently running on any asynchronous event loop. "
+                f"Available async backends: {', '.join(get_all_backends())}"
+            )
+
+    # We use our own dict instead of sys.modules to get the already imported back-end
+    # class because the appropriate modules in sys.modules could potentially be only
+    # partially initialized
+    try:
+        return loaded_backends[asynclib_name]
+    except KeyError:
+        module = import_module(f"anyio._backends._{asynclib_name}")
+        loaded_backends[asynclib_name] = module.backend_class
+        return module.backend_class
+
+
+def current_async_library() -> str | None:
+    if sniffio is None:
+        # If sniffio is not installed, we assume we're either running asyncio or nothing
+        import asyncio
+
+        try:
+            asyncio.get_running_loop()
+            return "asyncio"
+        except RuntimeError:
+            pass
+    else:
+        try:
+            return sniffio.current_async_library()
+        except sniffio.AsyncLibraryNotFoundError:
+            pass
+
+    return None
+
+
+def set_current_async_library(asynclib_name: str | None) -> Token | None:
+    # no-op if sniffio is not installed
+    if sniffio is None:
+        return None
+
+    return sniffio.current_async_library_cvar.set(asynclib_name)
+
+
+def reset_current_async_library(token: Token | None) -> None:
+    if token is not None:
+        sniffio.current_async_library_cvar.reset(token)

+ 177 - 0
vendor/anyio/_core/_exceptions.py

@@ -0,0 +1,177 @@
+from __future__ import annotations
+
+import sys
+from collections.abc import Generator
+from textwrap import dedent
+from typing import Any
+
+if sys.version_info < (3, 11):
+    from exceptiongroup import BaseExceptionGroup
+
+
+class BrokenResourceError(Exception):
+    """
+    Raised when trying to use a resource that has been rendered unusable due to external
+    causes (e.g. a send stream whose peer has disconnected).
+    """
+
+
+class BrokenWorkerProcess(Exception):
+    """
+    Raised by :meth:`~anyio.to_process.run_sync` if the worker process terminates abruptly or
+    otherwise misbehaves.
+    """
+
+
+class BrokenWorkerInterpreter(Exception):
+    """
+    Raised by :meth:`~anyio.to_interpreter.run_sync` if an unexpected exception is
+    raised in the subinterpreter.
+    """
+
+    def __init__(self, excinfo: Any):
+        # This was adapted from concurrent.futures.interpreter.ExecutionFailed
+        msg = excinfo.formatted
+        if not msg:
+            if excinfo.type and excinfo.msg:
+                msg = f"{excinfo.type.__name__}: {excinfo.msg}"
+            else:
+                msg = excinfo.type.__name__ or excinfo.msg
+
+        super().__init__(msg)
+        self.excinfo = excinfo
+
+    def __str__(self) -> str:
+        try:
+            formatted = self.excinfo.errdisplay
+        except Exception:
+            return super().__str__()
+        else:
+            return dedent(
+                f"""
+                {super().__str__()}
+
+                Uncaught in the interpreter:
+
+                {formatted}
+                """.strip()
+            )
+
+
+class BusyResourceError(Exception):
+    """
+    Raised when two tasks are trying to read from or write to the same resource
+    concurrently.
+    """
+
+    def __init__(self, action: str):
+        super().__init__(f"Another task is already {action} this resource")
+
+
+class ClosedResourceError(Exception):
+    """Raised when trying to use a resource that has been closed."""
+
+
+class ConnectionFailed(OSError):
+    """
+    Raised when a connection attempt fails.
+
+    .. note:: This class inherits from :exc:`OSError` for backwards compatibility.
+    """
+
+
+def iterate_exceptions(
+    exception: BaseException,
+) -> Generator[BaseException, None, None]:
+    if isinstance(exception, BaseExceptionGroup):
+        for exc in exception.exceptions:
+            yield from iterate_exceptions(exc)
+    else:
+        yield exception
+
+
+class DelimiterNotFound(Exception):
+    """
+    Raised during
+    :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the
+    maximum number of bytes has been read without the delimiter being found.
+    """
+
+    def __init__(self, max_bytes: int) -> None:
+        super().__init__(
+            f"The delimiter was not found among the first {max_bytes} bytes"
+        )
+
+
+class EndOfStream(Exception):
+    """
+    Raised when trying to read from a stream that has been closed from the other end.
+    """
+
+
+class IncompleteRead(Exception):
+    """
+    Raised during
+    :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_exactly` or
+    :meth:`~anyio.streams.buffered.BufferedByteReceiveStream.receive_until` if the
+    connection is closed before the requested amount of bytes has been read.
+    """
+
+    def __init__(self) -> None:
+        super().__init__(
+            "The stream was closed before the read operation could be completed"
+        )
+
+
+class TypedAttributeLookupError(LookupError):
+    """
+    Raised by :meth:`~anyio.TypedAttributeProvider.extra` when the given typed attribute
+    is not found and no default value has been given.
+    """
+
+
+class WouldBlock(Exception):
+    """Raised by ``X_nowait`` functions if ``X()`` would block."""
+
+
+class NoEventLoopError(RuntimeError):
+    """
+    Raised by several functions that require an event loop to be running in the current
+    thread when there is no running event loop.
+
+    This is also raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync`
+    if not calling from an AnyIO worker thread, and no ``token`` was passed.
+    """
+
+
+class RunFinishedError(RuntimeError):
+    """
+    Raised by :func:`.from_thread.run` and :func:`.from_thread.run_sync` if the event
+    loop associated with the explicitly passed token has already finished.
+    """
+
+    def __init__(self) -> None:
+        super().__init__(
+            "The event loop associated with the given token has already finished"
+        )
+
+
+class TaskFailed(Exception):
+    """
+    Raised when awaiting on, or attempting to access the return value of, a
+    :class:`.TaskHandle` that raised an exception.
+    """
+
+
+class TaskCancelled(TaskFailed):
+    """
+    Raised when awaiting on, or attempting to access the return value of, a
+    :class:`.TaskHandle` that was cancelled.
+    """
+
+
+class TaskNotFinished(Exception):
+    """
+    Raised when attempting to access the return value or exception of a
+    :class:`.TaskHandle` that is still pending completion.
+    """

+ 960 - 0
vendor/anyio/_core/_fileio.py

@@ -0,0 +1,960 @@
+from __future__ import annotations
+
+import os
+import pathlib
+import sys
+from collections.abc import (
+    AsyncIterator,
+    Callable,
+    Iterable,
+    Iterator,
+    Sequence,
+)
+from dataclasses import dataclass
+from functools import partial
+from os import PathLike
+from typing import (
+    IO,
+    TYPE_CHECKING,
+    Any,
+    AnyStr,
+    ClassVar,
+    Final,
+    Generic,
+    TypeVar,
+    overload,
+)
+
+from .. import to_thread
+from ..abc import AsyncResource
+from ._synchronization import CapacityLimiter
+
+if sys.version_info >= (3, 11):
+    from typing import Self
+else:
+    from typing_extensions import Self
+
+if sys.version_info >= (3, 14):
+    from pathlib.types import PathInfo
+
+if TYPE_CHECKING:
+    from types import ModuleType
+
+    from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer
+else:
+    ReadableBuffer = OpenBinaryMode = OpenTextMode = WriteableBuffer = object
+
+
+T = TypeVar("T", bound="Path")
+
+
+class AsyncFile(AsyncResource, Generic[AnyStr]):
+    """
+    An asynchronous file object.
+
+    This class wraps a standard file object and provides async friendly versions of the
+    following blocking methods (where available on the original file object):
+
+    * read
+    * read1
+    * readline
+    * readlines
+    * readinto
+    * readinto1
+    * write
+    * writelines
+    * truncate
+    * seek
+    * tell
+    * flush
+
+    All other methods are directly passed through.
+
+    This class supports the asynchronous context manager protocol which closes the
+    underlying file at the end of the context block.
+
+    This class also supports asynchronous iteration::
+
+        async with await open_file(...) as f:
+            async for line in f:
+                print(line)
+    """
+
+    def __init__(
+        self, fp: IO[AnyStr], *, limiter: CapacityLimiter | None = None
+    ) -> None:
+        if limiter is not None and not isinstance(limiter, CapacityLimiter):
+            raise TypeError(
+                f"limiter must be a CapacityLimiter or None, not "
+                f"{limiter.__class__.__name__}"
+            )
+
+        self._fp: Any = fp
+        self._limiter = limiter
+
+    def __getattr__(self, name: str) -> object:
+        return getattr(self._fp, name)
+
+    @property
+    def limiter(self) -> CapacityLimiter | None:
+        """The capacity limiter used by this file object, if not the global limiter."""
+        return self._limiter
+
+    @property
+    def wrapped(self) -> IO[AnyStr]:
+        """The wrapped file object."""
+        return self._fp
+
+    async def __aiter__(self) -> AsyncIterator[AnyStr]:
+        while True:
+            line = await self.readline()
+            if line:
+                yield line
+            else:
+                break
+
+    async def aclose(self) -> None:
+        return await to_thread.run_sync(self._fp.close, limiter=self._limiter)
+
+    async def read(self, size: int = -1) -> AnyStr:
+        return await to_thread.run_sync(self._fp.read, size, limiter=self._limiter)
+
+    async def read1(self: AsyncFile[bytes], size: int = -1) -> bytes:
+        return await to_thread.run_sync(self._fp.read1, size, limiter=self._limiter)
+
+    async def readline(self) -> AnyStr:
+        return await to_thread.run_sync(self._fp.readline, limiter=self._limiter)
+
+    async def readlines(self) -> list[AnyStr]:
+        return await to_thread.run_sync(self._fp.readlines, limiter=self._limiter)
+
+    async def readinto(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
+        return await to_thread.run_sync(self._fp.readinto, b, limiter=self._limiter)
+
+    async def readinto1(self: AsyncFile[bytes], b: WriteableBuffer) -> int:
+        return await to_thread.run_sync(self._fp.readinto1, b, limiter=self._limiter)
+
+    @overload
+    async def write(self: AsyncFile[bytes], b: ReadableBuffer) -> int: ...
+
+    @overload
+    async def write(self: AsyncFile[str], b: str) -> int: ...
+
+    async def write(self, b: ReadableBuffer | str) -> int:
+        return await to_thread.run_sync(self._fp.write, b, limiter=self._limiter)
+
+    @overload
+    async def writelines(
+        self: AsyncFile[bytes], lines: Iterable[ReadableBuffer]
+    ) -> None: ...
+
+    @overload
+    async def writelines(self: AsyncFile[str], lines: Iterable[str]) -> None: ...
+
+    async def writelines(self, lines: Iterable[ReadableBuffer] | Iterable[str]) -> None:
+        return await to_thread.run_sync(
+            self._fp.writelines, lines, limiter=self._limiter
+        )
+
+    async def truncate(self, size: int | None = None) -> int:
+        return await to_thread.run_sync(self._fp.truncate, size, limiter=self._limiter)
+
+    async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int:
+        return await to_thread.run_sync(
+            self._fp.seek, offset, whence, limiter=self._limiter
+        )
+
+    async def tell(self) -> int:
+        return await to_thread.run_sync(self._fp.tell, limiter=self._limiter)
+
+    async def flush(self) -> None:
+        return await to_thread.run_sync(self._fp.flush, limiter=self._limiter)
+
+
+@overload
+async def open_file(
+    file: str | PathLike[str] | int,
+    mode: OpenBinaryMode,
+    buffering: int = ...,
+    encoding: str | None = ...,
+    errors: str | None = ...,
+    newline: str | None = ...,
+    closefd: bool = ...,
+    opener: Callable[[str, int], int] | None = ...,
+    *,
+    limiter: CapacityLimiter | None = ...,
+) -> AsyncFile[bytes]: ...
+
+
+@overload
+async def open_file(
+    file: str | PathLike[str] | int,
+    mode: OpenTextMode = ...,
+    buffering: int = ...,
+    encoding: str | None = ...,
+    errors: str | None = ...,
+    newline: str | None = ...,
+    closefd: bool = ...,
+    opener: Callable[[str, int], int] | None = ...,
+    *,
+    limiter: CapacityLimiter | None = ...,
+) -> AsyncFile[str]: ...
+
+
+async def open_file(
+    file: str | PathLike[str] | int,
+    mode: str = "r",
+    buffering: int = -1,
+    encoding: str | None = None,
+    errors: str | None = None,
+    newline: str | None = None,
+    closefd: bool = True,
+    opener: Callable[[str, int], int] | None = None,
+    *,
+    limiter: CapacityLimiter | None = None,
+) -> AsyncFile[Any]:
+    """
+    Open a file asynchronously.
+
+    Except for ``limiter``, the arguments are exactly the same as for the builtin :func:`open`.
+
+    :param limiter: an optional capacity limiter to use with the file
+        instead of the default one
+    :return: an asynchronous file object
+
+    .. versionchanged:: 4.14.0
+        Added the ``limiter`` keyword argument.
+
+    """
+    fp = await to_thread.run_sync(
+        open,
+        file,
+        mode,
+        buffering,
+        encoding,
+        errors,
+        newline,
+        closefd,
+        opener,
+        limiter=limiter,
+    )
+    return AsyncFile(fp, limiter=limiter)
+
+
+def wrap_file(
+    file: IO[AnyStr], *, limiter: CapacityLimiter | None = None
+) -> AsyncFile[AnyStr]:
+    """
+    Wrap an existing file as an asynchronous file.
+
+    :param file: an existing file-like object
+    :param limiter: an optional capacity limiter to use with the file
+        instead of the default one
+    :return: an asynchronous file object
+
+    .. versionchanged:: 4.14.0
+        Added the ``limiter`` keyword argument.
+
+    """
+    return AsyncFile(file, limiter=limiter)
+
+
+@dataclass(eq=False)
+class _PathIterator(AsyncIterator[T]):
+    iterator: Iterator[PathLike[str]]
+    limiter: CapacityLimiter | None
+    # This was added to ensure that iterating over a subclass of Path yields instances
+    # of that subclass rather than the base Path class.
+    path_cls: type[T]
+
+    async def __anext__(self) -> T:
+        nextval = await to_thread.run_sync(
+            next, self.iterator, None, abandon_on_cancel=True, limiter=self.limiter
+        )
+        if nextval is None:
+            raise StopAsyncIteration from None
+
+        return self.path_cls(nextval, limiter=self.limiter)
+
+
+class Path:
+    """
+    An asynchronous version of :class:`pathlib.Path`.
+
+    This class cannot be substituted for :class:`pathlib.Path` or
+    :class:`pathlib.PurePath`, but it is compatible with the :class:`os.PathLike`
+    interface.
+
+    It implements the Python 3.10 version of :class:`pathlib.Path` interface, except for
+    the deprecated :meth:`~pathlib.Path.link_to` method.
+
+    Some methods may be unavailable or have limited functionality, based on the Python
+    version:
+
+    * :meth:`~pathlib.Path.copy` (available on Python 3.14 or later)
+    * :meth:`~pathlib.Path.copy_into` (available on Python 3.14 or later)
+    * :meth:`~pathlib.Path.from_uri` (available on Python 3.13 or later)
+    * :meth:`~pathlib.PurePath.full_match` (available on Python 3.13 or later)
+    * :attr:`~pathlib.Path.info` (available on Python 3.14 or later)
+    * :meth:`~pathlib.Path.is_junction` (available on Python 3.12 or later)
+    * :meth:`~pathlib.PurePath.match` (the ``case_sensitive`` parameter is only
+      available on Python 3.13 or later)
+    * :meth:`~pathlib.Path.move` (available on Python 3.14 or later)
+    * :meth:`~pathlib.Path.move_into` (available on Python 3.14 or later)
+    * :meth:`~pathlib.PurePath.relative_to` (the ``walk_up`` parameter is only available
+      on Python 3.12 or later)
+    * :meth:`~pathlib.Path.walk` (available on Python 3.12 or later)
+
+    Any methods that do disk I/O need to be awaited on. These methods are:
+
+    * :meth:`~pathlib.Path.absolute`
+    * :meth:`~pathlib.Path.chmod`
+    * :meth:`~pathlib.Path.cwd`
+    * :meth:`~pathlib.Path.exists`
+    * :meth:`~pathlib.Path.expanduser`
+    * :meth:`~pathlib.Path.group`
+    * :meth:`~pathlib.Path.hardlink_to`
+    * :meth:`~pathlib.Path.home`
+    * :meth:`~pathlib.Path.is_block_device`
+    * :meth:`~pathlib.Path.is_char_device`
+    * :meth:`~pathlib.Path.is_dir`
+    * :meth:`~pathlib.Path.is_fifo`
+    * :meth:`~pathlib.Path.is_file`
+    * :meth:`~pathlib.Path.is_junction`
+    * :meth:`~pathlib.Path.is_mount`
+    * :meth:`~pathlib.Path.is_socket`
+    * :meth:`~pathlib.Path.is_symlink`
+    * :meth:`~pathlib.Path.lchmod`
+    * :meth:`~pathlib.Path.lstat`
+    * :meth:`~pathlib.Path.mkdir`
+    * :meth:`~pathlib.Path.open`
+    * :meth:`~pathlib.Path.owner`
+    * :meth:`~pathlib.Path.read_bytes`
+    * :meth:`~pathlib.Path.read_text`
+    * :meth:`~pathlib.Path.readlink`
+    * :meth:`~pathlib.Path.rename`
+    * :meth:`~pathlib.Path.replace`
+    * :meth:`~pathlib.Path.resolve`
+    * :meth:`~pathlib.Path.rmdir`
+    * :meth:`~pathlib.Path.samefile`
+    * :meth:`~pathlib.Path.stat`
+    * :meth:`~pathlib.Path.symlink_to`
+    * :meth:`~pathlib.Path.touch`
+    * :meth:`~pathlib.Path.unlink`
+    * :meth:`~pathlib.Path.walk`
+    * :meth:`~pathlib.Path.write_bytes`
+    * :meth:`~pathlib.Path.write_text`
+
+    Additionally, the following methods return an async iterator yielding
+    :class:`~.Path` objects:
+
+    * :meth:`~pathlib.Path.glob`
+    * :meth:`~pathlib.Path.iterdir`
+    * :meth:`~pathlib.Path.rglob`
+
+    .. versionchanged:: 4.14.0
+        Added the ``limiter`` keyword argument.
+    """
+
+    __slots__ = "_path", "_limiter", "__weakref__"
+
+    __weakref__: Any
+
+    def __init__(
+        self, *args: str | PathLike[str], limiter: CapacityLimiter | None = None
+    ) -> None:
+        if limiter is not None and not isinstance(limiter, CapacityLimiter):
+            raise TypeError(
+                f"limiter must be a CapacityLimiter or None, not "
+                f"{limiter.__class__.__name__}"
+            )
+
+        self._path: Final[pathlib.Path] = pathlib.Path(*args)
+        self._limiter = limiter
+
+    def __fspath__(self) -> str:
+        return self._path.__fspath__()
+
+    if sys.version_info >= (3, 15):
+
+        def __vfspath__(self) -> str:
+            return self._path.__vfspath__()
+
+    def __str__(self) -> str:
+        return self._path.__str__()
+
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}({self.as_posix()!r})"
+
+    def __bytes__(self) -> bytes:
+        return self._path.__bytes__()
+
+    def __hash__(self) -> int:
+        return self._path.__hash__()
+
+    def __eq__(self, other: object) -> bool:
+        target = other._path if isinstance(other, Path) else other
+        return self._path.__eq__(target)
+
+    def __lt__(self, other: pathlib.PurePath | Path) -> bool:
+        target = other._path if isinstance(other, Path) else other
+        return self._path.__lt__(target)
+
+    def __le__(self, other: pathlib.PurePath | Path) -> bool:
+        target = other._path if isinstance(other, Path) else other
+        return self._path.__le__(target)
+
+    def __gt__(self, other: pathlib.PurePath | Path) -> bool:
+        target = other._path if isinstance(other, Path) else other
+        return self._path.__gt__(target)
+
+    def __ge__(self, other: pathlib.PurePath | Path) -> bool:
+        target = other._path if isinstance(other, Path) else other
+        return self._path.__ge__(target)
+
+    def __truediv__(self, other: str | PathLike[str]) -> Self:
+        return type(self)(self._path / other, limiter=self._limiter)
+
+    def __rtruediv__(self, other: str | PathLike[str]) -> Self:
+        return type(self)(other, limiter=self._limiter) / self
+
+    @property
+    def limiter(self) -> CapacityLimiter | None:
+        """The capacity limiter used by this path, if not the global limiter."""
+        return self._limiter
+
+    @property
+    def parts(self) -> tuple[str, ...]:
+        return self._path.parts
+
+    @property
+    def drive(self) -> str:
+        return self._path.drive
+
+    @property
+    def root(self) -> str:
+        return self._path.root
+
+    @property
+    def anchor(self) -> str:
+        return self._path.anchor
+
+    @property
+    def parents(self) -> Sequence[Self]:
+        return tuple(type(self)(p, limiter=self._limiter) for p in self._path.parents)
+
+    @property
+    def parent(self) -> Self:
+        return type(self)(self._path.parent, limiter=self._limiter)
+
+    @property
+    def name(self) -> str:
+        return self._path.name
+
+    @property
+    def suffix(self) -> str:
+        return self._path.suffix
+
+    @property
+    def suffixes(self) -> list[str]:
+        return self._path.suffixes
+
+    @property
+    def stem(self) -> str:
+        return self._path.stem
+
+    async def absolute(self) -> Self:
+        path = await to_thread.run_sync(self._path.absolute, limiter=self._limiter)
+        return type(self)(path, limiter=self._limiter)
+
+    def as_posix(self) -> str:
+        return self._path.as_posix()
+
+    def as_uri(self) -> str:
+        return self._path.as_uri()
+
+    if sys.version_info >= (3, 13):
+        parser: ClassVar[ModuleType] = pathlib.Path.parser
+
+        @classmethod
+        def from_uri(cls, uri: str, *, limiter: CapacityLimiter | None = None) -> Self:
+            return cls(pathlib.Path.from_uri(uri), limiter=limiter)
+
+        def full_match(
+            self, path_pattern: str, *, case_sensitive: bool | None = None
+        ) -> bool:
+            return self._path.full_match(path_pattern, case_sensitive=case_sensitive)
+
+        def match(
+            self, path_pattern: str, *, case_sensitive: bool | None = None
+        ) -> bool:
+            return self._path.match(path_pattern, case_sensitive=case_sensitive)
+    else:
+
+        def match(self, path_pattern: str) -> bool:
+            return self._path.match(path_pattern)
+
+    if sys.version_info >= (3, 14):
+
+        @property
+        def info(self) -> PathInfo:
+            return self._path.info
+
+        async def copy(
+            self,
+            target: str | os.PathLike[str],
+            *,
+            follow_symlinks: bool = True,
+            preserve_metadata: bool = False,
+        ) -> Self:
+            func = partial(
+                self._path.copy,
+                follow_symlinks=follow_symlinks,
+                preserve_metadata=preserve_metadata,
+            )
+            return type(self)(
+                await to_thread.run_sync(
+                    func, pathlib.Path(target), limiter=self._limiter
+                ),
+                limiter=self._limiter,
+            )
+
+        async def copy_into(
+            self,
+            target_dir: str | os.PathLike[str],
+            *,
+            follow_symlinks: bool = True,
+            preserve_metadata: bool = False,
+        ) -> Self:
+            func = partial(
+                self._path.copy_into,
+                follow_symlinks=follow_symlinks,
+                preserve_metadata=preserve_metadata,
+            )
+            return type(self)(
+                await to_thread.run_sync(
+                    func, pathlib.Path(target_dir), limiter=self._limiter
+                ),
+                limiter=self._limiter,
+            )
+
+        async def move(self, target: str | os.PathLike[str]) -> Self:
+            # Upstream does not handle anyio.Path properly as a PathLike
+            target = pathlib.Path(target)
+            return type(self)(
+                await to_thread.run_sync(
+                    self._path.move, target, limiter=self._limiter
+                ),
+                limiter=self._limiter,
+            )
+
+        async def move_into(
+            self,
+            target_dir: str | os.PathLike[str],
+        ) -> Self:
+            return type(self)(
+                await to_thread.run_sync(
+                    self._path.move_into, target_dir, limiter=self._limiter
+                ),
+                limiter=self._limiter,
+            )
+
+    def is_relative_to(self, other: str | PathLike[str]) -> bool:
+        try:
+            self.relative_to(other)
+            return True
+        except ValueError:
+            return False
+
+    async def chmod(self, mode: int, *, follow_symlinks: bool = True) -> None:
+        func = partial(os.chmod, follow_symlinks=follow_symlinks)
+        return await to_thread.run_sync(func, self._path, mode, limiter=self._limiter)
+
+    @classmethod
+    async def cwd(cls, *, limiter: CapacityLimiter | None = None) -> Self:
+        path = await to_thread.run_sync(pathlib.Path.cwd, limiter=limiter)
+        return cls(path, limiter=limiter)
+
+    async def exists(self) -> bool:
+        return await to_thread.run_sync(
+            self._path.exists, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    async def expanduser(self) -> Self:
+        return type(self)(
+            await to_thread.run_sync(
+                self._path.expanduser, abandon_on_cancel=True, limiter=self._limiter
+            ),
+            limiter=self._limiter,
+        )
+
+    if sys.version_info < (3, 12):
+        # Python 3.11 and earlier
+        def glob(self, pattern: str) -> AsyncIterator[Self]:
+            gen = self._path.glob(pattern)
+            return _PathIterator(gen, self._limiter, type(self))
+    elif (3, 12) <= sys.version_info < (3, 13):
+        # changed in Python 3.12:
+        # - The case_sensitive parameter was added.
+        def glob(
+            self,
+            pattern: str,
+            *,
+            case_sensitive: bool | None = None,
+        ) -> AsyncIterator[Self]:
+            gen = self._path.glob(pattern, case_sensitive=case_sensitive)
+            return _PathIterator(gen, self._limiter, type(self))
+    elif sys.version_info >= (3, 13):
+        # Changed in Python 3.13:
+        # - The recurse_symlinks parameter was added.
+        # - The pattern parameter accepts a path-like object.
+        def glob(  # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
+            self,
+            pattern: str | PathLike[str],
+            *,
+            case_sensitive: bool | None = None,
+            recurse_symlinks: bool = False,
+        ) -> AsyncIterator[Self]:
+            gen = self._path.glob(
+                pattern,  # type: ignore[arg-type]
+                case_sensitive=case_sensitive,
+                recurse_symlinks=recurse_symlinks,
+            )
+            return _PathIterator(gen, self._limiter, type(self))
+
+    async def group(self) -> str:
+        return await to_thread.run_sync(
+            self._path.group, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    async def hardlink_to(
+        self, target: str | bytes | PathLike[str] | PathLike[bytes]
+    ) -> None:
+        if isinstance(target, Path):
+            target = target._path
+
+        await to_thread.run_sync(os.link, target, self, limiter=self._limiter)
+
+    @classmethod
+    async def home(cls, *, limiter: CapacityLimiter | None = None) -> Self:
+        home_path = await to_thread.run_sync(pathlib.Path.home, limiter=limiter)
+        return cls(home_path, limiter=limiter)
+
+    def is_absolute(self) -> bool:
+        return self._path.is_absolute()
+
+    async def is_block_device(self) -> bool:
+        return await to_thread.run_sync(
+            self._path.is_block_device, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    async def is_char_device(self) -> bool:
+        return await to_thread.run_sync(
+            self._path.is_char_device, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    async def is_dir(self) -> bool:
+        return await to_thread.run_sync(
+            self._path.is_dir, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    async def is_fifo(self) -> bool:
+        return await to_thread.run_sync(
+            self._path.is_fifo, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    async def is_file(self) -> bool:
+        return await to_thread.run_sync(
+            self._path.is_file, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    if sys.version_info >= (3, 12):
+
+        async def is_junction(self) -> bool:
+            return await to_thread.run_sync(
+                self._path.is_junction, limiter=self._limiter
+            )
+
+    async def is_mount(self) -> bool:
+        return await to_thread.run_sync(
+            os.path.ismount, self._path, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    if sys.version_info < (3, 15):
+
+        def is_reserved(self) -> bool:
+            return self._path.is_reserved()
+
+    async def is_socket(self) -> bool:
+        return await to_thread.run_sync(
+            self._path.is_socket, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    async def is_symlink(self) -> bool:
+        return await to_thread.run_sync(
+            self._path.is_symlink, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    async def iterdir(self) -> AsyncIterator[Self]:
+        gen = (
+            self._path.iterdir()
+            if sys.version_info < (3, 13)
+            else await to_thread.run_sync(
+                self._path.iterdir, abandon_on_cancel=True, limiter=self._limiter
+            )
+        )
+        async for path in _PathIterator(gen, self._limiter, type(self)):
+            yield path
+
+    def joinpath(self, *args: str | PathLike[str]) -> Self:
+        return type(self)(self._path.joinpath(*args), limiter=self._limiter)
+
+    async def lchmod(self, mode: int) -> None:
+        await to_thread.run_sync(self._path.lchmod, mode, limiter=self._limiter)
+
+    async def lstat(self) -> os.stat_result:
+        return await to_thread.run_sync(
+            self._path.lstat, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    async def mkdir(
+        self, mode: int = 0o777, parents: bool = False, exist_ok: bool = False
+    ) -> None:
+        await to_thread.run_sync(
+            self._path.mkdir, mode, parents, exist_ok, limiter=self._limiter
+        )
+
+    @overload
+    async def open(
+        self,
+        mode: OpenBinaryMode,
+        buffering: int = ...,
+        encoding: str | None = ...,
+        errors: str | None = ...,
+        newline: str | None = ...,
+    ) -> AsyncFile[bytes]: ...
+
+    @overload
+    async def open(
+        self,
+        mode: OpenTextMode = ...,
+        buffering: int = ...,
+        encoding: str | None = ...,
+        errors: str | None = ...,
+        newline: str | None = ...,
+    ) -> AsyncFile[str]: ...
+
+    async def open(
+        self,
+        mode: str = "r",
+        buffering: int = -1,
+        encoding: str | None = None,
+        errors: str | None = None,
+        newline: str | None = None,
+    ) -> AsyncFile[Any]:
+        fp = await to_thread.run_sync(
+            self._path.open,
+            mode,
+            buffering,
+            encoding,
+            errors,
+            newline,
+            limiter=self._limiter,
+        )
+        return AsyncFile(fp, limiter=self._limiter)
+
+    async def owner(self) -> str:
+        return await to_thread.run_sync(
+            self._path.owner, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    async def read_bytes(self) -> bytes:
+        return await to_thread.run_sync(self._path.read_bytes, limiter=self._limiter)
+
+    async def read_text(
+        self, encoding: str | None = None, errors: str | None = None
+    ) -> str:
+        return await to_thread.run_sync(
+            self._path.read_text, encoding, errors, limiter=self._limiter
+        )
+
+    if sys.version_info >= (3, 12):
+
+        def relative_to(
+            self, *other: str | PathLike[str], walk_up: bool = False
+        ) -> Self:
+            # relative_to() should work with any PathLike but it doesn't
+            others = [pathlib.Path(other) for other in other]
+            return type(self)(
+                self._path.relative_to(*others, walk_up=walk_up), limiter=self._limiter
+            )
+
+    else:
+
+        def relative_to(self, *other: str | PathLike[str]) -> Self:
+            return type(self)(self._path.relative_to(*other), limiter=self._limiter)
+
+    async def readlink(self) -> Self:
+        target = await to_thread.run_sync(
+            os.readlink, self._path, limiter=self._limiter
+        )
+        return type(self)(target, limiter=self._limiter)
+
+    async def rename(self, target: str | pathlib.PurePath | Path) -> Self:
+        if isinstance(target, Path):
+            target = target._path
+
+        await to_thread.run_sync(self._path.rename, target, limiter=self._limiter)
+        return type(self)(target, limiter=self._limiter)
+
+    async def replace(self, target: str | pathlib.PurePath | Path) -> Self:
+        if isinstance(target, Path):
+            target = target._path
+
+        await to_thread.run_sync(self._path.replace, target, limiter=self._limiter)
+        return type(self)(target, limiter=self._limiter)
+
+    async def resolve(self, strict: bool = False) -> Self:
+        func = partial(self._path.resolve, strict=strict)
+        return type(self)(
+            await to_thread.run_sync(
+                func, abandon_on_cancel=True, limiter=self._limiter
+            ),
+            limiter=self._limiter,
+        )
+
+    if sys.version_info < (3, 12):
+        # Pre Python 3.12
+        def rglob(self, pattern: str) -> AsyncIterator[Self]:
+            gen = self._path.rglob(pattern)
+            return _PathIterator(gen, self._limiter, type(self))
+    elif (3, 12) <= sys.version_info < (3, 13):
+        # Changed in Python 3.12:
+        # - The case_sensitive parameter was added.
+        def rglob(
+            self, pattern: str, *, case_sensitive: bool | None = None
+        ) -> AsyncIterator[Self]:
+            gen = self._path.rglob(pattern, case_sensitive=case_sensitive)
+            return _PathIterator(gen, self._limiter, type(self))
+    elif sys.version_info >= (3, 13):
+        # Changed in Python 3.13:
+        # - The recurse_symlinks parameter was added.
+        # - The pattern parameter accepts a path-like object.
+        def rglob(  # type: ignore[misc] # mypy doesn't allow for differing signatures in a conditional block
+            self,
+            pattern: str | PathLike[str],
+            *,
+            case_sensitive: bool | None = None,
+            recurse_symlinks: bool = False,
+        ) -> AsyncIterator[Self]:
+            gen = self._path.rglob(
+                pattern,  # type: ignore[arg-type]
+                case_sensitive=case_sensitive,
+                recurse_symlinks=recurse_symlinks,
+            )
+            return _PathIterator(gen, self._limiter, type(self))
+
+    async def rmdir(self) -> None:
+        await to_thread.run_sync(self._path.rmdir, limiter=self._limiter)
+
+    async def samefile(self, other_path: str | PathLike[str]) -> bool:
+        if isinstance(other_path, Path):
+            other_path = other_path._path
+
+        return await to_thread.run_sync(
+            self._path.samefile,
+            other_path,
+            abandon_on_cancel=True,
+            limiter=self._limiter,
+        )
+
+    async def stat(self, *, follow_symlinks: bool = True) -> os.stat_result:
+        func = partial(os.stat, follow_symlinks=follow_symlinks)
+        return await to_thread.run_sync(
+            func, self._path, abandon_on_cancel=True, limiter=self._limiter
+        )
+
+    async def symlink_to(
+        self,
+        target: str | bytes | PathLike[str] | PathLike[bytes],
+        target_is_directory: bool = False,
+    ) -> None:
+        if isinstance(target, Path):
+            target = target._path
+
+        await to_thread.run_sync(
+            self._path.symlink_to, target, target_is_directory, limiter=self._limiter
+        )
+
+    async def touch(self, mode: int = 0o666, exist_ok: bool = True) -> None:
+        await to_thread.run_sync(
+            self._path.touch, mode, exist_ok, limiter=self._limiter
+        )
+
+    async def unlink(self, missing_ok: bool = False) -> None:
+        try:
+            await to_thread.run_sync(self._path.unlink, limiter=self._limiter)
+        except FileNotFoundError:
+            if not missing_ok:
+                raise
+
+    if sys.version_info >= (3, 12):
+
+        async def walk(
+            self,
+            top_down: bool = True,
+            on_error: Callable[[OSError], object] | None = None,
+            follow_symlinks: bool = False,
+        ) -> AsyncIterator[tuple[Self, list[str], list[str]]]:
+            def get_next_value() -> tuple[pathlib.Path, list[str], list[str]] | None:
+                try:
+                    return next(gen)
+                except StopIteration:
+                    return None
+
+            gen = self._path.walk(top_down, on_error, follow_symlinks)
+            while True:
+                value = await to_thread.run_sync(get_next_value, limiter=self._limiter)
+                if value is None:
+                    return
+
+                root, dirs, paths = value
+                yield type(self)(root, limiter=self._limiter), dirs, paths
+
+    def with_name(self, name: str) -> Self:
+        return type(self)(self._path.with_name(name), limiter=self._limiter)
+
+    def with_stem(self, stem: str) -> Self:
+        return type(self)(
+            self._path.with_name(stem + self._path.suffix), limiter=self._limiter
+        )
+
+    def with_suffix(self, suffix: str) -> Self:
+        return type(self)(self._path.with_suffix(suffix), limiter=self._limiter)
+
+    def with_segments(self, *pathsegments: str | PathLike[str]) -> Self:
+        return type(self)(*pathsegments, limiter=self._limiter)
+
+    async def write_bytes(self, data: ReadableBuffer) -> int:
+        return await to_thread.run_sync(
+            self._path.write_bytes, data, limiter=self._limiter
+        )
+
+    async def write_text(
+        self,
+        data: str,
+        encoding: str | None = None,
+        errors: str | None = None,
+        newline: str | None = None,
+    ) -> int:
+        return await to_thread.run_sync(
+            self._path.write_text,
+            data,
+            encoding,
+            errors,
+            newline,
+            limiter=self._limiter,
+        )
+
+
+PathLike.register(Path)

+ 18 - 0
vendor/anyio/_core/_resources.py

@@ -0,0 +1,18 @@
+from __future__ import annotations
+
+from ..abc import AsyncResource
+from ._tasks import CancelScope
+
+
+async def aclose_forcefully(resource: AsyncResource) -> None:
+    """
+    Close an asynchronous resource in a cancelled scope.
+
+    Doing this closes the resource without waiting on anything.
+
+    :param resource: the resource to close
+
+    """
+    with CancelScope() as scope:
+        scope.cancel()
+        await resource.aclose()

+ 29 - 0
vendor/anyio/_core/_signals.py

@@ -0,0 +1,29 @@
+from __future__ import annotations
+
+from collections.abc import AsyncIterator
+from contextlib import AbstractContextManager
+from signal import Signals
+
+from ._eventloop import get_async_backend
+
+
+def open_signal_receiver(
+    *signals: Signals,
+) -> AbstractContextManager[AsyncIterator[Signals]]:
+    """
+    Start receiving operating system signals.
+
+    :param signals: signals to receive (e.g. ``signal.SIGINT``)
+    :return: an asynchronous context manager for an asynchronous iterator which yields
+        signal numbers
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    .. warning:: Windows does not support signals natively so it is best to avoid
+        relying on this in cross-platform applications.
+
+    .. warning:: On asyncio, this permanently replaces any previous signal handler for
+        the given signals, as set via :meth:`~asyncio.loop.add_signal_handler`.
+
+    """
+    return get_async_backend().open_signal_receiver(*signals)

+ 1011 - 0
vendor/anyio/_core/_sockets.py

@@ -0,0 +1,1011 @@
+from __future__ import annotations
+
+import errno
+import os
+import socket
+import ssl
+import stat
+import sys
+from collections.abc import Awaitable
+from dataclasses import dataclass
+from ipaddress import IPv4Address, IPv6Address, ip_address
+from os import PathLike, chmod
+from socket import AddressFamily, SocketKind
+from typing import TYPE_CHECKING, Any, Literal, cast, overload
+
+from .. import ConnectionFailed, to_thread
+from ..abc import (
+    ByteStreamConnectable,
+    ConnectedUDPSocket,
+    ConnectedUNIXDatagramSocket,
+    IPAddressType,
+    IPSockAddrType,
+    SocketListener,
+    SocketStream,
+    UDPSocket,
+    UNIXDatagramSocket,
+    UNIXSocketStream,
+)
+from ..streams.stapled import MultiListener
+from ..streams.tls import TLSConnectable, TLSStream
+from ._eventloop import get_async_backend
+from ._resources import aclose_forcefully
+from ._synchronization import Event
+from ._tasks import create_task_group, move_on_after
+
+if TYPE_CHECKING:
+    from _typeshed import FileDescriptorLike
+else:
+    FileDescriptorLike = object
+
+if sys.version_info < (3, 11):
+    from exceptiongroup import ExceptionGroup
+
+if sys.version_info >= (3, 12):
+    from typing import override
+else:
+    from typing_extensions import override
+
+if sys.version_info < (3, 13):
+    from typing_extensions import deprecated
+else:
+    from warnings import deprecated
+
+IPPROTO_IPV6 = getattr(socket, "IPPROTO_IPV6", 41)  # https://bugs.python.org/issue29515
+
+AnyIPAddressFamily = Literal[
+    AddressFamily.AF_UNSPEC, AddressFamily.AF_INET, AddressFamily.AF_INET6
+]
+IPAddressFamily = Literal[AddressFamily.AF_INET, AddressFamily.AF_INET6]
+
+
+def idna2008_resolve(host: str) -> bytes:
+    try:
+        return host.encode("ascii")
+    except UnicodeEncodeError:
+        import idna
+
+        return idna.encode(host, uts46=True)
+
+
+# tls_hostname given
+@overload
+async def connect_tcp(
+    remote_host: IPAddressType,
+    remote_port: int,
+    *,
+    local_host: IPAddressType | None = ...,
+    local_port: int | None = ...,
+    ssl_context: ssl.SSLContext | None = ...,
+    tls_standard_compatible: bool = ...,
+    tls_hostname: str,
+    happy_eyeballs_delay: float = ...,
+) -> TLSStream: ...
+
+
+# ssl_context given
+@overload
+async def connect_tcp(
+    remote_host: IPAddressType,
+    remote_port: int,
+    *,
+    local_host: IPAddressType | None = ...,
+    local_port: int | None = ...,
+    ssl_context: ssl.SSLContext,
+    tls_standard_compatible: bool = ...,
+    tls_hostname: str | None = ...,
+    happy_eyeballs_delay: float = ...,
+) -> TLSStream: ...
+
+
+# tls=True
+@overload
+async def connect_tcp(
+    remote_host: IPAddressType,
+    remote_port: int,
+    *,
+    local_host: IPAddressType | None = ...,
+    local_port: int | None = ...,
+    tls: Literal[True],
+    ssl_context: ssl.SSLContext | None = ...,
+    tls_standard_compatible: bool = ...,
+    tls_hostname: str | None = ...,
+    happy_eyeballs_delay: float = ...,
+) -> TLSStream: ...
+
+
+# tls=False
+@overload
+async def connect_tcp(
+    remote_host: IPAddressType,
+    remote_port: int,
+    *,
+    local_host: IPAddressType | None = ...,
+    local_port: int | None = ...,
+    tls: Literal[False],
+    ssl_context: ssl.SSLContext | None = ...,
+    tls_standard_compatible: bool = ...,
+    tls_hostname: str | None = ...,
+    happy_eyeballs_delay: float = ...,
+) -> SocketStream: ...
+
+
+# No TLS arguments
+@overload
+async def connect_tcp(
+    remote_host: IPAddressType,
+    remote_port: int,
+    *,
+    local_host: IPAddressType | None = ...,
+    local_port: int | None = ...,
+    happy_eyeballs_delay: float = ...,
+) -> SocketStream: ...
+
+
+async def connect_tcp(
+    remote_host: IPAddressType,
+    remote_port: int,
+    *,
+    local_host: IPAddressType | None = None,
+    local_port: int | None = None,
+    tls: bool = False,
+    ssl_context: ssl.SSLContext | None = None,
+    tls_standard_compatible: bool = True,
+    tls_hostname: str | None = None,
+    happy_eyeballs_delay: float = 0.25,
+) -> SocketStream | TLSStream:
+    """
+    Connect to a host using the TCP protocol.
+
+    This function implements the stateless version of the Happy Eyeballs algorithm (RFC
+    6555). If ``remote_host`` is a host name that resolves to multiple IP addresses,
+    each one is tried until one connection attempt succeeds. If the first attempt does
+    not connected within 250 milliseconds, a second attempt is started using the next
+    address in the list, and so on. On IPv6 enabled systems, an IPv6 address (if
+    available) is tried first.
+
+    When the connection has been established, a TLS handshake will be done if either
+    ``ssl_context`` or ``tls_hostname`` is not ``None``, or if ``tls`` is ``True``.
+
+    :param remote_host: the IP address or host name to connect to
+    :param remote_port: port on the target host to connect to
+    :param local_host: the interface address or name to bind the socket to before
+        connecting
+    :param local_port: the local port to bind to (requires ``local_host`` to also be
+        set)
+    :param tls: ``True`` to do a TLS handshake with the connected stream and return a
+        :class:`~anyio.streams.tls.TLSStream` instead
+    :param ssl_context: the SSL context object to use (if omitted, a default context is
+        created)
+    :param tls_standard_compatible: If ``True``, performs the TLS shutdown handshake
+        before closing the stream and requires that the server does this as well.
+        Otherwise, :exc:`~ssl.SSLEOFError` may be raised during reads from the stream.
+        Some protocols, such as HTTP, require this option to be ``False``.
+        See :meth:`~ssl.SSLContext.wrap_socket` for details.
+    :param tls_hostname: host name to check the server certificate against (defaults to
+        the value of ``remote_host``)
+    :param happy_eyeballs_delay: delay (in seconds) before starting the next connection
+        attempt
+    :return: a socket stream object if no TLS handshake was done, otherwise a TLS stream
+    :raises ConnectionFailed: if the connection fails
+
+    """
+    # Placed here due to https://github.com/python/mypy/issues/7057
+    connected_stream: SocketStream | None = None
+
+    async def try_connect(remote_host: str, event: Event) -> None:
+        nonlocal connected_stream
+        try:
+            stream = await asynclib.connect_tcp(remote_host, remote_port, local_address)
+        except OSError as exc:
+            oserrors.append(exc)
+            return
+        else:
+            if connected_stream is None:
+                connected_stream = stream
+                tg.cancel_scope.cancel()
+            else:
+                await stream.aclose()
+        finally:
+            event.set()
+
+    asynclib = get_async_backend()
+    local_address: IPSockAddrType | None = None
+    family = socket.AF_UNSPEC
+    if local_host:
+        gai_res = await getaddrinfo(str(local_host), local_port)
+        family, *_, local_address = gai_res[0]
+
+    target_host = str(remote_host)
+    try:
+        addr_obj = ip_address(remote_host)
+    except ValueError:
+        addr_obj = None
+
+    if addr_obj is not None:
+        if isinstance(addr_obj, IPv6Address):
+            target_addrs = [(socket.AF_INET6, addr_obj.compressed)]
+        else:
+            target_addrs = [(socket.AF_INET, addr_obj.compressed)]
+    else:
+        # getaddrinfo() will raise an exception if name resolution fails
+        gai_res = await getaddrinfo(
+            target_host, remote_port, family=family, type=socket.SOCK_STREAM
+        )
+
+        # Organize the list so that the first address is an IPv6 address (if available)
+        # and the second one is an IPv4 addresses. The rest can be in whatever order.
+        v6_found = v4_found = False
+        target_addrs = []
+        for af, *_, sa in gai_res:
+            if af == socket.AF_INET6 and not v6_found:
+                v6_found = True
+                target_addrs.insert(0, (af, sa[0]))
+            elif af == socket.AF_INET and not v4_found and v6_found:
+                v4_found = True
+                target_addrs.insert(1, (af, sa[0]))
+            else:
+                target_addrs.append((af, sa[0]))
+
+    oserrors: list[OSError] = []
+    try:
+        async with create_task_group() as tg:
+            for _af, addr in target_addrs:
+                event = Event()
+                tg.start_soon(try_connect, addr, event)
+                with move_on_after(happy_eyeballs_delay):
+                    await event.wait()
+
+        if connected_stream is None:
+            cause = (
+                oserrors[0]
+                if len(oserrors) == 1
+                else ExceptionGroup("multiple connection attempts failed", oserrors)
+            )
+            raise OSError("All connection attempts failed") from cause
+    finally:
+        oserrors.clear()
+
+    if tls or tls_hostname or ssl_context:
+        try:
+            return await TLSStream.wrap(
+                connected_stream,
+                server_side=False,
+                hostname=tls_hostname or str(remote_host),
+                ssl_context=ssl_context,
+                standard_compatible=tls_standard_compatible,
+            )
+        except BaseException:
+            await aclose_forcefully(connected_stream)
+            raise
+
+    return connected_stream
+
+
+async def connect_unix(path: str | bytes | PathLike[Any]) -> UNIXSocketStream:
+    """
+    Connect to the given UNIX socket.
+
+    Not available on Windows.
+
+    :param path: path to the socket
+    :return: a socket stream object
+    :raises ConnectionFailed: if the connection fails
+
+    """
+    path = os.fspath(path)
+    return await get_async_backend().connect_unix(path)
+
+
+async def create_tcp_listener(
+    *,
+    local_host: IPAddressType | None = None,
+    local_port: int = 0,
+    family: AnyIPAddressFamily = socket.AddressFamily.AF_UNSPEC,
+    backlog: int = 65536,
+    reuse_port: bool = False,
+) -> MultiListener[SocketStream]:
+    """
+    Create a TCP socket listener.
+
+    :param local_port: port number to listen on
+    :param local_host: IP address of the interface to listen on. If omitted, listen on
+        all IPv4 and IPv6 interfaces. To listen on all interfaces on a specific address
+        family, use ``0.0.0.0`` for IPv4 or ``::`` for IPv6.
+    :param family: address family (used if ``local_host`` was omitted)
+    :param backlog: maximum number of queued incoming connections (up to a maximum of
+        2**16, or 65536)
+    :param reuse_port: ``True`` to allow multiple sockets to bind to the same
+        address/port (not supported on Windows)
+    :return: a multi-listener object containing one or more socket listeners
+    :raises OSError: if there's an error creating a socket, or binding to one or more
+        interfaces failed
+
+    """
+    asynclib = get_async_backend()
+    backlog = min(backlog, 65536)
+    local_host = str(local_host) if local_host is not None else None
+
+    def setup_raw_socket(
+        fam: AddressFamily,
+        bind_addr: tuple[str, int] | tuple[str, int, int, int],
+        *,
+        v6only: bool = True,
+    ) -> socket.socket:
+        sock = socket.socket(fam)
+        try:
+            sock.setblocking(False)
+
+            if fam == AddressFamily.AF_INET6:
+                sock.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, v6only)
+
+            # For Windows, enable exclusive address use. For others, enable address
+            # reuse.
+            if sys.platform == "win32":
+                sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
+            else:
+                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+            if reuse_port:
+                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+
+            # Workaround for #554
+            if fam == socket.AF_INET6 and "%" in bind_addr[0]:
+                addr, scope_id = bind_addr[0].split("%", 1)
+                bind_addr = (addr, bind_addr[1], 0, int(scope_id))
+
+            sock.bind(bind_addr)
+            sock.listen(backlog)
+        except BaseException:
+            sock.close()
+            raise
+
+        return sock
+
+    # We passing type=0 on non-Windows platforms as a workaround for a uvloop bug
+    # where we don't get the correct scope ID for IPv6 link-local addresses when passing
+    # type=socket.SOCK_STREAM to getaddrinfo():
+    # https://github.com/MagicStack/uvloop/issues/539
+    gai_res = await getaddrinfo(
+        local_host,
+        local_port,
+        family=family,
+        type=socket.SOCK_STREAM if sys.platform == "win32" else 0,
+        flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG,
+    )
+
+    # The set comprehension is here to work around a glibc bug:
+    # https://sourceware.org/bugzilla/show_bug.cgi?id=14969
+    sockaddrs = sorted({res for res in gai_res if res[1] == SocketKind.SOCK_STREAM})
+
+    # Special case for dual-stack binding on the "any" interface
+    if (
+        local_host is None
+        and family == AddressFamily.AF_UNSPEC
+        and socket.has_dualstack_ipv6()
+        and any(fam == AddressFamily.AF_INET6 for fam, *_ in gai_res)
+    ):
+        raw_socket = setup_raw_socket(
+            AddressFamily.AF_INET6, ("::", local_port), v6only=False
+        )
+        listener = asynclib.create_tcp_listener(raw_socket)
+        return MultiListener([listener])
+
+    errors: list[OSError] = []
+    try:
+        for _ in range(len(sockaddrs)):
+            listeners: list[SocketListener] = []
+            bound_ephemeral_port = local_port
+            try:
+                for fam, *_, sockaddr in sockaddrs:
+                    sockaddr = sockaddr[0], bound_ephemeral_port, *sockaddr[2:]
+                    raw_socket = setup_raw_socket(fam, sockaddr)
+
+                    # Store the assigned port if an ephemeral port was requested, so
+                    # we'll bind to the same port on all interfaces
+                    if local_port == 0 and len(gai_res) > 1:
+                        bound_ephemeral_port = raw_socket.getsockname()[1]
+
+                    listeners.append(asynclib.create_tcp_listener(raw_socket))
+            except BaseException as exc:
+                for listener in listeners:
+                    await listener.aclose()
+
+                # If an ephemeral port was requested but binding the assigned port
+                # failed for another interface, rotate the address list and try again
+                if (
+                    isinstance(exc, OSError)
+                    and exc.errno == errno.EADDRINUSE
+                    and local_port == 0
+                    and bound_ephemeral_port
+                ):
+                    errors.append(exc)
+                    sockaddrs.append(sockaddrs.pop(0))
+                    continue
+
+                raise
+
+            return MultiListener(listeners)
+
+        raise OSError(
+            f"Could not create {len(sockaddrs)} listeners with a consistent port"
+        ) from ExceptionGroup("Several bind attempts failed", errors)
+    finally:
+        del errors  # Prevent reference cycles
+
+
+async def create_unix_listener(
+    path: str | bytes | PathLike[Any],
+    *,
+    mode: int | None = None,
+    backlog: int = 65536,
+) -> SocketListener:
+    """
+    Create a UNIX socket listener.
+
+    Not available on Windows.
+
+    :param path: path of the socket
+    :param mode: permissions to set on the socket
+    :param backlog: maximum number of queued incoming connections (up to a maximum of
+        2**16, or 65536)
+    :return: a listener object
+
+    .. versionchanged:: 3.0
+        If a socket already exists on the file system in the given path, it will be
+        removed first.
+
+    """
+    backlog = min(backlog, 65536)
+    raw_socket = await setup_unix_local_socket(path, mode, socket.SOCK_STREAM)
+    try:
+        raw_socket.listen(backlog)
+        return get_async_backend().create_unix_listener(raw_socket)
+    except BaseException:
+        raw_socket.close()
+        raise
+
+
+async def create_udp_socket(
+    family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC,
+    *,
+    local_host: IPAddressType | None = None,
+    local_port: int = 0,
+    reuse_port: bool = False,
+) -> UDPSocket:
+    """
+    Create a UDP socket.
+
+    If ``port`` has been given, the socket will be bound to this port on the local
+    machine, making this socket suitable for providing UDP based services.
+
+    :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically
+        determined from ``local_host`` if omitted
+    :param local_host: IP address or host name of the local interface to bind to
+    :param local_port: local port to bind to
+    :param reuse_port: ``True`` to allow multiple sockets to bind to the same
+        address/port (not supported on Windows)
+    :return: a UDP socket
+
+    """
+    if family is AddressFamily.AF_UNSPEC and not local_host:
+        raise ValueError('Either "family" or "local_host" must be given')
+
+    if local_host:
+        gai_res = await getaddrinfo(
+            str(local_host),
+            local_port,
+            family=family,
+            type=socket.SOCK_DGRAM,
+            flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG,
+        )
+        family = cast(AnyIPAddressFamily, gai_res[0][0])
+        local_address = gai_res[0][-1]
+    elif family is AddressFamily.AF_INET6:
+        local_address = ("::", 0)
+    else:
+        local_address = ("0.0.0.0", 0)
+
+    sock = await get_async_backend().create_udp_socket(
+        family, local_address, None, reuse_port
+    )
+    return cast(UDPSocket, sock)
+
+
+async def create_connected_udp_socket(
+    remote_host: IPAddressType,
+    remote_port: int,
+    *,
+    family: AnyIPAddressFamily = AddressFamily.AF_UNSPEC,
+    local_host: IPAddressType | None = None,
+    local_port: int = 0,
+    reuse_port: bool = False,
+) -> ConnectedUDPSocket:
+    """
+    Create a connected UDP socket.
+
+    Connected UDP sockets can only communicate with the specified remote host/port, an
+    any packets sent from other sources are dropped.
+
+    :param remote_host: remote host to set as the default target
+    :param remote_port: port on the remote host to set as the default target
+    :param family: address family (``AF_INET`` or ``AF_INET6``) – automatically
+        determined from ``local_host`` or ``remote_host`` if omitted
+    :param local_host: IP address or host name of the local interface to bind to
+    :param local_port: local port to bind to
+    :param reuse_port: ``True`` to allow multiple sockets to bind to the same
+        address/port (not supported on Windows)
+    :return: a connected UDP socket
+
+    """
+    local_address = None
+    if local_host:
+        gai_res = await getaddrinfo(
+            str(local_host),
+            local_port,
+            family=family,
+            type=socket.SOCK_DGRAM,
+            flags=socket.AI_PASSIVE | socket.AI_ADDRCONFIG,
+        )
+        family = cast(AnyIPAddressFamily, gai_res[0][0])
+        local_address = gai_res[0][-1]
+
+    gai_res = await getaddrinfo(
+        str(remote_host), remote_port, family=family, type=socket.SOCK_DGRAM
+    )
+    family = cast(AnyIPAddressFamily, gai_res[0][0])
+    remote_address = gai_res[0][-1]
+
+    sock = await get_async_backend().create_udp_socket(
+        family, local_address, remote_address, reuse_port
+    )
+    return cast(ConnectedUDPSocket, sock)
+
+
+async def create_unix_datagram_socket(
+    *,
+    local_path: None | str | bytes | PathLike[Any] = None,
+    local_mode: int | None = None,
+) -> UNIXDatagramSocket:
+    """
+    Create a UNIX datagram socket.
+
+    Not available on Windows.
+
+    If ``local_path`` has been given, the socket will be bound to this path, making this
+    socket suitable for receiving datagrams from other processes. Other processes can
+    send datagrams to this socket only if ``local_path`` is set.
+
+    If a socket already exists on the file system in the ``local_path``, it will be
+    removed first.
+
+    :param local_path: the path on which to bind to
+    :param local_mode: permissions to set on the local socket
+    :return: a UNIX datagram socket
+
+    """
+    raw_socket = await setup_unix_local_socket(
+        local_path, local_mode, socket.SOCK_DGRAM
+    )
+    return await get_async_backend().create_unix_datagram_socket(raw_socket, None)
+
+
+async def create_connected_unix_datagram_socket(
+    remote_path: str | bytes | PathLike[Any],
+    *,
+    local_path: None | str | bytes | PathLike[Any] = None,
+    local_mode: int | None = None,
+) -> ConnectedUNIXDatagramSocket:
+    """
+    Create a connected UNIX datagram socket.
+
+    Connected datagram sockets can only communicate with the specified remote path.
+
+    If ``local_path`` has been given, the socket will be bound to this path, making
+    this socket suitable for receiving datagrams from other processes. Other processes
+    can send datagrams to this socket only if ``local_path`` is set.
+
+    If a socket already exists on the file system in the ``local_path``, it will be
+    removed first.
+
+    :param remote_path: the path to set as the default target
+    :param local_path: the path on which to bind to
+    :param local_mode: permissions to set on the local socket
+    :return: a connected UNIX datagram socket
+
+    """
+    remote_path = os.fspath(remote_path)
+    raw_socket = await setup_unix_local_socket(
+        local_path, local_mode, socket.SOCK_DGRAM
+    )
+    return await get_async_backend().create_unix_datagram_socket(
+        raw_socket, remote_path
+    )
+
+
+async def getaddrinfo(
+    host: bytes | str | None,
+    port: str | int | None,
+    *,
+    family: int | AddressFamily = 0,
+    type: int | SocketKind = 0,
+    proto: int = 0,
+    flags: int = 0,
+) -> list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int]]]:
+    """
+    Look up a numeric IP address given a host name.
+
+    Internationalized domain names are translated according to the (non-transitional)
+    IDNA 2008 standard.
+
+    .. note:: 4-tuple IPv6 socket addresses are automatically converted to 2-tuples of
+        (host, port), unlike what :func:`socket.getaddrinfo` does.
+
+    :param host: host name
+    :param port: port number
+    :param family: socket family (`'AF_INET``, ...)
+    :param type: socket type (``SOCK_STREAM``, ...)
+    :param proto: protocol number
+    :param flags: flags to pass to upstream ``getaddrinfo()``
+    :return: list of tuples containing (family, type, proto, canonname, sockaddr)
+
+    .. seealso:: :func:`socket.getaddrinfo`
+
+    """
+    # Handle unicode hostnames
+    encoded_host = idna2008_resolve(host) if isinstance(host, str) else host
+    gai_res = await get_async_backend().getaddrinfo(
+        encoded_host, port, family=family, type=type, proto=proto, flags=flags
+    )
+    return [
+        (family, type, proto, canonname, convert_ipv6_sockaddr(sockaddr))
+        for family, type, proto, canonname, sockaddr in gai_res
+        # filter out IPv6 results when IPv6 is disabled
+        if not isinstance(sockaddr[0], int)
+    ]
+
+
+def getnameinfo(sockaddr: IPSockAddrType, flags: int = 0) -> Awaitable[tuple[str, str]]:
+    """
+    Look up the host name of an IP address.
+
+    :param sockaddr: socket address (e.g. (ipaddress, port) for IPv4)
+    :param flags: flags to pass to upstream ``getnameinfo()``
+    :return: a tuple of (host name, service name)
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    .. seealso:: :func:`socket.getnameinfo`
+
+    """
+    return get_async_backend().getnameinfo(sockaddr, flags)
+
+
+@deprecated("This function is deprecated; use `wait_readable` instead")
+def wait_socket_readable(sock: socket.socket) -> Awaitable[None]:
+    """
+    .. deprecated:: 4.7.0
+       Use :func:`wait_readable` instead.
+
+    Wait until the given socket has data to be read.
+
+    .. warning:: Only use this on raw sockets that have not been wrapped by any higher
+        level constructs like socket streams!
+
+    :param sock: a socket object
+    :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the
+        socket to become readable
+    :raises ~anyio.BusyResourceError: if another task is already waiting for the socket
+        to become readable
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    return get_async_backend().wait_readable(sock.fileno())
+
+
+@deprecated("This function is deprecated; use `wait_writable` instead")
+def wait_socket_writable(sock: socket.socket) -> Awaitable[None]:
+    """
+    .. deprecated:: 4.7.0
+       Use :func:`wait_writable` instead.
+
+    Wait until the given socket can be written to.
+
+    This does **NOT** work on Windows when using the asyncio backend with a proactor
+    event loop (default on py3.8+).
+
+    .. warning:: Only use this on raw sockets that have not been wrapped by any higher
+        level constructs like socket streams!
+
+    :param sock: a socket object
+    :raises ~anyio.ClosedResourceError: if the socket was closed while waiting for the
+        socket to become writable
+    :raises ~anyio.BusyResourceError: if another task is already waiting for the socket
+        to become writable
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    return get_async_backend().wait_writable(sock.fileno())
+
+
+def wait_readable(obj: FileDescriptorLike) -> Awaitable[None]:
+    """
+    Wait until the given object has data to be read.
+
+    On Unix systems, ``obj`` must either be an integer file descriptor, or else an
+    object with a ``.fileno()`` method which returns an integer file descriptor. Any
+    kind of file descriptor can be passed, though the exact semantics will depend on
+    your kernel. For example, this probably won't do anything useful for on-disk files.
+
+    On Windows systems, ``obj`` must either be an integer ``SOCKET`` handle, or else an
+    object with a ``.fileno()`` method which returns an integer ``SOCKET`` handle. File
+    descriptors aren't supported, and neither are handles that refer to anything besides
+    a ``SOCKET``.
+
+    On backends where this functionality is not natively provided (asyncio
+    ``ProactorEventLoop`` on Windows), it is provided using a separate selector thread
+    which is set to shut down when the interpreter shuts down.
+
+    .. warning:: Don't use this on raw sockets that have been wrapped by any higher
+        level constructs like socket streams!
+
+    :param obj: an object with a ``.fileno()`` method or an integer handle
+    :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the
+        object to become readable
+    :raises ~anyio.BusyResourceError: if another task is already waiting for the object
+        to become readable
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    return get_async_backend().wait_readable(obj)
+
+
+def wait_writable(obj: FileDescriptorLike) -> Awaitable[None]:
+    """
+    Wait until the given object can be written to.
+
+    :param obj: an object with a ``.fileno()`` method or an integer handle
+    :raises ~anyio.ClosedResourceError: if the object was closed while waiting for the
+        object to become writable
+    :raises ~anyio.BusyResourceError: if another task is already waiting for the object
+        to become writable
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    .. seealso:: See the documentation of :func:`wait_readable` for the definition of
+       ``obj`` and notes on backend compatibility.
+
+    .. warning:: Don't use this on raw sockets that have been wrapped by any higher
+        level constructs like socket streams!
+
+    """
+    return get_async_backend().wait_writable(obj)
+
+
+def notify_closing(obj: FileDescriptorLike) -> None:
+    """
+    Call this before closing a file descriptor (on Unix) or socket (on
+    Windows). This will cause any `wait_readable` or `wait_writable`
+    calls on the given object to immediately wake up and raise
+    `~anyio.ClosedResourceError`.
+
+    This doesn't actually close the object – you still have to do that
+    yourself afterwards. Also, you want to be careful to make sure no
+    new tasks start waiting on the object in between when you call this
+    and when it's actually closed. So to close something properly, you
+    usually want to do these steps in order:
+
+    1. Explicitly mark the object as closed, so that any new attempts
+       to use it will abort before they start.
+    2. Call `notify_closing` to wake up any already-existing users.
+    3. Actually close the object.
+
+    It's also possible to do them in a different order if that's more
+    convenient, *but only if* you make sure not to have any checkpoints in
+    between the steps. This way they all happen in a single atomic
+    step, so other tasks won't be able to tell what order they happened
+    in anyway.
+
+    :param obj: an object with a ``.fileno()`` method or an integer handle
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    get_async_backend().notify_closing(obj)
+
+
+#
+# Private API
+#
+
+
+def convert_ipv6_sockaddr(
+    sockaddr: tuple[str, int, int, int] | tuple[str, int],
+) -> tuple[str, int]:
+    """
+    Convert a 4-tuple IPv6 socket address to a 2-tuple (address, port) format.
+
+    If the scope ID is nonzero, it is added to the address, separated with ``%``.
+    Otherwise the flow id and scope id are simply cut off from the tuple.
+    Any other kinds of socket addresses are returned as-is.
+
+    :param sockaddr: the result of :meth:`~socket.socket.getsockname`
+    :return: the converted socket address
+
+    """
+    # This is more complicated than it should be because of MyPy
+    if isinstance(sockaddr, tuple) and len(sockaddr) == 4:
+        host, port, flowinfo, scope_id = sockaddr
+        if scope_id:
+            # PyPy (as of v7.3.11) leaves the interface name in the result, so
+            # we discard it and only get the scope ID from the end
+            # (https://foss.heptapod.net/pypy/pypy/-/issues/3938)
+            host = host.split("%")[0]
+
+            # Add scope_id to the address
+            return f"{host}%{scope_id}", port
+        else:
+            return host, port
+    else:
+        return sockaddr
+
+
+async def setup_unix_local_socket(
+    path: None | str | bytes | PathLike[Any],
+    mode: int | None,
+    socktype: int,
+) -> socket.socket:
+    """
+    Create a UNIX local socket object, deleting the socket at the given path if it
+    exists.
+
+    Not available on Windows.
+
+    :param path: path of the socket
+    :param mode: permissions to set on the socket
+    :param socktype: socket.SOCK_STREAM or socket.SOCK_DGRAM
+
+    """
+    path_str: str | None
+    if path is not None:
+        path_str = os.fsdecode(path)
+
+        # Linux abstract namespace sockets aren't backed by a concrete file so skip stat call
+        if not path_str.startswith("\0"):
+            # Copied from pathlib...
+            try:
+                stat_result = os.stat(path)
+            except OSError as e:
+                if e.errno not in (
+                    errno.ENOENT,
+                    errno.ENOTDIR,
+                    errno.EBADF,
+                    errno.ELOOP,
+                ):
+                    raise
+            else:
+                if stat.S_ISSOCK(stat_result.st_mode):
+                    os.unlink(path)
+    else:
+        path_str = None
+
+    raw_socket = socket.socket(socket.AF_UNIX, socktype)
+    raw_socket.setblocking(False)
+
+    if path_str is not None:
+        try:
+            await to_thread.run_sync(raw_socket.bind, path_str, abandon_on_cancel=True)
+            if mode is not None:
+                await to_thread.run_sync(chmod, path_str, mode, abandon_on_cancel=True)
+        except BaseException:
+            raw_socket.close()
+            raise
+
+    return raw_socket
+
+
+@dataclass
+class TCPConnectable(ByteStreamConnectable):
+    """
+    Connects to a TCP server at the given host and port.
+
+    :param host: host name or IP address of the server
+    :param port: TCP port number of the server
+    """
+
+    host: str | IPv4Address | IPv6Address
+    port: int
+
+    def __post_init__(self) -> None:
+        if self.port < 1 or self.port > 65535:
+            raise ValueError("TCP port number out of range")
+
+    @override
+    async def connect(self) -> SocketStream:
+        try:
+            return await connect_tcp(self.host, self.port)
+        except OSError as exc:
+            raise ConnectionFailed(
+                f"error connecting to {self.host}:{self.port}: {exc}"
+            ) from exc
+
+
+@dataclass
+class UNIXConnectable(ByteStreamConnectable):
+    """
+    Connects to a UNIX domain socket at the given path.
+
+    :param path: the file system path of the socket
+    """
+
+    path: str | bytes | PathLike[str] | PathLike[bytes]
+
+    @override
+    async def connect(self) -> UNIXSocketStream:
+        try:
+            return await connect_unix(self.path)
+        except OSError as exc:
+            raise ConnectionFailed(f"error connecting to {self.path!r}: {exc}") from exc
+
+
+def as_connectable(
+    remote: ByteStreamConnectable
+    | tuple[str | IPv4Address | IPv6Address, int]
+    | str
+    | bytes
+    | PathLike[str],
+    /,
+    *,
+    tls: bool = False,
+    ssl_context: ssl.SSLContext | None = None,
+    tls_hostname: str | None = None,
+    tls_standard_compatible: bool = True,
+) -> ByteStreamConnectable:
+    """
+    Return a byte stream connectable from the given object.
+
+    If a bytestream connectable is given, it is returned unchanged.
+    If a tuple of (host, port) is given, a TCP connectable is returned.
+    If a string or bytes path is given, a UNIX connectable is returned.
+
+    If ``tls=True``, the connectable will be wrapped in a
+    :class:`~.streams.tls.TLSConnectable`.
+
+    :param remote: a connectable, a tuple of (host, port) or a path to a UNIX socket
+    :param tls: if ``True``, wrap the plaintext connectable in a
+        :class:`~.streams.tls.TLSConnectable`, using the provided TLS settings)
+    :param ssl_context: if ``tls=True``, the SSLContext object to use  (if not provided,
+        a secure default will be created)
+    :param tls_hostname: if ``tls=True``, host name of the server to use for checking
+        the server certificate (defaults to the host portion of the address for TCP
+        connectables)
+    :param tls_standard_compatible: if ``False`` and ``tls=True``, makes the TLS stream
+        skip the closing handshake when closing the connection, so it won't raise an
+        exception if the server does the same
+
+    """
+    connectable: TCPConnectable | UNIXConnectable | TLSConnectable
+    if isinstance(remote, ByteStreamConnectable):
+        return remote
+    elif isinstance(remote, tuple) and len(remote) == 2:
+        connectable = TCPConnectable(*remote)
+    elif isinstance(remote, (str, bytes, PathLike)):
+        connectable = UNIXConnectable(remote)
+    else:
+        raise TypeError(f"cannot convert {remote!r} to a connectable")
+
+    if tls:
+        if not tls_hostname and isinstance(connectable, TCPConnectable):
+            tls_hostname = str(connectable.host)
+
+        connectable = TLSConnectable(
+            connectable,
+            ssl_context=ssl_context,
+            hostname=tls_hostname,
+            standard_compatible=tls_standard_compatible,
+        )
+
+    return connectable

+ 52 - 0
vendor/anyio/_core/_streams.py

@@ -0,0 +1,52 @@
+from __future__ import annotations
+
+import math
+from typing import TypeVar
+from warnings import warn
+
+from ..streams.memory import (
+    MemoryObjectReceiveStream,
+    MemoryObjectSendStream,
+    _MemoryObjectStreamState,
+)
+
+T_Item = TypeVar("T_Item")
+
+
+class create_memory_object_stream(
+    tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]],
+):
+    """
+    Create a memory object stream.
+
+    The stream's item type can be annotated like
+    :func:`create_memory_object_stream[T_Item]`.
+
+    :param max_buffer_size: number of items held in the buffer until ``send()`` starts
+        blocking
+    :param item_type: old way of marking the streams with the right generic type for
+        static typing (does nothing on AnyIO 4)
+
+        .. deprecated:: 4.0
+          Use ``create_memory_object_stream[YourItemType](...)`` instead.
+    :return: a tuple of (send stream, receive stream)
+
+    """
+
+    def __new__(  # type: ignore[misc]
+        cls, max_buffer_size: float = 0, item_type: object = None
+    ) -> tuple[MemoryObjectSendStream[T_Item], MemoryObjectReceiveStream[T_Item]]:
+        if max_buffer_size != math.inf and not isinstance(max_buffer_size, int):
+            raise ValueError("max_buffer_size must be either an integer or math.inf")
+        if max_buffer_size < 0:
+            raise ValueError("max_buffer_size cannot be negative")
+        if item_type is not None:
+            warn(
+                "The item_type argument has been deprecated in AnyIO 4.0. "
+                "Use create_memory_object_stream[YourItemType](...) instead.",
+                DeprecationWarning,
+                stacklevel=2,
+            )
+
+        state = _MemoryObjectStreamState[T_Item](max_buffer_size)
+        return (MemoryObjectSendStream(state), MemoryObjectReceiveStream(state))

+ 196 - 0
vendor/anyio/_core/_subprocesses.py

@@ -0,0 +1,196 @@
+from __future__ import annotations
+
+from collections.abc import AsyncIterable, Iterable, Mapping, Sequence
+from io import BytesIO
+from os import PathLike
+from subprocess import PIPE, CalledProcessError, CompletedProcess
+from typing import IO, Any, TypeAlias, cast
+
+from ..abc import Process
+from ._eventloop import get_async_backend
+from ._tasks import create_task_group
+
+StrOrBytesPath: TypeAlias = str | bytes | PathLike[str] | PathLike[bytes]
+
+
+async def run_process(
+    command: StrOrBytesPath | Sequence[StrOrBytesPath],
+    *,
+    input: bytes | None = None,
+    stdin: int | IO[Any] | None = None,
+    stdout: int | IO[Any] | None = PIPE,
+    stderr: int | IO[Any] | None = PIPE,
+    check: bool = True,
+    cwd: StrOrBytesPath | None = None,
+    env: Mapping[str, str] | None = None,
+    startupinfo: Any = None,
+    creationflags: int = 0,
+    start_new_session: bool = False,
+    pass_fds: Sequence[int] = (),
+    user: str | int | None = None,
+    group: str | int | None = None,
+    extra_groups: Iterable[str | int] | None = None,
+    umask: int = -1,
+) -> CompletedProcess[bytes]:
+    """
+    Run an external command in a subprocess and wait until it completes.
+
+    .. seealso:: :func:`subprocess.run`
+
+    :param command: either a string to pass to the shell, or an iterable of strings
+        containing the executable name or path and its arguments
+    :param input: bytes passed to the standard input of the subprocess
+    :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
+        a file-like object, or `None`; ``input`` overrides this
+    :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
+        a file-like object, or `None`
+    :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
+        :data:`subprocess.STDOUT`, a file-like object, or `None`
+    :param check: if ``True``, raise :exc:`~subprocess.CalledProcessError` if the
+        process terminates with a return code other than 0
+    :param cwd: If not ``None``, change the working directory to this before running the
+        command
+    :param env: if not ``None``, this mapping replaces the inherited environment
+        variables from the parent process
+    :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used
+        to specify process startup parameters (Windows only)
+    :param creationflags: flags that can be used to control the creation of the
+        subprocess (see :class:`subprocess.Popen` for the specifics)
+    :param start_new_session: if ``true`` the setsid() system call will be made in the
+        child process prior to the execution of the subprocess. (POSIX only)
+    :param pass_fds: sequence of file descriptors to keep open between the parent and
+        child processes. (POSIX only)
+    :param user: effective user to run the process as (Python >= 3.9, POSIX only)
+    :param group: effective group to run the process as (Python >= 3.9, POSIX only)
+    :param extra_groups: supplementary groups to set in the subprocess (Python >= 3.9,
+        POSIX only)
+    :param umask: if not negative, this umask is applied in the child process before
+        running the given command (Python >= 3.9, POSIX only)
+    :return: an object representing the completed process
+    :raises ~subprocess.CalledProcessError: if ``check`` is ``True`` and the process
+        exits with a nonzero return code
+
+    """
+
+    async def drain_stream(stream: AsyncIterable[bytes], index: int) -> None:
+        buffer = BytesIO()
+        async for chunk in stream:
+            buffer.write(chunk)
+
+        stream_contents[index] = buffer.getvalue()
+
+    if stdin is not None and input is not None:
+        raise ValueError("only one of stdin and input is allowed")
+
+    async with await open_process(
+        command,
+        stdin=PIPE if input else stdin,
+        stdout=stdout,
+        stderr=stderr,
+        cwd=cwd,
+        env=env,
+        startupinfo=startupinfo,
+        creationflags=creationflags,
+        start_new_session=start_new_session,
+        pass_fds=pass_fds,
+        user=user,
+        group=group,
+        extra_groups=extra_groups,
+        umask=umask,
+    ) as process:
+        stream_contents: list[bytes | None] = [None, None]
+        async with create_task_group() as tg:
+            if process.stdout:
+                tg.start_soon(drain_stream, process.stdout, 0)
+
+            if process.stderr:
+                tg.start_soon(drain_stream, process.stderr, 1)
+
+            if process.stdin and input:
+                await process.stdin.send(input)
+                await process.stdin.aclose()
+
+            await process.wait()
+
+    output, errors = stream_contents
+    if check and process.returncode != 0:
+        raise CalledProcessError(cast(int, process.returncode), command, output, errors)
+
+    return CompletedProcess(command, cast(int, process.returncode), output, errors)
+
+
+async def open_process(
+    command: StrOrBytesPath | Sequence[StrOrBytesPath],
+    *,
+    stdin: int | IO[Any] | None = PIPE,
+    stdout: int | IO[Any] | None = PIPE,
+    stderr: int | IO[Any] | None = PIPE,
+    cwd: StrOrBytesPath | None = None,
+    env: Mapping[str, str] | None = None,
+    startupinfo: Any = None,
+    creationflags: int = 0,
+    start_new_session: bool = False,
+    pass_fds: Sequence[int] = (),
+    user: str | int | None = None,
+    group: str | int | None = None,
+    extra_groups: Iterable[str | int] | None = None,
+    umask: int = -1,
+) -> Process:
+    """
+    Start an external command in a subprocess.
+
+    .. seealso:: :class:`subprocess.Popen`
+
+    :param command: either a string to pass to the shell, or an iterable of strings
+        containing the executable name or path and its arguments
+    :param stdin: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`, a
+        file-like object, or ``None``
+    :param stdout: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
+        a file-like object, or ``None``
+    :param stderr: one of :data:`subprocess.PIPE`, :data:`subprocess.DEVNULL`,
+        :data:`subprocess.STDOUT`, a file-like object, or ``None``
+    :param cwd: If not ``None``, the working directory is changed before executing
+    :param env: If env is not ``None``, it must be a mapping that defines the
+        environment variables for the new process
+    :param creationflags: flags that can be used to control the creation of the
+        subprocess (see :class:`subprocess.Popen` for the specifics)
+    :param startupinfo: an instance of :class:`subprocess.STARTUPINFO` that can be used
+        to specify process startup parameters (Windows only)
+    :param start_new_session: if ``true`` the setsid() system call will be made in the
+        child process prior to the execution of the subprocess. (POSIX only)
+    :param pass_fds: sequence of file descriptors to keep open between the parent and
+        child processes. (POSIX only)
+    :param user: effective user to run the process as (POSIX only)
+    :param group: effective group to run the process as (POSIX only)
+    :param extra_groups: supplementary groups to set in the subprocess (POSIX only)
+    :param umask: if not negative, this umask is applied in the child process before
+        running the given command (POSIX only)
+    :return: an asynchronous process object
+
+    """
+    kwargs: dict[str, Any] = {}
+    if user is not None:
+        kwargs["user"] = user
+
+    if group is not None:
+        kwargs["group"] = group
+
+    if extra_groups is not None:
+        kwargs["extra_groups"] = extra_groups
+
+    if umask >= 0:
+        kwargs["umask"] = umask
+
+    return await get_async_backend().open_process(
+        command,
+        stdin=stdin,
+        stdout=stdout,
+        stderr=stderr,
+        cwd=cwd,
+        env=env,
+        startupinfo=startupinfo,
+        creationflags=creationflags,
+        start_new_session=start_new_session,
+        pass_fds=pass_fds,
+        **kwargs,
+    )

+ 772 - 0
vendor/anyio/_core/_synchronization.py

@@ -0,0 +1,772 @@
+from __future__ import annotations
+
+import math
+from collections import deque
+from collections.abc import Callable
+from dataclasses import dataclass
+from types import TracebackType
+from typing import TypeVar
+
+from ..lowlevel import checkpoint_if_cancelled
+from ._eventloop import get_async_backend
+from ._exceptions import BusyResourceError, NoEventLoopError
+from ._tasks import CancelScope
+from ._testing import TaskInfo, get_current_task
+
+T = TypeVar("T")
+
+
+@dataclass(frozen=True)
+class EventStatistics:
+    """
+    :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Event.wait`
+    """
+
+    tasks_waiting: int
+
+
+@dataclass(frozen=True)
+class CapacityLimiterStatistics:
+    """
+    :ivar int borrowed_tokens: number of tokens currently borrowed by tasks
+    :ivar float total_tokens: total number of available tokens
+    :ivar tuple borrowers: tasks or other objects currently holding tokens borrowed from
+        this limiter
+    :ivar int tasks_waiting: number of tasks waiting on
+        :meth:`~.CapacityLimiter.acquire` or
+        :meth:`~.CapacityLimiter.acquire_on_behalf_of`
+    """
+
+    borrowed_tokens: int
+    total_tokens: float
+    borrowers: tuple[object, ...]
+    tasks_waiting: int
+
+
+@dataclass(frozen=True)
+class LockStatistics:
+    """
+    :ivar bool locked: flag indicating if this lock is locked or not
+    :ivar ~anyio.TaskInfo owner: task currently holding the lock (or ``None`` if the
+        lock is not held by any task)
+    :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Lock.acquire`
+    """
+
+    locked: bool
+    owner: TaskInfo | None
+    tasks_waiting: int
+
+
+@dataclass(frozen=True)
+class ConditionStatistics:
+    """
+    :ivar int tasks_waiting: number of tasks blocked on :meth:`~.Condition.wait`
+    :ivar ~anyio.LockStatistics lock_statistics: statistics of the underlying
+        :class:`~.Lock`
+    """
+
+    tasks_waiting: int
+    lock_statistics: LockStatistics
+
+
+@dataclass(frozen=True)
+class SemaphoreStatistics:
+    """
+    :ivar int tasks_waiting: number of tasks waiting on :meth:`~.Semaphore.acquire`
+
+    """
+
+    tasks_waiting: int
+
+
+class Event:
+    __slots__ = ("__weakref__",)
+
+    def __new__(cls) -> Event:
+        try:
+            return get_async_backend().create_event()
+        except NoEventLoopError:
+            return EventAdapter()
+
+    def set(self) -> None:
+        """Set the flag, notifying all listeners."""
+        raise NotImplementedError
+
+    def is_set(self) -> bool:
+        """Return ``True`` if the flag is set, ``False`` if not."""
+        raise NotImplementedError
+
+    async def wait(self) -> None:
+        """
+        Wait until the flag has been set.
+
+        If the flag has already been set when this method is called, it returns
+        immediately.
+
+        """
+        raise NotImplementedError
+
+    def statistics(self) -> EventStatistics:
+        """Return statistics about the current state of this event."""
+        raise NotImplementedError
+
+
+class EventAdapter(Event):
+    __slots__ = "_internal_event", "_is_set"
+
+    def __new__(cls) -> EventAdapter:
+        return object.__new__(cls)
+
+    def __init__(self) -> None:
+        self._internal_event: Event | None = None
+        self._is_set = False
+
+    @property
+    def _event(self) -> Event:
+        if self._internal_event is None:
+            self._internal_event = get_async_backend().create_event()
+            if self._is_set:
+                self._internal_event.set()
+
+        return self._internal_event
+
+    def set(self) -> None:
+        if self._internal_event is None:
+            self._is_set = True
+        else:
+            self._event.set()
+
+    def is_set(self) -> bool:
+        if self._internal_event is None:
+            return self._is_set
+
+        return self._internal_event.is_set()
+
+    async def wait(self) -> None:
+        await self._event.wait()
+
+    def statistics(self) -> EventStatistics:
+        if self._internal_event is None:
+            return EventStatistics(tasks_waiting=0)
+
+        return self._internal_event.statistics()
+
+
+class Lock:
+    __slots__ = ("__weakref__",)
+
+    def __new__(cls, *, fast_acquire: bool = False) -> Lock:
+        try:
+            return get_async_backend().create_lock(fast_acquire=fast_acquire)
+        except NoEventLoopError:
+            return LockAdapter(fast_acquire=fast_acquire)
+
+    async def __aenter__(self) -> None:
+        await self.acquire()
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self.release()
+
+    async def acquire(self) -> None:
+        """Acquire the lock."""
+        raise NotImplementedError
+
+    def acquire_nowait(self) -> None:
+        """
+        Acquire the lock, without blocking.
+
+        :raises ~anyio.WouldBlock: if the operation would block
+
+        """
+        raise NotImplementedError
+
+    def release(self) -> None:
+        """Release the lock."""
+        raise NotImplementedError
+
+    def locked(self) -> bool:
+        """Return True if the lock is currently held."""
+        raise NotImplementedError
+
+    def statistics(self) -> LockStatistics:
+        """
+        Return statistics about the current state of this lock.
+
+        .. versionadded:: 3.0
+        """
+        raise NotImplementedError
+
+
+class LockAdapter(Lock):
+    __slots__ = "_internal_lock", "_fast_acquire"
+
+    def __new__(cls, *, fast_acquire: bool = False) -> LockAdapter:
+        return object.__new__(cls)
+
+    def __init__(self, *, fast_acquire: bool = False):
+        self._internal_lock: Lock | None = None
+        self._fast_acquire = fast_acquire
+
+    @property
+    def _lock(self) -> Lock:
+        if self._internal_lock is None:
+            self._internal_lock = get_async_backend().create_lock(
+                fast_acquire=self._fast_acquire
+            )
+
+        return self._internal_lock
+
+    async def __aenter__(self) -> None:
+        await self._lock.acquire()
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        if self._internal_lock is not None:
+            self._internal_lock.release()
+
+    async def acquire(self) -> None:
+        """Acquire the lock."""
+        await self._lock.acquire()
+
+    def acquire_nowait(self) -> None:
+        """
+        Acquire the lock, without blocking.
+
+        :raises ~anyio.WouldBlock: if the operation would block
+
+        """
+        self._lock.acquire_nowait()
+
+    def release(self) -> None:
+        """Release the lock."""
+        self._lock.release()
+
+    def locked(self) -> bool:
+        """Return True if the lock is currently held."""
+        return self._lock.locked()
+
+    def statistics(self) -> LockStatistics:
+        """
+        Return statistics about the current state of this lock.
+
+        .. versionadded:: 3.0
+
+        """
+        if self._internal_lock is None:
+            return LockStatistics(False, None, 0)
+
+        return self._internal_lock.statistics()
+
+
+class Condition:
+    __slots__ = "__weakref__", "_owner_task", "_lock", "_waiters"
+
+    def __init__(self, lock: Lock | None = None):
+        self._owner_task: TaskInfo | None = None
+        self._lock = lock or Lock()
+        self._waiters: deque[Event] = deque()
+
+    async def __aenter__(self) -> None:
+        await self.acquire()
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self.release()
+
+    def _check_acquired(self) -> None:
+        if self._owner_task != get_current_task():
+            raise RuntimeError("The current task is not holding the underlying lock")
+
+    async def acquire(self) -> None:
+        """Acquire the underlying lock."""
+        await self._lock.acquire()
+        self._owner_task = get_current_task()
+
+    def acquire_nowait(self) -> None:
+        """
+        Acquire the underlying lock, without blocking.
+
+        :raises ~anyio.WouldBlock: if the operation would block
+
+        """
+        self._lock.acquire_nowait()
+        self._owner_task = get_current_task()
+
+    def release(self) -> None:
+        """Release the underlying lock."""
+        self._lock.release()
+
+    def locked(self) -> bool:
+        """Return True if the lock is set."""
+        return self._lock.locked()
+
+    def notify(self, n: int = 1) -> None:
+        """Notify exactly n listeners."""
+        self._check_acquired()
+        for _ in range(n):
+            try:
+                event = self._waiters.popleft()
+            except IndexError:
+                break
+
+            event.set()
+
+    def notify_all(self) -> None:
+        """Notify all the listeners."""
+        self._check_acquired()
+        for event in self._waiters:
+            event.set()
+
+        self._waiters.clear()
+
+    async def wait(self) -> None:
+        """Wait for a notification."""
+        await checkpoint_if_cancelled()
+        self._check_acquired()
+        event = Event()
+        self._waiters.append(event)
+        self.release()
+        try:
+            await event.wait()
+        except BaseException:
+            if not event.is_set():
+                self._waiters.remove(event)
+            elif self._waiters:
+                # This task was notified by could not act on it, so pass
+                # it on to the next task
+                self._waiters.popleft().set()
+
+            raise
+        finally:
+            with CancelScope(shield=True):
+                await self.acquire()
+
+    async def wait_for(self, predicate: Callable[[], T]) -> T:
+        """
+        Wait until a predicate becomes true.
+
+        :param predicate: a callable that returns a truthy value when the condition is
+            met
+        :return: the result of the predicate
+
+        .. versionadded:: 4.11.0
+
+        """
+        while not (result := predicate()):
+            await self.wait()
+
+        return result
+
+    def statistics(self) -> ConditionStatistics:
+        """
+        Return statistics about the current state of this condition.
+
+        .. versionadded:: 3.0
+        """
+        return ConditionStatistics(len(self._waiters), self._lock.statistics())
+
+
+class Semaphore:
+    __slots__ = "__weakref__", "_fast_acquire"
+
+    def __new__(
+        cls,
+        initial_value: int,
+        *,
+        max_value: int | None = None,
+        fast_acquire: bool = False,
+    ) -> Semaphore:
+        try:
+            return get_async_backend().create_semaphore(
+                initial_value, max_value=max_value, fast_acquire=fast_acquire
+            )
+        except NoEventLoopError:
+            return SemaphoreAdapter(initial_value, max_value=max_value)
+
+    def __init__(
+        self,
+        initial_value: int,
+        *,
+        max_value: int | None = None,
+        fast_acquire: bool = False,
+    ):
+        if not isinstance(initial_value, int):
+            raise TypeError("initial_value must be an integer")
+        if initial_value < 0:
+            raise ValueError("initial_value must be >= 0")
+        if max_value is not None:
+            if not isinstance(max_value, int):
+                raise TypeError("max_value must be an integer or None")
+            if max_value < initial_value:
+                raise ValueError(
+                    "max_value must be equal to or higher than initial_value"
+                )
+
+        self._fast_acquire = fast_acquire
+
+    async def __aenter__(self) -> Semaphore:
+        await self.acquire()
+        return self
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self.release()
+
+    async def acquire(self) -> None:
+        """Decrement the semaphore value, blocking if necessary."""
+        raise NotImplementedError
+
+    def acquire_nowait(self) -> None:
+        """
+        Acquire the underlying lock, without blocking.
+
+        :raises ~anyio.WouldBlock: if the operation would block
+
+        """
+        raise NotImplementedError
+
+    def release(self) -> None:
+        """Increment the semaphore value."""
+        raise NotImplementedError
+
+    @property
+    def value(self) -> int:
+        """The current value of the semaphore."""
+        raise NotImplementedError
+
+    @property
+    def max_value(self) -> int | None:
+        """The maximum value of the semaphore."""
+        raise NotImplementedError
+
+    def statistics(self) -> SemaphoreStatistics:
+        """
+        Return statistics about the current state of this semaphore.
+
+        .. versionadded:: 3.0
+        """
+        raise NotImplementedError
+
+
+class SemaphoreAdapter(Semaphore):
+    __slots__ = "_internal_semaphore", "_initial_value", "_max_value"
+
+    def __new__(
+        cls,
+        initial_value: int,
+        *,
+        max_value: int | None = None,
+        fast_acquire: bool = False,
+    ) -> SemaphoreAdapter:
+        return object.__new__(cls)
+
+    def __init__(
+        self,
+        initial_value: int,
+        *,
+        max_value: int | None = None,
+        fast_acquire: bool = False,
+    ) -> None:
+        super().__init__(initial_value, max_value=max_value, fast_acquire=fast_acquire)
+        self._internal_semaphore: Semaphore | None = None
+        self._initial_value = initial_value
+        self._max_value = max_value
+
+    @property
+    def _semaphore(self) -> Semaphore:
+        if self._internal_semaphore is None:
+            self._internal_semaphore = get_async_backend().create_semaphore(
+                self._initial_value, max_value=self._max_value
+            )
+
+        return self._internal_semaphore
+
+    async def acquire(self) -> None:
+        await self._semaphore.acquire()
+
+    def acquire_nowait(self) -> None:
+        self._semaphore.acquire_nowait()
+
+    def release(self) -> None:
+        self._semaphore.release()
+
+    @property
+    def value(self) -> int:
+        if self._internal_semaphore is None:
+            return self._initial_value
+
+        return self._semaphore.value
+
+    @property
+    def max_value(self) -> int | None:
+        return self._max_value
+
+    def statistics(self) -> SemaphoreStatistics:
+        if self._internal_semaphore is None:
+            return SemaphoreStatistics(tasks_waiting=0)
+
+        return self._semaphore.statistics()
+
+
+class CapacityLimiter:
+    __slots__ = ("__weakref__",)
+
+    def __new__(cls, total_tokens: float) -> CapacityLimiter:
+        try:
+            return get_async_backend().create_capacity_limiter(total_tokens)
+        except NoEventLoopError:
+            return CapacityLimiterAdapter(total_tokens)
+
+    async def __aenter__(self) -> None:
+        raise NotImplementedError
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        raise NotImplementedError
+
+    @property
+    def total_tokens(self) -> float:
+        """
+        The total number of tokens available for borrowing.
+
+        This is a read-write property. If the total number of tokens is increased, the
+        proportionate number of tasks waiting on this limiter will be granted their
+        tokens.
+
+        .. versionchanged:: 3.0
+            The property is now writable.
+        .. versionchanged:: 4.12
+            The value can now be set to 0.
+
+        """
+        raise NotImplementedError
+
+    @total_tokens.setter
+    def total_tokens(self, value: float) -> None:
+        raise NotImplementedError
+
+    @property
+    def borrowed_tokens(self) -> int:
+        """The number of tokens that have currently been borrowed."""
+        raise NotImplementedError
+
+    @property
+    def available_tokens(self) -> float:
+        """The number of tokens currently available to be borrowed"""
+        raise NotImplementedError
+
+    def acquire_nowait(self) -> None:
+        """
+        Acquire a token for the current task without waiting for one to become
+        available.
+
+        :raises ~anyio.WouldBlock: if there are no tokens available for borrowing
+
+        """
+        raise NotImplementedError
+
+    def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
+        """
+        Acquire a token without waiting for one to become available.
+
+        :param borrower: the entity borrowing a token
+        :raises ~anyio.WouldBlock: if there are no tokens available for borrowing
+
+        """
+        raise NotImplementedError
+
+    async def acquire(self) -> None:
+        """
+        Acquire a token for the current task, waiting if necessary for one to become
+        available.
+
+        """
+        raise NotImplementedError
+
+    async def acquire_on_behalf_of(self, borrower: object) -> None:
+        """
+        Acquire a token, waiting if necessary for one to become available.
+
+        :param borrower: the entity borrowing a token
+
+        """
+        raise NotImplementedError
+
+    def release(self) -> None:
+        """
+        Release the token held by the current task.
+
+        :raises RuntimeError: if the current task has not borrowed a token from this
+            limiter.
+
+        """
+        raise NotImplementedError
+
+    def release_on_behalf_of(self, borrower: object) -> None:
+        """
+        Release the token held by the given borrower.
+
+        :raises RuntimeError: if the borrower has not borrowed a token from this
+            limiter.
+
+        """
+        raise NotImplementedError
+
+    def statistics(self) -> CapacityLimiterStatistics:
+        """
+        Return statistics about the current state of this limiter.
+
+        .. versionadded:: 3.0
+
+        """
+        raise NotImplementedError
+
+
+class CapacityLimiterAdapter(CapacityLimiter):
+    __slots__ = "_internal_limiter", "_total_tokens"
+
+    def __new__(cls, total_tokens: float) -> CapacityLimiterAdapter:
+        return object.__new__(cls)
+
+    def __init__(self, total_tokens: float) -> None:
+        self._internal_limiter: CapacityLimiter | None = None
+        self.total_tokens = total_tokens
+
+    @property
+    def _limiter(self) -> CapacityLimiter:
+        if self._internal_limiter is None:
+            self._internal_limiter = get_async_backend().create_capacity_limiter(
+                self._total_tokens
+            )
+
+        return self._internal_limiter
+
+    async def __aenter__(self) -> None:
+        await self._limiter.__aenter__()
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        return await self._limiter.__aexit__(exc_type, exc_val, exc_tb)
+
+    @property
+    def total_tokens(self) -> float:
+        if self._internal_limiter is None:
+            return self._total_tokens
+
+        return self._internal_limiter.total_tokens
+
+    @total_tokens.setter
+    def total_tokens(self, value: float) -> None:
+        if not isinstance(value, int) and not math.isinf(value):
+            raise TypeError("total_tokens must be an int or math.inf")
+        elif value < 0:
+            raise ValueError("total_tokens must be >= 0")
+
+        if self._internal_limiter is None:
+            self._total_tokens = value
+            return
+
+        self._limiter.total_tokens = value
+
+    @property
+    def borrowed_tokens(self) -> int:
+        if self._internal_limiter is None:
+            return 0
+
+        return self._internal_limiter.borrowed_tokens
+
+    @property
+    def available_tokens(self) -> float:
+        if self._internal_limiter is None:
+            return self._total_tokens
+
+        return self._internal_limiter.available_tokens
+
+    def acquire_nowait(self) -> None:
+        self._limiter.acquire_nowait()
+
+    def acquire_on_behalf_of_nowait(self, borrower: object) -> None:
+        self._limiter.acquire_on_behalf_of_nowait(borrower)
+
+    async def acquire(self) -> None:
+        await self._limiter.acquire()
+
+    async def acquire_on_behalf_of(self, borrower: object) -> None:
+        await self._limiter.acquire_on_behalf_of(borrower)
+
+    def release(self) -> None:
+        self._limiter.release()
+
+    def release_on_behalf_of(self, borrower: object) -> None:
+        self._limiter.release_on_behalf_of(borrower)
+
+    def statistics(self) -> CapacityLimiterStatistics:
+        if self._internal_limiter is None:
+            return CapacityLimiterStatistics(
+                borrowed_tokens=0,
+                total_tokens=self.total_tokens,
+                borrowers=(),
+                tasks_waiting=0,
+            )
+
+        return self._internal_limiter.statistics()
+
+
+class ResourceGuard:
+    """
+    A context manager for ensuring that a resource is only used by a single task at a
+    time.
+
+    Entering this context manager while the previous has not exited it yet will trigger
+    :exc:`BusyResourceError`.
+
+    :param action: the action to guard against (visible in the :exc:`BusyResourceError`
+        when triggered, e.g. "Another task is already {action} this resource")
+
+    .. versionadded:: 4.1
+    """
+
+    __slots__ = "__weakref__", "action", "_guarded"
+
+    def __init__(self, action: str = "using"):
+        self.action: str = action
+        self._guarded = False
+
+    def __enter__(self) -> None:
+        if self._guarded:
+            raise BusyResourceError(self.action)
+
+        self._guarded = True
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self._guarded = False

+ 412 - 0
vendor/anyio/_core/_tasks.py

@@ -0,0 +1,412 @@
+from __future__ import annotations
+
+import math
+import sys
+from collections.abc import (
+    Coroutine,
+    Generator,
+)
+from contextlib import (
+    contextmanager,
+)
+from enum import Enum, auto
+from inspect import iscoroutine
+from types import TracebackType
+from typing import Any, Generic, final
+
+from ..abc import TaskGroup, TaskStatus
+from ._eventloop import get_async_backend, get_cancelled_exc_class
+from ._exceptions import TaskCancelled, TaskFailed, TaskNotFinished
+
+if sys.version_info >= (3, 13):
+    from typing import TypeVar
+else:
+    from typing_extensions import TypeVar
+
+if sys.version_info >= (3, 11):
+    from typing import Never, TypeVarTuple
+else:
+    from typing_extensions import Never, TypeVarTuple
+
+T = TypeVar("T")
+T_co = TypeVar("T_co", covariant=True)
+T_startval = TypeVar("T_startval", covariant=True, default=Never)
+PosArgsT = TypeVarTuple("PosArgsT")
+
+
+class _IgnoredTaskStatus(TaskStatus[object]):
+    def started(self, value: object = None) -> None:
+        pass
+
+
+TASK_STATUS_IGNORED = _IgnoredTaskStatus()
+
+
+class CancelScope:
+    """
+    Wraps a unit of work that can be made separately cancellable.
+
+    :param deadline: The time (clock value) when this scope is cancelled automatically
+    :param shield: ``True`` to shield the cancel scope from external cancellation
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+    """
+
+    __slots__ = ("__weakref__",)
+
+    def __new__(
+        cls, *, deadline: float = math.inf, shield: bool = False
+    ) -> CancelScope:
+        return get_async_backend().create_cancel_scope(shield=shield, deadline=deadline)
+
+    def cancel(self, reason: str | None = None) -> None:
+        """
+        Cancel this scope immediately.
+
+        :param reason: a message describing the reason for the cancellation
+
+        """
+        raise NotImplementedError
+
+    @property
+    def deadline(self) -> float:
+        """
+        The time (clock value) when this scope is cancelled automatically.
+
+        Will be ``float('inf')`` if no timeout has been set.
+
+        """
+        raise NotImplementedError
+
+    @deadline.setter
+    def deadline(self, value: float) -> None:
+        raise NotImplementedError
+
+    @property
+    def cancel_called(self) -> bool:
+        """``True`` if :meth:`cancel` has been called."""
+        raise NotImplementedError
+
+    @property
+    def cancelled_caught(self) -> bool:
+        """
+        ``True`` if this scope suppressed a cancellation exception it itself raised.
+
+        This is typically used to check if any work was interrupted, or to see if the
+        scope was cancelled due to its deadline being reached. The value will, however,
+        only be ``True`` if the cancellation was triggered by the scope itself (and not
+        an outer scope).
+
+        """
+        raise NotImplementedError
+
+    @property
+    def shield(self) -> bool:
+        """
+        ``True`` if this scope is shielded from external cancellation.
+
+        While a scope is shielded, it will not receive cancellations from outside.
+
+        """
+        raise NotImplementedError
+
+    @shield.setter
+    def shield(self, value: bool) -> None:
+        raise NotImplementedError
+
+    def __enter__(self) -> CancelScope:
+        raise NotImplementedError
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> bool:
+        raise NotImplementedError
+
+
+@contextmanager
+def fail_after(
+    delay: float | None, shield: bool = False
+) -> Generator[CancelScope, None, None]:
+    """
+    Create a context manager which raises a :class:`TimeoutError` if does not finish in
+    time.
+
+    :param delay: maximum allowed time (in seconds) before raising the exception, or
+        ``None`` to disable the timeout
+    :param shield: ``True`` to shield the cancel scope from external cancellation
+    :return: a context manager that yields a cancel scope
+    :rtype: :class:`~typing.ContextManager`\\[:class:`~anyio.CancelScope`\\]
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    current_time = get_async_backend().current_time
+    deadline = (current_time() + delay) if delay is not None else math.inf
+    with get_async_backend().create_cancel_scope(
+        deadline=deadline, shield=shield
+    ) as cancel_scope:
+        yield cancel_scope
+
+    if cancel_scope.cancelled_caught and current_time() >= cancel_scope.deadline:
+        raise TimeoutError
+
+
+def move_on_after(delay: float | None, shield: bool = False) -> CancelScope:
+    """
+    Create a cancel scope with a deadline that expires after the given delay.
+
+    :param delay: maximum allowed time (in seconds) before exiting the context block, or
+        ``None`` to disable the timeout
+    :param shield: ``True`` to shield the cancel scope from external cancellation
+    :return: a cancel scope
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    deadline = (
+        (get_async_backend().current_time() + delay) if delay is not None else math.inf
+    )
+    return get_async_backend().create_cancel_scope(deadline=deadline, shield=shield)
+
+
+def current_effective_deadline() -> float:
+    """
+    Return the nearest deadline among all the cancel scopes effective for the current
+    task.
+
+    :return: a clock value from the event loop's internal clock (or ``float('inf')`` if
+        there is no deadline in effect, or ``float('-inf')`` if the current scope has
+        been cancelled)
+    :rtype: float
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    return get_async_backend().current_effective_deadline()
+
+
+def create_task_group() -> TaskGroup:
+    """
+    Create a task group.
+
+    :return: a task group
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    return get_async_backend().create_task_group()
+
+
+@final
+class TaskHandle(Generic[T_co, T_startval]):
+    """
+    Returned from the task-spawning methods of :class:`TaskGroup`. Can be awaited on to
+    get the return value of the task (or the raised exception). If the task was
+    terminated by a :exc:`BaseException`, :exc:`TaskFailed` will be raised (or its
+    subclass :exc:`TaskCancelled` if the task was cancelled).
+
+    .. versionadded:: 4.14.0
+    """
+
+    class Status(Enum):
+        """
+        The status of a task handle.
+
+        .. attribute:: PENDING
+
+            The task has not finished yet.
+        .. attribute:: FINISHED
+
+            The task has finished with a return value.
+        .. attribute:: CANCELLING
+
+            The task has been cancelled but has not finished yet.
+        .. attribute:: CANCELLED
+
+            The task was cancelled and has finished since.
+        .. attribute:: FAILED
+
+            The task raised an exception.
+        """
+
+        PENDING = auto()
+        FINISHED = auto()
+        CANCELLING = auto()
+        CANCELLED = auto()
+        FAILED = auto()
+
+    __slots__ = (
+        "__weakref__",
+        "_coro",
+        "_name",
+        "_cancel_scope",
+        "_finished_event",
+        "_return_value",
+        "_start_value",
+        "_exception",
+    )
+
+    _return_value: T_co
+    _start_value: T_startval
+
+    def __init__(self, coro: Coroutine[Any, Any, T_co], name: object) -> None:
+        from ._synchronization import Event
+
+        self._coro = coro
+        self._cancel_scope = CancelScope()
+        self._finished_event = Event()
+        self._exception: BaseException | None = None
+
+        if name is not None:
+            self._name = str(name)
+        elif iscoroutine(coro):
+            self._name = coro.__qualname__
+        else:
+            self._name = str(coro)  # coroutine-like object (e.g. asend() objects)
+
+    async def _run_coro(self) -> None:
+        __tracebackhide__ = True
+
+        with self._cancel_scope:
+            try:
+                retval = await self._coro
+            except BaseException as exc:
+                self._exception = exc
+                raise
+            else:
+                self._return_value = retval
+            finally:
+                self._finished_event.set()
+                del self  # Break the reference cycle
+
+    def cancel(self) -> None:
+        """
+        Set the task to a cancelled state.
+
+        This will interrupt any interruptible asynchronous operation, and will cause
+        any further awaits on this task to get immediately cancelled, unless done in
+        a shielded cancel scope.
+
+        If the task has already finished, this method has no effect.
+        """
+        if not self._finished_event.is_set():
+            self._cancel_scope.cancel()
+
+    @property
+    def coro(self) -> Coroutine[Any, Any, T_co]:
+        """
+        The coroutine object that was passed to one of the task-spawning methods in
+        :class:`TaskGroup`.
+        """
+        return self._coro
+
+    @property
+    def status(self) -> TaskHandle.Status:
+        """
+        The current status of the task.
+
+        Every task starts in the :attr:`~TaskHandle.Status.PENDING` state.
+        If a task is cancelled while in this state, it will transition to the
+        :attr:`~TaskHandle.Status.CANCELLING` state. When the task finishes, it will
+        transition to one of the three final states (
+        :attr:`~TaskHandle.Status.FINISHED`, :attr:`~TaskHandle.Status.FAILED`, or
+        :attr:`~TaskHandle.Status.CANCELLING`) depending on the exception the task
+        raised, if any. No other status transitions will happen.
+        """
+        if not self._finished_event.is_set():
+            if self._cancel_scope.cancel_called:
+                return TaskHandle.Status.CANCELLING
+            else:
+                return TaskHandle.Status.PENDING
+        elif self._exception is not None:
+            if isinstance(self._exception, get_cancelled_exc_class()):
+                return TaskHandle.Status.CANCELLED
+            else:
+                return TaskHandle.Status.FAILED
+        else:
+            return TaskHandle.Status.FINISHED
+
+    @property
+    def name(self) -> str:
+        """The name of the task."""
+        return self._name
+
+    @property
+    def exception(self) -> BaseException | None:
+        """
+        The exception raised by the task, or ``None`` if it finished without raising.
+
+        :raises TaskNotFinished: if the task has not finished yet
+        :raises TaskCancelled: if the task was cancelled
+
+        """
+        match self.status:
+            case TaskHandle.Status.PENDING:
+                raise TaskNotFinished("the task has not finished yet")
+            case TaskHandle.Status.FINISHED:
+                return None
+            case TaskHandle.Status.CANCELLING:
+                raise TaskCancelled("the task was cancelled")
+            case TaskHandle.Status.CANCELLED:
+                raise TaskCancelled("the task was cancelled") from self._exception
+            case TaskHandle.Status.FAILED:
+                return self._exception
+
+    @property
+    def return_value(self) -> T_co:
+        """
+        The return value of the task.
+
+        :raises TaskNotFinished: if the task has not finished yet
+        :raises TaskCancelled: if the task was cancelled
+        :raises TaskFailed: if the task raised an exception
+
+        """
+        match self.status:
+            case TaskHandle.Status.PENDING:
+                raise TaskNotFinished("the task has not finished yet")
+            case TaskHandle.Status.FINISHED:
+                return self._return_value
+            case TaskHandle.Status.CANCELLING:
+                raise TaskCancelled("the task was cancelled")
+            case TaskHandle.Status.CANCELLED:
+                raise TaskCancelled("the task was cancelled") from self._exception
+            case TaskHandle.Status.FAILED:
+                raise TaskFailed("the task raised an exception") from self._exception
+
+    @property
+    def start_value(self) -> T_startval:
+        """
+        The value passed to :meth:`task_status.started() <.abc.TaskStatus.started>`,
+
+        :raises RuntimeError: if the task was not started with :meth:`TaskGroup.start()
+            <.abc.TaskGroup.start>`
+        """
+        try:
+            return self._start_value
+        except AttributeError:
+            raise RuntimeError(
+                "the task was not started with TaskGroup.start()"
+            ) from None
+
+    async def wait(self) -> None:
+        """
+        Wait for the task to finish.
+
+        This method will return as soon as the task has finished, no matter how it
+        happened.
+        """
+        await self._finished_event.wait()
+
+    def __await__(self) -> Generator[Any, Any, T_co]:
+        yield from self._finished_event.wait().__await__()
+        return self.return_value
+
+    def __repr__(self) -> str:
+        return (
+            f"<{self.__class__.__name__} {self.status.name.lower()} "
+            f"name={self._name!r} coro={self._coro!r}>"
+        )

+ 613 - 0
vendor/anyio/_core/_tempfile.py

@@ -0,0 +1,613 @@
+from __future__ import annotations
+
+import os
+import sys
+import tempfile
+from collections.abc import Iterable
+from io import BytesIO, TextIOWrapper
+from types import TracebackType
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    AnyStr,
+    Generic,
+    overload,
+)
+
+from .. import to_thread
+from .._core._fileio import AsyncFile
+from ..lowlevel import checkpoint_if_cancelled
+
+if TYPE_CHECKING:
+    from _typeshed import OpenBinaryMode, OpenTextMode, ReadableBuffer, WriteableBuffer
+
+
+class TemporaryFile(Generic[AnyStr]):
+    """
+    An asynchronous temporary file that is automatically created and cleaned up.
+
+    This class provides an asynchronous context manager interface to a temporary file.
+    The file is created using Python's standard `tempfile.TemporaryFile` function in a
+    background thread, and is wrapped as an asynchronous file using `AsyncFile`.
+
+    :param mode: The mode in which the file is opened. Defaults to "w+b".
+    :param buffering: The buffering policy (-1 means the default buffering).
+    :param encoding: The encoding used to decode or encode the file. Only applicable in
+        text mode.
+    :param newline: Controls how universal newlines mode works (only applicable in text
+        mode).
+    :param suffix: The suffix for the temporary file name.
+    :param prefix: The prefix for the temporary file name.
+    :param dir: The directory in which the temporary file is created.
+    :param errors: The error handling scheme used for encoding/decoding errors.
+    """
+
+    _async_file: AsyncFile[AnyStr]
+
+    @overload
+    def __init__(
+        self: TemporaryFile[bytes],
+        mode: OpenBinaryMode = ...,
+        buffering: int = ...,
+        encoding: str | None = ...,
+        newline: str | None = ...,
+        suffix: str | None = ...,
+        prefix: str | None = ...,
+        dir: str | None = ...,
+        *,
+        errors: str | None = ...,
+    ): ...
+    @overload
+    def __init__(
+        self: TemporaryFile[str],
+        mode: OpenTextMode,
+        buffering: int = ...,
+        encoding: str | None = ...,
+        newline: str | None = ...,
+        suffix: str | None = ...,
+        prefix: str | None = ...,
+        dir: str | None = ...,
+        *,
+        errors: str | None = ...,
+    ): ...
+
+    def __init__(
+        self,
+        mode: OpenTextMode | OpenBinaryMode = "w+b",
+        buffering: int = -1,
+        encoding: str | None = None,
+        newline: str | None = None,
+        suffix: str | None = None,
+        prefix: str | None = None,
+        dir: str | None = None,
+        *,
+        errors: str | None = None,
+    ) -> None:
+        self.mode = mode
+        self.buffering = buffering
+        self.encoding = encoding
+        self.newline = newline
+        self.suffix: str | None = suffix
+        self.prefix: str | None = prefix
+        self.dir: str | None = dir
+        self.errors = errors
+
+    async def __aenter__(self) -> AsyncFile[AnyStr]:
+        fp = await to_thread.run_sync(
+            lambda: tempfile.TemporaryFile(
+                self.mode,
+                self.buffering,
+                self.encoding,
+                self.newline,
+                self.suffix,
+                self.prefix,
+                self.dir,
+                errors=self.errors,
+            )
+        )
+        self._async_file = AsyncFile(fp)
+        return self._async_file
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_value: BaseException | None,
+        traceback: TracebackType | None,
+    ) -> None:
+        await self._async_file.aclose()
+
+
+class NamedTemporaryFile(Generic[AnyStr]):
+    """
+    An asynchronous named temporary file that is automatically created and cleaned up.
+
+    This class provides an asynchronous context manager for a temporary file with a
+    visible name in the file system. It uses Python's standard
+    :func:`~tempfile.NamedTemporaryFile` function and wraps the file object with
+    :class:`AsyncFile` for asynchronous operations.
+
+    :param mode: The mode in which the file is opened. Defaults to "w+b".
+    :param buffering: The buffering policy (-1 means the default buffering).
+    :param encoding: The encoding used to decode or encode the file. Only applicable in
+        text mode.
+    :param newline: Controls how universal newlines mode works (only applicable in text
+        mode).
+    :param suffix: The suffix for the temporary file name.
+    :param prefix: The prefix for the temporary file name.
+    :param dir: The directory in which the temporary file is created.
+    :param delete: Whether to delete the file when it is closed.
+    :param errors: The error handling scheme used for encoding/decoding errors.
+    :param delete_on_close: (Python 3.12+) Whether to delete the file on close.
+    """
+
+    _async_file: AsyncFile[AnyStr]
+
+    @overload
+    def __init__(
+        self: NamedTemporaryFile[bytes],
+        mode: OpenBinaryMode = ...,
+        buffering: int = ...,
+        encoding: str | None = ...,
+        newline: str | None = ...,
+        suffix: str | None = ...,
+        prefix: str | None = ...,
+        dir: str | None = ...,
+        delete: bool = ...,
+        *,
+        errors: str | None = ...,
+        delete_on_close: bool = ...,
+    ): ...
+    @overload
+    def __init__(
+        self: NamedTemporaryFile[str],
+        mode: OpenTextMode,
+        buffering: int = ...,
+        encoding: str | None = ...,
+        newline: str | None = ...,
+        suffix: str | None = ...,
+        prefix: str | None = ...,
+        dir: str | None = ...,
+        delete: bool = ...,
+        *,
+        errors: str | None = ...,
+        delete_on_close: bool = ...,
+    ): ...
+
+    def __init__(
+        self,
+        mode: OpenBinaryMode | OpenTextMode = "w+b",
+        buffering: int = -1,
+        encoding: str | None = None,
+        newline: str | None = None,
+        suffix: str | None = None,
+        prefix: str | None = None,
+        dir: str | None = None,
+        delete: bool = True,
+        *,
+        errors: str | None = None,
+        delete_on_close: bool = True,
+    ) -> None:
+        self._params: dict[str, Any] = {
+            "mode": mode,
+            "buffering": buffering,
+            "encoding": encoding,
+            "newline": newline,
+            "suffix": suffix,
+            "prefix": prefix,
+            "dir": dir,
+            "delete": delete,
+            "errors": errors,
+        }
+        if sys.version_info >= (3, 12):
+            self._params["delete_on_close"] = delete_on_close
+
+    async def __aenter__(self) -> AsyncFile[AnyStr]:
+        fp = await to_thread.run_sync(
+            lambda: tempfile.NamedTemporaryFile(**self._params)
+        )
+        self._async_file = AsyncFile(fp)
+        return self._async_file
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_value: BaseException | None,
+        traceback: TracebackType | None,
+    ) -> None:
+        await self._async_file.aclose()
+
+
+class SpooledTemporaryFile(AsyncFile[AnyStr]):
+    """
+    An asynchronous spooled temporary file that starts in memory and is spooled to disk.
+
+    This class provides an asynchronous interface to a spooled temporary file, much like
+    Python's standard :class:`~tempfile.SpooledTemporaryFile`. It supports asynchronous
+    write operations and provides a method to force a rollover to disk.
+
+    :param max_size: Maximum size in bytes before the file is rolled over to disk.
+    :param mode: The mode in which the file is opened. Defaults to "w+b".
+    :param buffering: The buffering policy (-1 means the default buffering).
+    :param encoding: The encoding used to decode or encode the file (text mode only).
+    :param newline: Controls how universal newlines mode works (text mode only).
+    :param suffix: The suffix for the temporary file name.
+    :param prefix: The prefix for the temporary file name.
+    :param dir: The directory in which the temporary file is created.
+    :param errors: The error handling scheme used for encoding/decoding errors.
+    """
+
+    _rolled: bool = False
+
+    @overload
+    def __init__(
+        self: SpooledTemporaryFile[bytes],
+        max_size: int = ...,
+        mode: OpenBinaryMode = ...,
+        buffering: int = ...,
+        encoding: str | None = ...,
+        newline: str | None = ...,
+        suffix: str | None = ...,
+        prefix: str | None = ...,
+        dir: str | None = ...,
+        *,
+        errors: str | None = ...,
+    ): ...
+    @overload
+    def __init__(
+        self: SpooledTemporaryFile[str],
+        max_size: int = ...,
+        mode: OpenTextMode = ...,
+        buffering: int = ...,
+        encoding: str | None = ...,
+        newline: str | None = ...,
+        suffix: str | None = ...,
+        prefix: str | None = ...,
+        dir: str | None = ...,
+        *,
+        errors: str | None = ...,
+    ): ...
+
+    def __init__(
+        self,
+        max_size: int = 0,
+        mode: OpenBinaryMode | OpenTextMode = "w+b",
+        buffering: int = -1,
+        encoding: str | None = None,
+        newline: str | None = None,
+        suffix: str | None = None,
+        prefix: str | None = None,
+        dir: str | None = None,
+        *,
+        errors: str | None = None,
+    ) -> None:
+        self._tempfile_params: dict[str, Any] = {
+            "mode": mode,
+            "buffering": buffering,
+            "encoding": encoding,
+            "newline": newline,
+            "suffix": suffix,
+            "prefix": prefix,
+            "dir": dir,
+            "errors": errors,
+        }
+        self._max_size = max_size
+        if "b" in mode:
+            super().__init__(BytesIO())  # type: ignore[arg-type]
+        else:
+            super().__init__(
+                TextIOWrapper(  # type: ignore[arg-type]
+                    BytesIO(),
+                    encoding=encoding,
+                    errors=errors,
+                    newline=newline,
+                    write_through=True,
+                )
+            )
+
+    async def aclose(self) -> None:
+        if not self._rolled:
+            self._fp.close()
+            return
+
+        await super().aclose()
+
+    async def _check(self) -> None:
+        if self._rolled or self._fp.tell() <= self._max_size:
+            return
+
+        await self.rollover()
+
+    async def rollover(self) -> None:
+        if self._rolled:
+            return
+
+        self._rolled = True
+        buffer = self._fp
+        buffer.seek(0)
+        self._fp = await to_thread.run_sync(
+            lambda: tempfile.TemporaryFile(**self._tempfile_params)
+        )
+        await self.write(buffer.read())
+        buffer.close()
+
+    @property
+    def closed(self) -> bool:
+        return self._fp.closed
+
+    async def read(self, size: int = -1) -> AnyStr:
+        if not self._rolled:
+            await checkpoint_if_cancelled()
+            return self._fp.read(size)
+
+        return await super().read(size)  # type: ignore[return-value]
+
+    async def read1(self: SpooledTemporaryFile[bytes], size: int = -1) -> bytes:
+        if not self._rolled:
+            await checkpoint_if_cancelled()
+            return self._fp.read1(size)
+
+        return await super().read1(size)
+
+    async def readline(self) -> AnyStr:
+        if not self._rolled:
+            await checkpoint_if_cancelled()
+            return self._fp.readline()
+
+        return await super().readline()  # type: ignore[return-value]
+
+    async def readlines(self) -> list[AnyStr]:
+        if not self._rolled:
+            await checkpoint_if_cancelled()
+            return self._fp.readlines()
+
+        return await super().readlines()  # type: ignore[return-value]
+
+    async def readinto(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int:
+        if not self._rolled:
+            await checkpoint_if_cancelled()
+            self._fp.readinto(b)
+
+        return await super().readinto(b)
+
+    async def readinto1(self: SpooledTemporaryFile[bytes], b: WriteableBuffer) -> int:
+        if not self._rolled:
+            await checkpoint_if_cancelled()
+            self._fp.readinto(b)
+
+        return await super().readinto1(b)
+
+    async def seek(self, offset: int, whence: int | None = os.SEEK_SET) -> int:
+        if not self._rolled:
+            await checkpoint_if_cancelled()
+            return self._fp.seek(offset, whence)
+
+        return await super().seek(offset, whence)
+
+    async def tell(self) -> int:
+        if not self._rolled:
+            await checkpoint_if_cancelled()
+            return self._fp.tell()
+
+        return await super().tell()
+
+    async def truncate(self, size: int | None = None) -> int:
+        if not self._rolled:
+            await checkpoint_if_cancelled()
+            return self._fp.truncate(size)
+
+        return await super().truncate(size)
+
+    @overload
+    async def write(self: SpooledTemporaryFile[bytes], b: ReadableBuffer) -> int: ...
+    @overload
+    async def write(self: SpooledTemporaryFile[str], b: str) -> int: ...
+
+    async def write(self, b: ReadableBuffer | str) -> int:
+        """
+        Asynchronously write data to the spooled temporary file.
+
+        If the file has not yet been rolled over, the data is written synchronously,
+        and a rollover is triggered if the size exceeds the maximum size.
+
+        :param s: The data to write.
+        :return: The number of bytes written.
+        :raises RuntimeError: If the underlying file is not initialized.
+
+        """
+        if not self._rolled:
+            await checkpoint_if_cancelled()
+            result = self._fp.write(b)
+            await self._check()
+            return result
+
+        return await super().write(b)  # type: ignore[misc]
+
+    @overload
+    async def writelines(
+        self: SpooledTemporaryFile[bytes], lines: Iterable[ReadableBuffer]
+    ) -> None: ...
+    @overload
+    async def writelines(
+        self: SpooledTemporaryFile[str], lines: Iterable[str]
+    ) -> None: ...
+
+    async def writelines(self, lines: Iterable[str] | Iterable[ReadableBuffer]) -> None:
+        """
+        Asynchronously write a list of lines to the spooled temporary file.
+
+        If the file has not yet been rolled over, the lines are written synchronously,
+        and a rollover is triggered if the size exceeds the maximum size.
+
+        :param lines: An iterable of lines to write.
+        :raises RuntimeError: If the underlying file is not initialized.
+
+        """
+        if not self._rolled:
+            await checkpoint_if_cancelled()
+            result = self._fp.writelines(lines)
+            await self._check()
+            return result
+
+        return await super().writelines(lines)  # type: ignore[misc]
+
+
+class TemporaryDirectory(Generic[AnyStr]):
+    """
+    An asynchronous temporary directory that is created and cleaned up automatically.
+
+    This class provides an asynchronous context manager for creating a temporary
+    directory. It wraps Python's standard :class:`~tempfile.TemporaryDirectory` to
+    perform directory creation and cleanup operations in a background thread.
+
+    :param suffix: Suffix to be added to the temporary directory name.
+    :param prefix: Prefix to be added to the temporary directory name.
+    :param dir: The parent directory where the temporary directory is created.
+    :param ignore_cleanup_errors: Whether to ignore errors during cleanup
+    :param delete: Whether to delete the directory upon closing (Python 3.12+).
+    """
+
+    def __init__(
+        self,
+        suffix: AnyStr | None = None,
+        prefix: AnyStr | None = None,
+        dir: AnyStr | None = None,
+        *,
+        ignore_cleanup_errors: bool = False,
+        delete: bool = True,
+    ) -> None:
+        self.suffix: AnyStr | None = suffix
+        self.prefix: AnyStr | None = prefix
+        self.dir: AnyStr | None = dir
+        self.ignore_cleanup_errors = ignore_cleanup_errors
+        self.delete = delete
+
+        self._tempdir: tempfile.TemporaryDirectory | None = None
+
+    async def __aenter__(self) -> str:
+        params: dict[str, Any] = {
+            "suffix": self.suffix,
+            "prefix": self.prefix,
+            "dir": self.dir,
+            "ignore_cleanup_errors": self.ignore_cleanup_errors,
+        }
+        if sys.version_info >= (3, 12):
+            params["delete"] = self.delete
+
+        self._tempdir = await to_thread.run_sync(
+            lambda: tempfile.TemporaryDirectory(**params)
+        )
+        return await to_thread.run_sync(self._tempdir.__enter__)
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_value: BaseException | None,
+        traceback: TracebackType | None,
+    ) -> None:
+        if self._tempdir is not None:
+            await to_thread.run_sync(
+                self._tempdir.__exit__, exc_type, exc_value, traceback
+            )
+
+    async def cleanup(self) -> None:
+        if self._tempdir is not None:
+            await to_thread.run_sync(self._tempdir.cleanup)
+
+
+@overload
+async def mkstemp(
+    suffix: str | None = None,
+    prefix: str | None = None,
+    dir: str | None = None,
+    text: bool = False,
+) -> tuple[int, str]: ...
+
+
+@overload
+async def mkstemp(
+    suffix: bytes | None = None,
+    prefix: bytes | None = None,
+    dir: bytes | None = None,
+    text: bool = False,
+) -> tuple[int, bytes]: ...
+
+
+async def mkstemp(
+    suffix: AnyStr | None = None,
+    prefix: AnyStr | None = None,
+    dir: AnyStr | None = None,
+    text: bool = False,
+) -> tuple[int, str | bytes]:
+    """
+    Asynchronously create a temporary file and return an OS-level handle and the file
+    name.
+
+    This function wraps `tempfile.mkstemp` and executes it in a background thread.
+
+    :param suffix: Suffix to be added to the file name.
+    :param prefix: Prefix to be added to the file name.
+    :param dir: Directory in which the temporary file is created.
+    :param text: Whether the file is opened in text mode.
+    :return: A tuple containing the file descriptor and the file name.
+
+    """
+    return await to_thread.run_sync(tempfile.mkstemp, suffix, prefix, dir, text)
+
+
+@overload
+async def mkdtemp(
+    suffix: str | None = None,
+    prefix: str | None = None,
+    dir: str | None = None,
+) -> str: ...
+
+
+@overload
+async def mkdtemp(
+    suffix: bytes | None = None,
+    prefix: bytes | None = None,
+    dir: bytes | None = None,
+) -> bytes: ...
+
+
+async def mkdtemp(
+    suffix: AnyStr | None = None,
+    prefix: AnyStr | None = None,
+    dir: AnyStr | None = None,
+) -> str | bytes:
+    """
+    Asynchronously create a temporary directory and return its path.
+
+    This function wraps `tempfile.mkdtemp` and executes it in a background thread.
+
+    :param suffix: Suffix to be added to the directory name.
+    :param prefix: Prefix to be added to the directory name.
+    :param dir: Parent directory where the temporary directory is created.
+    :return: The path of the created temporary directory.
+
+    """
+    return await to_thread.run_sync(tempfile.mkdtemp, suffix, prefix, dir)
+
+
+async def gettempdir() -> str:
+    """
+    Asynchronously return the name of the directory used for temporary files.
+
+    This function wraps `tempfile.gettempdir` and executes it in a background thread.
+
+    :return: The path of the temporary directory as a string.
+
+    """
+    return await to_thread.run_sync(tempfile.gettempdir)
+
+
+async def gettempdirb() -> bytes:
+    """
+    Asynchronously return the name of the directory used for temporary files in bytes.
+
+    This function wraps `tempfile.gettempdirb` and executes it in a background thread.
+
+    :return: The path of the temporary directory as bytes.
+
+    """
+    return await to_thread.run_sync(tempfile.gettempdirb)

+ 82 - 0
vendor/anyio/_core/_testing.py

@@ -0,0 +1,82 @@
+from __future__ import annotations
+
+from collections.abc import Awaitable, Generator
+from typing import Any, cast
+
+from ._eventloop import get_async_backend
+
+
+class TaskInfo:
+    """
+    Represents an asynchronous task.
+
+    :ivar int id: the unique identifier of the task
+    :ivar parent_id: the identifier of the parent task, if any
+    :vartype parent_id: Optional[int]
+    :ivar str name: the description of the task (if any)
+    :ivar ~collections.abc.Coroutine coro: the coroutine object of the task
+    """
+
+    __slots__ = "_name", "id", "parent_id", "name", "coro"
+
+    def __init__(
+        self,
+        id: int,
+        parent_id: int | None,
+        name: str | None,
+        coro: Generator[Any, Any, Any] | Awaitable[Any],
+    ):
+        func = get_current_task
+        self._name = f"{func.__module__}.{func.__qualname__}"
+        self.id: int = id
+        self.parent_id: int | None = parent_id
+        self.name: str | None = name
+        self.coro: Generator[Any, Any, Any] | Awaitable[Any] = coro
+
+    def __eq__(self, other: object) -> bool:
+        if isinstance(other, TaskInfo):
+            return self.id == other.id
+
+        return NotImplemented
+
+    def __hash__(self) -> int:
+        return hash(self.id)
+
+    def __repr__(self) -> str:
+        return f"{self.__class__.__name__}(id={self.id!r}, name={self.name!r})"
+
+    def has_pending_cancellation(self) -> bool:
+        """
+        Return ``True`` if the task has a cancellation pending, ``False`` otherwise.
+
+        """
+        return False
+
+
+def get_current_task() -> TaskInfo:
+    """
+    Return the current task.
+
+    :return: a representation of the current task
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    return get_async_backend().get_current_task()
+
+
+def get_running_tasks() -> list[TaskInfo]:
+    """
+    Return a list of running tasks in the current event loop.
+
+    :return: a list of task info objects
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    return cast("list[TaskInfo]", get_async_backend().get_running_tasks())
+
+
+async def wait_all_tasks_blocked() -> None:
+    """Wait until all other tasks are waiting for something."""
+    await get_async_backend().wait_all_tasks_blocked()

+ 81 - 0
vendor/anyio/_core/_typedattr.py

@@ -0,0 +1,81 @@
+from __future__ import annotations
+
+from collections.abc import Callable, Mapping
+from typing import Any, TypeVar, final, overload
+
+from ._exceptions import TypedAttributeLookupError
+
+T_Attr = TypeVar("T_Attr")
+T_Default = TypeVar("T_Default")
+undefined = object()
+
+
+def typed_attribute() -> Any:
+    """Return a unique object, used to mark typed attributes."""
+    return object()
+
+
+class TypedAttributeSet:
+    """
+    Superclass for typed attribute collections.
+
+    Checks that every public attribute of every subclass has a type annotation.
+    """
+
+    def __init_subclass__(cls) -> None:
+        annotations: dict[str, Any] = getattr(cls, "__annotations__", {})
+        for attrname in dir(cls):
+            if not attrname.startswith("_") and attrname not in annotations:
+                raise TypeError(
+                    f"Attribute {attrname!r} is missing its type annotation"
+                )
+
+        super().__init_subclass__()
+
+
+class TypedAttributeProvider:
+    """Base class for classes that wish to provide typed extra attributes."""
+
+    @property
+    def extra_attributes(self) -> Mapping[T_Attr, Callable[[], T_Attr]]:
+        """
+        A mapping of the extra attributes to callables that return the corresponding
+        values.
+
+        If the provider wraps another provider, the attributes from that wrapper should
+        also be included in the returned mapping (but the wrapper may override the
+        callables from the wrapped instance).
+
+        """
+        return {}
+
+    @overload
+    def extra(self, attribute: T_Attr) -> T_Attr: ...
+
+    @overload
+    def extra(self, attribute: T_Attr, default: T_Default) -> T_Attr | T_Default: ...
+
+    @final
+    def extra(self, attribute: Any, default: object = undefined) -> object:
+        """
+        extra(attribute, default=undefined)
+
+        Return the value of the given typed extra attribute.
+
+        :param attribute: the attribute (member of a :class:`~TypedAttributeSet`) to
+            look for
+        :param default: the value that should be returned if no value is found for the
+            attribute
+        :raises ~anyio.TypedAttributeLookupError: if the search failed and no default
+            value was given
+
+        """
+        try:
+            getter = self.extra_attributes[attribute]
+        except KeyError:
+            if default is undefined:
+                raise TypedAttributeLookupError("Attribute not found") from None
+            else:
+                return default
+
+        return getter()

+ 58 - 0
vendor/anyio/abc/__init__.py

@@ -0,0 +1,58 @@
+from __future__ import annotations
+
+from ._eventloop import AsyncBackend as AsyncBackend
+from ._resources import AsyncResource as AsyncResource
+from ._sockets import ConnectedUDPSocket as ConnectedUDPSocket
+from ._sockets import ConnectedUNIXDatagramSocket as ConnectedUNIXDatagramSocket
+from ._sockets import IPAddressType as IPAddressType
+from ._sockets import IPSockAddrType as IPSockAddrType
+from ._sockets import SocketAttribute as SocketAttribute
+from ._sockets import SocketListener as SocketListener
+from ._sockets import SocketStream as SocketStream
+from ._sockets import UDPPacketType as UDPPacketType
+from ._sockets import UDPSocket as UDPSocket
+from ._sockets import UNIXDatagramPacketType as UNIXDatagramPacketType
+from ._sockets import UNIXDatagramSocket as UNIXDatagramSocket
+from ._sockets import UNIXSocketStream as UNIXSocketStream
+from ._streams import AnyByteReceiveStream as AnyByteReceiveStream
+from ._streams import AnyByteSendStream as AnyByteSendStream
+from ._streams import AnyByteStream as AnyByteStream
+from ._streams import AnyByteStreamConnectable as AnyByteStreamConnectable
+from ._streams import AnyUnreliableByteReceiveStream as AnyUnreliableByteReceiveStream
+from ._streams import AnyUnreliableByteSendStream as AnyUnreliableByteSendStream
+from ._streams import AnyUnreliableByteStream as AnyUnreliableByteStream
+from ._streams import ByteReceiveStream as ByteReceiveStream
+from ._streams import ByteSendStream as ByteSendStream
+from ._streams import ByteStream as ByteStream
+from ._streams import ByteStreamConnectable as ByteStreamConnectable
+from ._streams import Listener as Listener
+from ._streams import ObjectReceiveStream as ObjectReceiveStream
+from ._streams import ObjectSendStream as ObjectSendStream
+from ._streams import ObjectStream as ObjectStream
+from ._streams import ObjectStreamConnectable as ObjectStreamConnectable
+from ._streams import UnreliableObjectReceiveStream as UnreliableObjectReceiveStream
+from ._streams import UnreliableObjectSendStream as UnreliableObjectSendStream
+from ._streams import UnreliableObjectStream as UnreliableObjectStream
+from ._subprocesses import Process as Process
+from ._tasks import TaskGroup as TaskGroup
+from ._tasks import TaskStatus as TaskStatus
+from ._testing import TestRunner as TestRunner
+
+# Re-exported here, for backwards compatibility
+# isort: off
+from .._core._synchronization import (
+    CapacityLimiter as CapacityLimiter,
+    Condition as Condition,
+    Event as Event,
+    Lock as Lock,
+    Semaphore as Semaphore,
+)
+from .._core._tasks import CancelScope as CancelScope
+from ..from_thread import BlockingPortal as BlockingPortal
+
+# Re-export imports so they look like they live directly in this package
+for __value in list(locals().values()):
+    if getattr(__value, "__module__", "").startswith("anyio.abc."):
+        __value.__module__ = __name__
+
+del __value

+ 410 - 0
vendor/anyio/abc/_eventloop.py

@@ -0,0 +1,410 @@
+from __future__ import annotations
+
+import math
+import sys
+from abc import ABCMeta, abstractmethod
+from collections.abc import AsyncIterator, Awaitable, Callable, Coroutine, Sequence
+from contextlib import AbstractContextManager
+from os import PathLike
+from signal import Signals
+from socket import AddressFamily, SocketKind, socket
+from typing import (
+    IO,
+    TYPE_CHECKING,
+    Any,
+    TypeAlias,
+    TypeVar,
+    overload,
+)
+
+if sys.version_info >= (3, 11):
+    from typing import TypeVarTuple, Unpack
+else:
+    from typing_extensions import TypeVarTuple, Unpack
+
+if TYPE_CHECKING:
+    from _typeshed import FileDescriptorLike
+
+    from .._core._synchronization import CapacityLimiter, Event, Lock, Semaphore
+    from .._core._tasks import CancelScope
+    from .._core._testing import TaskInfo
+    from ._sockets import (
+        ConnectedUDPSocket,
+        ConnectedUNIXDatagramSocket,
+        IPSockAddrType,
+        SocketListener,
+        SocketStream,
+        UDPSocket,
+        UNIXDatagramSocket,
+        UNIXSocketStream,
+    )
+    from ._subprocesses import Process
+    from ._tasks import TaskGroup
+    from ._testing import TestRunner
+
+T_Retval = TypeVar("T_Retval")
+T_co = TypeVar("T_co", covariant=True)
+PosArgsT = TypeVarTuple("PosArgsT")
+StrOrBytesPath: TypeAlias = str | bytes | PathLike[str] | PathLike[bytes]
+
+
+class AsyncBackend(metaclass=ABCMeta):
+    @classmethod
+    @abstractmethod
+    def run(
+        cls,
+        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
+        args: tuple[Unpack[PosArgsT]],
+        kwargs: dict[str, Any],
+        options: dict[str, Any],
+    ) -> T_Retval:
+        """
+        Run the given coroutine function in an asynchronous event loop.
+
+        The current thread must not be already running an event loop.
+
+        :param func: a coroutine function
+        :param args: positional arguments to ``func``
+        :param kwargs: positional arguments to ``func``
+        :param options: keyword arguments to call the backend ``run()`` implementation
+            with
+        :return: the return value of the coroutine function
+        """
+
+    @classmethod
+    @abstractmethod
+    def current_token(cls) -> object:
+        """
+        Return an object that allows other threads to run code inside the event loop.
+
+        :return: a token object, specific to the event loop running in the current
+            thread
+        """
+
+    @classmethod
+    @abstractmethod
+    def current_time(cls) -> float:
+        """
+        Return the current value of the event loop's internal clock.
+
+        :return: the clock value (seconds)
+        """
+
+    @classmethod
+    @abstractmethod
+    def cancelled_exception_class(cls) -> type[BaseException]:
+        """Return the exception class that is raised in a task if it's cancelled."""
+
+    @classmethod
+    @abstractmethod
+    async def checkpoint(cls) -> None:
+        """
+        Check if the task has been cancelled, and allow rescheduling of other tasks.
+
+        This is effectively the same as running :meth:`checkpoint_if_cancelled` and then
+        :meth:`cancel_shielded_checkpoint`.
+        """
+
+    @classmethod
+    async def checkpoint_if_cancelled(cls) -> None:
+        """
+        Check if the current task group has been cancelled.
+
+        This will check if the task has been cancelled, but will not allow other tasks
+        to be scheduled if not.
+
+        """
+        if cls.current_effective_deadline() == -math.inf:
+            await cls.checkpoint()
+
+    @classmethod
+    async def cancel_shielded_checkpoint(cls) -> None:
+        """
+        Allow the rescheduling of other tasks.
+
+        This will give other tasks the opportunity to run, but without checking if the
+        current task group has been cancelled, unlike with :meth:`checkpoint`.
+
+        """
+        with cls.create_cancel_scope(shield=True):
+            await cls.sleep(0)
+
+    @classmethod
+    @abstractmethod
+    async def sleep(cls, delay: float) -> None:
+        """
+        Pause the current task for the specified duration.
+
+        :param delay: the duration, in seconds
+        """
+
+    @classmethod
+    @abstractmethod
+    def create_cancel_scope(
+        cls, *, deadline: float = math.inf, shield: bool = False
+    ) -> CancelScope:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def current_effective_deadline(cls) -> float:
+        """
+        Return the nearest deadline among all the cancel scopes effective for the
+        current task.
+
+        :return:
+            - a clock value from the event loop's internal clock
+            - ``inf`` if there is no deadline in effect
+            - ``-inf`` if the current scope has been cancelled
+        :rtype: float
+        """
+
+    @classmethod
+    @abstractmethod
+    def create_task_group(cls) -> TaskGroup:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def create_event(cls) -> Event:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def create_lock(cls, *, fast_acquire: bool) -> Lock:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def create_semaphore(
+        cls,
+        initial_value: int,
+        *,
+        max_value: int | None = None,
+        fast_acquire: bool = False,
+    ) -> Semaphore:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def create_capacity_limiter(cls, total_tokens: float) -> CapacityLimiter:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def run_sync_in_worker_thread(
+        cls,
+        func: Callable[[Unpack[PosArgsT]], T_Retval],
+        args: tuple[Unpack[PosArgsT]],
+        abandon_on_cancel: bool = False,
+        limiter: CapacityLimiter | None = None,
+    ) -> T_Retval:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def check_cancelled(cls) -> None:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def run_async_from_thread(
+        cls,
+        func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
+        args: tuple[Unpack[PosArgsT]],
+        token: object,
+    ) -> T_co:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def run_sync_from_thread(
+        cls,
+        func: Callable[[Unpack[PosArgsT]], T_Retval],
+        args: tuple[Unpack[PosArgsT]],
+        token: object,
+    ) -> T_Retval:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def open_process(
+        cls,
+        command: StrOrBytesPath | Sequence[StrOrBytesPath],
+        *,
+        stdin: int | IO[Any] | None,
+        stdout: int | IO[Any] | None,
+        stderr: int | IO[Any] | None,
+        **kwargs: Any,
+    ) -> Process:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def setup_process_pool_exit_at_shutdown(cls, workers: set[Process]) -> None:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def connect_tcp(
+        cls, host: str, port: int, local_address: IPSockAddrType | None = None
+    ) -> SocketStream:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def connect_unix(cls, path: str | bytes) -> UNIXSocketStream:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def create_tcp_listener(cls, sock: socket) -> SocketListener:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def create_unix_listener(cls, sock: socket) -> SocketListener:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def create_udp_socket(
+        cls,
+        family: AddressFamily,
+        local_address: IPSockAddrType | None,
+        remote_address: IPSockAddrType | None,
+        reuse_port: bool,
+    ) -> UDPSocket | ConnectedUDPSocket:
+        pass
+
+    @classmethod
+    @overload
+    async def create_unix_datagram_socket(
+        cls, raw_socket: socket, remote_path: None
+    ) -> UNIXDatagramSocket: ...
+
+    @classmethod
+    @overload
+    async def create_unix_datagram_socket(
+        cls, raw_socket: socket, remote_path: str | bytes
+    ) -> ConnectedUNIXDatagramSocket: ...
+
+    @classmethod
+    @abstractmethod
+    async def create_unix_datagram_socket(
+        cls, raw_socket: socket, remote_path: str | bytes | None
+    ) -> UNIXDatagramSocket | ConnectedUNIXDatagramSocket:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def getaddrinfo(
+        cls,
+        host: bytes | str | None,
+        port: str | int | None,
+        *,
+        family: int | AddressFamily = 0,
+        type: int | SocketKind = 0,
+        proto: int = 0,
+        flags: int = 0,
+    ) -> Sequence[
+        tuple[
+            AddressFamily,
+            SocketKind,
+            int,
+            str,
+            tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes],
+        ]
+    ]:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def getnameinfo(
+        cls, sockaddr: IPSockAddrType, flags: int = 0
+    ) -> tuple[str, str]:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def wait_readable(cls, obj: FileDescriptorLike) -> None:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def wait_writable(cls, obj: FileDescriptorLike) -> None:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def notify_closing(cls, obj: FileDescriptorLike) -> None:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def wrap_listener_socket(cls, sock: socket) -> SocketListener:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def wrap_stream_socket(cls, sock: socket) -> SocketStream:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def wrap_unix_stream_socket(cls, sock: socket) -> UNIXSocketStream:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def wrap_udp_socket(cls, sock: socket) -> UDPSocket:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def wrap_connected_udp_socket(cls, sock: socket) -> ConnectedUDPSocket:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def wrap_unix_datagram_socket(cls, sock: socket) -> UNIXDatagramSocket:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def wrap_connected_unix_datagram_socket(
+        cls, sock: socket
+    ) -> ConnectedUNIXDatagramSocket:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def current_default_thread_limiter(cls) -> CapacityLimiter:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def open_signal_receiver(
+        cls, *signals: Signals
+    ) -> AbstractContextManager[AsyncIterator[Signals]]:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def get_current_task(cls) -> TaskInfo:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def get_running_tasks(cls) -> Sequence[TaskInfo]:
+        pass
+
+    @classmethod
+    @abstractmethod
+    async def wait_all_tasks_blocked(cls) -> None:
+        pass
+
+    @classmethod
+    @abstractmethod
+    def create_test_runner(cls, options: dict[str, Any]) -> TestRunner:
+        pass

+ 33 - 0
vendor/anyio/abc/_resources.py

@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+from abc import ABCMeta, abstractmethod
+from types import TracebackType
+from typing import TypeVar
+
+T = TypeVar("T")
+
+
+class AsyncResource(metaclass=ABCMeta):
+    """
+    Abstract base class for all closeable asynchronous resources.
+
+    Works as an asynchronous context manager which returns the instance itself on enter,
+    and calls :meth:`aclose` on exit.
+    """
+
+    __slots__ = ()
+
+    async def __aenter__(self: T) -> T:
+        return self
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        await self.aclose()
+
+    @abstractmethod
+    async def aclose(self) -> None:
+        """Close the resource."""

+ 399 - 0
vendor/anyio/abc/_sockets.py

@@ -0,0 +1,399 @@
+from __future__ import annotations
+
+import errno
+import socket
+from abc import abstractmethod
+from collections.abc import Callable, Collection, Mapping
+from contextlib import AsyncExitStack
+from io import IOBase
+from ipaddress import IPv4Address, IPv6Address
+from socket import AddressFamily
+from typing import Any, TypeAlias, TypeVar
+
+from .._core._eventloop import get_async_backend
+from .._core._typedattr import (
+    TypedAttributeProvider,
+    TypedAttributeSet,
+    typed_attribute,
+)
+from ._streams import ByteStream, Listener, UnreliableObjectStream
+from ._tasks import TaskGroup
+
+IPAddressType: TypeAlias = str | IPv4Address | IPv6Address
+IPSockAddrType: TypeAlias = tuple[str, int]
+SockAddrType: TypeAlias = IPSockAddrType | str
+UDPPacketType: TypeAlias = tuple[bytes, IPSockAddrType]
+UNIXDatagramPacketType: TypeAlias = tuple[bytes, str]
+T_Retval = TypeVar("T_Retval")
+
+
+def _validate_socket(
+    sock_or_fd: socket.socket | int,
+    sock_type: socket.SocketKind,
+    addr_family: socket.AddressFamily = socket.AF_UNSPEC,
+    *,
+    require_connected: bool = False,
+    require_bound: bool = False,
+) -> socket.socket:
+    if isinstance(sock_or_fd, int):
+        try:
+            sock = socket.socket(fileno=sock_or_fd)
+        except OSError as exc:
+            if exc.errno == errno.ENOTSOCK:
+                raise ValueError(
+                    "the file descriptor does not refer to a socket"
+                ) from exc
+            elif require_connected:
+                raise ValueError("the socket must be connected") from exc
+            elif require_bound:
+                raise ValueError("the socket must be bound to a local address") from exc
+            else:
+                raise
+    elif isinstance(sock_or_fd, socket.socket):
+        sock = sock_or_fd
+    else:
+        raise TypeError(
+            f"expected an int or socket, got {type(sock_or_fd).__qualname__} instead"
+        )
+
+    try:
+        if require_connected:
+            try:
+                sock.getpeername()
+            except OSError as exc:
+                raise ValueError("the socket must be connected") from exc
+
+        if require_bound:
+            try:
+                if sock.family in (socket.AF_INET, socket.AF_INET6):
+                    bound_addr = sock.getsockname()[1]
+                else:
+                    bound_addr = sock.getsockname()
+            except OSError:
+                bound_addr = None
+
+            if not bound_addr:
+                raise ValueError("the socket must be bound to a local address")
+
+        if addr_family != socket.AF_UNSPEC and sock.family != addr_family:
+            raise ValueError(
+                f"address family mismatch: expected {addr_family.name}, got "
+                f"{sock.family.name}"
+            )
+
+        if sock.type != sock_type:
+            raise ValueError(
+                f"socket type mismatch: expected {sock_type.name}, got {sock.type.name}"
+            )
+    except BaseException:
+        # Avoid ResourceWarning from the locally constructed socket object
+        if isinstance(sock_or_fd, int):
+            sock.detach()
+
+        raise
+
+    sock.setblocking(False)
+    return sock
+
+
+class SocketAttribute(TypedAttributeSet):
+    """
+    .. attribute:: family
+        :type: socket.AddressFamily
+
+        the address family of the underlying socket
+
+    .. attribute:: local_address
+        :type: tuple[str, int] | str
+
+        the local address the underlying socket is connected to
+
+    .. attribute:: local_port
+        :type: int
+
+        for IP based sockets, the local port the underlying socket is bound to
+
+    .. attribute:: raw_socket
+        :type: socket.socket
+
+        the underlying stdlib socket object
+
+    .. attribute:: remote_address
+        :type: tuple[str, int] | str
+
+        the remote address the underlying socket is connected to
+
+    .. attribute:: remote_port
+        :type: int
+
+        for IP based sockets, the remote port the underlying socket is connected to
+    """
+
+    family: AddressFamily = typed_attribute()
+    local_address: SockAddrType = typed_attribute()
+    local_port: int = typed_attribute()
+    raw_socket: socket.socket = typed_attribute()
+    remote_address: SockAddrType = typed_attribute()
+    remote_port: int = typed_attribute()
+
+
+class _SocketProvider(TypedAttributeProvider):
+    @property
+    def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
+        from .._core._sockets import convert_ipv6_sockaddr as convert
+
+        attributes: dict[Any, Callable[[], Any]] = {
+            SocketAttribute.family: lambda: self._raw_socket.family,
+            SocketAttribute.local_address: lambda: convert(
+                self._raw_socket.getsockname()
+            ),
+            SocketAttribute.raw_socket: lambda: self._raw_socket,
+        }
+        try:
+            peername: tuple[str, int] | None = convert(self._raw_socket.getpeername())
+        except OSError:
+            peername = None
+
+        # Provide the remote address for connected sockets
+        if peername is not None:
+            attributes[SocketAttribute.remote_address] = lambda: peername
+
+        # Provide local and remote ports for IP based sockets
+        if self._raw_socket.family in (AddressFamily.AF_INET, AddressFamily.AF_INET6):
+            attributes[SocketAttribute.local_port] = lambda: (
+                self._raw_socket.getsockname()[1]
+            )
+            if peername is not None:
+                remote_port = peername[1]
+                attributes[SocketAttribute.remote_port] = lambda: remote_port
+
+        return attributes
+
+    @property
+    @abstractmethod
+    def _raw_socket(self) -> socket.socket:
+        pass
+
+
+class SocketStream(ByteStream, _SocketProvider):
+    """
+    Transports bytes over a socket.
+
+    Supports all relevant extra attributes from :class:`~SocketAttribute`.
+    """
+
+    @classmethod
+    async def from_socket(cls, sock_or_fd: socket.socket | int) -> SocketStream:
+        """
+        Wrap an existing socket object or file descriptor as a socket stream.
+
+        The newly created socket wrapper takes ownership of the socket being passed in.
+        The existing socket must already be connected.
+
+        :param sock_or_fd: a socket object or file descriptor
+        :return: a socket stream
+
+        """
+        sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_connected=True)
+        return await get_async_backend().wrap_stream_socket(sock)
+
+
+class UNIXSocketStream(SocketStream):
+    @classmethod
+    async def from_socket(cls, sock_or_fd: socket.socket | int) -> UNIXSocketStream:
+        """
+        Wrap an existing socket object or file descriptor as a UNIX socket stream.
+
+        The newly created socket wrapper takes ownership of the socket being passed in.
+        The existing socket must already be connected.
+
+        :param sock_or_fd: a socket object or file descriptor
+        :return: a UNIX socket stream
+
+        """
+        sock = _validate_socket(
+            sock_or_fd, socket.SOCK_STREAM, socket.AF_UNIX, require_connected=True
+        )
+        return await get_async_backend().wrap_unix_stream_socket(sock)
+
+    @abstractmethod
+    async def send_fds(self, message: bytes, fds: Collection[int | IOBase]) -> None:
+        """
+        Send file descriptors along with a message to the peer.
+
+        :param message: a non-empty bytestring
+        :param fds: a collection of files (either numeric file descriptors or open file
+            or socket objects)
+        """
+
+    @abstractmethod
+    async def receive_fds(self, msglen: int, maxfds: int) -> tuple[bytes, list[int]]:
+        """
+        Receive file descriptors along with a message from the peer.
+
+        :param msglen: length of the message to expect from the peer
+        :param maxfds: maximum number of file descriptors to expect from the peer
+        :return: a tuple of (message, file descriptors)
+        """
+
+
+class SocketListener(Listener[SocketStream], _SocketProvider):
+    """
+    Listens to incoming socket connections.
+
+    Supports all relevant extra attributes from :class:`~SocketAttribute`.
+    """
+
+    @classmethod
+    async def from_socket(
+        cls,
+        sock_or_fd: socket.socket | int,
+    ) -> SocketListener:
+        """
+        Wrap an existing socket object or file descriptor as a socket listener.
+
+        The newly created listener takes ownership of the socket being passed in.
+
+        :param sock_or_fd: a socket object or file descriptor
+        :return: a socket listener
+
+        """
+        sock = _validate_socket(sock_or_fd, socket.SOCK_STREAM, require_bound=True)
+        return await get_async_backend().wrap_listener_socket(sock)
+
+    @abstractmethod
+    async def accept(self) -> SocketStream:
+        """Accept an incoming connection."""
+
+    async def serve(
+        self,
+        handler: Callable[[SocketStream], Any],
+        task_group: TaskGroup | None = None,
+    ) -> None:
+        from .. import create_task_group
+
+        async with AsyncExitStack() as stack:
+            if task_group is None:
+                task_group = await stack.enter_async_context(create_task_group())
+
+            while True:
+                stream = await self.accept()
+                task_group.start_soon(handler, stream)
+
+
+class UDPSocket(UnreliableObjectStream[UDPPacketType], _SocketProvider):
+    """
+    Represents an unconnected UDP socket.
+
+    Supports all relevant extra attributes from :class:`~SocketAttribute`.
+    """
+
+    @classmethod
+    async def from_socket(cls, sock_or_fd: socket.socket | int) -> UDPSocket:
+        """
+        Wrap an existing socket object or file descriptor as a UDP socket.
+
+        The newly created socket wrapper takes ownership of the socket being passed in.
+        The existing socket must be bound to a local address.
+
+        :param sock_or_fd: a socket object or file descriptor
+        :return: a UDP socket
+
+        """
+        sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, require_bound=True)
+        return await get_async_backend().wrap_udp_socket(sock)
+
+    async def sendto(self, data: bytes, host: str, port: int) -> None:
+        """
+        Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, (host, port))).
+
+        """
+        return await self.send((data, (host, port)))
+
+
+class ConnectedUDPSocket(UnreliableObjectStream[bytes], _SocketProvider):
+    """
+    Represents an connected UDP socket.
+
+    Supports all relevant extra attributes from :class:`~SocketAttribute`.
+    """
+
+    @classmethod
+    async def from_socket(cls, sock_or_fd: socket.socket | int) -> ConnectedUDPSocket:
+        """
+        Wrap an existing socket object or file descriptor as a connected UDP socket.
+
+        The newly created socket wrapper takes ownership of the socket being passed in.
+        The existing socket must already be connected.
+
+        :param sock_or_fd: a socket object or file descriptor
+        :return: a connected UDP socket
+
+        """
+        sock = _validate_socket(
+            sock_or_fd,
+            socket.SOCK_DGRAM,
+            require_connected=True,
+        )
+        return await get_async_backend().wrap_connected_udp_socket(sock)
+
+
+class UNIXDatagramSocket(
+    UnreliableObjectStream[UNIXDatagramPacketType], _SocketProvider
+):
+    """
+    Represents an unconnected Unix datagram socket.
+
+    Supports all relevant extra attributes from :class:`~SocketAttribute`.
+    """
+
+    @classmethod
+    async def from_socket(
+        cls,
+        sock_or_fd: socket.socket | int,
+    ) -> UNIXDatagramSocket:
+        """
+        Wrap an existing socket object or file descriptor as a UNIX datagram
+        socket.
+
+        The newly created socket wrapper takes ownership of the socket being passed in.
+
+        :param sock_or_fd: a socket object or file descriptor
+        :return: a UNIX datagram socket
+
+        """
+        sock = _validate_socket(sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX)
+        return await get_async_backend().wrap_unix_datagram_socket(sock)
+
+    async def sendto(self, data: bytes, path: str) -> None:
+        """Alias for :meth:`~.UnreliableObjectSendStream.send` ((data, path))."""
+        return await self.send((data, path))
+
+
+class ConnectedUNIXDatagramSocket(UnreliableObjectStream[bytes], _SocketProvider):
+    """
+    Represents a connected Unix datagram socket.
+
+    Supports all relevant extra attributes from :class:`~SocketAttribute`.
+    """
+
+    @classmethod
+    async def from_socket(
+        cls,
+        sock_or_fd: socket.socket | int,
+    ) -> ConnectedUNIXDatagramSocket:
+        """
+        Wrap an existing socket object or file descriptor as a connected UNIX datagram
+        socket.
+
+        The newly created socket wrapper takes ownership of the socket being passed in.
+        The existing socket must already be connected.
+
+        :param sock_or_fd: a socket object or file descriptor
+        :return: a connected UNIX datagram socket
+
+        """
+        sock = _validate_socket(
+            sock_or_fd, socket.SOCK_DGRAM, socket.AF_UNIX, require_connected=True
+        )
+        return await get_async_backend().wrap_connected_unix_datagram_socket(sock)

+ 235 - 0
vendor/anyio/abc/_streams.py

@@ -0,0 +1,235 @@
+from __future__ import annotations
+
+from abc import ABCMeta, abstractmethod
+from collections.abc import Callable
+from typing import Any, Generic, TypeAlias, TypeVar
+
+from .._core._exceptions import EndOfStream
+from .._core._typedattr import TypedAttributeProvider
+from ._resources import AsyncResource
+from ._tasks import TaskGroup
+
+T_Item = TypeVar("T_Item")
+T_co = TypeVar("T_co", covariant=True)
+T_contra = TypeVar("T_contra", contravariant=True)
+
+
+class UnreliableObjectReceiveStream(
+    Generic[T_co], AsyncResource, TypedAttributeProvider
+):
+    """
+    An interface for receiving objects.
+
+    This interface makes no guarantees that the received messages arrive in the order in
+    which they were sent, or that no messages are missed.
+
+    Asynchronously iterating over objects of this type will yield objects matching the
+    given type parameter.
+    """
+
+    def __aiter__(self) -> UnreliableObjectReceiveStream[T_co]:
+        return self
+
+    async def __anext__(self) -> T_co:
+        try:
+            return await self.receive()
+        except EndOfStream:
+            raise StopAsyncIteration from None
+
+    @abstractmethod
+    async def receive(self) -> T_co:
+        """
+        Receive the next item.
+
+        :raises ~anyio.ClosedResourceError: if the receive stream has been explicitly
+            closed
+        :raises ~anyio.EndOfStream: if this stream has been closed from the other end
+        :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable
+            due to external causes
+        """
+
+
+class UnreliableObjectSendStream(
+    Generic[T_contra], AsyncResource, TypedAttributeProvider
+):
+    """
+    An interface for sending objects.
+
+    This interface makes no guarantees that the messages sent will reach the
+    recipient(s) in the same order in which they were sent, or at all.
+    """
+
+    @abstractmethod
+    async def send(self, item: T_contra) -> None:
+        """
+        Send an item to the peer(s).
+
+        :param item: the item to send
+        :raises ~anyio.ClosedResourceError: if the send stream has been explicitly
+            closed
+        :raises ~anyio.BrokenResourceError: if this stream has been rendered unusable
+            due to external causes
+        """
+
+
+class UnreliableObjectStream(
+    UnreliableObjectReceiveStream[T_Item], UnreliableObjectSendStream[T_Item]
+):
+    """
+    A bidirectional message stream which does not guarantee the order or reliability of
+    message delivery.
+    """
+
+
+class ObjectReceiveStream(UnreliableObjectReceiveStream[T_co]):
+    """
+    A receive message stream which guarantees that messages are received in the same
+    order in which they were sent, and that no messages are missed.
+    """
+
+
+class ObjectSendStream(UnreliableObjectSendStream[T_contra]):
+    """
+    A send message stream which guarantees that messages are delivered in the same order
+    in which they were sent, without missing any messages in the middle.
+    """
+
+
+class ObjectStream(
+    ObjectReceiveStream[T_Item],
+    ObjectSendStream[T_Item],
+    UnreliableObjectStream[T_Item],
+):
+    """
+    A bidirectional message stream which guarantees the order and reliability of message
+    delivery.
+    """
+
+    @abstractmethod
+    async def send_eof(self) -> None:
+        """
+        Send an end-of-file indication to the peer.
+
+        You should not try to send any further data to this stream after calling this
+        method. This method is idempotent (does nothing on successive calls).
+        """
+
+
+class ByteReceiveStream(AsyncResource, TypedAttributeProvider):
+    """
+    An interface for receiving bytes from a single peer.
+
+    Iterating this byte stream will yield a byte string of arbitrary length, but no more
+    than 65536 bytes.
+    """
+
+    def __aiter__(self) -> ByteReceiveStream:
+        return self
+
+    async def __anext__(self) -> bytes:
+        try:
+            return await self.receive()
+        except EndOfStream:
+            raise StopAsyncIteration from None
+
+    @abstractmethod
+    async def receive(self, max_bytes: int = 65536) -> bytes:
+        """
+        Receive at most ``max_bytes`` bytes from the peer.
+
+        .. note:: Implementers of this interface should not return an empty
+            :class:`bytes` object, and users should ignore them.
+
+        :param max_bytes: maximum number of bytes to receive (must be a positive
+            integer)
+        :return: the received bytes
+        :raises ValueError: if ``max_bytes`` is less than 1
+        :raises ~anyio.EndOfStream: if this stream has been closed from the other end
+        """
+
+
+class ByteSendStream(AsyncResource, TypedAttributeProvider):
+    """An interface for sending bytes to a single peer."""
+
+    @abstractmethod
+    async def send(self, item: bytes) -> None:
+        """
+        Send the given bytes to the peer.
+
+        :param item: the bytes to send
+        """
+
+
+class ByteStream(ByteReceiveStream, ByteSendStream):
+    """A bidirectional byte stream."""
+
+    @abstractmethod
+    async def send_eof(self) -> None:
+        """
+        Send an end-of-file indication to the peer.
+
+        You should not try to send any further data to this stream after calling this
+        method. This method is idempotent (does nothing on successive calls).
+        """
+
+
+#: Type alias for all unreliable bytes-oriented receive streams.
+AnyUnreliableByteReceiveStream: TypeAlias = (
+    UnreliableObjectReceiveStream[bytes] | ByteReceiveStream
+)
+#: Type alias for all unreliable bytes-oriented send streams.
+AnyUnreliableByteSendStream: TypeAlias = (
+    UnreliableObjectSendStream[bytes] | ByteSendStream
+)
+#: Type alias for all unreliable bytes-oriented streams.
+AnyUnreliableByteStream: TypeAlias = UnreliableObjectStream[bytes] | ByteStream
+#: Type alias for all bytes-oriented receive streams.
+AnyByteReceiveStream: TypeAlias = ObjectReceiveStream[bytes] | ByteReceiveStream
+#: Type alias for all bytes-oriented send streams.
+AnyByteSendStream: TypeAlias = ObjectSendStream[bytes] | ByteSendStream
+#: Type alias for all bytes-oriented streams.
+AnyByteStream: TypeAlias = ObjectStream[bytes] | ByteStream
+
+
+class Listener(Generic[T_co], AsyncResource, TypedAttributeProvider):
+    """An interface for objects that let you accept incoming connections."""
+
+    @abstractmethod
+    async def serve(
+        self, handler: Callable[[T_co], Any], task_group: TaskGroup | None = None
+    ) -> None:
+        """
+        Accept incoming connections as they come in and start tasks to handle them.
+
+        :param handler: a callable that will be used to handle each accepted connection
+        :param task_group: the task group that will be used to start tasks for handling
+            each accepted connection (if omitted, an ad-hoc task group will be created)
+        """
+
+
+class ObjectStreamConnectable(Generic[T_co], metaclass=ABCMeta):
+    @abstractmethod
+    async def connect(self) -> ObjectStream[T_co]:
+        """
+        Connect to the remote endpoint.
+
+        :return: an object stream connected to the remote end
+        :raises ConnectionFailed: if the connection fails
+        """
+
+
+class ByteStreamConnectable(metaclass=ABCMeta):
+    @abstractmethod
+    async def connect(self) -> ByteStream:
+        """
+        Connect to the remote endpoint.
+
+        :return: a bytestream connected to the remote end
+        :raises ConnectionFailed: if the connection fails
+        """
+
+
+#: Type alias for all connectables returning bytestreams or bytes-oriented object streams
+AnyByteStreamConnectable: TypeAlias = (
+    ObjectStreamConnectable[bytes] | ByteStreamConnectable
+)

+ 79 - 0
vendor/anyio/abc/_subprocesses.py

@@ -0,0 +1,79 @@
+from __future__ import annotations
+
+from abc import abstractmethod
+from signal import Signals
+
+from ._resources import AsyncResource
+from ._streams import ByteReceiveStream, ByteSendStream
+
+
+class Process(AsyncResource):
+    """An asynchronous version of :class:`subprocess.Popen`."""
+
+    @abstractmethod
+    async def wait(self) -> int:
+        """
+        Wait until the process exits.
+
+        :return: the exit code of the process
+        """
+
+    @abstractmethod
+    def terminate(self) -> None:
+        """
+        Terminates the process, gracefully if possible.
+
+        On Windows, this calls ``TerminateProcess()``.
+        On POSIX systems, this sends ``SIGTERM`` to the process.
+
+        .. seealso:: :meth:`subprocess.Popen.terminate`
+        """
+
+    @abstractmethod
+    def kill(self) -> None:
+        """
+        Kills the process.
+
+        On Windows, this calls ``TerminateProcess()``.
+        On POSIX systems, this sends ``SIGKILL`` to the process.
+
+        .. seealso:: :meth:`subprocess.Popen.kill`
+        """
+
+    @abstractmethod
+    def send_signal(self, signal: Signals) -> None:
+        """
+        Send a signal to the subprocess.
+
+        .. seealso:: :meth:`subprocess.Popen.send_signal`
+
+        :param signal: the signal number (e.g. :data:`signal.SIGHUP`)
+        """
+
+    @property
+    @abstractmethod
+    def pid(self) -> int:
+        """The process ID of the process."""
+
+    @property
+    @abstractmethod
+    def returncode(self) -> int | None:
+        """
+        The return code of the process. If the process has not yet terminated, this will
+        be ``None``.
+        """
+
+    @property
+    @abstractmethod
+    def stdin(self) -> ByteSendStream | None:
+        """The stream for the standard input of the process."""
+
+    @property
+    @abstractmethod
+    def stdout(self) -> ByteReceiveStream | None:
+        """The stream for the standard output of the process."""
+
+    @property
+    @abstractmethod
+    def stderr(self) -> ByteReceiveStream | None:
+        """The stream for the standard error output of the process."""

+ 209 - 0
vendor/anyio/abc/_tasks.py

@@ -0,0 +1,209 @@
+from __future__ import annotations
+
+import sys
+from abc import ABCMeta, abstractmethod
+from collections.abc import Callable, Coroutine
+from contextvars import Context
+from types import TracebackType
+from typing import TYPE_CHECKING, Any, Literal, Protocol, final, overload
+
+if sys.version_info >= (3, 13):
+    from typing import TypeVar
+else:
+    from typing_extensions import TypeVar
+
+if sys.version_info >= (3, 11):
+    from typing import TypeVarTuple, Unpack
+else:
+    from typing_extensions import TypeVarTuple, Unpack
+
+if TYPE_CHECKING:
+    from .._core._tasks import CancelScope, TaskHandle
+
+T_co = TypeVar("T_co", covariant=True)
+T_contra = TypeVar("T_contra", contravariant=True, default=None)
+PosArgsT = TypeVarTuple("PosArgsT")
+
+
+def get_callable_name(func: Callable, override: object = None) -> str:
+    if override is not None:
+        return str(override)
+
+    module = getattr(func, "__module__", None)
+    qualname = getattr(func, "__qualname__", None)
+    return ".".join([x for x in (module, qualname) if x])
+
+
+def call_for_coroutine(
+    func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
+    args: tuple[Unpack[PosArgsT]],
+    **kwargs: Any,
+) -> Coroutine[Any, Any, T_co]:
+    """
+    Call the given function with the given positional and keyword arguments.
+
+    :return: the resulting coroutine
+    :raises TypeError: if the return value was not a coroutine object
+
+    """
+    coro = func(*args, **kwargs)
+    if not isinstance(coro, Coroutine):
+        prefix = f"{func.__module__}." if hasattr(func, "__module__") else ""
+        raise TypeError(
+            f"Expected {prefix}{func.__qualname__}() to return a coroutine, but "
+            f"the return value ({coro!r}) is not a coroutine object"
+        )
+
+    return coro
+
+
+class TaskStatus(Protocol[T_contra]):
+    @overload
+    def started(self: TaskStatus[None]) -> None: ...
+
+    @overload
+    def started(self, value: T_contra) -> None: ...
+
+    def started(self, value: T_contra | None = None) -> None:
+        """
+        Signal that the task has started.
+
+        :param value: object passed back to the starter of the task
+        """
+
+
+class TaskGroup(metaclass=ABCMeta):
+    """
+    Groups several asynchronous tasks together.
+
+    :ivar cancel_scope: the cancel scope inherited by all child tasks
+    :vartype cancel_scope: CancelScope
+
+    .. note:: On asyncio, support for eager task factories is considered to be
+        **experimental**. In particular, they don't follow the usual semantics of new
+        tasks being scheduled on the next iteration of the event loop, and may thus
+        cause unexpected behavior in code that wasn't written with such semantics in
+        mind.
+    """
+
+    cancel_scope: CancelScope
+
+    def cancel(self, reason: str | None = None) -> None:
+        """
+        Cancel this task group's cancel scope immediately.
+
+        This is a shortcut for calling ``.cancel_scope.cancel()`` on the task group.
+
+        :param reason: a message describing the reason for the cancellation
+
+        .. versionadded:: 4.14.0
+
+        """
+        self.cancel_scope.cancel(reason)
+
+    @abstractmethod
+    def create_task(
+        self,
+        coro: Coroutine[Any, Any, T_co],
+        *,
+        name: object = None,
+        context: Context | None = None,
+    ) -> TaskHandle[T_co]:
+        """
+        Create a new task from a coroutine object and schedule it to run.
+
+        :param coro: a coroutine object
+        :param name: optional name to give the task
+        :param context: optional context to run the task in
+        :return: a task handle
+
+        .. versionadded:: 4.14.0
+        """
+
+    @final
+    def start_soon(
+        self,
+        func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
+        *args: Unpack[PosArgsT],
+        name: object = None,
+    ) -> TaskHandle[T_co]:
+        """
+        Start a new task in this task group.
+
+        :param func: a coroutine function
+        :param args: positional arguments to call the function with
+        :param name: name of the task, for the purposes of introspection and debugging
+        :return: a task handle
+
+        .. versionadded:: 3.0
+        .. versionchanged:: 4.14.0
+            This method now returns a task handle.
+
+        """
+        final_name = get_callable_name(func, name)
+        return self.create_task(call_for_coroutine(func, args), name=final_name)
+
+    @overload
+    async def start(
+        self,
+        func: Callable[..., Coroutine[Any, Any, T_co]],
+        *args: object,
+        name: object = None,
+        return_handle: Literal[False] = ...,
+    ) -> Any: ...
+
+    @overload
+    async def start(
+        self,
+        func: Callable[..., Coroutine[Any, Any, T_co]],
+        *args: object,
+        name: object = None,
+        return_handle: Literal[True],
+    ) -> TaskHandle[T_co, Any]: ...
+
+    @abstractmethod
+    async def start(
+        self,
+        func: Callable[..., Coroutine[Any, Any, T_co]],
+        *args: object,
+        name: object = None,
+        return_handle: Literal[False] | Literal[True] = False,
+    ) -> Any:
+        """
+        Start a new task and wait until it signals for readiness.
+
+        The target callable must accept a keyword argument ``task_status`` (of type
+        :class:`TaskStatus`). Awaiting on this method will return whatever was passed to
+        ``task_status.started()`` (``None`` by default).
+
+        .. note:: The :class:`TaskStatus` class is generic, and the type argument should
+            indicate the type of the value that will be passed to
+            ``task_status.started()``.
+
+        :param func: a coroutine function that accepts the ``task_status`` keyword
+            argument
+        :param args: positional arguments to call the function with
+        :param name: an optional name for the task, for introspection and debugging
+        :param return_handle: if ``True``, return a :class:`TaskHandle` which also
+            contains the start value in ``start_value``
+        :return: the value passed to ``task_status.started()``
+        :raises RuntimeError: if the task finishes without calling
+            ``task_status.started()``
+
+        .. seealso:: :ref:`start_initialize`
+
+        .. versionadded:: 3.0
+        """
+
+    @abstractmethod
+    async def __aenter__(self) -> TaskGroup:
+        """Enter the task group context and allow starting new tasks."""
+
+    @abstractmethod
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> bool:
+        """Exit the task group context waiting for all tasks to finish."""

+ 73 - 0
vendor/anyio/abc/_testing.py

@@ -0,0 +1,73 @@
+from __future__ import annotations
+
+import types
+from abc import ABCMeta, abstractmethod
+from collections.abc import AsyncGenerator, Callable, Coroutine, Iterable
+from typing import Any, TypeVar
+
+_T = TypeVar("_T")
+
+
+class TestRunner(metaclass=ABCMeta):
+    """
+    Encapsulates a running event loop. Every call made through this object will use the
+    same event loop.
+    """
+
+    def __enter__(self) -> TestRunner:
+        return self
+
+    @abstractmethod
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: types.TracebackType | None,
+    ) -> bool | None: ...
+
+    @abstractmethod
+    def run_asyncgen_fixture(
+        self,
+        fixture_func: Callable[..., AsyncGenerator[_T, Any]],
+        kwargs: dict[str, Any],
+    ) -> Iterable[_T]:
+        """
+        Run an async generator fixture.
+
+        :param fixture_func: the fixture function
+        :param kwargs: keyword arguments to call the fixture function with
+        :return: an iterator yielding the value yielded from the async generator
+        """
+
+    @abstractmethod
+    def run_fixture(
+        self,
+        fixture_func: Callable[..., Coroutine[Any, Any, _T]],
+        kwargs: dict[str, Any],
+    ) -> _T:
+        """
+        Run an async fixture.
+
+        :param fixture_func: the fixture function
+        :param kwargs: keyword arguments to call the fixture function with
+        :return: the return value of the fixture function
+        """
+
+    @abstractmethod
+    def run_test(
+        self, test_func: Callable[..., Coroutine[Any, Any, Any]], kwargs: dict[str, Any]
+    ) -> None:
+        """
+        Run an async test function.
+
+        :param test_func: the test function
+        :param kwargs: keyword arguments to call the test function with
+        """
+
+    @abstractmethod
+    def is_running(self) -> bool:
+        """
+        Check if the test runner is running.
+
+        :return: ``True`` if the coroutine is currently being run, ``False`` otherwise.
+        """

+ 582 - 0
vendor/anyio/from_thread.py

@@ -0,0 +1,582 @@
+from __future__ import annotations
+
+__all__ = (
+    "BlockingPortal",
+    "BlockingPortalProvider",
+    "check_cancelled",
+    "run",
+    "run_sync",
+    "start_blocking_portal",
+)
+
+import sys
+from collections.abc import Awaitable, Callable, Coroutine, Generator
+from concurrent.futures import Future
+from contextlib import (
+    AbstractAsyncContextManager,
+    AbstractContextManager,
+    contextmanager,
+)
+from dataclasses import dataclass, field
+from functools import partial
+from inspect import isawaitable
+from threading import Lock, Thread, current_thread, get_ident
+from types import TracebackType
+from typing import (
+    Any,
+    Generic,
+    TypeVar,
+    cast,
+    overload,
+)
+
+from ._core._eventloop import (
+    get_cancelled_exc_class,
+    threadlocals,
+)
+from ._core._eventloop import run as run_eventloop
+from ._core._exceptions import NoEventLoopError
+from ._core._synchronization import Event
+from ._core._tasks import CancelScope, create_task_group
+from .abc._tasks import TaskStatus
+from .lowlevel import EventLoopToken, current_token
+
+if sys.version_info >= (3, 11):
+    from typing import TypeVarTuple, Unpack
+else:
+    from typing_extensions import TypeVarTuple, Unpack
+
+T_Retval = TypeVar("T_Retval")
+T_co = TypeVar("T_co", covariant=True)
+PosArgsT = TypeVarTuple("PosArgsT")
+
+
+def _token_or_error(token: EventLoopToken | None) -> EventLoopToken:
+    if token is not None:
+        return token
+
+    try:
+        return threadlocals.current_token
+    except AttributeError:
+        raise NoEventLoopError(
+            "Not running inside an AnyIO worker thread, and no event loop token was "
+            "provided"
+        ) from None
+
+
+def run(
+    func: Callable[[Unpack[PosArgsT]], Coroutine[Any, Any, T_co]],
+    *args: Unpack[PosArgsT],
+    token: EventLoopToken | None = None,
+) -> T_co:
+    """
+    Call a coroutine function from a worker thread.
+
+    :param func: a coroutine function
+    :param args: positional arguments for the callable
+    :param token: an event loop token to use to get back to the event loop thread
+        (required if calling this function from outside an AnyIO worker thread)
+    :return: the return value of the coroutine function
+    :raises MissingTokenError: if no token was provided and called from outside an
+        AnyIO worker thread
+    :raises RunFinishedError: if the event loop tied to ``token`` is no longer running
+
+    .. versionchanged:: 4.11.0
+        Added the ``token`` parameter.
+
+    """
+    explicit_token = token is not None
+    token = _token_or_error(token)
+    return token.backend_class.run_async_from_thread(
+        func, args, token=token.native_token if explicit_token else None
+    )
+
+
+def run_sync(
+    func: Callable[[Unpack[PosArgsT]], T_Retval],
+    *args: Unpack[PosArgsT],
+    token: EventLoopToken | None = None,
+) -> T_Retval:
+    """
+    Call a function in the event loop thread from a worker thread.
+
+    :param func: a callable
+    :param args: positional arguments for the callable
+    :param token: an event loop token to use to get back to the event loop thread
+        (required if calling this function from outside an AnyIO worker thread)
+    :return: the return value of the callable
+    :raises MissingTokenError: if no token was provided and called from outside an
+        AnyIO worker thread
+    :raises RunFinishedError: if the event loop tied to ``token`` is no longer running
+
+    .. versionchanged:: 4.11.0
+        Added the ``token`` parameter.
+
+    """
+    explicit_token = token is not None
+    token = _token_or_error(token)
+    return token.backend_class.run_sync_from_thread(
+        func, args, token=token.native_token if explicit_token else None
+    )
+
+
+class _BlockingAsyncContextManager(Generic[T_co], AbstractContextManager):
+    _enter_future: Future[T_co]
+    _exit_future: Future[bool | None]
+    _exit_event: Event
+    _exit_exc_info: tuple[
+        type[BaseException] | None, BaseException | None, TracebackType | None
+    ] = (None, None, None)
+
+    def __init__(
+        self, async_cm: AbstractAsyncContextManager[T_co], portal: BlockingPortal
+    ):
+        self._async_cm = async_cm
+        self._portal = portal
+
+    async def run_async_cm(self) -> bool | None:
+        try:
+            self._exit_event = Event()
+            value = await self._async_cm.__aenter__()
+        except BaseException as exc:
+            self._enter_future.set_exception(exc)
+            raise
+        else:
+            self._enter_future.set_result(value)
+
+        try:
+            # Wait for the sync context manager to exit.
+            # This next statement can raise `get_cancelled_exc_class()` if
+            # something went wrong in a task group in this async context
+            # manager.
+            await self._exit_event.wait()
+        finally:
+            # In case of cancellation, it could be that we end up here before
+            # `_BlockingAsyncContextManager.__exit__` is called, and an
+            # `_exit_exc_info` has been set.
+            result = await self._async_cm.__aexit__(*self._exit_exc_info)
+
+        return result
+
+    def __enter__(self) -> T_co:
+        self._enter_future = Future()
+        self._exit_future = self._portal.start_task_soon(self.run_async_cm)
+        return self._enter_future.result()
+
+    def __exit__(
+        self,
+        __exc_type: type[BaseException] | None,
+        __exc_value: BaseException | None,
+        __traceback: TracebackType | None,
+    ) -> bool | None:
+        self._exit_exc_info = __exc_type, __exc_value, __traceback
+        self._portal.call(self._exit_event.set)
+        return self._exit_future.result()
+
+
+class _BlockingPortalTaskStatus(TaskStatus):
+    def __init__(self, future: Future):
+        self._future = future
+
+    def started(self, value: object = None) -> None:
+        self._future.set_result(value)
+
+
+class BlockingPortal:
+    """
+    An object that lets external threads run code in an asynchronous event loop.
+
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+    """
+
+    def __init__(self) -> None:
+        self._token = current_token()
+        self._event_loop_thread_id: int | None = get_ident()
+        self._stop_event = Event()
+        self._task_group = create_task_group()
+
+    async def __aenter__(self) -> BlockingPortal:
+        await self._task_group.__aenter__()
+        return self
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> bool:
+        await self.stop()
+        return await self._task_group.__aexit__(exc_type, exc_val, exc_tb)
+
+    def _check_running(self) -> None:
+        if self._event_loop_thread_id is None:
+            raise RuntimeError("This portal is not running")
+        if self._event_loop_thread_id == get_ident():
+            raise RuntimeError(
+                "This method cannot be called from the event loop thread"
+            )
+
+    async def sleep_until_stopped(self) -> None:
+        """Sleep until :meth:`stop` is called."""
+        await self._stop_event.wait()
+
+    async def stop(self, cancel_remaining: bool = False) -> None:
+        """
+        Signal the portal to shut down.
+
+        This marks the portal as no longer accepting new calls and exits from
+        :meth:`sleep_until_stopped`.
+
+        :param cancel_remaining: ``True`` to cancel all the remaining tasks, ``False``
+            to let them finish before returning
+
+        """
+        self._event_loop_thread_id = None
+        self._stop_event.set()
+        if cancel_remaining:
+            self._task_group.cancel_scope.cancel("the blocking portal is shutting down")
+
+    async def _call_func(
+        self,
+        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
+        args: tuple[Unpack[PosArgsT]],
+        kwargs: dict[str, Any],
+        future: Future[T_Retval],
+    ) -> None:
+        event_loop_thread_id = self._event_loop_thread_id
+
+        def callback(f: Future[T_Retval]) -> None:
+            if f.cancelled():
+                if event_loop_thread_id == get_ident():
+                    scope.cancel("the future was cancelled")
+                elif event_loop_thread_id is not None:
+                    run_sync(
+                        scope.cancel, "the future was cancelled", token=self._token
+                    )
+
+        try:
+            retval_or_awaitable = func(*args, **kwargs)
+            if isawaitable(retval_or_awaitable):
+                with CancelScope() as scope:
+                    future.add_done_callback(callback)
+                    retval = await retval_or_awaitable
+            else:
+                retval = retval_or_awaitable
+        except get_cancelled_exc_class():
+            future.cancel()
+            future.set_running_or_notify_cancel()
+        except BaseException as exc:
+            if not future.cancelled():
+                future.set_exception(exc)
+
+            # Let base exceptions fall through
+            if not isinstance(exc, Exception):
+                raise
+        else:
+            if not future.cancelled():
+                future.set_result(retval)
+        finally:
+            scope = None  # type: ignore[assignment]
+
+    def _spawn_task_from_thread(
+        self,
+        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
+        args: tuple[Unpack[PosArgsT]],
+        kwargs: dict[str, Any],
+        name: object,
+        future: Future[T_Retval],
+    ) -> None:
+        """
+        Spawn a new task using the given callable.
+
+        :param func: a callable
+        :param args: positional arguments to be passed to the callable
+        :param kwargs: keyword arguments to be passed to the callable
+        :param name: name of the task (will be coerced to a string if not ``None``)
+        :param future: a future that will resolve to the return value of the callable,
+            or the exception raised during its execution
+
+        """
+        run_sync(
+            partial(self._task_group.start_soon, name=name),
+            self._call_func,
+            func,
+            args,
+            kwargs,
+            future,
+            token=self._token,
+        )
+
+    @overload
+    def call(
+        self,
+        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
+        *args: Unpack[PosArgsT],
+    ) -> T_Retval: ...
+
+    @overload
+    def call(
+        self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT]
+    ) -> T_Retval: ...
+
+    def call(
+        self,
+        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
+        *args: Unpack[PosArgsT],
+    ) -> T_Retval:
+        """
+        Call the given function in the event loop thread.
+
+        If the callable returns a coroutine object, it is awaited on.
+
+        :param func: any callable
+        :raises RuntimeError: if the portal is not running or if this method is called
+            from within the event loop thread
+
+        """
+        return cast(T_Retval, self.start_task_soon(func, *args).result())
+
+    @overload
+    def start_task_soon(
+        self,
+        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval]],
+        *args: Unpack[PosArgsT],
+        name: object = None,
+    ) -> Future[T_Retval]: ...
+
+    @overload
+    def start_task_soon(
+        self,
+        func: Callable[[Unpack[PosArgsT]], T_Retval],
+        *args: Unpack[PosArgsT],
+        name: object = None,
+    ) -> Future[T_Retval]: ...
+
+    def start_task_soon(
+        self,
+        func: Callable[[Unpack[PosArgsT]], Awaitable[T_Retval] | T_Retval],
+        *args: Unpack[PosArgsT],
+        name: object = None,
+    ) -> Future[T_Retval]:
+        """
+        Start a task in the portal's task group.
+
+        The task will be run inside a cancel scope which can be cancelled by cancelling
+        the returned future.
+
+        :param func: the target function
+        :param args: positional arguments passed to ``func``
+        :param name: name of the task (will be coerced to a string if not ``None``)
+        :return: a future that resolves with the return value of the callable if the
+            task completes successfully, or with the exception raised in the task
+        :raises RuntimeError: if the portal is not running or if this method is called
+            from within the event loop thread
+        :rtype: concurrent.futures.Future[T_Retval]
+
+        .. versionadded:: 3.0
+
+        """
+        self._check_running()
+        f: Future[T_Retval] = Future()
+        self._spawn_task_from_thread(func, args, {}, name, f)
+        return f
+
+    def start_task(
+        self,
+        func: Callable[..., Awaitable[T_Retval]],
+        *args: object,
+        name: object = None,
+    ) -> tuple[Future[T_Retval], Any]:
+        """
+        Start a task in the portal's task group and wait until it signals for readiness.
+
+        This method works the same way as :meth:`.abc.TaskGroup.start`.
+
+        :param func: the target function
+        :param args: positional arguments passed to ``func``
+        :param name: name of the task (will be coerced to a string if not ``None``)
+        :return: a tuple of (future, task_status_value) where the ``task_status_value``
+            is the value passed to ``task_status.started()`` from within the target
+            function
+        :rtype: tuple[concurrent.futures.Future[T_Retval], Any]
+
+        .. versionadded:: 3.0
+
+        """
+
+        def task_done(future: Future[T_Retval]) -> None:
+            if not task_status_future.done():
+                if future.cancelled():
+                    task_status_future.cancel()
+                elif future.exception():
+                    task_status_future.set_exception(future.exception())
+                else:
+                    exc = RuntimeError(
+                        "Task exited without calling task_status.started()"
+                    )
+                    task_status_future.set_exception(exc)
+
+        self._check_running()
+        task_status_future: Future = Future()
+        task_status = _BlockingPortalTaskStatus(task_status_future)
+        f: Future = Future()
+        f.add_done_callback(task_done)
+        self._spawn_task_from_thread(func, args, {"task_status": task_status}, name, f)
+        return f, task_status_future.result()
+
+    def wrap_async_context_manager(
+        self, cm: AbstractAsyncContextManager[T_co]
+    ) -> AbstractContextManager[T_co]:
+        """
+        Wrap an async context manager as a synchronous context manager via this portal.
+
+        Spawns a task that will call both ``__aenter__()`` and ``__aexit__()``, stopping
+        in the middle until the synchronous context manager exits.
+
+        :param cm: an asynchronous context manager
+        :return: a synchronous context manager
+
+        .. versionadded:: 2.1
+
+        """
+        return _BlockingAsyncContextManager(cm, self)
+
+
+@dataclass
+class BlockingPortalProvider:
+    """
+    A manager for a blocking portal. Used as a context manager. The first thread to
+    enter this context manager causes a blocking portal to be started with the specific
+    parameters, and the last thread to exit causes the portal to be shut down. Thus,
+    there will be exactly one blocking portal running in this context as long as at
+    least one thread has entered this context manager.
+
+    The parameters are the same as for :func:`~anyio.run`.
+
+    :param backend: name of the backend
+    :param backend_options: backend options
+
+    .. versionadded:: 4.4
+    """
+
+    backend: str = "asyncio"
+    backend_options: dict[str, Any] | None = None
+    _lock: Lock = field(init=False, default_factory=Lock)
+    _leases: int = field(init=False, default=0)
+    _portal: BlockingPortal = field(init=False)
+    _portal_cm: AbstractContextManager[BlockingPortal] | None = field(
+        init=False, default=None
+    )
+
+    def __enter__(self) -> BlockingPortal:
+        with self._lock:
+            if self._portal_cm is None:
+                self._portal_cm = start_blocking_portal(
+                    self.backend, self.backend_options
+                )
+                self._portal = self._portal_cm.__enter__()
+
+            self._leases += 1
+            return self._portal
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        portal_cm: AbstractContextManager[BlockingPortal] | None = None
+        with self._lock:
+            assert self._portal_cm
+            assert self._leases > 0
+            self._leases -= 1
+            if not self._leases:
+                portal_cm = self._portal_cm
+                self._portal_cm = None
+                del self._portal
+
+        if portal_cm:
+            portal_cm.__exit__(None, None, None)
+
+
+@contextmanager
+def start_blocking_portal(
+    backend: str = "asyncio",
+    backend_options: dict[str, Any] | None = None,
+    *,
+    name: str | None = None,
+) -> Generator[BlockingPortal, Any, None]:
+    """
+    Start a new event loop in a new thread and run a blocking portal in its main task.
+
+    The parameters are the same as for :func:`~anyio.run`.
+
+    :param backend: name of the backend
+    :param backend_options: backend options
+    :param name: name of the thread
+    :return: a context manager that yields a blocking portal
+
+    .. versionchanged:: 3.0
+        Usage as a context manager is now required.
+
+    """
+
+    async def run_portal() -> None:
+        async with BlockingPortal() as portal_:
+            if name is None:
+                current_thread().name = f"{backend}-portal-{id(portal_):x}"
+
+            future.set_result(portal_)
+            await portal_.sleep_until_stopped()
+
+    def run_blocking_portal() -> None:
+        if future.set_running_or_notify_cancel():
+            try:
+                run_eventloop(
+                    run_portal, backend=backend, backend_options=backend_options
+                )
+            except BaseException as exc:
+                if not future.done():
+                    future.set_exception(exc)
+
+    future: Future[BlockingPortal] = Future()
+    thread = Thread(target=run_blocking_portal, daemon=True, name=name)
+    thread.start()
+    try:
+        cancel_remaining_tasks = False
+        portal = future.result()
+        try:
+            yield portal
+        except BaseException:
+            cancel_remaining_tasks = True
+            raise
+        finally:
+            try:
+                portal.call(portal.stop, cancel_remaining_tasks)
+            except RuntimeError:
+                pass
+    finally:
+        thread.join()
+
+
+def check_cancelled() -> None:
+    """
+    Check if the cancel scope of the host task's running the current worker thread has
+    been cancelled.
+
+    If the host task's current cancel scope has indeed been cancelled, the
+    backend-specific cancellation exception will be raised.
+
+    :raises RuntimeError: if the current thread was not spawned by
+        :func:`.to_thread.run_sync`
+
+    """
+    try:
+        token: EventLoopToken = threadlocals.current_token
+    except AttributeError:
+        raise NoEventLoopError(
+            "This function can only be called inside an AnyIO worker thread"
+        ) from None
+
+    token.backend_class.check_cancelled()

+ 400 - 0
vendor/anyio/functools.py

@@ -0,0 +1,400 @@
+from __future__ import annotations
+
+__all__ = (
+    "AsyncCacheInfo",
+    "AsyncCacheParameters",
+    "AsyncLRUCacheWrapper",
+    "cache",
+    "lru_cache",
+    "reduce",
+)
+
+import functools
+from collections import OrderedDict
+from collections.abc import (
+    AsyncIterable,
+    Awaitable,
+    Callable,
+    Coroutine,
+    Hashable,
+    Iterable,
+)
+from functools import update_wrapper
+from inspect import iscoroutinefunction
+from typing import (
+    Any,
+    Generic,
+    NamedTuple,
+    ParamSpec,
+    TypedDict,
+    TypeVar,
+    cast,
+    final,
+    overload,
+)
+from weakref import WeakKeyDictionary
+
+from ._core._eventloop import current_time
+from ._core._synchronization import Lock
+from .lowlevel import RunVar, checkpoint
+
+T = TypeVar("T")
+S = TypeVar("S")
+P = ParamSpec("P")
+lru_cache_items: RunVar[
+    WeakKeyDictionary[
+        AsyncLRUCacheWrapper[Any, Any],
+        OrderedDict[
+            Hashable,
+            tuple[_InitialMissingType, Lock, float | None]
+            | tuple[Any, None, float | None],
+        ],
+    ]
+] = RunVar("lru_cache_items")
+
+
+class _InitialMissingType:
+    pass
+
+
+initial_missing: _InitialMissingType = _InitialMissingType()
+
+
+class AsyncCacheInfo(NamedTuple):
+    hits: int
+    misses: int
+    maxsize: int | None
+    currsize: int
+    ttl: int | None
+
+
+class AsyncCacheParameters(TypedDict):
+    maxsize: int | None
+    typed: bool
+    always_checkpoint: bool
+    ttl: int | None
+
+
+class _LRUMethodWrapper(Generic[T]):
+    def __init__(self, wrapper: AsyncLRUCacheWrapper[..., T], instance: object):
+        self.__wrapper = wrapper
+        self.__instance = instance
+
+    def cache_info(self) -> AsyncCacheInfo:
+        return self.__wrapper.cache_info()
+
+    def cache_parameters(self) -> AsyncCacheParameters:
+        return self.__wrapper.cache_parameters()
+
+    def cache_clear(self) -> None:
+        self.__wrapper.cache_clear()
+
+    async def __call__(self, *args: Any, **kwargs: Any) -> T:
+        if self.__instance is None:
+            return await self.__wrapper(*args, **kwargs)
+
+        return await self.__wrapper(self.__instance, *args, **kwargs)
+
+
+@final
+class AsyncLRUCacheWrapper(Generic[P, T]):
+    def __init__(
+        self,
+        func: Callable[P, Awaitable[T]],
+        maxsize: int | None,
+        typed: bool,
+        always_checkpoint: bool,
+        ttl: int | None,
+    ):
+        self.__wrapped__ = func
+        self._hits: int = 0
+        self._misses: int = 0
+        self._maxsize = max(maxsize, 0) if maxsize is not None else None
+        self._currsize: int = 0
+        self._typed = typed
+        self._always_checkpoint = always_checkpoint
+        self._ttl = ttl
+        update_wrapper(self, func)
+
+    def cache_info(self) -> AsyncCacheInfo:
+        return AsyncCacheInfo(
+            self._hits, self._misses, self._maxsize, self._currsize, self._ttl
+        )
+
+    def cache_parameters(self) -> AsyncCacheParameters:
+        return {
+            "maxsize": self._maxsize,
+            "typed": self._typed,
+            "always_checkpoint": self._always_checkpoint,
+            "ttl": self._ttl,
+        }
+
+    def cache_clear(self) -> None:
+        if cache := lru_cache_items.get(None):
+            cache.pop(self, None)
+            self._hits = self._misses = self._currsize = 0
+
+    async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
+        # Easy case first: if maxsize == 0, no caching is done
+        if self._maxsize == 0:
+            value = await self.__wrapped__(*args, **kwargs)
+            self._misses += 1
+            return value
+
+        # The key is constructed as a flat tuple to avoid memory overhead
+        key: tuple[Any, ...] = args
+        if kwargs:
+            # initial_missing is used as a separator
+            key += (initial_missing,) + sum(kwargs.items(), ())
+
+        if self._typed:
+            key += tuple(type(arg) for arg in args)
+            if kwargs:
+                key += (initial_missing,) + tuple(type(val) for val in kwargs.values())
+
+        try:
+            cache = lru_cache_items.get()
+        except LookupError:
+            cache = WeakKeyDictionary()
+            lru_cache_items.set(cache)
+
+        try:
+            cache_entry = cache[self]
+        except KeyError:
+            cache_entry = cache[self] = OrderedDict()
+
+        cached_value: T | _InitialMissingType
+        try:
+            cached_value, lock, expires_at = cache_entry[key]
+        except KeyError:
+            # We're the first task to call this function
+            cached_value, lock, expires_at = (
+                initial_missing,
+                Lock(fast_acquire=not self._always_checkpoint),
+                None,
+            )
+            cache_entry[key] = cached_value, lock, expires_at
+
+        if lock is None:
+            if expires_at is not None and current_time() >= expires_at:
+                self._currsize -= 1
+                cached_value, lock, expires_at = (
+                    initial_missing,
+                    Lock(fast_acquire=not self._always_checkpoint),
+                    None,
+                )
+                cache_entry[key] = cached_value, lock, expires_at
+            else:
+                # The value was already cached
+                self._hits += 1
+                cache_entry.move_to_end(key)
+                if self._always_checkpoint:
+                    await checkpoint()
+
+                return cast(T, cached_value)
+
+        async with lock:
+            # Check if another task filled the cache while we acquired the lock
+            if (cached_value := cache_entry[key][0]) is initial_missing:
+                self._misses += 1
+                if self._maxsize is not None and self._currsize >= self._maxsize:
+                    cache_entry.popitem(last=False)
+                else:
+                    self._currsize += 1
+
+                value = await self.__wrapped__(*args, **kwargs)
+                expires_at = (
+                    current_time() + self._ttl if self._ttl is not None else None
+                )
+                cache_entry[key] = value, None, expires_at
+            else:
+                # Another task filled the cache while we were waiting for the lock
+                self._hits += 1
+                cache_entry.move_to_end(key)
+                value = cast(T, cached_value)
+
+        return value
+
+    def __get__(
+        self, instance: object, owner: type | None = None
+    ) -> _LRUMethodWrapper[T]:
+        wrapper = _LRUMethodWrapper(self, instance)
+        update_wrapper(wrapper, self.__wrapped__)
+        return wrapper
+
+
+class _LRUCacheWrapper:
+    def __init__(
+        self, maxsize: int | None, typed: bool, always_checkpoint: bool, ttl: int | None
+    ):
+        self._maxsize = maxsize
+        self._typed = typed
+        self._always_checkpoint = always_checkpoint
+        self._ttl = ttl
+
+    @overload
+    def __call__(  # type: ignore[overload-overlap]
+        self, func: Callable[P, Coroutine[Any, Any, T]], /
+    ) -> AsyncLRUCacheWrapper[P, T]: ...
+
+    @overload
+    def __call__(
+        self, func: Callable[..., T], /
+    ) -> functools._lru_cache_wrapper[T]: ...
+
+    def __call__(
+        self, f: Callable[P, Coroutine[Any, Any, T]] | Callable[..., T], /
+    ) -> AsyncLRUCacheWrapper[P, T] | functools._lru_cache_wrapper[T]:
+        if iscoroutinefunction(f):
+            return AsyncLRUCacheWrapper(
+                f, self._maxsize, self._typed, self._always_checkpoint, self._ttl
+            )
+
+        return functools.lru_cache(maxsize=self._maxsize, typed=self._typed)(f)  # type: ignore[arg-type]
+
+
+@overload
+def cache(  # type: ignore[overload-overlap]
+    func: Callable[P, Coroutine[Any, Any, T]], /
+) -> AsyncLRUCacheWrapper[P, T]: ...
+
+
+@overload
+def cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ...
+
+
+def cache(func: Callable[..., Any] | Callable[P, Coroutine[Any, Any, Any]], /) -> Any:
+    """
+    A convenient shortcut for :func:`lru_cache` with ``maxsize=None``.
+
+    This is the asynchronous equivalent to :func:`functools.cache`.
+
+    """
+    return lru_cache(maxsize=None)(func)
+
+
+@overload
+def lru_cache(
+    *,
+    maxsize: int | None = ...,
+    typed: bool = ...,
+    always_checkpoint: bool = ...,
+    ttl: int | None = ...,
+) -> _LRUCacheWrapper: ...
+
+
+@overload
+def lru_cache(  # type: ignore[overload-overlap]
+    func: Callable[P, Coroutine[Any, Any, T]], /
+) -> AsyncLRUCacheWrapper[P, T]: ...
+
+
+@overload
+def lru_cache(func: Callable[..., T], /) -> functools._lru_cache_wrapper[T]: ...
+
+
+def lru_cache(
+    func: Callable[..., Coroutine[Any, Any, Any]] | Callable[..., Any] | None = None,
+    /,
+    *,
+    maxsize: int | None = 128,
+    typed: bool = False,
+    always_checkpoint: bool = False,
+    ttl: int | None = None,
+) -> Any:
+    """
+    An asynchronous version of :func:`functools.lru_cache`.
+
+    If a synchronous function is passed, the standard library
+    :func:`functools.lru_cache` is applied instead.
+
+    :param always_checkpoint: if ``True``, every call to the cached function will be
+        guaranteed to yield control to the event loop at least once
+    :param ttl: time in seconds after which to invalidate cache entries
+
+    .. note:: Caches and locks are managed on a per-event loop basis.
+
+    """
+    if func is None:
+        return _LRUCacheWrapper(maxsize, typed, always_checkpoint, ttl)
+
+    if not callable(func):
+        raise TypeError("the first argument must be callable")
+
+    return _LRUCacheWrapper(maxsize, typed, always_checkpoint, ttl)(func)
+
+
+@overload
+async def reduce(
+    function: Callable[[T, S], Awaitable[T]],
+    iterable: Iterable[S] | AsyncIterable[S],
+    /,
+    initial: T,
+) -> T: ...
+
+
+@overload
+async def reduce(
+    function: Callable[[T, T], Awaitable[T]],
+    iterable: Iterable[T] | AsyncIterable[T],
+    /,
+) -> T: ...
+
+
+async def reduce(  # type: ignore[misc]
+    function: Callable[[T, T], Awaitable[T]] | Callable[[T, S], Awaitable[T]],
+    iterable: Iterable[T] | Iterable[S] | AsyncIterable[T] | AsyncIterable[S],
+    /,
+    initial: T | _InitialMissingType = initial_missing,
+) -> T:
+    """
+    Asynchronous version of :func:`functools.reduce`.
+
+    :param function: a coroutine function that takes two arguments: the accumulated
+        value and the next element from the iterable
+    :param iterable: an iterable or async iterable
+    :param initial: the initial value (if missing, the first element of the iterable is
+        used as the initial value)
+
+    """
+    element: Any
+    function_called = False
+    if isinstance(iterable, AsyncIterable):
+        async_it = iterable.__aiter__()
+        if initial is initial_missing:
+            try:
+                value = cast(T, await async_it.__anext__())
+            except StopAsyncIteration:
+                raise TypeError(
+                    "reduce() of empty sequence with no initial value"
+                ) from None
+        else:
+            value = cast(T, initial)
+
+        async for element in async_it:
+            value = await function(value, element)
+            function_called = True
+    elif isinstance(iterable, Iterable):
+        it = iter(iterable)
+        if initial is initial_missing:
+            try:
+                value = cast(T, next(it))
+            except StopIteration:
+                raise TypeError(
+                    "reduce() of empty sequence with no initial value"
+                ) from None
+        else:
+            value = cast(T, initial)
+
+        for element in it:
+            value = await function(value, element)
+            function_called = True
+    else:
+        raise TypeError("reduce() argument 2 must be an iterable or async iterable")
+
+    # Make sure there is at least one checkpoint, even if an empty iterable and an
+    # initial value were given
+    if not function_called:
+        await checkpoint()
+
+    return value

+ 626 - 0
vendor/anyio/itertools.py

@@ -0,0 +1,626 @@
+from __future__ import annotations
+
+__all__ = (
+    "accumulate",
+    "batched",
+    "Chain",
+    "combinations",
+    "combinations_with_replacement",
+    "compress",
+    "count",
+    "cycle",
+    "dropwhile",
+    "filterfalse",
+    "groupby",
+    "islice",
+    "pairwise",
+    "permutations",
+    "product",
+    "repeat",
+    "starmap",
+    "tee",
+    "takewhile",
+    "zip_longest",
+)
+
+import itertools
+import operator
+import sys
+from collections.abc import (
+    AsyncGenerator,
+    AsyncIterable,
+    AsyncIterator,
+    Awaitable,
+    Callable,
+    Iterable,
+    Iterator,
+)
+from dataclasses import dataclass, field
+from typing import Any, Generic, TypeVar, cast, overload
+
+from ._core._synchronization import Lock
+from ._core._tasks import CancelScope
+from .lowlevel import cancel_shielded_checkpoint, checkpoint, checkpoint_if_cancelled
+
+T = TypeVar("T")
+R = TypeVar("R")
+_tee_end = object()
+
+
+@dataclass(eq=False)
+class _IterableAsyncIterator(AsyncIterator[T]):
+    iterator: Iterator[T]
+
+    async def __anext__(self) -> T:
+        await checkpoint_if_cancelled()
+        try:
+            result = next(self.iterator)
+        except StopIteration:
+            await cancel_shielded_checkpoint()
+            raise StopAsyncIteration from None
+
+        await cancel_shielded_checkpoint()
+        return result
+
+
+def _iterate(iterable: Iterable[T] | AsyncIterable[T]) -> AsyncIterator[T]:
+    if isinstance(iterable, AsyncIterator):
+        return iterable
+
+    if isinstance(iterable, AsyncIterable):
+        return iterable.__aiter__()
+
+    return _IterableAsyncIterator(iter(iterable))
+
+
+@dataclass(eq=False)
+class _TeeLink(Generic[T]):
+    value: object | None = None
+    next: _TeeLink[T] | None = None
+    filled: bool = False
+
+
+@dataclass(eq=False)
+class _TeeState(Generic[T]):
+    iterator: AsyncIterator[T]
+    lock: Lock = field(default_factory=Lock)
+
+    async def fill(self, link: _TeeLink[T]) -> bool:
+        if link.filled:
+            return False
+
+        async with self.lock:
+            if link.filled:
+                return True
+
+            link.value = await anext(self.iterator, _tee_end)
+            if link.value is not _tee_end:
+                link.next = _TeeLink()
+
+            link.filled = True
+            return True
+
+
+class _TeeAsyncIterator(AsyncIterator[T]):
+    _state: _TeeState[T]
+    _link: _TeeLink[T]
+    _element_yielded: bool
+
+    def __init__(
+        self, iterable: Iterable[T] | AsyncIterable[T] | _TeeAsyncIterator[T]
+    ) -> None:
+        if isinstance(iterable, _TeeAsyncIterator):
+            self._state = iterable._state
+            self._link = iterable._link
+        else:
+            self._state = _TeeState(_iterate(iterable))
+            self._link = _TeeLink()
+
+        self._element_yielded = False
+
+    async def __anext__(self) -> T:
+        had_yieldpoint = await self._state.fill(self._link)
+        if self._link.value is _tee_end:
+            if not self._element_yielded:
+                await checkpoint()
+
+            raise StopAsyncIteration
+
+        if not had_yieldpoint:
+            await checkpoint_if_cancelled()
+
+        self._element_yielded = True
+        value = cast(T, self._link.value)
+        next_link = self._link.next
+        assert next_link is not None
+        self._link = next_link
+        if not had_yieldpoint:
+            await cancel_shielded_checkpoint()
+
+        return value
+
+
+async def _operator_add(x: T, y: T) -> T:
+    return operator.add(x, y)
+
+
+async def accumulate(
+    iterable: Iterable[T] | AsyncIterable[T],
+    function: Callable[[T, T], Awaitable[T]] = _operator_add,
+    *,
+    initial: T | None = None,
+) -> AsyncGenerator[T, None]:
+    iterator = _iterate(iterable)
+    if initial is None:
+        try:
+            total = await anext(iterator)
+        except StopAsyncIteration:
+            await checkpoint()
+            return
+    else:
+        await checkpoint_if_cancelled()
+        total = initial
+        await cancel_shielded_checkpoint()
+
+    yield total
+
+    async for element in iterator:
+        total = await function(total, element)
+        yield total
+
+
+async def batched(
+    iterable: Iterable[T] | AsyncIterable[T], n: int, *, strict: bool = False
+) -> AsyncGenerator[tuple[T, ...], None]:
+    if n < 1:
+        raise ValueError("n must be at least one")
+
+    iterator = _iterate(iterable)
+
+    while True:
+        batch: list[T] = []
+        for _ in range(n):
+            try:
+                batch.append(await anext(iterator))
+            except StopAsyncIteration:
+                if not batch:
+                    await checkpoint()
+                    return
+                if strict:
+                    raise ValueError("batched(): incomplete batch") from None
+
+                yield tuple(batch)
+                return
+
+        yield tuple(batch)
+
+
+class Chain:
+    def __call__(
+        self, *iterables: Iterable[T] | AsyncIterable[T]
+    ) -> AsyncGenerator[T, None]:
+        return self.from_iterable(iterables)
+
+    async def from_iterable(
+        self,
+        iterables: (
+            Iterable[Iterable[T] | AsyncIterable[T]]
+            | AsyncIterable[Iterable[T] | AsyncIterable[T]]
+        ),
+    ) -> AsyncGenerator[T, None]:
+        element_yielded = False
+        outer_iter = _iterate(iterables)
+
+        try:
+            async for iterable in outer_iter:
+                async for element in _iterate(iterable):
+                    element_yielded = True
+                    yield element
+        finally:
+            aclose = getattr(outer_iter, "aclose", None)
+            if aclose is not None:
+                with CancelScope(shield=True):
+                    await aclose()
+
+        if not element_yielded:
+            await checkpoint()
+
+
+chain: Chain = Chain()
+
+
+async def combinations(
+    iterable: Iterable[T] | AsyncIterable[T], r: int
+) -> AsyncGenerator[tuple[T, ...], None]:
+    pool: list[T] = [element async for element in _iterate(iterable)]
+    async for combination in _iterate(itertools.combinations(pool, r)):
+        yield combination
+
+
+async def combinations_with_replacement(
+    iterable: Iterable[T] | AsyncIterable[T], r: int
+) -> AsyncGenerator[tuple[T, ...], None]:
+    pool: list[T] = [element async for element in _iterate(iterable)]
+    async for combination in _iterate(itertools.combinations_with_replacement(pool, r)):
+        yield combination
+
+
+async def compress(
+    data: Iterable[T] | AsyncIterable[T],
+    selectors: Iterable[object] | AsyncIterable[object],
+) -> AsyncGenerator[T, None]:
+    data_iterator = _iterate(data)
+    selector_iterator = _iterate(selectors)
+    element_yielded = False
+
+    while True:
+        try:
+            datum = await anext(data_iterator)
+            selector = await anext(selector_iterator)
+        except StopAsyncIteration:
+            if not element_yielded:
+                await checkpoint()
+
+            return
+
+        if selector:
+            element_yielded = True
+            yield datum
+
+
+async def count(start: int = 0, step: int = 1) -> AsyncGenerator[int, None]:
+    n = start
+    while True:
+        await checkpoint_if_cancelled()
+        value = n
+        n += step
+        await cancel_shielded_checkpoint()
+        yield value
+
+
+async def cycle(
+    iterable: Iterable[T] | AsyncIterable[T],
+) -> AsyncGenerator[T, None]:
+    saved: list[T] = []
+    async for element in _iterate(iterable):
+        saved.append(element)
+        yield element
+
+    if not saved:
+        await checkpoint()
+        return
+
+    while True:
+        for element in saved:
+            await checkpoint()
+            yield element
+
+
+async def dropwhile(
+    predicate: Callable[[T], Awaitable[object]],
+    iterable: Iterable[T] | AsyncIterable[T],
+) -> AsyncGenerator[T, None]:
+    element_yielded = False
+    dropping = True
+
+    async for element in _iterate(iterable):
+        if dropping and await predicate(element):
+            continue
+
+        dropping = False
+        element_yielded = True
+        yield element
+
+    if not element_yielded:
+        await checkpoint()
+
+
+async def filterfalse(
+    predicate: Callable[[T], Awaitable[object]],
+    iterable: Iterable[T] | AsyncIterable[T],
+) -> AsyncGenerator[T, None]:
+    element_yielded = False
+
+    async for element in _iterate(iterable):
+        if not await predicate(element):
+            element_yielded = True
+            yield element
+
+    if not element_yielded:
+        await checkpoint()
+
+
+@overload
+def groupby(
+    iterable: Iterable[T] | AsyncIterable[T],
+) -> AsyncGenerator[tuple[T, list[T]], None]: ...
+
+
+@overload
+def groupby(
+    iterable: Iterable[T] | AsyncIterable[T],
+    key: Callable[[T], Awaitable[R]],
+) -> AsyncGenerator[tuple[R, list[T]], None]: ...
+
+
+async def groupby(
+    iterable: Iterable[T] | AsyncIterable[T],
+    key: Callable[[T], Awaitable[object]] | None = None,
+) -> AsyncGenerator[tuple[object, list[T]], None]:
+    iterator = _iterate(iterable)
+    try:
+        element = await anext(iterator)
+    except StopAsyncIteration:
+        await checkpoint()
+        return
+
+    group_key = element if key is None else await key(element)
+    values = [element]
+
+    async for element in iterator:
+        next_key = element if key is None else await key(element)
+        if next_key != group_key:
+            completed_group = group_key, values
+            group_key = next_key
+            values = [element]
+            yield completed_group
+        else:
+            values.append(element)
+
+    yield group_key, values
+
+
+@overload
+def islice(
+    iterable: Iterable[T] | AsyncIterable[T],
+    stop: int | None,
+    /,
+) -> AsyncGenerator[T, None]: ...
+
+
+@overload
+def islice(
+    iterable: Iterable[T] | AsyncIterable[T],
+    start: int | None,
+    stop: int | None,
+    step: int | None = 1,
+    /,
+) -> AsyncGenerator[T, None]: ...
+
+
+async def islice(
+    iterable: Iterable[T] | AsyncIterable[T],
+    *args: int | None,
+) -> AsyncGenerator[T, None]:
+    if not args:
+        raise TypeError("islice expected at least 2 arguments, got 1")
+    if len(args) > 3:
+        raise TypeError(f"islice expected at most 4 arguments, got {len(args) + 1}")
+
+    slice_args = slice(*args)
+
+    start_message = (
+        "Indices for islice() must be None or an integer: 0 <= x <= sys.maxsize."
+    )
+    stop_message = (
+        "Stop argument for islice() must be None or an integer: 0 <= x <= sys.maxsize."
+    )
+    step_message = "Step for islice() must be a positive integer or None."
+
+    def normalize_index(value: object, message: str) -> int:
+        try:
+            index = operator.index(cast(Any, value))
+        except TypeError:
+            raise ValueError(message) from None
+
+        if index < 0 or index > sys.maxsize:
+            raise ValueError(message)
+
+        return index
+
+    start = (
+        0
+        if slice_args.start is None
+        else normalize_index(slice_args.start, start_message)
+    )
+    stop = (
+        None
+        if slice_args.stop is None
+        else normalize_index(slice_args.stop, stop_message)
+    )
+    step = (
+        1 if slice_args.step is None else normalize_index(slice_args.step, step_message)
+    )
+
+    if step <= 0:
+        raise ValueError(step_message)
+
+    if stop == 0 or start == stop:
+        await checkpoint()
+        return
+
+    iterator = _iterate(iterable)
+    index = 0
+    element_yielded = False
+
+    while stop is None or index < stop:
+        try:
+            element = await anext(iterator)
+        except StopAsyncIteration:
+            if not element_yielded:
+                await checkpoint()
+
+            return
+
+        if index >= start and (index - start) % step == 0:
+            index += 1
+            element_yielded = True
+            yield element
+        else:
+            index += 1
+
+    if not element_yielded:
+        await checkpoint()
+
+
+async def pairwise(
+    iterable: Iterable[T] | AsyncIterable[T],
+) -> AsyncGenerator[tuple[T, T], None]:
+    iterator = _iterate(iterable)
+    try:
+        previous = await anext(iterator)
+    except StopAsyncIteration:
+        await checkpoint()
+        return
+
+    element_yielded = False
+    async for element in iterator:
+        element_yielded = True
+        pair = (previous, element)
+        previous = element
+        yield pair
+
+    if not element_yielded:
+        await checkpoint()
+
+
+async def permutations(
+    iterable: Iterable[T] | AsyncIterable[T], r: int | None = None
+) -> AsyncGenerator[tuple[T, ...], None]:
+    pool: list[T] = [element async for element in _iterate(iterable)]
+    n = len(pool)
+    if r is None:
+        r = n
+    elif not isinstance(r, int):
+        raise TypeError("Expected int as r")
+    elif r < 0:
+        raise ValueError("r must be non-negative")
+
+    async for permutation in _iterate(itertools.permutations(pool, r)):
+        yield permutation
+
+
+async def product(
+    *iterables: Iterable[T] | AsyncIterable[T], repeat: int = 1
+) -> AsyncGenerator[tuple[T, ...], None]:
+    repeat = operator.index(repeat)
+    if repeat < 0:
+        raise ValueError("repeat argument cannot be negative")
+
+    pools: list[tuple[T, ...]] = []
+    for iterable in iterables:
+        pool: list[T] = [element async for element in _iterate(iterable)]
+        pools.append(tuple(pool))
+
+    async for value in _iterate(itertools.product(*pools, repeat=repeat)):
+        yield value
+
+
+async def repeat(element: T, times: int | None = None) -> AsyncGenerator[T, None]:
+    if times is None:
+        while True:
+            await checkpoint()
+            yield element
+
+    remaining = operator.index(cast(Any, times))
+    if remaining <= 0:
+        await checkpoint()
+        return
+
+    while remaining > 0:
+        await checkpoint_if_cancelled()
+        remaining -= 1
+        await cancel_shielded_checkpoint()
+        yield element
+
+
+async def starmap(
+    function: Callable[..., Awaitable[R]],
+    iterable: (
+        Iterable[Iterable[object] | AsyncIterable[object]]
+        | AsyncIterable[Iterable[object] | AsyncIterable[object]]
+    ),
+) -> AsyncGenerator[R, None]:
+    result_yielded = False
+
+    async for args_iterable in _iterate(iterable):
+        args = [element async for element in _iterate(args_iterable)]
+        result_yielded = True
+        yield await function(*args)
+
+    if not result_yielded:
+        await checkpoint()
+
+
+def tee(
+    iterable: Iterable[T] | AsyncIterable[T], n: int = 2
+) -> tuple[AsyncIterator[T], ...]:
+    n = operator.index(cast(Any, n))
+    if n < 0:
+        raise ValueError("n must be >= 0")
+    if n == 0:
+        return ()
+
+    iterator = _TeeAsyncIterator(iterable)
+    iterators: list[AsyncIterator[T]] = [iterator]
+    iterators.extend(_TeeAsyncIterator(iterator) for _ in range(n - 1))
+    return tuple(iterators)
+
+
+async def takewhile(
+    predicate: Callable[[T], Awaitable[object]],
+    iterable: Iterable[T] | AsyncIterable[T],
+) -> AsyncGenerator[T, None]:
+    element_yielded = False
+
+    async for element in _iterate(iterable):
+        if not await predicate(element):
+            if not element_yielded:
+                await checkpoint()
+
+            return
+
+        element_yielded = True
+        yield element
+
+    if not element_yielded:
+        await checkpoint()
+
+
+async def zip_longest(
+    *iterables: Iterable[object] | AsyncIterable[object],
+    fillvalue: object = None,
+) -> AsyncGenerator[tuple[object, ...], None]:
+    iterators = [_iterate(iterable) for iterable in iterables]
+    num_active = len(iterators)
+    if not num_active:
+        await checkpoint()
+        return
+
+    active = [True] * num_active
+    tuple_yielded = False
+
+    while True:
+        values: list[object] = []
+        for index, iterator in enumerate(iterators):
+            if not active[index]:
+                values.append(fillvalue)
+                continue
+
+            try:
+                value = await anext(iterator)
+            except StopAsyncIteration:
+                active[index] = False
+                num_active -= 1
+                if not num_active:
+                    if not tuple_yielded:
+                        await checkpoint()
+
+                    return
+
+                value = fillvalue
+
+            values.append(value)
+
+        tuple_yielded = True
+        yield tuple(values)

+ 228 - 0
vendor/anyio/lowlevel.py

@@ -0,0 +1,228 @@
+from __future__ import annotations
+
+__all__ = (
+    "EventLoopToken",
+    "RunvarToken",
+    "RunVar",
+    "checkpoint",
+    "checkpoint_if_cancelled",
+    "cancel_shielded_checkpoint",
+    "current_token",
+)
+
+import enum
+from dataclasses import dataclass
+from types import TracebackType
+from typing import TYPE_CHECKING, Any, Generic, Literal, TypeVar, final, overload
+from weakref import WeakKeyDictionary
+
+from ._core._eventloop import get_async_backend
+
+if TYPE_CHECKING:
+    from .abc import AsyncBackend
+
+T = TypeVar("T")
+D = TypeVar("D")
+
+
+async def checkpoint() -> None:
+    """
+    Check for cancellation and allow the scheduler to switch to another task.
+
+    Equivalent to (but more efficient than)::
+
+        await checkpoint_if_cancelled()
+        await cancel_shielded_checkpoint()
+
+    .. versionadded:: 3.0
+
+    """
+    await get_async_backend().checkpoint()
+
+
+async def checkpoint_if_cancelled() -> None:
+    """
+    Enter a checkpoint if the enclosing cancel scope has been cancelled.
+
+    This does not allow the scheduler to switch to a different task.
+
+    .. versionadded:: 3.0
+
+    """
+    await get_async_backend().checkpoint_if_cancelled()
+
+
+async def cancel_shielded_checkpoint() -> None:
+    """
+    Allow the scheduler to switch to another task but without checking for cancellation.
+
+    Equivalent to (but potentially more efficient than)::
+
+        with CancelScope(shield=True):
+            await checkpoint()
+
+    .. versionadded:: 3.0
+
+    """
+    await get_async_backend().cancel_shielded_checkpoint()
+
+
+@final
+@dataclass(frozen=True, repr=False)
+class EventLoopToken:
+    """
+    An opaque object that holds a reference to an event loop.
+
+    .. versionadded:: 4.11.0
+    """
+
+    backend_class: type[AsyncBackend]
+    native_token: object
+
+
+def current_token() -> EventLoopToken:
+    """
+    Return a token object that can be used to call code in the current event loop from
+    another thread.
+
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    .. versionadded:: 4.11.0
+
+    """
+    backend_class = get_async_backend()
+    raw_token = backend_class.current_token()
+    return EventLoopToken(backend_class, raw_token)
+
+
+_run_vars: WeakKeyDictionary[object, dict[RunVar[Any], Any]] = WeakKeyDictionary()
+
+
+class _NoValueSet(enum.Enum):
+    NO_VALUE_SET = enum.auto()
+
+
+class RunvarToken(Generic[T]):
+    """
+    A token that can be used to restore a :class:`RunVar` to its previous value.
+
+    Returned by :meth:`RunVar.set`. Can be used as a context manager to automatically
+    reset the variable on exit, or passed directly to :meth:`RunVar.reset`.
+    """
+
+    __slots__ = "_var", "_value", "_redeemed"
+
+    def __init__(self, var: RunVar[T], value: T | Literal[_NoValueSet.NO_VALUE_SET]):
+        self._var = var
+        self._value: T | Literal[_NoValueSet.NO_VALUE_SET] = value
+        self._redeemed = False
+
+    def __enter__(self) -> RunvarToken[T]:
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self._var.reset(self)
+
+
+class RunVar(Generic[T]):
+    """
+    Like a :class:`~contextvars.ContextVar`, except scoped to the running event loop.
+
+    Can be used as a context manager, Just like :class:`~contextvars.ContextVar`, that
+    will reset the variable to its previous value when the context block is exited.
+    """
+
+    __slots__ = "_name", "_default"
+
+    NO_VALUE_SET: Literal[_NoValueSet.NO_VALUE_SET] = _NoValueSet.NO_VALUE_SET
+
+    def __init__(
+        self, name: str, default: T | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET
+    ):
+        self._name = name
+        self._default = default
+
+    @property
+    def _current_vars(self) -> dict[RunVar[T], T]:
+        native_token = current_token().native_token
+        try:
+            return _run_vars[native_token]
+        except KeyError:
+            run_vars = _run_vars[native_token] = {}
+            return run_vars
+
+    @overload
+    def get(self, default: D) -> T | D: ...
+
+    @overload
+    def get(self) -> T: ...
+
+    def get(
+        self, default: D | Literal[_NoValueSet.NO_VALUE_SET] = NO_VALUE_SET
+    ) -> T | D:
+        """
+        Return the current value of this run variable.
+
+        :param default: a fallback value to return if no value has been set
+        :return: the current value, the provided default, or the variable's own default
+        :raises LookupError: if no value is set and no default is available
+
+        """
+        try:
+            return self._current_vars[self]
+        except KeyError:
+            if default is not RunVar.NO_VALUE_SET:
+                return default
+            elif self._default is not RunVar.NO_VALUE_SET:
+                return self._default
+
+        raise LookupError(
+            f'Run variable "{self._name}" has no value and no default set'
+        )
+
+    def set(self, value: T) -> RunvarToken[T]:
+        """
+        Set the value of this run variable for the current event loop.
+
+        :param value: the new value
+        :return: a token that can be used to restore the previous value
+
+        """
+        current_vars = self._current_vars
+        token = RunvarToken(self, current_vars.get(self, RunVar.NO_VALUE_SET))
+        current_vars[self] = value
+        return token
+
+    def reset(self, token: RunvarToken[T]) -> None:
+        """
+        Restore this run variable to the value it held before the matching :meth:`set`.
+
+        :param token: the token returned by :meth:`set`
+        :raises ValueError: if the token belongs to a different :class:`RunVar` or the token
+            has already been used
+
+        """
+        if token._var is not self:
+            raise ValueError("This token does not belong to this RunVar")
+
+        if token._redeemed:
+            raise ValueError("This token has already been used")
+
+        if token._value is _NoValueSet.NO_VALUE_SET:
+            try:
+                del self._current_vars[self]
+            except KeyError:
+                pass
+        else:
+            self._current_vars[self] = token._value
+
+        token._redeemed = True
+
+    def __repr__(self) -> str:
+        return f"<RunVar name={self._name!r}>"

+ 0 - 0
vendor/anyio/py.typed


+ 375 - 0
vendor/anyio/pytest_plugin.py

@@ -0,0 +1,375 @@
+from __future__ import annotations
+
+import dataclasses
+import socket
+import sys
+from collections.abc import Callable, Generator, Iterator
+from contextlib import ExitStack, contextmanager
+from inspect import isasyncgenfunction, iscoroutinefunction, ismethod
+from typing import Any, cast
+
+import pytest
+from _pytest.fixtures import FuncFixtureInfo, SubRequest
+from _pytest.outcomes import Exit
+from _pytest.python import CallSpec2
+from _pytest.scope import Scope
+
+from . import get_available_backends
+from ._core._eventloop import (
+    current_async_library,
+    get_async_backend,
+    reset_current_async_library,
+    set_current_async_library,
+)
+from ._core._exceptions import iterate_exceptions
+from .abc import TestRunner
+
+if sys.version_info < (3, 11):
+    from exceptiongroup import ExceptionGroup
+
+_current_runner: TestRunner | None = None
+_runner_stack: ExitStack | None = None
+_runner_leases = 0
+
+
+def extract_backend_and_options(backend: object) -> tuple[str, dict[str, Any]]:
+    if isinstance(backend, str):
+        return backend, {}
+    elif isinstance(backend, tuple) and len(backend) == 2:
+        if isinstance(backend[0], str) and isinstance(backend[1], dict):
+            return cast(tuple[str, dict[str, Any]], backend)
+
+    raise TypeError("anyio_backend must be either a string or tuple of (string, dict)")
+
+
+@contextmanager
+def get_runner(
+    backend_name: str, backend_options: dict[str, Any]
+) -> Iterator[TestRunner]:
+    global _current_runner, _runner_leases, _runner_stack
+    if _current_runner is None:
+        asynclib = get_async_backend(backend_name)
+        _runner_stack = ExitStack()
+        if current_async_library() is None:
+            # Since we're in control of the event loop, we can cache the name of the
+            # async library
+            token = set_current_async_library(backend_name)
+            _runner_stack.callback(reset_current_async_library, token)
+
+        backend_options = backend_options or {}
+        _current_runner = _runner_stack.enter_context(
+            asynclib.create_test_runner(backend_options)
+        )
+
+    _runner_leases += 1
+    try:
+        yield _current_runner
+    finally:
+        _runner_leases -= 1
+        if not _runner_leases:
+            assert _runner_stack is not None
+            _runner_stack.close()
+            _runner_stack = _current_runner = None
+
+
+def pytest_addoption(parser: pytest.Parser) -> None:
+    parser.addini(
+        "anyio_mode",
+        default="strict",
+        help='AnyIO plugin mode (either "strict" or "auto")',
+    )
+
+
+def pytest_configure(config: pytest.Config) -> None:
+    config.addinivalue_line(
+        "markers",
+        "anyio: mark the (coroutine function) test to be run asynchronously via anyio.",
+    )
+    if (
+        config.getini("anyio_mode") == "auto"
+        and config.pluginmanager.has_plugin("asyncio")
+        and config.getini("asyncio_mode") == "auto"
+    ):
+        config.issue_config_time_warning(
+            pytest.PytestConfigWarning(
+                "AnyIO auto mode has been enabled together with pytest-asyncio auto "
+                "mode. This may cause unexpected behavior."
+            ),
+            1,
+        )
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_fixture_setup(fixturedef: Any, request: Any) -> Generator[Any]:
+    def wrapper(anyio_backend: Any, request: SubRequest, **kwargs: Any) -> Any:
+        # Rebind any fixture methods to the request instance
+        if (
+            request.instance
+            and ismethod(func)
+            and type(func.__self__) is type(request.instance)
+        ):
+            local_func = func.__func__.__get__(request.instance)
+        else:
+            local_func = func
+
+        backend_name, backend_options = extract_backend_and_options(anyio_backend)
+        if has_backend_arg:
+            kwargs["anyio_backend"] = anyio_backend
+
+        if has_request_arg:
+            kwargs["request"] = request
+
+        with get_runner(backend_name, backend_options) as runner:
+            # re-entrant call into the test runner detected. this happens when an async fixture
+            # is dynamically requested via request.getfixturevalue() from inside a running async
+            # test or fixture. on asyncio this raises RuntimeError: This event loop is already
+            # running, on trio the runner deadlocks - the host loop blocks waiting for the
+            # coroutine to return, but the coroutine is waiting for the host loop. raising here
+            # prevents the hang and gives a consistent error across backends.
+            if runner.is_running():
+                raise RuntimeError(
+                    "Cannot schedule a coroutine in the test runner while another is already running; "
+                    "likely caused by request.getfixturevalue() on an async fixture."
+                )
+
+            if isasyncgenfunction(local_func):
+                yield from runner.run_asyncgen_fixture(local_func, kwargs)
+            else:
+                yield runner.run_fixture(local_func, kwargs)
+
+    # Only apply this to coroutine functions and async generator functions in requests
+    # that involve the anyio_backend fixture
+    func = fixturedef.func
+    if isasyncgenfunction(func) or iscoroutinefunction(func):
+        if "anyio_backend" in request.fixturenames:
+            fixturedef.func = wrapper
+            original_argname = fixturedef.argnames
+
+            if not (has_backend_arg := "anyio_backend" in fixturedef.argnames):
+                fixturedef.argnames += ("anyio_backend",)
+
+            if not (has_request_arg := "request" in fixturedef.argnames):
+                fixturedef.argnames += ("request",)
+
+            try:
+                return (yield)
+            finally:
+                fixturedef.func = func
+                fixturedef.argnames = original_argname
+
+    return (yield)
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_pycollect_makeitem(
+    collector: pytest.Module | pytest.Class, name: str, obj: object
+) -> None:
+    if collector.istestfunction(obj, name):
+        inner_func = obj.hypothesis.inner_test if hasattr(obj, "hypothesis") else obj
+        if iscoroutinefunction(inner_func):
+            anyio_auto_mode = collector.config.getini("anyio_mode") == "auto"
+            marker = collector.get_closest_marker("anyio")
+            own_markers = getattr(obj, "pytestmark", ())
+            if (
+                anyio_auto_mode
+                or marker
+                or any(marker.name == "anyio" for marker in own_markers)
+            ):
+                pytest.mark.usefixtures("anyio_backend")(obj)
+
+
+def pytest_collection_finish(session: pytest.Session) -> None:
+    for i, item in reversed(list(enumerate(session.items))):
+        if (
+            isinstance(item, pytest.Function)
+            and iscoroutinefunction(item.function)
+            and item.get_closest_marker("anyio") is not None
+            and "anyio_backend" not in item.fixturenames
+        ):
+            new_items = []
+            try:
+                cs_fields = {f.name for f in dataclasses.fields(CallSpec2)}
+            except TypeError:
+                cs_fields = set()
+
+            for param_index, backend in enumerate(get_available_backends()):
+                if "_arg2scope" in cs_fields:  # pytest >= 8
+                    callspec = CallSpec2(
+                        params={"anyio_backend": backend},
+                        indices={"anyio_backend": param_index},
+                        _arg2scope={"anyio_backend": Scope.Module},
+                        _idlist=[backend],
+                        marks=[],
+                    )
+                else:  # pytest 7.x
+                    callspec = CallSpec2(  # type: ignore[call-arg]
+                        funcargs={},
+                        params={"anyio_backend": backend},
+                        indices={"anyio_backend": param_index},
+                        arg2scope={"anyio_backend": Scope.Module},
+                        idlist=[backend],
+                        marks=[],
+                    )
+
+                fi = item._fixtureinfo
+                new_names_closure = list(fi.names_closure)
+                if "anyio_backend" not in new_names_closure:
+                    new_names_closure.append("anyio_backend")
+
+                new_fixtureinfo = FuncFixtureInfo(
+                    argnames=fi.argnames,
+                    initialnames=fi.initialnames,
+                    names_closure=new_names_closure,
+                    name2fixturedefs=fi.name2fixturedefs,
+                )
+                new_item = pytest.Function.from_parent(
+                    item.parent,
+                    name=f"{item.originalname}[{backend}]",
+                    callspec=callspec,
+                    callobj=item.obj,
+                    fixtureinfo=new_fixtureinfo,
+                    keywords=item.keywords,
+                    originalname=item.originalname,
+                )
+                new_items.append(new_item)
+
+            session.items[i : i + 1] = new_items
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_pyfunc_call(pyfuncitem: Any) -> bool | None:
+    def run_with_hypothesis(**kwargs: Any) -> None:
+        with get_runner(backend_name, backend_options) as runner:
+            runner.run_test(original_func, kwargs)
+
+    backend = pyfuncitem.funcargs.get("anyio_backend")
+    if backend:
+        backend_name, backend_options = extract_backend_and_options(backend)
+
+        if hasattr(pyfuncitem.obj, "hypothesis"):
+            # Wrap the inner test function unless it's already wrapped
+            original_func = pyfuncitem.obj.hypothesis.inner_test
+            if original_func.__qualname__ != run_with_hypothesis.__qualname__:
+                if iscoroutinefunction(original_func):
+                    pyfuncitem.obj.hypothesis.inner_test = run_with_hypothesis
+
+            return None
+
+        if iscoroutinefunction(pyfuncitem.obj):
+            funcargs = pyfuncitem.funcargs
+            testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
+            with get_runner(backend_name, backend_options) as runner:
+                try:
+                    runner.run_test(pyfuncitem.obj, testargs)
+                except ExceptionGroup as excgrp:
+                    for exc in iterate_exceptions(excgrp):
+                        if isinstance(exc, (Exit, KeyboardInterrupt, SystemExit)):
+                            raise exc from excgrp
+
+                    raise
+
+            return True
+
+    return None
+
+
+@pytest.fixture(scope="module", params=get_available_backends())
+def anyio_backend(request: Any) -> Any:
+    return request.param
+
+
+@pytest.fixture
+def anyio_backend_name(anyio_backend: Any) -> str:
+    if isinstance(anyio_backend, str):
+        return anyio_backend
+    else:
+        return anyio_backend[0]
+
+
+@pytest.fixture
+def anyio_backend_options(anyio_backend: Any) -> dict[str, Any]:
+    if isinstance(anyio_backend, str):
+        return {}
+    else:
+        return anyio_backend[1]
+
+
+class FreePortFactory:
+    """
+    Manages port generation based on specified socket kind, ensuring no duplicate
+    ports are generated.
+
+    This class provides functionality for generating available free ports on the
+    system. It is initialized with a specific socket kind and can generate ports
+    for given address families while avoiding reuse of previously generated ports.
+
+    Users should not instantiate this class directly, but use the
+    ``free_tcp_port_factory`` and ``free_udp_port_factory`` fixtures instead. For simple
+    uses cases, ``free_tcp_port`` and ``free_udp_port`` can be used instead.
+    """
+
+    def __init__(self, kind: socket.SocketKind) -> None:
+        self._kind = kind
+        self._generated = set[int]()
+
+    @property
+    def kind(self) -> socket.SocketKind:
+        """
+        The type of socket connection (e.g., :data:`~socket.SOCK_STREAM` or
+        :data:`~socket.SOCK_DGRAM`) used to bind for checking port availability
+
+        """
+        return self._kind
+
+    def __call__(self, family: socket.AddressFamily | None = None) -> int:
+        """
+        Return an unbound port for the given address family.
+
+        :param family: if omitted, both IPv4 and IPv6 addresses will be tried
+        :return: a port number
+
+        """
+        if family is not None:
+            families = [family]
+        else:
+            families = [socket.AF_INET]
+            if socket.has_ipv6:
+                families.append(socket.AF_INET6)
+
+        while True:
+            port = 0
+            with ExitStack() as stack:
+                for family in families:
+                    sock = stack.enter_context(socket.socket(family, self._kind))
+                    addr = "::1" if family == socket.AF_INET6 else "127.0.0.1"
+                    try:
+                        sock.bind((addr, port))
+                    except OSError:
+                        break
+
+                    if not port:
+                        port = sock.getsockname()[1]
+                else:
+                    if port not in self._generated:
+                        self._generated.add(port)
+                        return port
+
+
+@pytest.fixture(scope="session")
+def free_tcp_port_factory() -> FreePortFactory:
+    return FreePortFactory(socket.SOCK_STREAM)
+
+
+@pytest.fixture(scope="session")
+def free_udp_port_factory() -> FreePortFactory:
+    return FreePortFactory(socket.SOCK_DGRAM)
+
+
+@pytest.fixture
+def free_tcp_port(free_tcp_port_factory: Callable[[], int]) -> int:
+    return free_tcp_port_factory()
+
+
+@pytest.fixture
+def free_udp_port(free_udp_port_factory: Callable[[], int]) -> int:
+    return free_udp_port_factory()

+ 0 - 0
vendor/anyio/streams/__init__.py


+ 201 - 0
vendor/anyio/streams/buffered.py

@@ -0,0 +1,201 @@
+from __future__ import annotations
+
+__all__ = (
+    "BufferedByteReceiveStream",
+    "BufferedByteStream",
+    "BufferedConnectable",
+)
+
+import sys
+from collections.abc import Callable, Iterable, Mapping
+from dataclasses import dataclass, field
+from typing import Any, SupportsIndex
+
+from .. import ClosedResourceError, DelimiterNotFound, EndOfStream, IncompleteRead
+from ..abc import (
+    AnyByteReceiveStream,
+    AnyByteStream,
+    AnyByteStreamConnectable,
+    ByteReceiveStream,
+    ByteStream,
+    ByteStreamConnectable,
+)
+
+if sys.version_info >= (3, 12):
+    from typing import override
+else:
+    from typing_extensions import override
+
+
+@dataclass(eq=False)
+class BufferedByteReceiveStream(ByteReceiveStream):
+    """
+    Wraps any bytes-based receive stream and uses a buffer to provide sophisticated
+    receiving capabilities in the form of a byte stream.
+    """
+
+    receive_stream: AnyByteReceiveStream
+    _buffer: bytearray = field(init=False, default_factory=bytearray)
+    _closed: bool = field(init=False, default=False)
+
+    async def aclose(self) -> None:
+        await self.receive_stream.aclose()
+        self._closed = True
+
+    @property
+    def buffer(self) -> bytes:
+        """The bytes currently in the buffer."""
+        return bytes(self._buffer)
+
+    @property
+    def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
+        return self.receive_stream.extra_attributes
+
+    def feed_data(self, data: Iterable[SupportsIndex], /) -> None:
+        """
+        Append data directly into the buffer.
+
+        Any data in the buffer will be consumed by receive operations before receiving
+        anything from the wrapped stream.
+
+        :param data: the data to append to the buffer (can be bytes or anything else
+            that supports ``__index__()``)
+
+        """
+        self._buffer.extend(data)
+
+    async def receive(self, max_bytes: int = 65536) -> bytes:
+        if max_bytes < 1:
+            raise ValueError("max_bytes must be a positive integer")
+
+        if self._closed:
+            raise ClosedResourceError
+
+        if self._buffer:
+            chunk = bytes(self._buffer[:max_bytes])
+            del self._buffer[:max_bytes]
+            return chunk
+        elif isinstance(self.receive_stream, ByteReceiveStream):
+            return await self.receive_stream.receive(max_bytes)
+        else:
+            # With a bytes-oriented object stream, we need to handle any surplus bytes
+            # we get from the receive() call
+            chunk = await self.receive_stream.receive()
+            if len(chunk) > max_bytes:
+                # Save the surplus bytes in the buffer
+                self._buffer.extend(chunk[max_bytes:])
+                return chunk[:max_bytes]
+            else:
+                return chunk
+
+    async def receive_exactly(self, nbytes: int) -> bytes:
+        """
+        Read exactly the given amount of bytes from the stream.
+
+        :param nbytes: the number of bytes to read
+        :return: the bytes read
+        :raises ~anyio.IncompleteRead: if the stream was closed before the requested
+            amount of bytes could be read from the stream
+
+        """
+        while True:
+            remaining = nbytes - len(self._buffer)
+            if remaining <= 0:
+                retval = self._buffer[:nbytes]
+                del self._buffer[:nbytes]
+                return bytes(retval)
+
+            try:
+                if isinstance(self.receive_stream, ByteReceiveStream):
+                    chunk = await self.receive_stream.receive(remaining)
+                else:
+                    chunk = await self.receive_stream.receive()
+            except EndOfStream as exc:
+                raise IncompleteRead from exc
+
+            self._buffer.extend(chunk)
+
+    async def receive_until(self, delimiter: bytes, max_bytes: int) -> bytes:
+        """
+        Read from the stream until the delimiter is found or max_bytes have been read.
+
+        :param delimiter: the marker to look for in the stream
+        :param max_bytes: maximum number of bytes that will be read before raising
+            :exc:`~anyio.DelimiterNotFound`
+        :return: the bytes read (not including the delimiter)
+        :raises ~anyio.IncompleteRead: if the stream was closed before the delimiter
+            was found
+        :raises ~anyio.DelimiterNotFound: if the delimiter is not found within the
+            bytes read up to the maximum allowed
+
+        """
+        delimiter_size = len(delimiter)
+        offset = 0
+        while True:
+            # Check if the delimiter can be found in the current buffer
+            index = self._buffer.find(delimiter, offset)
+            if index >= 0:
+                found = self._buffer[:index]
+                del self._buffer[: index + len(delimiter) :]
+                return bytes(found)
+
+            # Check if the buffer is already at or over the limit
+            if len(self._buffer) >= max_bytes:
+                raise DelimiterNotFound(max_bytes)
+
+            # Read more data into the buffer from the socket
+            try:
+                data = await self.receive_stream.receive()
+            except EndOfStream as exc:
+                raise IncompleteRead from exc
+
+            # Move the offset forward and add the new data to the buffer
+            offset = max(len(self._buffer) - delimiter_size + 1, 0)
+            self._buffer.extend(data)
+
+
+class BufferedByteStream(BufferedByteReceiveStream, ByteStream):
+    """
+    A full-duplex variant of :class:`BufferedByteReceiveStream`. All writes are passed
+    through to the wrapped stream as-is.
+    """
+
+    def __init__(self, stream: AnyByteStream):
+        """
+        :param stream: the stream to be wrapped
+
+        """
+        super().__init__(stream)
+        self._stream = stream
+
+    @override
+    async def send_eof(self) -> None:
+        await self._stream.send_eof()
+
+    @override
+    async def send(self, item: bytes) -> None:
+        await self._stream.send(item)
+
+
+class BufferedConnectable(ByteStreamConnectable):
+    """
+    Wraps a byte stream connectable to produce :class:`BufferedByteStream` connections.
+
+    Use this when you want the streams returned by :meth:`connect` to have the buffered
+    receive API (e.g. :meth:`~BufferedByteReceiveStream.receive_exactly` and
+    :meth:`~BufferedByteReceiveStream.receive_until`).
+
+    :param connectable: the byte stream connectable to wrap
+    """
+
+    def __init__(self, connectable: AnyByteStreamConnectable):
+        """
+        :param connectable: the connectable to wrap
+
+        """
+        self.connectable = connectable
+
+    @override
+    async def connect(self) -> BufferedByteStream:
+        stream = await self.connectable.connect()
+        return BufferedByteStream(stream)

+ 157 - 0
vendor/anyio/streams/file.py

@@ -0,0 +1,157 @@
+from __future__ import annotations
+
+__all__ = (
+    "FileReadStream",
+    "FileStreamAttribute",
+    "FileWriteStream",
+)
+
+from collections.abc import Callable, Mapping
+from io import SEEK_SET, UnsupportedOperation
+from os import PathLike
+from pathlib import Path
+from typing import IO, Any
+
+from .. import (
+    BrokenResourceError,
+    ClosedResourceError,
+    EndOfStream,
+    TypedAttributeSet,
+    to_thread,
+    typed_attribute,
+)
+from ..abc import ByteReceiveStream, ByteSendStream
+
+
+class FileStreamAttribute(TypedAttributeSet):
+    #: the open file descriptor
+    file: IO[bytes] = typed_attribute()
+    #: the path of the file on the file system, if available (file must be a real file)
+    path: Path = typed_attribute()
+    #: the file number, if available (file must be a real file or a TTY)
+    fileno: int = typed_attribute()
+
+
+class _BaseFileStream:
+    def __init__(self, file: IO[bytes]):
+        self._file = file
+
+    async def aclose(self) -> None:
+        await to_thread.run_sync(self._file.close)
+
+    @property
+    def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
+        attributes: dict[Any, Callable[[], Any]] = {
+            FileStreamAttribute.file: lambda: self._file,
+        }
+
+        if hasattr(self._file, "name"):
+            attributes[FileStreamAttribute.path] = lambda: Path(self._file.name)
+
+        try:
+            self._file.fileno()
+        except UnsupportedOperation:
+            pass
+        else:
+            attributes[FileStreamAttribute.fileno] = lambda: self._file.fileno()
+
+        return attributes
+
+
+class FileReadStream(_BaseFileStream, ByteReceiveStream):
+    """
+    A byte stream that reads from a file in the file system.
+
+    :param file: a file that has been opened for reading in binary mode
+
+    .. versionadded:: 3.0
+    """
+
+    @classmethod
+    async def from_path(cls, path: str | PathLike[str]) -> FileReadStream:
+        """
+        Create a file read stream by opening the given file.
+
+        :param path: path of the file to read from
+
+        """
+        file = await to_thread.run_sync(Path(path).open, "rb")
+        return cls(file)
+
+    async def receive(self, max_bytes: int = 65536) -> bytes:
+        if max_bytes < 1:
+            raise ValueError("max_bytes must be a positive integer")
+
+        try:
+            data = await to_thread.run_sync(self._file.read, max_bytes)
+        except ValueError:
+            raise ClosedResourceError from None
+        except OSError as exc:
+            raise BrokenResourceError from exc
+
+        if data:
+            return data
+        else:
+            raise EndOfStream
+
+    async def seek(self, position: int, whence: int = SEEK_SET) -> int:
+        """
+        Seek the file to the given position.
+
+        .. seealso:: :meth:`io.IOBase.seek`
+
+        .. note:: Not all file descriptors are seekable.
+
+        :param position: position to seek the file to
+        :param whence: controls how ``position`` is interpreted
+        :return: the new absolute position
+        :raises OSError: if the file is not seekable
+
+        """
+        return await to_thread.run_sync(self._file.seek, position, whence)
+
+    async def tell(self) -> int:
+        """
+        Return the current stream position.
+
+        .. note:: Not all file descriptors are seekable.
+
+        :return: the current absolute position
+        :raises OSError: if the file is not seekable
+
+        """
+        return await to_thread.run_sync(self._file.tell)
+
+
+class FileWriteStream(_BaseFileStream, ByteSendStream):
+    """
+    A byte stream that writes to a file in the file system.
+
+    :param file: a file that has been opened for writing in binary mode
+
+    .. versionadded:: 3.0
+    """
+
+    @classmethod
+    async def from_path(
+        cls, path: str | PathLike[str], append: bool = False
+    ) -> FileWriteStream:
+        """
+        Create a file write stream by opening the given file for writing.
+
+        :param path: path of the file to write to
+        :param append: if ``True``, open the file for appending; if ``False``, any
+            existing file at the given path will be truncated
+
+        """
+        mode = "ab" if append else "wb"
+        file = await to_thread.run_sync(Path(path).open, mode)
+        return cls(file)
+
+    async def send(self, item: bytes) -> None:
+        try:
+            await to_thread.run_sync(self._file.write, item)
+        except ValueError:
+            raise ClosedResourceError from None
+        except OSError as exc:
+            raise BrokenResourceError from exc

+ 326 - 0
vendor/anyio/streams/memory.py

@@ -0,0 +1,326 @@
+from __future__ import annotations
+
+__all__ = (
+    "MemoryObjectReceiveStream",
+    "MemoryObjectSendStream",
+    "MemoryObjectStreamStatistics",
+)
+
+import warnings
+from collections import OrderedDict, deque
+from dataclasses import dataclass, field
+from types import TracebackType
+from typing import Generic, NamedTuple, TypeVar
+
+from .. import (
+    BrokenResourceError,
+    ClosedResourceError,
+    EndOfStream,
+    WouldBlock,
+)
+from .._core._synchronization import Event
+from .._core._testing import TaskInfo, get_current_task
+from ..abc import ObjectReceiveStream, ObjectSendStream
+from ..lowlevel import checkpoint
+
+T_Item = TypeVar("T_Item")
+T_co = TypeVar("T_co", covariant=True)
+T_contra = TypeVar("T_contra", contravariant=True)
+
+
+class MemoryObjectStreamStatistics(NamedTuple):
+    current_buffer_used: int  #: number of items stored in the buffer
+    #: maximum number of items that can be stored on this stream (or :data:`math.inf`)
+    max_buffer_size: float
+    open_send_streams: int  #: number of unclosed clones of the send stream
+    open_receive_streams: int  #: number of unclosed clones of the receive stream
+    #: number of tasks blocked on :meth:`MemoryObjectSendStream.send`
+    tasks_waiting_send: int
+    #: number of tasks blocked on :meth:`MemoryObjectReceiveStream.receive`
+    tasks_waiting_receive: int
+
+
+@dataclass(eq=False)
+class _MemoryObjectItemReceiver(Generic[T_Item]):
+    task_info: TaskInfo = field(init=False, default_factory=get_current_task)
+    item: T_Item = field(init=False)
+
+    def __repr__(self) -> str:
+        # When item is not defined, we get following error with default __repr__:
+        # AttributeError: 'MemoryObjectItemReceiver' object has no attribute 'item'
+        item = getattr(self, "item", None)
+        return f"{self.__class__.__name__}(task_info={self.task_info}, item={item!r})"
+
+
+@dataclass(eq=False)
+class _MemoryObjectStreamState(Generic[T_Item]):
+    max_buffer_size: float = field()
+    buffer: deque[T_Item] = field(init=False, default_factory=deque)
+    open_send_channels: int = field(init=False, default=0)
+    open_receive_channels: int = field(init=False, default=0)
+    waiting_receivers: OrderedDict[Event, _MemoryObjectItemReceiver[T_Item]] = field(
+        init=False, default_factory=OrderedDict
+    )
+    waiting_senders: OrderedDict[Event, T_Item] = field(
+        init=False, default_factory=OrderedDict
+    )
+
+    def statistics(self) -> MemoryObjectStreamStatistics:
+        return MemoryObjectStreamStatistics(
+            len(self.buffer),
+            self.max_buffer_size,
+            self.open_send_channels,
+            self.open_receive_channels,
+            len(self.waiting_senders),
+            len(self.waiting_receivers),
+        )
+
+
+@dataclass(eq=False)
+class MemoryObjectReceiveStream(Generic[T_co], ObjectReceiveStream[T_co]):
+    _state: _MemoryObjectStreamState[T_co]
+    _closed: bool = field(init=False, default=False)
+
+    def __post_init__(self) -> None:
+        self._state.open_receive_channels += 1
+
+    def receive_nowait(self) -> T_co:
+        """
+        Receive the next item if it can be done without waiting.
+
+        :return: the received item
+        :raises ~anyio.ClosedResourceError: if this send stream has been closed
+        :raises ~anyio.EndOfStream: if the buffer is empty and this stream has been
+            closed from the sending end
+        :raises ~anyio.WouldBlock: if there are no items in the buffer and no tasks
+            waiting to send
+
+        """
+        if self._closed:
+            raise ClosedResourceError
+
+        if self._state.waiting_senders:
+            # Get the item from the next sender
+            send_event, item = self._state.waiting_senders.popitem(last=False)
+            self._state.buffer.append(item)
+            send_event.set()
+
+        if self._state.buffer:
+            return self._state.buffer.popleft()
+        elif not self._state.open_send_channels:
+            raise EndOfStream
+
+        raise WouldBlock
+
+    async def receive(self) -> T_co:
+        await checkpoint()
+        try:
+            return self.receive_nowait()
+        except WouldBlock:
+            # Add ourselves in the queue
+            receive_event = Event()
+            receiver = _MemoryObjectItemReceiver[T_co]()
+            self._state.waiting_receivers[receive_event] = receiver
+
+            try:
+                await receive_event.wait()
+            finally:
+                self._state.waiting_receivers.pop(receive_event, None)
+
+            try:
+                return receiver.item
+            except AttributeError:
+                raise EndOfStream from None
+
+    def clone(self) -> MemoryObjectReceiveStream[T_co]:
+        """
+        Create a clone of this receive stream.
+
+        Each clone can be closed separately. Only when all clones have been closed will
+        the receiving end of the memory stream be considered closed by the sending ends.
+
+        :return: the cloned stream
+
+        """
+        if self._closed:
+            raise ClosedResourceError
+
+        return MemoryObjectReceiveStream(_state=self._state)
+
+    def close(self) -> None:
+        """
+        Close the stream.
+
+        This works the exact same way as :meth:`aclose`, but is provided as a special
+        case for the benefit of synchronous callbacks.
+
+        """
+        if not self._closed:
+            self._closed = True
+            self._state.open_receive_channels -= 1
+            if self._state.open_receive_channels == 0:
+                send_events = list(self._state.waiting_senders.keys())
+                for event in send_events:
+                    event.set()
+
+    async def aclose(self) -> None:
+        self.close()
+
+    def statistics(self) -> MemoryObjectStreamStatistics:
+        """
+        Return statistics about the current state of this stream.
+
+        .. versionadded:: 3.0
+        """
+        return self._state.statistics()
+
+    def __enter__(self) -> MemoryObjectReceiveStream[T_co]:
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self.close()
+
+    def __del__(self) -> None:
+        if not self._closed:
+            warnings.warn(
+                f"Unclosed <{self.__class__.__name__} at {id(self):x}>",
+                ResourceWarning,
+                stacklevel=1,
+                source=self,
+            )
+
+
+@dataclass(eq=False)
+class MemoryObjectSendStream(Generic[T_contra], ObjectSendStream[T_contra]):
+    _state: _MemoryObjectStreamState[T_contra]
+    _closed: bool = field(init=False, default=False)
+
+    def __post_init__(self) -> None:
+        self._state.open_send_channels += 1
+
+    def send_nowait(self, item: T_contra) -> None:
+        """
+        Send an item immediately if it can be done without waiting.
+
+        :param item: the item to send
+        :raises ~anyio.ClosedResourceError: if this send stream has been closed
+        :raises ~anyio.BrokenResourceError: if the stream has been closed from the
+            receiving end
+        :raises ~anyio.WouldBlock: if the buffer is full and there are no tasks waiting
+            to receive
+
+        """
+        if self._closed:
+            raise ClosedResourceError
+        if not self._state.open_receive_channels:
+            raise BrokenResourceError
+
+        while self._state.waiting_receivers:
+            receive_event, receiver = self._state.waiting_receivers.popitem(last=False)
+            if not receiver.task_info.has_pending_cancellation():
+                receiver.item = item
+                receive_event.set()
+                return
+
+        if len(self._state.buffer) < self._state.max_buffer_size:
+            self._state.buffer.append(item)
+        else:
+            raise WouldBlock
+
+    async def send(self, item: T_contra) -> None:
+        """
+        Send an item to the stream.
+
+        If the buffer is full, this method blocks until there is again room in the
+        buffer or the item can be sent directly to a receiver.
+
+        :param item: the item to send
+        :raises ~anyio.ClosedResourceError: if this send stream has been closed
+        :raises ~anyio.BrokenResourceError: if the stream has been closed from the
+            receiving end
+
+        """
+        await checkpoint()
+        try:
+            self.send_nowait(item)
+        except WouldBlock:
+            # Wait until there's someone on the receiving end
+            send_event = Event()
+            self._state.waiting_senders[send_event] = item
+            try:
+                await send_event.wait()
+            except BaseException:
+                self._state.waiting_senders.pop(send_event, None)
+                raise
+
+            if send_event in self._state.waiting_senders:
+                del self._state.waiting_senders[send_event]
+                raise BrokenResourceError from None
+
+    def clone(self) -> MemoryObjectSendStream[T_contra]:
+        """
+        Create a clone of this send stream.
+
+        Each clone can be closed separately. Only when all clones have been closed will
+        the sending end of the memory stream be considered closed by the receiving ends.
+
+        :return: the cloned stream
+
+        """
+        if self._closed:
+            raise ClosedResourceError
+
+        return MemoryObjectSendStream(_state=self._state)
+
+    def close(self) -> None:
+        """
+        Close the stream.
+
+        This works the exact same way as :meth:`aclose`, but is provided as a special
+        case for the benefit of synchronous callbacks.
+
+        """
+        if not self._closed:
+            self._closed = True
+            self._state.open_send_channels -= 1
+            if self._state.open_send_channels == 0:
+                receive_events = list(self._state.waiting_receivers.keys())
+                self._state.waiting_receivers.clear()
+                for event in receive_events:
+                    event.set()
+
+    async def aclose(self) -> None:
+        self.close()
+
+    def statistics(self) -> MemoryObjectStreamStatistics:
+        """
+        Return statistics about the current state of this stream.
+
+        .. versionadded:: 3.0
+        """
+        return self._state.statistics()
+
+    def __enter__(self) -> MemoryObjectSendStream[T_contra]:
+        return self
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
+        self.close()
+
+    def __del__(self) -> None:
+        if not self._closed:
+            warnings.warn(
+                f"Unclosed <{self.__class__.__name__} at {id(self):x}>",
+                ResourceWarning,
+                stacklevel=1,
+                source=self,
+            )

+ 150 - 0
vendor/anyio/streams/stapled.py

@@ -0,0 +1,150 @@
+from __future__ import annotations
+
+__all__ = (
+    "MultiListener",
+    "StapledByteStream",
+    "StapledObjectStream",
+)
+
+from collections.abc import Callable, Mapping, Sequence
+from dataclasses import dataclass
+from typing import Any, Generic, TypeVar
+
+from ..abc import (
+    ByteReceiveStream,
+    ByteSendStream,
+    ByteStream,
+    Listener,
+    ObjectReceiveStream,
+    ObjectSendStream,
+    ObjectStream,
+    TaskGroup,
+)
+
+T_Item = TypeVar("T_Item")
+T_Stream = TypeVar("T_Stream")
+
+
+@dataclass(eq=False)
+class StapledByteStream(ByteStream):
+    """
+    Combines two byte streams into a single, bidirectional byte stream.
+
+    Extra attributes will be provided from both streams, with the receive stream
+    providing the values in case of a conflict.
+
+    :param ByteSendStream send_stream: the sending byte stream
+    :param ByteReceiveStream receive_stream: the receiving byte stream
+    """
+
+    send_stream: ByteSendStream
+    receive_stream: ByteReceiveStream
+
+    async def receive(self, max_bytes: int = 65536) -> bytes:
+        if max_bytes < 1:
+            raise ValueError("max_bytes must be a positive integer")
+
+        return await self.receive_stream.receive(max_bytes)
+
+    async def send(self, item: bytes) -> None:
+        await self.send_stream.send(item)
+
+    async def send_eof(self) -> None:
+        await self.send_stream.aclose()
+
+    async def aclose(self) -> None:
+        await self.send_stream.aclose()
+        await self.receive_stream.aclose()
+
+    @property
+    def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
+        return {
+            **self.send_stream.extra_attributes,
+            **self.receive_stream.extra_attributes,
+        }
+
+
+@dataclass(eq=False)
+class StapledObjectStream(Generic[T_Item], ObjectStream[T_Item]):
+    """
+    Combines two object streams into a single, bidirectional object stream.
+
+    Extra attributes will be provided from both streams, with the receive stream
+    providing the values in case of a conflict.
+
+    :param ObjectSendStream send_stream: the sending object stream
+    :param ObjectReceiveStream receive_stream: the receiving object stream
+    """
+
+    send_stream: ObjectSendStream[T_Item]
+    receive_stream: ObjectReceiveStream[T_Item]
+
+    async def receive(self) -> T_Item:
+        return await self.receive_stream.receive()
+
+    async def send(self, item: T_Item) -> None:
+        await self.send_stream.send(item)
+
+    async def send_eof(self) -> None:
+        await self.send_stream.aclose()
+
+    async def aclose(self) -> None:
+        await self.send_stream.aclose()
+        await self.receive_stream.aclose()
+
+    @property
+    def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
+        return {
+            **self.send_stream.extra_attributes,
+            **self.receive_stream.extra_attributes,
+        }
+
+
+@dataclass(eq=False)
+class MultiListener(Generic[T_Stream], Listener[T_Stream]):
+    """
+    Combines multiple listeners into one, serving connections from all of them at once.
+
+    Any MultiListeners in the given collection of listeners will have their listeners
+    moved into this one.
+
+    Extra attributes are provided from each listener, with each successive listener
+    overriding any conflicting attributes from the previous one.
+
+    :param listeners: listeners to serve
+    :type listeners: Sequence[Listener[T_Stream]]
+    """
+
+    listeners: Sequence[Listener[T_Stream]]
+
+    def __post_init__(self) -> None:
+        listeners: list[Listener[T_Stream]] = []
+        for listener in self.listeners:
+            if isinstance(listener, MultiListener):
+                listeners.extend(listener.listeners)
+                del listener.listeners[:]  # type: ignore[attr-defined]
+            else:
+                listeners.append(listener)
+
+        self.listeners = listeners
+
+    async def serve(
+        self, handler: Callable[[T_Stream], Any], task_group: TaskGroup | None = None
+    ) -> None:
+        from .. import create_task_group
+
+        async with create_task_group() as tg:
+            for listener in self.listeners:
+                tg.start_soon(listener.serve, handler, task_group)
+
+    async def aclose(self) -> None:
+        for listener in self.listeners:
+            await listener.aclose()
+
+    @property
+    def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
+        attributes: dict = {}
+        for listener in self.listeners:
+            attributes.update(listener.extra_attributes)
+
+        return attributes

+ 176 - 0
vendor/anyio/streams/text.py

@@ -0,0 +1,176 @@
+from __future__ import annotations
+
+__all__ = (
+    "TextConnectable",
+    "TextReceiveStream",
+    "TextSendStream",
+    "TextStream",
+)
+
+import codecs
+import sys
+from collections.abc import Callable, Mapping
+from dataclasses import InitVar, dataclass, field
+from typing import Any
+
+from ..abc import (
+    AnyByteReceiveStream,
+    AnyByteSendStream,
+    AnyByteStream,
+    AnyByteStreamConnectable,
+    ObjectReceiveStream,
+    ObjectSendStream,
+    ObjectStream,
+    ObjectStreamConnectable,
+)
+
+if sys.version_info >= (3, 12):
+    from typing import override
+else:
+    from typing_extensions import override
+
+
+@dataclass(eq=False)
+class TextReceiveStream(ObjectReceiveStream[str]):
+    """
+    Stream wrapper that decodes bytes to strings using the given encoding.
+
+    Decoding is done using :class:`~codecs.IncrementalDecoder` which returns any
+    completely received unicode characters as soon as they come in.
+
+    :param transport_stream: any bytes-based receive stream
+    :param encoding: character encoding to use for decoding bytes to strings (defaults
+        to ``utf-8``)
+    :param errors: handling scheme for decoding errors (defaults to ``strict``; see the
+        `codecs module documentation`_ for a comprehensive list of options)
+
+    .. _codecs module documentation:
+        https://docs.python.org/3/library/codecs.html#codec-objects
+    """
+
+    transport_stream: AnyByteReceiveStream
+    encoding: InitVar[str] = "utf-8"
+    errors: InitVar[str] = "strict"
+    _decoder: codecs.IncrementalDecoder = field(init=False)
+
+    def __post_init__(self, encoding: str, errors: str) -> None:
+        decoder_class = codecs.getincrementaldecoder(encoding)
+        self._decoder = decoder_class(errors=errors)
+
+    async def receive(self) -> str:
+        while True:
+            chunk = await self.transport_stream.receive()
+            decoded = self._decoder.decode(chunk)
+            if decoded:
+                return decoded
+
+    async def aclose(self) -> None:
+        await self.transport_stream.aclose()
+        self._decoder.reset()
+
+    @property
+    def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
+        return self.transport_stream.extra_attributes
+
+
+@dataclass(eq=False)
+class TextSendStream(ObjectSendStream[str]):
+    """
+    Sends strings to the wrapped stream as bytes using the given encoding.
+
+    :param AnyByteSendStream transport_stream: any bytes-based send stream
+    :param str encoding: character encoding to use for encoding strings to bytes
+        (defaults to ``utf-8``)
+    :param str errors: handling scheme for encoding errors (defaults to ``strict``; see
+        the `codecs module documentation`_ for a comprehensive list of options)
+
+    .. _codecs module documentation:
+        https://docs.python.org/3/library/codecs.html#codec-objects
+    """
+
+    transport_stream: AnyByteSendStream
+    encoding: InitVar[str] = "utf-8"
+    errors: str = "strict"
+    _encoder: Callable[..., tuple[bytes, int]] = field(init=False)
+
+    def __post_init__(self, encoding: str) -> None:
+        self._encoder = codecs.getencoder(encoding)
+
+    async def send(self, item: str) -> None:
+        encoded = self._encoder(item, self.errors)[0]
+        await self.transport_stream.send(encoded)
+
+    async def aclose(self) -> None:
+        await self.transport_stream.aclose()
+
+    @property
+    def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
+        return self.transport_stream.extra_attributes
+
+
+@dataclass(eq=False)
+class TextStream(ObjectStream[str]):
+    """
+    A bidirectional stream that decodes bytes to strings on receive and encodes strings
+    to bytes on send.
+
+    Extra attributes will be provided from both streams, with the receive stream
+    providing the values in case of a conflict.
+
+    :param AnyByteStream transport_stream: any bytes-based stream
+    :param str encoding: character encoding to use for encoding/decoding strings to/from
+        bytes (defaults to ``utf-8``)
+    :param str errors: handling scheme for encoding errors (defaults to ``strict``; see
+        the `codecs module documentation`_ for a comprehensive list of options)
+
+    .. _codecs module documentation:
+        https://docs.python.org/3/library/codecs.html#codec-objects
+    """
+
+    transport_stream: AnyByteStream
+    encoding: InitVar[str] = "utf-8"
+    errors: InitVar[str] = "strict"
+    _receive_stream: TextReceiveStream = field(init=False)
+    _send_stream: TextSendStream = field(init=False)
+
+    def __post_init__(self, encoding: str, errors: str) -> None:
+        self._receive_stream = TextReceiveStream(
+            self.transport_stream, encoding=encoding, errors=errors
+        )
+        self._send_stream = TextSendStream(
+            self.transport_stream, encoding=encoding, errors=errors
+        )
+
+    async def receive(self) -> str:
+        return await self._receive_stream.receive()
+
+    async def send(self, item: str) -> None:
+        await self._send_stream.send(item)
+
+    async def send_eof(self) -> None:
+        await self.transport_stream.send_eof()
+
+    async def aclose(self) -> None:
+        await self._send_stream.aclose()
+        await self._receive_stream.aclose()
+
+    @property
+    def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
+        return {
+            **self._send_stream.extra_attributes,
+            **self._receive_stream.extra_attributes,
+        }
+
+
+class TextConnectable(ObjectStreamConnectable[str]):
+    def __init__(self, connectable: AnyByteStreamConnectable):
+        """
+        :param connectable: the bytestream endpoint to wrap
+
+        """
+        self.connectable = connectable
+
+    @override
+    async def connect(self) -> TextStream:
+        stream = await self.connectable.connect()
+        return TextStream(stream)

+ 436 - 0
vendor/anyio/streams/tls.py

@@ -0,0 +1,436 @@
+from __future__ import annotations
+
+__all__ = (
+    "TLSAttribute",
+    "TLSConnectable",
+    "TLSListener",
+    "TLSStream",
+)
+
+import logging
+import re
+import ssl
+import sys
+from collections.abc import Callable, Mapping
+from dataclasses import dataclass
+from functools import wraps
+from ssl import SSLContext
+from typing import Any, TypeAlias, TypeVar
+
+from .. import (
+    BrokenResourceError,
+    EndOfStream,
+    aclose_forcefully,
+    get_cancelled_exc_class,
+    to_thread,
+)
+from .._core._typedattr import TypedAttributeSet, typed_attribute
+from ..abc import (
+    AnyByteStream,
+    AnyByteStreamConnectable,
+    ByteStream,
+    ByteStreamConnectable,
+    Listener,
+    TaskGroup,
+)
+
+if sys.version_info >= (3, 11):
+    from typing import TypeVarTuple, Unpack
+else:
+    from typing_extensions import TypeVarTuple, Unpack
+
+if sys.version_info >= (3, 12):
+    from typing import override
+else:
+    from typing_extensions import override
+
+T_Retval = TypeVar("T_Retval")
+PosArgsT = TypeVarTuple("PosArgsT")
+_PCTRTT: TypeAlias = tuple[tuple[str, str], ...]
+_PCTRTTT: TypeAlias = tuple[_PCTRTT, ...]
+
+
+class TLSAttribute(TypedAttributeSet):
+    """Contains Transport Layer Security related attributes."""
+
+    #: the selected ALPN protocol
+    alpn_protocol: str | None = typed_attribute()
+    #: the channel binding for type ``tls-unique``
+    channel_binding_tls_unique: bytes = typed_attribute()
+    #: the selected cipher
+    cipher: tuple[str, str, int] = typed_attribute()
+    #: the peer certificate in dictionary form (see :meth:`ssl.SSLSocket.getpeercert`
+    # for more information)
+    peer_certificate: None | (dict[str, str | _PCTRTTT | _PCTRTT]) = typed_attribute()
+    #: the peer certificate in binary form
+    peer_certificate_binary: bytes | None = typed_attribute()
+    #: ``True`` if this is the server side of the connection
+    server_side: bool = typed_attribute()
+    #: ciphers shared by the client during the TLS handshake (``None`` if this is the
+    #: client side)
+    shared_ciphers: list[tuple[str, str, int]] | None = typed_attribute()
+    #: the :class:`~ssl.SSLObject` used for encryption
+    ssl_object: ssl.SSLObject = typed_attribute()
+    #: ``True`` if this stream does (and expects) a closing TLS handshake when the
+    #: stream is being closed
+    standard_compatible: bool = typed_attribute()
+    #: the TLS protocol version (e.g. ``TLSv1.2``)
+    tls_version: str = typed_attribute()
+
+
+@dataclass(eq=False)
+class TLSStream(ByteStream):
+    """
+    A stream wrapper that encrypts all sent data and decrypts received data.
+
+    This class has no public initializer; use :meth:`wrap` instead.
+    All extra attributes from :class:`~TLSAttribute` are supported.
+
+    :var AnyByteStream transport_stream: the wrapped stream
+
+    """
+
+    transport_stream: AnyByteStream
+    standard_compatible: bool
+    _ssl_object: ssl.SSLObject
+    _read_bio: ssl.MemoryBIO
+    _write_bio: ssl.MemoryBIO
+
+    @classmethod
+    async def wrap(
+        cls,
+        transport_stream: AnyByteStream,
+        *,
+        server_side: bool | None = None,
+        hostname: str | None = None,
+        ssl_context: ssl.SSLContext | None = None,
+        standard_compatible: bool = True,
+    ) -> TLSStream:
+        """
+        Wrap an existing stream with Transport Layer Security.
+
+        This performs a TLS handshake with the peer.
+
+        :param transport_stream: a bytes-transporting stream to wrap
+        :param server_side: ``True`` if this is the server side of the connection,
+            ``False`` if this is the client side (if omitted, will be set to ``False``
+            if ``hostname`` has been provided, ``False`` otherwise). Used only to create
+            a default context when an explicit context has not been provided.
+        :param hostname: host name of the peer (if host name checking is desired)
+        :param ssl_context: the SSLContext object to use (if not provided, a secure
+            default will be created)
+        :param standard_compatible: if ``False``, skip the closing handshake when
+            closing the connection, and don't raise an exception if the peer does the
+            same
+        :raises ~ssl.SSLError: if the TLS handshake fails
+
+        """
+        if server_side is None:
+            server_side = not hostname
+
+        if not ssl_context:
+            purpose = (
+                ssl.Purpose.CLIENT_AUTH if server_side else ssl.Purpose.SERVER_AUTH
+            )
+            ssl_context = ssl.create_default_context(purpose)
+
+            # Re-enable detection of unexpected EOFs if it was disabled by Python
+            if hasattr(ssl, "OP_IGNORE_UNEXPECTED_EOF"):
+                ssl_context.options &= ~ssl.OP_IGNORE_UNEXPECTED_EOF
+
+        bio_in = ssl.MemoryBIO()
+        bio_out = ssl.MemoryBIO()
+
+        # Resolve international host names using IDNA 2008.
+        # Otherwise wrap_bio() would resolve them with IDNA 2003.
+        if hostname is not None:
+            from .._core._sockets import idna2008_resolve
+
+            server_hostname: bytes | None = idna2008_resolve(hostname)
+        else:
+            server_hostname = None
+
+        # External SSLContext implementations may do blocking I/O in wrap_bio(),
+        # but the standard library implementation won't
+        if type(ssl_context) is ssl.SSLContext:
+            ssl_object = ssl_context.wrap_bio(
+                bio_in,
+                bio_out,
+                server_side=server_side,
+                server_hostname=server_hostname,
+            )
+        else:
+            ssl_object = await to_thread.run_sync(
+                ssl_context.wrap_bio,
+                bio_in,
+                bio_out,
+                server_side,
+                server_hostname,
+                None,
+            )
+
+        wrapper = cls(
+            transport_stream=transport_stream,
+            standard_compatible=standard_compatible,
+            _ssl_object=ssl_object,
+            _read_bio=bio_in,
+            _write_bio=bio_out,
+        )
+        await wrapper._call_sslobject_method(ssl_object.do_handshake)
+        return wrapper
+
+    async def _call_sslobject_method(
+        self, func: Callable[[Unpack[PosArgsT]], T_Retval], *args: Unpack[PosArgsT]
+    ) -> T_Retval:
+        while True:
+            try:
+                result = func(*args)
+            except ssl.SSLWantReadError:
+                try:
+                    # Flush any pending writes first
+                    if self._write_bio.pending:
+                        await self.transport_stream.send(self._write_bio.read())
+
+                    data = await self.transport_stream.receive()
+                except EndOfStream:
+                    self._read_bio.write_eof()
+                except OSError as exc:
+                    self._read_bio.write_eof()
+                    self._write_bio.write_eof()
+                    raise BrokenResourceError from exc
+                else:
+                    self._read_bio.write(data)
+            except ssl.SSLWantWriteError:
+                await self.transport_stream.send(self._write_bio.read())
+            except ssl.SSLSyscallError as exc:
+                self._read_bio.write_eof()
+                self._write_bio.write_eof()
+                raise BrokenResourceError from exc
+            except ssl.SSLError as exc:
+                self._read_bio.write_eof()
+                self._write_bio.write_eof()
+                if isinstance(exc, ssl.SSLEOFError) or (
+                    exc.strerror and "UNEXPECTED_EOF_WHILE_READING" in exc.strerror
+                ):
+                    if self.standard_compatible:
+                        raise BrokenResourceError from exc
+                    else:
+                        raise EndOfStream from None
+
+                raise
+            else:
+                # Flush any pending writes first
+                if self._write_bio.pending:
+                    await self.transport_stream.send(self._write_bio.read())
+
+                return result
+
+    async def unwrap(self) -> tuple[AnyByteStream, bytes]:
+        """
+        Does the TLS closing handshake.
+
+        :return: a tuple of (wrapped byte stream, bytes left in the read buffer)
+
+        """
+        await self._call_sslobject_method(self._ssl_object.unwrap)
+        self._read_bio.write_eof()
+        self._write_bio.write_eof()
+        return self.transport_stream, self._read_bio.read()
+
+    async def aclose(self) -> None:
+        if self.standard_compatible:
+            try:
+                await self.unwrap()
+            except BaseException:
+                await aclose_forcefully(self.transport_stream)
+                raise
+
+        await self.transport_stream.aclose()
+
+    async def receive(self, max_bytes: int = 65536) -> bytes:
+        if max_bytes < 1:
+            raise ValueError("max_bytes must be a positive integer")
+
+        data = await self._call_sslobject_method(self._ssl_object.read, max_bytes)
+        if not data:
+            raise EndOfStream
+
+        return data
+
+    async def send(self, item: bytes) -> None:
+        await self._call_sslobject_method(self._ssl_object.write, item)
+
+    async def send_eof(self) -> None:
+        tls_version = self.extra(TLSAttribute.tls_version)
+        match = re.match(r"TLSv(\d+)(?:\.(\d+))?", tls_version)
+        if match:
+            major, minor = int(match.group(1)), int(match.group(2) or 0)
+            if (major, minor) < (1, 3):
+                raise NotImplementedError(
+                    f"send_eof() requires at least TLSv1.3; current "
+                    f"session uses {tls_version}"
+                )
+
+        raise NotImplementedError(
+            "send_eof() has not yet been implemented for TLS streams"
+        )
+
+    @property
+    def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
+        return {
+            **self.transport_stream.extra_attributes,
+            TLSAttribute.alpn_protocol: self._ssl_object.selected_alpn_protocol,
+            TLSAttribute.channel_binding_tls_unique: (
+                self._ssl_object.get_channel_binding
+            ),
+            TLSAttribute.cipher: self._ssl_object.cipher,
+            TLSAttribute.peer_certificate: lambda: self._ssl_object.getpeercert(False),
+            TLSAttribute.peer_certificate_binary: lambda: self._ssl_object.getpeercert(
+                True
+            ),
+            TLSAttribute.server_side: lambda: self._ssl_object.server_side,
+            TLSAttribute.shared_ciphers: lambda: (
+                self._ssl_object.shared_ciphers()
+                if self._ssl_object.server_side
+                else None
+            ),
+            TLSAttribute.standard_compatible: lambda: self.standard_compatible,
+            TLSAttribute.ssl_object: lambda: self._ssl_object,
+            TLSAttribute.tls_version: self._ssl_object.version,
+        }
+
+
+@dataclass(eq=False)
+class TLSListener(Listener[TLSStream]):
+    """
+    A convenience listener that wraps another listener and auto-negotiates a TLS session
+    on every accepted connection.
+
+    If the TLS handshake times out or raises an exception,
+    :meth:`handle_handshake_error` is called to do whatever post-mortem processing is
+    deemed necessary.
+
+    Supports only the :attr:`~TLSAttribute.standard_compatible` extra attribute.
+
+    :param Listener listener: the listener to wrap
+    :param ssl_context: the SSL context object
+    :param standard_compatible: a flag passed through to :meth:`TLSStream.wrap`
+    :param handshake_timeout: time limit for the TLS handshake
+        (passed to :func:`~anyio.fail_after`)
+    """
+
+    listener: Listener[Any]
+    ssl_context: ssl.SSLContext
+    standard_compatible: bool = True
+    handshake_timeout: float = 30
+
+    @staticmethod
+    async def handle_handshake_error(exc: BaseException, stream: AnyByteStream) -> None:
+        """
+        Handle an exception raised during the TLS handshake.
+
+        This method does 3 things:
+
+        #. Forcefully closes the original stream
+        #. Logs the exception (unless it was a cancellation exception) using the
+           ``anyio.streams.tls`` logger
+        #. Reraises the exception if it was a base exception or a cancellation exception
+
+        :param exc: the exception
+        :param stream: the original stream
+
+        """
+        await aclose_forcefully(stream)
+
+        # Log all except cancellation exceptions
+        if not isinstance(exc, get_cancelled_exc_class()):
+            # CPython (as of 3.11.5) returns incorrect `sys.exc_info()` here when using
+            # any asyncio implementation, so we explicitly pass the exception to log
+            # (https://github.com/python/cpython/issues/108668). Trio does not have this
+            # issue because it works around the CPython bug.
+            logging.getLogger(__name__).exception(
+                "Error during TLS handshake", exc_info=exc
+            )
+
+        # Only reraise base exceptions and cancellation exceptions
+        if not isinstance(exc, Exception) or isinstance(exc, get_cancelled_exc_class()):
+            raise
+
+    async def serve(
+        self,
+        handler: Callable[[TLSStream], Any],
+        task_group: TaskGroup | None = None,
+    ) -> None:
+        @wraps(handler)
+        async def handler_wrapper(stream: AnyByteStream) -> None:
+            from .. import fail_after
+
+            try:
+                with fail_after(self.handshake_timeout):
+                    wrapped_stream = await TLSStream.wrap(
+                        stream,
+                        ssl_context=self.ssl_context,
+                        standard_compatible=self.standard_compatible,
+                    )
+            except BaseException as exc:
+                await self.handle_handshake_error(exc, stream)
+            else:
+                await handler(wrapped_stream)
+
+        await self.listener.serve(handler_wrapper, task_group)
+
+    async def aclose(self) -> None:
+        await self.listener.aclose()
+
+    @property
+    def extra_attributes(self) -> Mapping[Any, Callable[[], Any]]:
+        return {
+            TLSAttribute.standard_compatible: lambda: self.standard_compatible,
+        }
+
+
+class TLSConnectable(ByteStreamConnectable):
+    """
+    Wraps another connectable and does TLS negotiation after a successful connection.
+
+    :param connectable: the connectable to wrap
+    :param hostname: host name of the server (if host name checking is desired)
+    :param ssl_context: the SSLContext object to use (if not provided, a secure default
+        will be created)
+    :param standard_compatible: if ``False``, skip the closing handshake when closing
+        the connection, and don't raise an exception if the server does the same
+    """
+
+    def __init__(
+        self,
+        connectable: AnyByteStreamConnectable,
+        *,
+        hostname: str | None = None,
+        ssl_context: ssl.SSLContext | None = None,
+        standard_compatible: bool = True,
+    ) -> None:
+        self.connectable = connectable
+        self.ssl_context: SSLContext = ssl_context or ssl.create_default_context(
+            ssl.Purpose.SERVER_AUTH
+        )
+        if not isinstance(self.ssl_context, ssl.SSLContext):
+            raise TypeError(
+                "ssl_context must be an instance of ssl.SSLContext, not "
+                f"{type(self.ssl_context).__name__}"
+            )
+        self.hostname = hostname
+        self.standard_compatible = standard_compatible
+
+    @override
+    async def connect(self) -> TLSStream:
+        stream = await self.connectable.connect()
+        try:
+            return await TLSStream.wrap(
+                stream,
+                hostname=self.hostname,
+                ssl_context=self.ssl_context,
+                standard_compatible=self.standard_compatible,
+            )
+        except BaseException:
+            await aclose_forcefully(stream)
+            raise

+ 246 - 0
vendor/anyio/to_interpreter.py

@@ -0,0 +1,246 @@
+from __future__ import annotations
+
+__all__ = (
+    "run_sync",
+    "current_default_interpreter_limiter",
+)
+
+import atexit
+import os
+import sys
+from collections import deque
+from collections.abc import Callable
+from typing import Any, Final, TypeVar
+
+from . import current_time, to_thread
+from ._core._exceptions import BrokenWorkerInterpreter
+from ._core._synchronization import CapacityLimiter
+from .lowlevel import RunVar
+
+if sys.version_info >= (3, 11):
+    from typing import TypeVarTuple, Unpack
+else:
+    from typing_extensions import TypeVarTuple, Unpack
+
+if sys.version_info >= (3, 14):
+    from concurrent.interpreters import ExecutionFailed, create
+
+    def _interp_call(
+        func: Callable[..., Any], args: tuple[Any, ...]
+    ) -> tuple[Any, bool]:
+        try:
+            retval = func(*args)
+        except BaseException as exc:
+            return exc, True
+        else:
+            return retval, False
+
+    class _Worker:
+        last_used: float = 0
+
+        def __init__(self) -> None:
+            self._interpreter = create()
+
+        def destroy(self) -> None:
+            self._interpreter.close()
+
+        def call(
+            self,
+            func: Callable[..., T_Retval],
+            args: tuple[Any, ...],
+        ) -> T_Retval:
+            try:
+                res, is_exception = self._interpreter.call(_interp_call, func, args)
+            except ExecutionFailed as exc:
+                raise BrokenWorkerInterpreter(exc.excinfo) from exc
+
+            if is_exception:
+                raise res
+
+            return res
+elif sys.version_info >= (3, 13):
+    import _interpqueues
+    import _interpreters
+
+    UNBOUND: Final = 2  # I have no clue how this works, but it was used in the stdlib
+    FMT_UNPICKLED: Final = 0
+    FMT_PICKLED: Final = 1
+    QUEUE_PICKLE_ARGS: Final = (FMT_PICKLED, UNBOUND)
+    QUEUE_UNPICKLE_ARGS: Final = (FMT_UNPICKLED, UNBOUND)
+
+    _run_func = compile(
+        """
+import _interpqueues
+from _interpreters import NotShareableError
+from pickle import loads, dumps, HIGHEST_PROTOCOL
+
+QUEUE_PICKLE_ARGS = (1, 2)
+QUEUE_UNPICKLE_ARGS = (0, 2)
+
+item = _interpqueues.get(queue_id)[0]
+try:
+    func, args = loads(item)
+    retval = func(*args)
+except BaseException as exc:
+    is_exception = True
+    retval = exc
+else:
+    is_exception = False
+
+try:
+    _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_UNPICKLE_ARGS)
+except NotShareableError:
+    retval = dumps(retval, HIGHEST_PROTOCOL)
+    _interpqueues.put(queue_id, (retval, is_exception), *QUEUE_PICKLE_ARGS)
+    """,
+        "<string>",
+        "exec",
+    )
+
+    class _Worker:
+        last_used: float = 0
+
+        def __init__(self) -> None:
+            self._interpreter_id = _interpreters.create()
+            self._queue_id = _interpqueues.create(1, *QUEUE_UNPICKLE_ARGS)
+            _interpreters.set___main___attrs(
+                self._interpreter_id, {"queue_id": self._queue_id}
+            )
+
+        def destroy(self) -> None:
+            _interpqueues.destroy(self._queue_id)
+            _interpreters.destroy(self._interpreter_id)
+
+        def call(
+            self,
+            func: Callable[..., T_Retval],
+            args: tuple[Any, ...],
+        ) -> T_Retval:
+            import pickle
+
+            item = pickle.dumps((func, args), pickle.HIGHEST_PROTOCOL)
+            _interpqueues.put(self._queue_id, item, *QUEUE_PICKLE_ARGS)
+            exc_info = _interpreters.exec(self._interpreter_id, _run_func)
+            if exc_info:
+                raise BrokenWorkerInterpreter(exc_info)
+
+            res = _interpqueues.get(self._queue_id)
+            (res, is_exception), fmt = res[:2]
+            if fmt == FMT_PICKLED:
+                res = pickle.loads(res)
+
+            if is_exception:
+                raise res
+
+            return res
+else:
+
+    class _Worker:
+        last_used: float = 0
+
+        def __init__(self) -> None:
+            raise RuntimeError("subinterpreters require at least Python 3.13")
+
+        def call(
+            self,
+            func: Callable[..., T_Retval],
+            args: tuple[Any, ...],
+        ) -> T_Retval:
+            raise NotImplementedError
+
+        def destroy(self) -> None:
+            pass
+
+
+DEFAULT_CPU_COUNT: Final = 8  # this is just an arbitrarily selected value
+MAX_WORKER_IDLE_TIME = (
+    30  # seconds a subinterpreter can be idle before becoming eligible for pruning
+)
+
+T_Retval = TypeVar("T_Retval")
+PosArgsT = TypeVarTuple("PosArgsT")
+
+_idle_workers = RunVar[deque[_Worker]]("_available_workers")
+_default_interpreter_limiter = RunVar[CapacityLimiter]("_default_interpreter_limiter")
+
+
+def _stop_workers(workers: deque[_Worker]) -> None:
+    for worker in workers:
+        worker.destroy()
+
+    workers.clear()
+
+
+async def run_sync(
+    func: Callable[[Unpack[PosArgsT]], T_Retval],
+    *args: Unpack[PosArgsT],
+    limiter: CapacityLimiter | None = None,
+) -> T_Retval:
+    """
+    Call the given function with the given arguments in a subinterpreter.
+
+    .. warning:: On Python 3.13, the :mod:`concurrent.interpreters` module was not yet
+        available, so the code path for that Python version relies on an undocumented,
+        private API. As such, it is recommended to not rely on this function for anything
+        mission-critical on Python 3.13.
+
+    :param func: a callable
+    :param args: the positional arguments for the callable
+    :param limiter: capacity limiter to use to limit the total number of subinterpreters
+        running (if omitted, the default limiter is used)
+    :return: the result of the call
+    :raises BrokenWorkerInterpreter: if there's an internal error in a subinterpreter
+
+    """
+    if limiter is None:
+        limiter = current_default_interpreter_limiter()
+
+    try:
+        idle_workers = _idle_workers.get()
+    except LookupError:
+        idle_workers = deque()
+        _idle_workers.set(idle_workers)
+        atexit.register(_stop_workers, idle_workers)
+
+    async with limiter:
+        try:
+            worker = idle_workers.pop()
+        except IndexError:
+            worker = _Worker()
+
+    try:
+        return await to_thread.run_sync(
+            worker.call,
+            func,
+            args,
+            limiter=limiter,
+        )
+    finally:
+        # Prune workers that have been idle for too long
+        now = current_time()
+        while idle_workers:
+            if now - idle_workers[0].last_used <= MAX_WORKER_IDLE_TIME:
+                break
+
+            await to_thread.run_sync(idle_workers.popleft().destroy, limiter=limiter)
+
+        worker.last_used = current_time()
+        idle_workers.append(worker)
+
+
+def current_default_interpreter_limiter() -> CapacityLimiter:
+    """
+    Return the capacity limiter used by default to limit the number of concurrently
+    running subinterpreters.
+
+    Defaults to the number of CPU cores.
+
+    :return: a capacity limiter object
+
+    """
+    try:
+        return _default_interpreter_limiter.get()
+    except LookupError:
+        limiter = CapacityLimiter(os.cpu_count() or DEFAULT_CPU_COUNT)
+        _default_interpreter_limiter.set(limiter)
+        return limiter

+ 269 - 0
vendor/anyio/to_process.py

@@ -0,0 +1,269 @@
+from __future__ import annotations
+
+__all__ = (
+    "current_default_process_limiter",
+    "process_worker",
+    "run_sync",
+)
+
+import os
+import pickle
+import runpy
+import subprocess
+import sys
+from collections import deque
+from collections.abc import Callable
+from types import ModuleType
+from typing import TypeVar, cast
+
+from ._core._eventloop import current_time, get_async_backend, get_cancelled_exc_class
+from ._core._exceptions import BrokenWorkerProcess
+from ._core._subprocesses import open_process
+from ._core._synchronization import CapacityLimiter
+from ._core._tasks import CancelScope, fail_after
+from .abc import ByteReceiveStream, ByteSendStream, Process
+from .lowlevel import RunVar, checkpoint_if_cancelled
+from .streams.buffered import BufferedByteReceiveStream
+
+if sys.version_info >= (3, 11):
+    from typing import TypeVarTuple, Unpack
+else:
+    from typing_extensions import TypeVarTuple, Unpack
+
+WORKER_MAX_IDLE_TIME = 300  # 5 minutes
+
+T_Retval = TypeVar("T_Retval")
+PosArgsT = TypeVarTuple("PosArgsT")
+
+_process_pool_workers: RunVar[set[Process]] = RunVar("_process_pool_workers")
+_process_pool_idle_workers: RunVar[deque[tuple[Process, float]]] = RunVar(
+    "_process_pool_idle_workers"
+)
+_default_process_limiter: RunVar[CapacityLimiter] = RunVar("_default_process_limiter")
+
+
+async def run_sync(  # type: ignore[return]
+    func: Callable[[Unpack[PosArgsT]], T_Retval],
+    *args: Unpack[PosArgsT],
+    cancellable: bool = False,
+    limiter: CapacityLimiter | None = None,
+) -> T_Retval:
+    """
+    Call the given function with the given arguments in a worker process.
+
+    If the ``cancellable`` option is enabled and the task waiting for its completion is
+    cancelled, the worker process running it will be abruptly terminated using SIGKILL
+    (or ``terminateProcess()`` on Windows).
+
+    :param func: a callable
+    :param args: positional arguments for the callable
+    :param cancellable: ``True`` to allow cancellation of the operation while it's
+        running
+    :param limiter: capacity limiter to use to limit the total amount of processes
+        running (if omitted, the default limiter is used)
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+    :return: an awaitable that yields the return value of the function.
+
+    """
+
+    async def send_raw_command(pickled_cmd: bytes) -> object:
+        try:
+            await stdin.send(pickled_cmd)
+            response = await buffered.receive_until(b"\n", 50)
+            status, length = response.split(b" ")
+            if status not in (b"RETURN", b"EXCEPTION"):
+                raise RuntimeError(
+                    f"Worker process returned unexpected response: {response!r}"
+                )
+
+            pickled_response = await buffered.receive_exactly(int(length))
+        except BaseException as exc:
+            workers.discard(process)
+            try:
+                process.kill()
+                with CancelScope(shield=True):
+                    await process.aclose()
+            except ProcessLookupError:
+                pass
+
+            if isinstance(exc, get_cancelled_exc_class()):
+                raise
+            else:
+                raise BrokenWorkerProcess from exc
+
+        retval = pickle.loads(pickled_response)
+        if status == b"EXCEPTION":
+            assert isinstance(retval, BaseException)
+            raise retval
+        else:
+            return retval
+
+    # First pickle the request before trying to reserve a worker process
+    await checkpoint_if_cancelled()
+    request = pickle.dumps(("run", func, args), protocol=pickle.HIGHEST_PROTOCOL)
+
+    # If this is the first run in this event loop thread, set up the necessary variables
+    try:
+        workers = _process_pool_workers.get()
+        idle_workers = _process_pool_idle_workers.get()
+    except LookupError:
+        workers = set()
+        idle_workers = deque()
+        _process_pool_workers.set(workers)
+        _process_pool_idle_workers.set(idle_workers)
+        get_async_backend().setup_process_pool_exit_at_shutdown(workers)
+
+    async with limiter or current_default_process_limiter():
+        # Pop processes from the pool (starting from the most recently used) until we
+        # find one that hasn't exited yet
+        process: Process
+        while idle_workers:
+            process, idle_since = idle_workers.pop()
+            if process.returncode is None:
+                stdin = cast(ByteSendStream, process.stdin)
+                buffered = BufferedByteReceiveStream(
+                    cast(ByteReceiveStream, process.stdout)
+                )
+
+                # Prune any other workers that have been idle for WORKER_MAX_IDLE_TIME
+                # seconds or longer
+                now = current_time()
+                killed_processes: list[Process] = []
+                while idle_workers:
+                    if now - idle_workers[0][1] < WORKER_MAX_IDLE_TIME:
+                        break
+
+                    process_to_kill, idle_since = idle_workers.popleft()
+                    process_to_kill.kill()
+                    workers.remove(process_to_kill)
+                    killed_processes.append(process_to_kill)
+
+                with CancelScope(shield=True):
+                    for killed_process in killed_processes:
+                        await killed_process.aclose()
+
+                break
+
+            workers.remove(process)
+        else:
+            command = [sys.executable, "-u", "-m", __name__]
+            process = await open_process(
+                command, stdin=subprocess.PIPE, stdout=subprocess.PIPE
+            )
+            try:
+                stdin = cast(ByteSendStream, process.stdin)
+                buffered = BufferedByteReceiveStream(
+                    cast(ByteReceiveStream, process.stdout)
+                )
+                with fail_after(20):
+                    message = await buffered.receive(6)
+
+                if message != b"READY\n":
+                    raise BrokenWorkerProcess(
+                        f"Worker process returned unexpected response: {message!r}"
+                    )
+
+                main_module_path = getattr(sys.modules["__main__"], "__file__", None)
+                pickled = pickle.dumps(
+                    ("init", sys.path, main_module_path),
+                    protocol=pickle.HIGHEST_PROTOCOL,
+                )
+                await send_raw_command(pickled)
+            except (BrokenWorkerProcess, get_cancelled_exc_class()):
+                raise
+            except BaseException as exc:
+                process.kill()
+                raise BrokenWorkerProcess(
+                    "Error during worker process initialization"
+                ) from exc
+
+            workers.add(process)
+
+        with CancelScope(shield=not cancellable):
+            try:
+                return cast(T_Retval, await send_raw_command(request))
+            finally:
+                if process in workers:
+                    idle_workers.append((process, current_time()))
+
+
+def current_default_process_limiter() -> CapacityLimiter:
+    """
+    Return the capacity limiter that is used by default to limit the number of worker
+    processes.
+
+    :return: a capacity limiter object
+
+    """
+    try:
+        return _default_process_limiter.get()
+    except LookupError:
+        limiter = CapacityLimiter(os.cpu_count() or 2)
+        _default_process_limiter.set(limiter)
+        return limiter
+
+
+def process_worker() -> None:
+    # Redirect standard streams to os.devnull so that user code won't interfere with the
+    # parent-worker communication
+    stdin = sys.stdin
+    stdout = sys.stdout
+    sys.stdin = open(os.devnull)
+    sys.stdout = open(os.devnull, "w")
+    sys.stderr = open(os.devnull, "w")
+
+    stdout.buffer.write(b"READY\n")
+    while True:
+        retval = exception = None
+        try:
+            command, *args = pickle.load(stdin.buffer)
+        except EOFError:
+            return
+        except BaseException as exc:
+            exception = exc
+        else:
+            if command == "run":
+                func, args = args
+                try:
+                    retval = func(*args)
+                except BaseException as exc:
+                    exception = exc
+            elif command == "init":
+                main_module_path: str | None
+                sys.path, main_module_path = args
+                del sys.modules["__main__"]
+                if main_module_path and os.path.isfile(main_module_path):
+                    # Load the parent's main module but as __mp_main__ instead of
+                    # __main__ (like multiprocessing does) to avoid infinite recursion
+                    try:
+                        main = ModuleType("__mp_main__")
+                        main_content = runpy.run_path(
+                            main_module_path, run_name="__mp_main__"
+                        )
+                        main.__dict__.update(main_content)
+                        sys.modules["__main__"] = sys.modules["__mp_main__"] = main
+                    except BaseException as exc:
+                        exception = exc
+        try:
+            if exception is not None:
+                status = b"EXCEPTION"
+                pickled = pickle.dumps(exception, pickle.HIGHEST_PROTOCOL)
+            else:
+                status = b"RETURN"
+                pickled = pickle.dumps(retval, pickle.HIGHEST_PROTOCOL)
+        except BaseException as exc:
+            exception = exc
+            status = b"EXCEPTION"
+            pickled = pickle.dumps(exc, pickle.HIGHEST_PROTOCOL)
+
+        stdout.buffer.write(b"%s %d\n" % (status, len(pickled)))
+        stdout.buffer.write(pickled)
+
+        # Respect SIGTERM
+        if isinstance(exception, SystemExit):
+            raise exception
+
+
+if __name__ == "__main__":
+    process_worker()

+ 80 - 0
vendor/anyio/to_thread.py

@@ -0,0 +1,80 @@
+from __future__ import annotations
+
+__all__ = (
+    "run_sync",
+    "current_default_thread_limiter",
+)
+
+import sys
+from collections.abc import Callable
+from typing import TYPE_CHECKING, TypeVar
+from warnings import warn
+
+from ._core._eventloop import get_async_backend
+
+if TYPE_CHECKING:
+    from ._core._synchronization import CapacityLimiter
+
+if sys.version_info >= (3, 11):
+    from typing import TypeVarTuple, Unpack
+else:
+    from typing_extensions import TypeVarTuple, Unpack
+
+T_Retval = TypeVar("T_Retval")
+PosArgsT = TypeVarTuple("PosArgsT")
+
+
+async def run_sync(
+    func: Callable[[Unpack[PosArgsT]], T_Retval],
+    *args: Unpack[PosArgsT],
+    abandon_on_cancel: bool = False,
+    cancellable: bool | None = None,
+    limiter: CapacityLimiter | None = None,
+) -> T_Retval:
+    """
+    Call the given function with the given arguments in a worker thread.
+
+    If the ``abandon_on_cancel`` option is enabled and the task waiting for its
+    completion is cancelled, the thread will still run its course but its
+    return value (or any raised exception) will be ignored.
+
+    :param func: a callable
+    :param args: positional arguments for the callable
+    :param abandon_on_cancel: ``True`` to abandon the thread (leaving it to run
+        unchecked on own) if the host task is cancelled, ``False`` to ignore
+        cancellations in the host task until the operation has completed in the worker
+        thread
+    :param cancellable: deprecated alias of ``abandon_on_cancel``; will override
+        ``abandon_on_cancel`` if both parameters are passed
+    :param limiter: capacity limiter to use to limit the total amount of threads running
+        (if omitted, the default limiter is used)
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+    :return: an awaitable that yields the return value of the function.
+
+    """
+    if cancellable is not None:
+        abandon_on_cancel = cancellable
+        warn(
+            "The `cancellable=` keyword argument to `anyio.to_thread.run_sync` is "
+            "deprecated since AnyIO 4.1.0; use `abandon_on_cancel=` instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+    return await get_async_backend().run_sync_in_worker_thread(
+        func, args, abandon_on_cancel=abandon_on_cancel, limiter=limiter
+    )
+
+
+def current_default_thread_limiter() -> CapacityLimiter:
+    """
+    Return the capacity limiter that is used by default to limit the number of
+    concurrent threads.
+
+    :return: a capacity limiter object
+    :raises NoEventLoopError: if no supported asynchronous event loop is running in the
+        current thread
+
+    """
+    return get_async_backend().current_default_thread_limiter()

+ 10 - 0
vendor/bin/httpx

@@ -0,0 +1,10 @@
+#!/home/paul/dev2/chatto-stuff/hermes-chatto-plugin/.venv/bin/python3
+# -*- coding: utf-8 -*-
+import sys
+from httpx import main
+if __name__ == "__main__":
+    if sys.argv[0].endswith("-script.pyw"):
+        sys.argv[0] = sys.argv[0][:-11]
+    elif sys.argv[0].endswith(".exe"):
+        sys.argv[0] = sys.argv[0][:-4]
+    sys.exit(main())

+ 10 - 0
vendor/bin/idna

@@ -0,0 +1,10 @@
+#!/home/paul/dev2/chatto-stuff/hermes-chatto-plugin/.venv/bin/python3
+# -*- coding: utf-8 -*-
+import sys
+from idna.cli import main
+if __name__ == "__main__":
+    if sys.argv[0].endswith("-script.pyw"):
+        sys.argv[0] = sys.argv[0][:-11]
+    elif sys.argv[0].endswith(".exe"):
+        sys.argv[0] = sys.argv[0][:-4]
+    sys.exit(main())

+ 1 - 0
vendor/certifi-2026.7.22.dist-info/INSTALLER

@@ -0,0 +1 @@
+uv

+ 78 - 0
vendor/certifi-2026.7.22.dist-info/METADATA

@@ -0,0 +1,78 @@
+Metadata-Version: 2.4
+Name: certifi
+Version: 2026.7.22
+Summary: Python package for providing Mozilla's CA Bundle.
+Home-page: https://github.com/certifi/python-certifi
+Author: Kenneth Reitz
+Author-email: me@kennethreitz.com
+License: MPL-2.0
+Project-URL: Source, https://github.com/certifi/python-certifi
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
+Classifier: Natural Language :: English
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Requires-Python: >=3.7
+License-File: LICENSE
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: license
+Dynamic: license-file
+Dynamic: project-url
+Dynamic: requires-python
+Dynamic: summary
+
+Certifi: Python SSL Certificates
+================================
+
+Certifi provides Mozilla's carefully curated collection of Root Certificates for
+validating the trustworthiness of SSL certificates while verifying the identity
+of TLS hosts. It has been extracted from the `Requests`_ project.
+
+Installation
+------------
+
+``certifi`` is available on PyPI. Simply install it with ``pip``::
+
+    $ pip install certifi
+
+Usage
+-----
+
+To reference the installed certificate authority (CA) bundle, you can use the
+built-in function::
+
+    >>> import certifi
+
+    >>> certifi.where()
+    '/usr/local/lib/python3.7/site-packages/certifi/cacert.pem'
+
+Or from the command line::
+
+    $ python -m certifi
+    /usr/local/lib/python3.7/site-packages/certifi/cacert.pem
+
+Enjoy!
+
+.. _`Requests`: https://requests.readthedocs.io/en/latest/
+
+Addition/Removal of Certificates
+--------------------------------
+
+Certifi does not support any addition/removal or other modification of the
+CA trust store content. This project is intended to provide a reliable and
+highly portable root of trust to python deployments. Look to upstream projects
+for methods to use alternate trust.

+ 14 - 0
vendor/certifi-2026.7.22.dist-info/RECORD

@@ -0,0 +1,14 @@
+certifi-2026.7.22.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
+certifi-2026.7.22.dist-info/METADATA,sha256=71rxY4-7I2dqw8V3ffz8LNnDSP5Bcu1bo9J3ZVskgJA,2474
+certifi-2026.7.22.dist-info/RECORD,,
+certifi-2026.7.22.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+certifi-2026.7.22.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
+certifi-2026.7.22.dist-info/licenses/LICENSE,sha256=6TcW2mucDVpKHfYP5pWzcPBpVgPSH2-D8FPkLPwQyvc,989
+certifi-2026.7.22.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8
+certifi/__init__.py,sha256=elLl9CBfmz0SoxiYzqp-P2g30ZzCMHALyuGp2R0UhsE,94
+certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243
+certifi/cacert.pem,sha256=nMKndLUZjc_xTZvh5mCR9TiXXYZ84Cmpa84VpV39cw8,240216
+certifi/core.py,sha256=XFXycndG5pf37ayeF8N32HUuDafsyhkVMbO4BAPWHa0,3394
+certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+certifi/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+certifi/tests/test_certify.py,sha256=fBedHt839-kSsTNmNzX7RsYzjV0OX4kV0Rw7wofSJ7g,467

+ 0 - 0
vendor/certifi-2026.7.22.dist-info/REQUESTED


+ 5 - 0
vendor/certifi-2026.7.22.dist-info/WHEEL

@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: setuptools (83.0.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+

+ 20 - 0
vendor/certifi-2026.7.22.dist-info/licenses/LICENSE

@@ -0,0 +1,20 @@
+This package contains a modified version of ca-bundle.crt:
+
+ca-bundle.crt -- Bundle of CA Root Certificates
+
+This is a bundle of X.509 certificates of public Certificate Authorities
+(CA). These were automatically extracted from Mozilla's root certificates
+file (certdata.txt).  This file can be found in the mozilla source tree:
+https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt
+It contains the certificates in PEM format and therefore
+can be directly used with curl / libcurl / php_curl, or with
+an Apache+mod_ssl webserver for SSL client authentication.
+Just configure this file as the SSLCACertificateFile.#
+
+***** BEGIN LICENSE BLOCK *****
+This Source Code Form is subject to the terms of the Mozilla Public License,
+v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain
+one at http://mozilla.org/MPL/2.0/.
+
+***** END LICENSE BLOCK *****
+@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $

+ 1 - 0
vendor/certifi-2026.7.22.dist-info/top_level.txt

@@ -0,0 +1 @@
+certifi

+ 4 - 0
vendor/certifi/__init__.py

@@ -0,0 +1,4 @@
+from .core import contents, where
+
+__all__ = ["contents", "where"]
+__version__ = "2026.07.22"

+ 12 - 0
vendor/certifi/__main__.py

@@ -0,0 +1,12 @@
+import argparse
+
+from certifi import contents, where
+
+parser = argparse.ArgumentParser()
+parser.add_argument("-c", "--contents", action="store_true")
+args = parser.parse_args()
+
+if args.contents:
+    print(contents())
+else:
+    print(where())

+ 3959 - 0
vendor/certifi/cacert.pem

@@ -0,0 +1,3959 @@
+
+# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited
+# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited
+# Label: "COMODO ECC Certification Authority"
+# Serial: 41578283867086692638256921589707938090
+# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23
+# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11
+# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7
+-----BEGIN CERTIFICATE-----
+MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
+MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
+BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
+IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
+MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
+ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
+T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
+biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
+FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
+cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
+BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
+fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
+GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
+-----END CERTIFICATE-----
+
+# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services)
+# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services)
+# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny"
+# Serial: 80544274841616
+# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88
+# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91
+# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98
+-----BEGIN CERTIFICATE-----
+MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
+EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
+MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
+cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
+dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
+pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
+b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
+aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
+IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
+lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
+AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
+VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
+ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
+BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
+AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
+U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
+bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
++C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
+bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
+uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
+XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd.
+# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd.
+# Label: "Microsec e-Szigno Root CA 2009"
+# Serial: 14014712776195784473
+# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1
+# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e
+# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78
+-----BEGIN CERTIFICATE-----
+MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
+VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
+ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
+CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y
+OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx
+FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp
+Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
+dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP
+kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc
+cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U
+fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7
+N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC
+xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1
++rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM
+Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG
+SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h
+mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk
+ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
+tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c
+2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t
+HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3
+# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3
+# Label: "GlobalSign Root CA - R3"
+# Serial: 4835703278459759426209954
+# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28
+# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad
+# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b
+-----BEGIN CERTIFICATE-----
+MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
+A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
+Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
+MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
+A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
+RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
+gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
+KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
+QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
+XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
+DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
+LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
+RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
+jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
+6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
+mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
+Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
+WD9f
+-----END CERTIFICATE-----
+
+# Issuer: CN=Izenpe.com O=IZENPE S.A.
+# Subject: CN=Izenpe.com O=IZENPE S.A.
+# Label: "Izenpe.com"
+# Serial: 917563065490389241595536686991402621
+# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73
+# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19
+# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f
+-----BEGIN CERTIFICATE-----
+MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
+MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
+ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
+VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
+b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
+scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
+xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
+LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
+uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
+yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
+JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
+rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
+BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
+hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
+QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
+HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
+Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
+QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
+BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
+MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
+A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
+laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
+awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
+JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
+LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
+VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
+LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
+UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
+QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
+naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
+QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc.
+# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc.
+# Label: "Go Daddy Root Certificate Authority - G2"
+# Serial: 0
+# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01
+# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b
+# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da
+-----BEGIN CERTIFICATE-----
+MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
+EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
+ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
+NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
+EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
+AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
+DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
+E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
+/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
+DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
+GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
+tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
+AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
+FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
+WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
+9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
+gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
+2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
+LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
+4uJEvlz36hz1
+-----END CERTIFICATE-----
+
+# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc.
+# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc.
+# Label: "Starfield Root Certificate Authority - G2"
+# Serial: 0
+# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96
+# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e
+# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5
+-----BEGIN CERTIFICATE-----
+MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
+HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
+ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw
+MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
+b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj
+aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp
+Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg
+nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1
+HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N
+Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN
+dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0
+HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G
+CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU
+sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3
+4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg
+8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
+pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
+mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
+-----END CERTIFICATE-----
+
+# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc.
+# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc.
+# Label: "Starfield Services Root Certificate Authority - G2"
+# Serial: 0
+# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2
+# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f
+# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5
+-----BEGIN CERTIFICATE-----
+MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx
+EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
+HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs
+ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
+MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD
+VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
+ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy
+dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p
+OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2
+8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K
+Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe
+hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk
+6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw
+DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q
+AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI
+bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB
+ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z
+qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
+iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn
+0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN
+sSi6
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority
+# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority
+# Label: "Certum Trusted Network CA"
+# Serial: 279744
+# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78
+# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e
+# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e
+-----BEGIN CERTIFICATE-----
+MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
+MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
+ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
+cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3
+WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg
+Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw
+IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH
+UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM
+TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU
+BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM
+kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x
+AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV
+HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y
+sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL
+I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8
+J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY
+VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
+03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
+-----END CERTIFICATE-----
+
+# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA
+# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA
+# Label: "TWCA Root Certification Authority"
+# Serial: 1
+# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79
+# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48
+# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44
+-----BEGIN CERTIFICATE-----
+MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES
+MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU
+V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz
+WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO
+LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
+AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE
+AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH
+K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX
+RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z
+rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx
+3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq
+hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC
+MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls
+XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D
+lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn
+aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ
+YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
+-----END CERTIFICATE-----
+
+# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2
+# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2
+# Label: "Security Communication RootCA2"
+# Serial: 0
+# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43
+# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74
+# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6
+-----BEGIN CERTIFICATE-----
+MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
+MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
+U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
+DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy
+dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj
+YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV
+OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr
+zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM
+VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ
+hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO
+ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw
+awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs
+OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
+DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF
+coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc
+okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8
+t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy
+1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/
+SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
+-----END CERTIFICATE-----
+
+# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967
+# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967
+# Label: "Actalis Authentication Root CA"
+# Serial: 6271844772424770508
+# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6
+# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac
+# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66
+-----BEGIN CERTIFICATE-----
+MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE
+BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w
+MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
+IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC
+SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1
+ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv
+UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX
+4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9
+KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/
+gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb
+rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ
+51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F
+be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe
+KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F
+v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn
+fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7
+jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz
+ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
+ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL
+e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70
+jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz
+WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V
+SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j
+pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX
+X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok
+fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R
+K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU
+ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU
+LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT
+LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327
+# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327
+# Label: "Buypass Class 2 Root CA"
+# Serial: 2
+# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29
+# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99
+# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
+Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow
+TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
+HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr
+6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV
+L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91
+1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx
+MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ
+QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB
+arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr
+Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi
+FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS
+P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN
+9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP
+AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz
+uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h
+9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
+A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t
+OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo
++fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7
+KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2
+DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us
+H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ
+I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7
+5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h
+3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz
+Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327
+# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327
+# Label: "Buypass Class 3 Root CA"
+# Serial: 2
+# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec
+# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57
+# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d
+-----BEGIN CERTIFICATE-----
+MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
+MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
+Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow
+TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
+HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
+BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y
+ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E
+N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9
+tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX
+0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c
+/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X
+KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY
+zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS
+O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D
+34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP
+K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3
+AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv
+Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj
+QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
+cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS
+IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2
+HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa
+O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv
+033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u
+dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE
+kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41
+3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD
+u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq
+4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=
+-----END CERTIFICATE-----
+
+# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
+# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
+# Label: "T-TeleSec GlobalRoot Class 3"
+# Serial: 1
+# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef
+# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1
+# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
+KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
+BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
+YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1
+OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
+aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
+ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN
+8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/
+RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4
+hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5
+ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM
+EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1
+A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy
+WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ
+1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30
+6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT
+91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
+e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p
+TpPDpFQUWw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH
+# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH
+# Label: "D-TRUST Root Class 3 CA 2 2009"
+# Serial: 623603
+# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f
+# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0
+# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1
+-----BEGIN CERTIFICATE-----
+MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
+MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
+bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha
+ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM
+HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03
+UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42
+tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R
+ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM
+lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp
+/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G
+A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G
+A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj
+dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy
+MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl
+cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js
+L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL
+BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni
+acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
+o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K
+zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8
+PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y
+Johw1+qRzT65ysCQblrGXnRl11z+o+I=
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH
+# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH
+# Label: "D-TRUST Root Class 3 CA 2 EV 2009"
+# Serial: 623604
+# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6
+# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83
+# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81
+-----BEGIN CERTIFICATE-----
+MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF
+MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD
+bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw
+NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV
+BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn
+ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0
+3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z
+qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR
+p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8
+HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw
+ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea
+HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw
+Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh
+c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E
+RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt
+dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku
+Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp
+3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
+nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF
+CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na
+xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX
+KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
+-----END CERTIFICATE-----
+
+# Issuer: CN=CA Disig Root R2 O=Disig a.s.
+# Subject: CN=CA Disig Root R2 O=Disig a.s.
+# Label: "CA Disig Root R2"
+# Serial: 10572350602393338211
+# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03
+# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71
+# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03
+-----BEGIN CERTIFICATE-----
+MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV
+BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
+MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy
+MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
+EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw
+ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe
+NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH
+PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I
+x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe
+QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR
+yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO
+QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912
+H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ
+QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD
+i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs
+nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1
+rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
+DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI
+hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
+tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf
+GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb
+lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka
++elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal
+TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i
+nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3
+gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr
+G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os
+zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x
+L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL
+-----END CERTIFICATE-----
+
+# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV
+# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV
+# Label: "ACCVRAIZ1"
+# Serial: 6828503384748696800
+# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02
+# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17
+# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13
+-----BEGIN CERTIFICATE-----
+MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE
+AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw
+CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ
+BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND
+VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb
+qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY
+HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo
+G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA
+lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr
+IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/
+0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH
+k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47
+4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO
+m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa
+cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl
+uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI
+KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls
+ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG
+AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
+VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT
+VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG
+CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA
+cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA
+QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA
+7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA
+cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA
+QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA
+czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu
+aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt
+aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud
+DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF
+BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp
+D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU
+JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m
+AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD
+vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms
+tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH
+7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
+I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA
+h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF
+d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H
+pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7
+-----END CERTIFICATE-----
+
+# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA
+# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA
+# Label: "TWCA Global Root CA"
+# Serial: 3262
+# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96
+# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65
+# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b
+-----BEGIN CERTIFICATE-----
+MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx
+EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT
+VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5
+NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT
+B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF
+10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz
+0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh
+MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH
+zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc
+46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2
+yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi
+laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP
+oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA
+BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE
+qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm
+4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL
+1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
+LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF
+H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo
+RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+
+nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh
+15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW
+6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW
+nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j
+wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz
+aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy
+KwbQBM0=
+-----END CERTIFICATE-----
+
+# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
+# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center
+# Label: "T-TeleSec GlobalRoot Class 2"
+# Serial: 1
+# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a
+# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9
+# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52
+-----BEGIN CERTIFICATE-----
+MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
+KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
+BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
+YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1
+OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
+aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
+ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd
+AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC
+FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi
+1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq
+jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ
+wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/
+WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy
+NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC
+uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw
+IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6
+g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
+9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP
+BSeOE6Fuwg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited
+# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited
+# Label: "QuoVadis Root CA 1 G3"
+# Serial: 687049649626669250736271037606554624078720034195
+# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab
+# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67
+# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00
+MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV
+wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe
+rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341
+68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh
+4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp
+UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o
+abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc
+3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G
+KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt
+hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO
+Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt
+zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD
+ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
+MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2
+cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN
+qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5
+YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv
+b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2
+8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k
+NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj
+ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp
+q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt
+nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD
+-----END CERTIFICATE-----
+
+# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited
+# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited
+# Label: "QuoVadis Root CA 2 G3"
+# Serial: 390156079458959257446133169266079962026824725800
+# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06
+# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36
+# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00
+MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf
+qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW
+n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym
+c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+
+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1
+o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j
+IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq
+IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz
+8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh
+vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l
+7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG
+cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD
+ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
+AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC
+roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga
+W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n
+lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE
++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV
+csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd
+dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg
+KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM
+HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4
+WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M
+-----END CERTIFICATE-----
+
+# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited
+# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited
+# Label: "QuoVadis Root CA 3 G3"
+# Serial: 268090761170461462463995952157327242137089239581
+# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7
+# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d
+# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL
+BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
+BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00
+MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
+aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR
+/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu
+FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR
+U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c
+ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR
+FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k
+A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw
+eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl
+sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp
+VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q
+A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+
+ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD
+ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
+KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI
+FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv
+oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg
+u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP
+0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf
+3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl
+8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+
+DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN
+PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/
+ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Assured ID Root G2"
+# Serial: 15385348160840213938643033620894905419
+# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d
+# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f
+# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85
+-----BEGIN CERTIFICATE-----
+MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
+b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG
+EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
+cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA
+n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc
+biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp
+EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA
+bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu
+YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB
+AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW
+BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI
+QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I
+0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni
+lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9
+B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv
+ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
+IhNzbM8m9Yop5w==
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Assured ID Root G3"
+# Serial: 15459312981008553731928384953135426796
+# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb
+# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89
+# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2
+-----BEGIN CERTIFICATE-----
+MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
+ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
+RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV
+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
+Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq
+hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf
+Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q
+RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
+BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD
+AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY
+JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
+6pZjamVFkpUBtA==
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Global Root G2"
+# Serial: 4293743540046975378534879503202253541
+# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44
+# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4
+# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f
+-----BEGIN CERTIFICATE-----
+MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
+MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
+MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
+b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
+9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
+2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
+1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
+q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
+tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
+vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
+5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
+1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
+NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
+Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
+8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
+pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
+MrY=
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Global Root G3"
+# Serial: 7089244469030293291760083333884364146
+# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca
+# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e
+# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0
+-----BEGIN CERTIFICATE-----
+MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
+CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
+ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
+Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw
+EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
+IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF
+K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG
+fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO
+Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd
+BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx
+AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/
+oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8
+sycX
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com
+# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com
+# Label: "DigiCert Trusted Root G4"
+# Serial: 7451500558977370777930084869016614236
+# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49
+# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4
+# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88
+-----BEGIN CERTIFICATE-----
+MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
+MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
+d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
+RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
+UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
+Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
+ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
+xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
+ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
+DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
+jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
+CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
+EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
+fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
+uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
+chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
+9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
+hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
+ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
+SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
+fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
+sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
+cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
+0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
+4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
+r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
+/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
+gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
+-----END CERTIFICATE-----
+
+# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited
+# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited
+# Label: "COMODO RSA Certification Authority"
+# Serial: 101909084537582093308941363524873193117
+# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18
+# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4
+# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34
+-----BEGIN CERTIFICATE-----
+MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
+hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
+A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
+BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
+MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
+EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
+Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
+6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
+pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
+9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
+/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
+Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
+qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
+SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
+u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
+Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
+crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
+FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
+/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
+wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
+4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
+2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
+FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
+CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
+boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
+jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
+S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
+QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
+0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
+NVOFBkpdn627G190
+-----END CERTIFICATE-----
+
+# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network
+# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network
+# Label: "USERTrust RSA Certification Authority"
+# Serial: 2645093764781058787591871645665788717
+# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5
+# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e
+# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2
+-----BEGIN CERTIFICATE-----
+MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
+iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
+cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
+BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
+MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
+BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
+aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
+dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
+3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
+tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
+Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
+VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
+79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
+c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
+Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
+c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
+UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
+Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
+BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
+A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
+Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
+VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
+ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
+8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
+iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
+Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
+XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
+qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
+VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
+L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
+jjxDah2nGN59PRbxYvnKkKj9
+-----END CERTIFICATE-----
+
+# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network
+# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network
+# Label: "USERTrust ECC Certification Authority"
+# Serial: 123013823720199481456569720443997572134
+# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1
+# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0
+# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a
+-----BEGIN CERTIFICATE-----
+MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
+MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
+eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
+JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx
+MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
+Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg
+VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm
+aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo
+I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng
+o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G
+A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB
+zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW
+RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5
+# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5
+# Label: "GlobalSign ECC Root CA - R5"
+# Serial: 32785792099990507226680698011560947931244
+# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08
+# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa
+# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24
+-----BEGIN CERTIFICATE-----
+MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
+MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
+bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
+DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
+QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc
+8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke
+hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI
+KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
+515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO
+xwy8p2Fp8fc74SrL+SvzZpA3
+-----END CERTIFICATE-----
+
+# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust
+# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust
+# Label: "IdenTrust Commercial Root CA 1"
+# Serial: 13298821034946342390520003877796839426
+# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7
+# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25
+# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu
+VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw
+MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw
+JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT
+3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU
++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp
+S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1
+bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi
+T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL
+vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK
+Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK
+dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT
+c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv
+l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N
+iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
+/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD
+ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
+6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt
+LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93
+nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3
++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK
+W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT
+AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq
+l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG
+4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ
+mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A
+7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H
+-----END CERTIFICATE-----
+
+# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust
+# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust
+# Label: "IdenTrust Public Sector Root CA 1"
+# Serial: 13298821034946342390521976156843933698
+# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba
+# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd
+# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f
+-----BEGIN CERTIFICATE-----
+MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu
+VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN
+MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0
+MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7
+ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy
+RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS
+bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF
+/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R
+3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw
+EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy
+9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V
+GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ
+2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV
+WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD
+W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN
+AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
+t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV
+DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9
+TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G
+lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW
+mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df
+WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5
++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ
+tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA
+GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv
+8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c
+-----END CERTIFICATE-----
+
+# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority
+# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority
+# Label: "CFCA EV ROOT"
+# Serial: 407555286
+# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30
+# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83
+# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd
+-----BEGIN CERTIFICATE-----
+MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD
+TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y
+aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx
+MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j
+aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP
+T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03
+sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL
+TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5
+/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp
+7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz
+EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt
+hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP
+a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot
+aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg
+TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV
+PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv
+cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL
+tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd
+BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
+ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT
+ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL
+jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS
+ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy
+P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19
+xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d
+Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN
+5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe
+/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z
+AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
+5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
+-----END CERTIFICATE-----
+
+# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed
+# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed
+# Label: "OISTE WISeKey Global Root GB CA"
+# Serial: 157768595616588414422159278966750757568
+# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d
+# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed
+# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6
+-----BEGIN CERTIFICATE-----
+MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt
+MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg
+Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i
+YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x
+CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG
+b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh
+bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3
+HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx
+WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX
+1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk
+u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P
+99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r
+M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB
+BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh
+cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5
+gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO
+ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf
+aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic
+Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
+-----END CERTIFICATE-----
+
+# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A.
+# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A.
+# Label: "SZAFIR ROOT CA2"
+# Serial: 357043034767186914217277344587386743377558296292
+# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99
+# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de
+# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe
+-----BEGIN CERTIFICATE-----
+MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL
+BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6
+ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw
+NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L
+cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg
+Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN
+QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT
+3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw
+3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6
+3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5
+BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN
+XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
+AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF
+AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw
+8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG
+nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP
+oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy
+d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg
+LvWpCz/UXeHPhJ/iGcJfitYgHuNztw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority
+# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority
+# Label: "Certum Trusted Network CA 2"
+# Serial: 44979900017204383099463764357512596969
+# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2
+# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92
+# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04
+-----BEGIN CERTIFICATE-----
+MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB
+gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu
+QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG
+A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz
+OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ
+VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3
+b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA
+DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn
+0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB
+OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE
+fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E
+Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m
+o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i
+sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW
+OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez
+Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS
+adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n
+3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
+AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC
+AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ
+F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf
+CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29
+XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm
+djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/
+WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb
+AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq
+P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko
+b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj
+XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P
+5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi
+DrW5viSP
+-----END CERTIFICATE-----
+
+# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
+# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
+# Label: "Hellenic Academic and Research Institutions RootCA 2015"
+# Serial: 0
+# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce
+# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6
+# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36
+-----BEGIN CERTIFICATE-----
+MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix
+DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k
+IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT
+N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v
+dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG
+A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh
+ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx
+QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
+dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
+AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA
+4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0
+AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10
+4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C
+ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV
+9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD
+gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6
+Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq
+NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko
+LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc
+Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV
+HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd
+ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I
+XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI
+M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot
+9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V
+Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea
+j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh
+X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ
+l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf
+bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4
+pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK
+e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0
+vm9qp/UsQu0yrbYhnr68
+-----END CERTIFICATE-----
+
+# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
+# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority
+# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015"
+# Serial: 0
+# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef
+# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66
+# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33
+-----BEGIN CERTIFICATE-----
+MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN
+BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
+c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl
+bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv
+b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ
+BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj
+YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5
+MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0
+dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg
+QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa
+jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC
+MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi
+C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep
+lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof
+TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
+-----END CERTIFICATE-----
+
+# Issuer: CN=ISRG Root X1 O=Internet Security Research Group
+# Subject: CN=ISRG Root X1 O=Internet Security Research Group
+# Label: "ISRG Root X1"
+# Serial: 172886928669790476064670243504169061120
+# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e
+# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8
+# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6
+-----BEGIN CERTIFICATE-----
+MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
+WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
+ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
+MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
+h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
+0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
+A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
+T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
+B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
+B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
+KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
+OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
+jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
+qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
+rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
+HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
+hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
+ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
+3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
+NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
+ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
+TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
+jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
+oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
+4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
+mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
+emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
+-----END CERTIFICATE-----
+
+# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM
+# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM
+# Label: "AC RAIZ FNMT-RCM"
+# Serial: 485876308206448804701554682760554759
+# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d
+# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20
+# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa
+-----BEGIN CERTIFICATE-----
+MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx
+CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ
+WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ
+BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG
+Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/
+yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf
+BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz
+WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF
+tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z
+374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC
+IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL
+mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7
+wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS
+MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2
+ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet
+UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw
+AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H
+YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3
+LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD
+nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1
+RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM
+LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf
+77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N
+JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm
+fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp
+6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp
+1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B
+9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok
+RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv
+uu8wd+RU4riEmViAqhOLUTpPSPaLtrM=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Amazon Root CA 1 O=Amazon
+# Subject: CN=Amazon Root CA 1 O=Amazon
+# Label: "Amazon Root CA 1"
+# Serial: 143266978916655856878034712317230054538369994
+# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6
+# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16
+# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e
+-----BEGIN CERTIFICATE-----
+MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
+ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
+b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL
+MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
+b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj
+ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM
+9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw
+IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6
+VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L
+93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm
+jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
+AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA
+A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI
+U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs
+N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv
+o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU
+5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy
+rqXRfboQnoZsG4q5WTP468SQvvG5
+-----END CERTIFICATE-----
+
+# Issuer: CN=Amazon Root CA 2 O=Amazon
+# Subject: CN=Amazon Root CA 2 O=Amazon
+# Label: "Amazon Root CA 2"
+# Serial: 143266982885963551818349160658925006970653239
+# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66
+# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a
+# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4
+-----BEGIN CERTIFICATE-----
+MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF
+ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6
+b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL
+MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv
+b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK
+gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ
+W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg
+1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K
+8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r
+2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me
+z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR
+8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj
+mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz
+7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6
++XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI
+0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB
+Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm
+UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2
+LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY
++gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS
+k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl
+7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm
+btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl
+urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+
+fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63
+n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE
+76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H
+9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT
+4PsJYGw=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Amazon Root CA 3 O=Amazon
+# Subject: CN=Amazon Root CA 3 O=Amazon
+# Label: "Amazon Root CA 3"
+# Serial: 143266986699090766294700635381230934788665930
+# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87
+# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e
+# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4
+-----BEGIN CERTIFICATE-----
+MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5
+MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
+Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
+A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
+Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl
+ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr
+ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr
+BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM
+YyRIHN8wfdVoOw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Amazon Root CA 4 O=Amazon
+# Subject: CN=Amazon Root CA 4 O=Amazon
+# Label: "Amazon Root CA 4"
+# Serial: 143266989758080763974105200630763877849284878
+# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd
+# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be
+# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92
+-----BEGIN CERTIFICATE-----
+MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5
+MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g
+Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG
+A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg
+Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi
+9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk
+M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB
+/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB
+MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw
+CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW
+1KyLa2tJElMzrdfkviT8tQp21KW8EA==
+-----END CERTIFICATE-----
+
+# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM
+# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM
+# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1"
+# Serial: 1
+# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49
+# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca
+# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16
+-----BEGIN CERTIFICATE-----
+MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx
+GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp
+bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w
+KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0
+BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy
+dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG
+EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll
+IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU
+QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT
+TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg
+LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7
+a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr
+LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr
+N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X
+YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/
+iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f
+AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH
+V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh
+AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf
+IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4
+lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c
+8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf
+lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM=
+-----END CERTIFICATE-----
+
+# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD.
+# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD.
+# Label: "GDCA TrustAUTH R5 ROOT"
+# Serial: 9009899650740120186
+# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4
+# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4
+# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93
+-----BEGIN CERTIFICATE-----
+MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE
+BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ
+IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0
+MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV
+BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w
+HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj
+Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj
+TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u
+KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj
+qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm
+MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12
+ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP
+zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk
+L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC
+jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA
+HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC
+AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg
+p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm
+DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5
+COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry
+L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf
+JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg
+IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io
+2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV
+09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ
+XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq
+T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe
+MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g==
+-----END CERTIFICATE-----
+
+# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation
+# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation
+# Label: "SSL.com Root Certification Authority RSA"
+# Serial: 8875640296558310041
+# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29
+# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb
+# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69
+-----BEGIN CERTIFICATE-----
+MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE
+BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK
+DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz
+OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv
+dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv
+bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R
+xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX
+qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC
+C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3
+6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh
+/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF
+YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E
+JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc
+US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8
+ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm
++Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi
+M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV
+HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G
+A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV
+cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc
+Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs
+PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/
+q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0
+cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr
+a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I
+H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y
+K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu
+nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf
+oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY
+Ic2wBlX7Jz9TkHCpBB5XJ7k=
+-----END CERTIFICATE-----
+
+# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation
+# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation
+# Label: "SSL.com Root Certification Authority ECC"
+# Serial: 8495723813297216424
+# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e
+# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a
+# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65
+-----BEGIN CERTIFICATE-----
+MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC
+VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
+U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0
+aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz
+WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0
+b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS
+b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI
+7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg
+CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud
+EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD
+VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T
+kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+
+gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
+-----END CERTIFICATE-----
+
+# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation
+# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation
+# Label: "SSL.com EV Root Certification Authority RSA R2"
+# Serial: 6248227494352943350
+# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95
+# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a
+# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c
+-----BEGIN CERTIFICATE-----
+MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV
+BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE
+CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy
+dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy
+MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G
+A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD
+DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq
+M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf
+OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa
+4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9
+HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR
+aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA
+b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ
+Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV
+PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO
+pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu
+UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY
+MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV
+HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4
+9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW
+s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5
+Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg
+cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM
+79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz
+/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt
+ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm
+Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK
+QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ
+w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi
+S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07
+mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w==
+-----END CERTIFICATE-----
+
+# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation
+# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation
+# Label: "SSL.com EV Root Certification Authority ECC"
+# Serial: 3182246526754555285
+# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90
+# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d
+# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8
+-----BEGIN CERTIFICATE-----
+MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC
+VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
+U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp
+Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx
+NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv
+dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv
+bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49
+AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA
+VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku
+WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP
+MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX
+5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ
+ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg
+h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
+# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
+# Label: "GlobalSign Root CA - R6"
+# Serial: 1417766617973444989252670301619537
+# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae
+# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1
+# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69
+-----BEGIN CERTIFICATE-----
+MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
+MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
+bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
+MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET
+MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI
+xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k
+ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD
+aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw
+LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw
+1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX
+k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2
+SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h
+bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n
+WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY
+rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce
+MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
+AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu
+bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
+nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt
+Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61
+55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj
+vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf
+cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz
+oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp
+nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs
+pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v
+JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
+8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4
+5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
+-----END CERTIFICATE-----
+
+# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
+# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
+# Label: "OISTE WISeKey Global Root GC CA"
+# Serial: 44084345621038548146064804565436152554
+# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23
+# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31
+# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d
+-----BEGIN CERTIFICATE-----
+MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw
+CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91
+bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg
+Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ
+BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu
+ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS
+b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni
+eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W
+p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T
+rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
+57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg
+Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
+-----END CERTIFICATE-----
+
+# Issuer: CN=UCA Global G2 Root O=UniTrust
+# Subject: CN=UCA Global G2 Root O=UniTrust
+# Label: "UCA Global G2 Root"
+# Serial: 124779693093741543919145257850076631279
+# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8
+# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a
+# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c
+-----BEGIN CERTIFICATE-----
+MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9
+MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH
+bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x
+CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds
+b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr
+b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9
+kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm
+VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R
+VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc
+C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj
+tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY
+D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv
+j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl
+NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6
+iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP
+O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/
+BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV
+ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj
+L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
+1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl
+1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU
+b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV
+PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj
+y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb
+EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg
+DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI
++Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy
+YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX
+UB+K+wb1whnw0A==
+-----END CERTIFICATE-----
+
+# Issuer: CN=UCA Extended Validation Root O=UniTrust
+# Subject: CN=UCA Extended Validation Root O=UniTrust
+# Label: "UCA Extended Validation Root"
+# Serial: 106100277556486529736699587978573607008
+# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2
+# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a
+# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24
+-----BEGIN CERTIFICATE-----
+MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH
+MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF
+eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx
+MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV
+BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog
+D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS
+sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop
+O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk
+sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi
+c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj
+VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz
+KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/
+TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G
+sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs
+1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD
+fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T
+AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN
+l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
+ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ
+VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5
+c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp
+4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s
+t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj
+2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO
+vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C
+xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx
+cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM
+fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
+# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
+# Label: "Certigna Root CA"
+# Serial: 269714418870597844693661054334862075617
+# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77
+# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43
+# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68
+-----BEGIN CERTIFICATE-----
+MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw
+WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw
+MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x
+MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD
+VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX
+BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
+ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO
+ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M
+CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu
+I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm
+TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh
+C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf
+ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz
+IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT
+Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k
+JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5
+hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB
+GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
+FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
+1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov
+L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo
+dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr
+aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq
+hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L
+6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG
+HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6
+0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB
+lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi
+o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1
+gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v
+faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63
+Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh
+jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
+3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
+-----END CERTIFICATE-----
+
+# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
+# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
+# Label: "emSign Root CA - G1"
+# Serial: 235931866688319308814040
+# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac
+# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c
+# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67
+-----BEGIN CERTIFICATE-----
+MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD
+VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU
+ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH
+MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO
+MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv
+Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN
+BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz
+f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO
+8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq
+d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM
+tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt
+Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB
+o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD
+AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x
+PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM
+wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d
+GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH
+6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby
+RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
+iN66zB+Afko=
+-----END CERTIFICATE-----
+
+# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
+# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
+# Label: "emSign ECC Root CA - G3"
+# Serial: 287880440101571086945156
+# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40
+# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1
+# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b
+-----BEGIN CERTIFICATE-----
+MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
+EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
+bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
+RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
+TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
+b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
+djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
+WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
+fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
+zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
+hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
+CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
++JbNR6iC8hZVdyR+EhCVBCyj
+-----END CERTIFICATE-----
+
+# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
+# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
+# Label: "emSign Root CA - C1"
+# Serial: 825510296613316004955058
+# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68
+# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01
+# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f
+-----BEGIN CERTIFICATE-----
+MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG
+A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg
+SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw
+MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
+biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v
+dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ
+BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ
+HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH
+3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH
+GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c
+xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1
+aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq
+TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
+BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87
+/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4
+kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG
+YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT
++xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo
+WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
+-----END CERTIFICATE-----
+
+# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
+# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
+# Label: "emSign ECC Root CA - C3"
+# Serial: 582948710642506000014504
+# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5
+# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66
+# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3
+-----BEGIN CERTIFICATE-----
+MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG
+EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx
+IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw
+MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
+biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND
+IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci
+MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti
+sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O
+BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
+Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c
+3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J
+0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post
+# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post
+# Label: "Hongkong Post Root CA 3"
+# Serial: 46170865288971385588281144162979347873371282084
+# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0
+# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02
+# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6
+-----BEGIN CERTIFICATE-----
+MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL
+BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ
+SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n
+a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5
+NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT
+CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u
+Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
+AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO
+dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI
+VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV
+9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY
+2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY
+vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt
+bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb
+x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+
+l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK
+TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj
+Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
+i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw
+DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG
+7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk
+MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr
+gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk
+GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS
+3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm
+Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+
+l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c
+JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP
+L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa
+LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG
+mpv0
+-----END CERTIFICATE-----
+
+# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation
+# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation
+# Label: "Microsoft ECC Root Certificate Authority 2017"
+# Serial: 136839042543790627607696632466672567020
+# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67
+# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5
+# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02
+-----BEGIN CERTIFICATE-----
+MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw
+CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD
+VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw
+MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV
+UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy
+b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq
+hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR
+ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb
+hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E
+BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3
+FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV
+L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB
+iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation
+# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation
+# Label: "Microsoft RSA Root Certificate Authority 2017"
+# Serial: 40975477897264996090493496164228220339
+# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47
+# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74
+# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0
+-----BEGIN CERTIFICATE-----
+MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl
+MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw
+NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
+IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG
+EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N
+aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ
+Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0
+ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1
+HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm
+gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ
+jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc
+aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG
+YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6
+W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K
+UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH
++FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q
+W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/
+BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC
+NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC
+LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC
+gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6
+tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh
+SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2
+TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3
+pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR
+xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp
+GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9
+dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN
+AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB
+RA+GsCyRxj3qrg+E
+-----END CERTIFICATE-----
+
+# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd.
+# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd.
+# Label: "e-Szigno Root CA 2017"
+# Serial: 411379200276854331539784714
+# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98
+# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1
+# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99
+-----BEGIN CERTIFICATE-----
+MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV
+BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk
+LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv
+b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ
+BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg
+THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v
+IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv
+xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H
+Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
+A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB
+eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo
+jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ
++efcMQ==
+-----END CERTIFICATE-----
+
+# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2
+# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2
+# Label: "certSIGN Root CA G2"
+# Serial: 313609486401300475190
+# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7
+# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32
+# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05
+-----BEGIN CERTIFICATE-----
+MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV
+BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g
+Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ
+BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ
+R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF
+dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw
+vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ
+uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp
+n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs
+cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW
+xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P
+rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF
+DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx
+DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy
+LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C
+eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB
+/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ
+d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq
+kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC
+b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl
+qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0
+OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c
+NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk
+ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO
+pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj
+03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk
+PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE
+1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX
+QRBdJ3NghVdJIgc=
+-----END CERTIFICATE-----
+
+# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp.
+# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp.
+# Label: "NAVER Global Root Certification Authority"
+# Serial: 9013692873798656336226253319739695165984492813
+# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b
+# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1
+# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65
+-----BEGIN CERTIFICATE-----
+MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM
+BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG
+T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0
+aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx
+CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD
+b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB
+dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA
+iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH
+38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE
+HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz
+kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP
+szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq
+vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf
+nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG
+YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo
+0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a
+CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K
+AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I
+36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB
+Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN
+qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj
+cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm
++LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL
+hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe
+lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7
+p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8
+piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR
+LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX
+5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO
+dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul
+9XXeifdy
+-----END CERTIFICATE-----
+
+# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres
+# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres
+# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS"
+# Serial: 131542671362353147877283741781055151509
+# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb
+# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a
+# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb
+-----BEGIN CERTIFICATE-----
+MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw
+CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw
+FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S
+Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5
+MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL
+DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS
+QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH
+sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK
+Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu
+SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC
+MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy
+v+c=
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa
+# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa
+# Label: "GlobalSign Root R46"
+# Serial: 1552617688466950547958867513931858518042577
+# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef
+# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90
+# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9
+-----BEGIN CERTIFICATE-----
+MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA
+MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD
+VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy
+MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt
+c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ
+OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG
+vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud
+316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo
+0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE
+y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF
+zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE
++cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN
+I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs
+x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa
+ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC
+4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4
+7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg
+JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti
+2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk
+pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF
+FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt
+rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk
+ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5
+u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP
+4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6
+N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3
+vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa
+# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa
+# Label: "GlobalSign Root E46"
+# Serial: 1552617690338932563915843282459653771421763
+# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f
+# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84
+# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58
+-----BEGIN CERTIFICATE-----
+MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx
+CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD
+ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw
+MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex
+HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq
+R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd
+yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
+DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ
+7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8
++RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A=
+-----END CERTIFICATE-----
+
+# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz
+# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz
+# Label: "ANF Secure Server Root CA"
+# Serial: 996390341000653745
+# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96
+# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74
+# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99
+-----BEGIN CERTIFICATE-----
+MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV
+BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk
+YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV
+BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN
+MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF
+UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD
+VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v
+dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj
+cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q
+yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH
+2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX
+H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL
+zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR
+p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz
+W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/
+SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn
+LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3
+n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B
+u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj
+o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO
+BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
+AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L
+9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej
+rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK
+pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0
+vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq
+OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ
+/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9
+2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI
++PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2
+MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo
+tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
+# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
+# Label: "Certum EC-384 CA"
+# Serial: 160250656287871593594747141429395092468
+# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1
+# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed
+# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6
+-----BEGIN CERTIFICATE-----
+MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw
+CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw
+JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT
+EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0
+WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT
+LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX
+BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE
+KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm
+Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj
+QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8
+EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J
+UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn
+nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
+# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority
+# Label: "Certum Trusted Root CA"
+# Serial: 40870380103424195783807378461123655149
+# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29
+# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5
+# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd
+-----BEGIN CERTIFICATE-----
+MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6
+MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu
+MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV
+BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw
+MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg
+U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo
+b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG
+SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ
+n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q
+p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq
+NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF
+8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3
+HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa
+mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi
+7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF
+ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P
+qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ
+v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6
+Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1
+vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD
+ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4
+WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo
+zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR
+5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ
+GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf
+5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq
+0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D
+P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM
+qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP
+0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf
+E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb
+-----END CERTIFICATE-----
+
+# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique
+# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique
+# Label: "TunTrust Root CA"
+# Serial: 108534058042236574382096126452369648152337120275
+# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4
+# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb
+# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41
+-----BEGIN CERTIFICATE-----
+MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL
+BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg
+Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv
+b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG
+EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u
+IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ
+n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd
+2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF
+VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ
+GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF
+li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU
+r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2
+eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb
+MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg
+jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB
+7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW
+5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE
+ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0
+90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z
+xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu
+QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4
+FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH
+22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP
+xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn
+dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5
+Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b
+nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ
+CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH
+u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj
+d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o=
+-----END CERTIFICATE-----
+
+# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Label: "HARICA TLS RSA Root CA 2021"
+# Serial: 76817823531813593706434026085292783742
+# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91
+# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d
+# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d
+-----BEGIN CERTIFICATE-----
+MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs
+MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
+c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg
+Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL
+MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl
+YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv
+b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l
+mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE
+4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv
+a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M
+pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw
+Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b
+LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY
+AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB
+AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq
+E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr
+W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ
+CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF
+MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE
+AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU
+X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3
+f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja
+H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP
+JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P
+zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt
+jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0
+/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT
+BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79
+aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW
+xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU
+63ZTGI0RmLo=
+-----END CERTIFICATE-----
+
+# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA
+# Label: "HARICA TLS ECC Root CA 2021"
+# Serial: 137515985548005187474074462014555733966
+# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0
+# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48
+# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01
+-----BEGIN CERTIFICATE-----
+MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw
+CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh
+cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v
+dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG
+A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj
+aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg
+Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7
+KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y
+STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw
+AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD
+AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw
+SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN
+nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps
+-----END CERTIFICATE-----
+
+# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068
+# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068
+# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068"
+# Serial: 1977337328857672817
+# MD5 Fingerprint: 4e:6e:9b:54:4c:ca:b7:fa:48:e4:90:b1:15:4b:1c:a3
+# SHA1 Fingerprint: 0b:be:c2:27:22:49:cb:39:aa:db:35:5c:53:e3:8c:ae:78:ff:b6:fe
+# SHA256 Fingerprint: 57:de:05:83:ef:d2:b2:6e:03:61:da:99:da:9d:f4:64:8d:ef:7e:e8:44:1c:3b:72:8a:fa:9b:cd:e0:f9:b2:6a
+-----BEGIN CERTIFICATE-----
+MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE
+BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
+cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1
+MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
+Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
+thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
+cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
+L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
+NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
+X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
+m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
+Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
+EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
+KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
+6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
+OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc
+tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd
+IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j
+b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC
+AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw
+ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m
+iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF
+Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ
+hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P
+Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE
+EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV
+1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t
+CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR
+5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw
+f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9
+ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK
+GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV
+-----END CERTIFICATE-----
+
+# Issuer: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd.
+# Subject: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd.
+# Label: "vTrus ECC Root CA"
+# Serial: 630369271402956006249506845124680065938238527194
+# MD5 Fingerprint: de:4b:c1:f5:52:8c:9b:43:e1:3e:8f:55:54:17:8d:85
+# SHA1 Fingerprint: f6:9c:db:b0:fc:f6:02:13:b6:52:32:a6:a3:91:3f:16:70:da:c3:e1
+# SHA256 Fingerprint: 30:fb:ba:2c:32:23:8e:2a:98:54:7a:f9:79:31:e5:50:42:8b:9b:3f:1c:8e:eb:66:33:dc:fa:86:c5:b2:7d:d3
+-----BEGIN CERTIFICATE-----
+MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw
+RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY
+BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz
+MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u
+LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF
+K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0
+v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd
+e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD
+VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw
+V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA
+AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG
+GJTO
+-----END CERTIFICATE-----
+
+# Issuer: CN=vTrus Root CA O=iTrusChina Co.,Ltd.
+# Subject: CN=vTrus Root CA O=iTrusChina Co.,Ltd.
+# Label: "vTrus Root CA"
+# Serial: 387574501246983434957692974888460947164905180485
+# MD5 Fingerprint: b8:c9:37:df:fa:6b:31:84:64:c5:ea:11:6a:1b:75:fc
+# SHA1 Fingerprint: 84:1a:69:fb:f5:cd:1a:25:34:13:3d:e3:f8:fc:b8:99:d0:c9:14:b7
+# SHA256 Fingerprint: 8a:71:de:65:59:33:6f:42:6c:26:e5:38:80:d0:0d:88:a1:8d:a4:c6:a9:1f:0d:cb:61:94:e2:06:c5:c9:63:87
+-----BEGIN CERTIFICATE-----
+MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL
+BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x
+FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx
+MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s
+THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD
+ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc
+IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU
+AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+
+GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9
+8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH
+flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt
+J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim
+0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN
+pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ
+UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW
+OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB
+AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E
+BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet
+8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd
+nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j
+bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM
+Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv
+TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS
+S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr
+I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9
+b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB
+UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P
+Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven
+sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s=
+-----END CERTIFICATE-----
+
+# Issuer: CN=ISRG Root X2 O=Internet Security Research Group
+# Subject: CN=ISRG Root X2 O=Internet Security Research Group
+# Label: "ISRG Root X2"
+# Serial: 87493402998870891108772069816698636114
+# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5
+# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af
+# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70
+-----BEGIN CERTIFICATE-----
+MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
+CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
+R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
+MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
+ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
+EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
++1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
+ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
+AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
+zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
+tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
+/q4AaOeMSQ+2b1tbFfLn
+-----END CERTIFICATE-----
+
+# Issuer: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd.
+# Subject: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd.
+# Label: "HiPKI Root CA - G1"
+# Serial: 60966262342023497858655262305426234976
+# MD5 Fingerprint: 69:45:df:16:65:4b:e8:68:9a:8f:76:5f:ff:80:9e:d3
+# SHA1 Fingerprint: 6a:92:e4:a8:ee:1b:ec:96:45:37:e3:29:57:49:cd:96:e3:e5:d2:60
+# SHA256 Fingerprint: f0:15:ce:3c:c2:39:bf:ef:06:4b:e9:f1:d2:c4:17:e1:a0:26:4a:0a:94:be:1f:0c:8d:12:18:64:eb:69:49:cc
+-----BEGIN CERTIFICATE-----
+MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP
+MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
+ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa
+Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3
+YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw
+qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv
+Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6
+lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz
+Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ
+KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK
+FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj
+HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr
+y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ
+/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM
+a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6
+fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG
+SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi
+7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc
+SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza
+ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc
+XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg
+iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho
+L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF
+Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr
+kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+
+vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU
+YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4
+# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4
+# Label: "GlobalSign ECC Root CA - R4"
+# Serial: 159662223612894884239637590694
+# MD5 Fingerprint: 26:29:f8:6d:e1:88:bf:a2:65:7f:aa:c4:cd:0f:7f:fc
+# SHA1 Fingerprint: 6b:a0:b0:98:e1:71:ef:5a:ad:fe:48:15:80:77:10:f4:bd:6f:0b:28
+# SHA256 Fingerprint: b0:85:d7:0b:96:4f:19:1a:73:e4:af:0d:54:ae:7a:0e:07:aa:fd:af:9b:71:dd:08:62:13:8a:b7:32:5a:24:a2
+-----BEGIN CERTIFICATE-----
+MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD
+VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh
+bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw
+MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g
+UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT
+BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx
+uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV
+HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/
++wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147
+bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm
+-----END CERTIFICATE-----
+
+# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
+# Subject: CN=GTS Root R1 O=Google Trust Services LLC
+# Label: "GTS Root R1"
+# Serial: 159662320309726417404178440727
+# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40
+# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a
+# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf
+-----BEGIN CERTIFICATE-----
+MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
+CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
+MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
+MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
+Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
+A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
+27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
+Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
+TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
+qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
+szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
+Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
+MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
+wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
+aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
+VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
+AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
+FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
+C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
+QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
+h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
+7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
+ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
+MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
+Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
+6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
+0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
+2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
+bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
+-----END CERTIFICATE-----
+
+# Issuer: CN=GTS Root R3 O=Google Trust Services LLC
+# Subject: CN=GTS Root R3 O=Google Trust Services LLC
+# Label: "GTS Root R3"
+# Serial: 159662495401136852707857743206
+# MD5 Fingerprint: 3e:e7:9d:58:02:94:46:51:94:e5:e0:22:4a:8b:e7:73
+# SHA1 Fingerprint: ed:e5:71:80:2b:c8:92:b9:5b:83:3c:d2:32:68:3f:09:cd:a0:1e:46
+# SHA256 Fingerprint: 34:d8:a7:3e:e2:08:d9:bc:db:0d:95:65:20:93:4b:4e:40:e6:94:82:59:6e:8b:6f:73:c8:42:6b:01:0a:6f:48
+-----BEGIN CERTIFICATE-----
+MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD
+VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
+A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
+WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
+IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G
+jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2
+4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7
+VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm
+ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X
+-----END CERTIFICATE-----
+
+# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
+# Subject: CN=GTS Root R4 O=Google Trust Services LLC
+# Label: "GTS Root R4"
+# Serial: 159662532700760215368942768210
+# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8
+# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47
+# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d
+-----BEGIN CERTIFICATE-----
+MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD
+VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG
+A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw
+WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz
+IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi
+QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR
+HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
+BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D
+9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8
+p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD
+-----END CERTIFICATE-----
+
+# Issuer: CN=Telia Root CA v2 O=Telia Finland Oyj
+# Subject: CN=Telia Root CA v2 O=Telia Finland Oyj
+# Label: "Telia Root CA v2"
+# Serial: 7288924052977061235122729490515358
+# MD5 Fingerprint: 0e:8f:ac:aa:82:df:85:b1:f4:dc:10:1c:fc:99:d9:48
+# SHA1 Fingerprint: b9:99:cd:d1:73:50:8a:c4:47:05:08:9c:8c:88:fb:be:a0:2b:40:cd
+# SHA256 Fingerprint: 24:2b:69:74:2f:cb:1e:5b:2a:bf:98:89:8b:94:57:21:87:54:4e:5b:4d:99:11:78:65:73:62:1f:6a:74:b8:2c
+-----BEGIN CERTIFICATE-----
+MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx
+CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE
+AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1
+NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ
+MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
+ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq
+AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9
+vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9
+lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD
+n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT
+7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o
+6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC
+TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6
+WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R
+DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI
+pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj
+YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy
+rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw
+AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ
+8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi
+0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM
+A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS
+SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K
+TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF
+6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er
+3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt
+Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT
+VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW
+ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA
+rBPuUBQemMc=
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH
+# Subject: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH
+# Label: "D-TRUST BR Root CA 1 2020"
+# Serial: 165870826978392376648679885835942448534
+# MD5 Fingerprint: b5:aa:4b:d5:ed:f7:e3:55:2e:8f:72:0a:f3:75:b8:ed
+# SHA1 Fingerprint: 1f:5b:98:f0:e3:b5:f7:74:3c:ed:e6:b0:36:7d:32:cd:f4:09:41:67
+# SHA256 Fingerprint: e5:9a:aa:81:60:09:c2:2b:ff:5b:25:ba:d3:7d:f3:06:f0:49:79:7c:1f:81:d8:5a:b0:89:e6:57:bd:8f:00:44
+-----BEGIN CERTIFICATE-----
+MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw
+CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
+VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5
+NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG
+A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS
+zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0
+QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/
+VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g
+PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf
+Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l
+dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1
+c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO
+PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW
+wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV
+dWNbFJWcHwHP2NVypw87
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH
+# Subject: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH
+# Label: "D-TRUST EV Root CA 1 2020"
+# Serial: 126288379621884218666039612629459926992
+# MD5 Fingerprint: 8c:2d:9d:70:9f:48:99:11:06:11:fb:e9:cb:30:c0:6e
+# SHA1 Fingerprint: 61:db:8c:21:59:69:03:90:d8:7c:9c:12:86:54:cf:9d:3d:f4:dd:07
+# SHA256 Fingerprint: 08:17:0d:1a:a3:64:53:90:1a:2f:95:92:45:e3:47:db:0c:8d:37:ab:aa:bc:56:b8:1a:a1:00:dc:95:89:70:db
+-----BEGIN CERTIFICATE-----
+MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw
+CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS
+VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5
+NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG
+A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB
+BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC
+/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD
+wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3
+OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g
+PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf
+Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l
+dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1
+c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO
+PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA
+y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb
+gfM0agPnIjhQW+0ZT0MW
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc.
+# Subject: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc.
+# Label: "DigiCert TLS ECC P384 Root G5"
+# Serial: 13129116028163249804115411775095713523
+# MD5 Fingerprint: d3:71:04:6a:43:1c:db:a6:59:e1:a8:a3:aa:c5:71:ed
+# SHA1 Fingerprint: 17:f3:de:5e:9f:0f:19:e9:8e:f6:1f:32:26:6e:20:c4:07:ae:30:ee
+# SHA256 Fingerprint: 01:8e:13:f0:77:25:32:cf:80:9b:d1:b1:72:81:86:72:83:fc:48:c6:e1:3b:e9:c6:98:12:85:4a:49:0c:1b:05
+-----BEGIN CERTIFICATE-----
+MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw
+CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp
+Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2
+MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
+bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG
+ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS
+7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp
+0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS
+B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49
+BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ
+LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4
+DXZDjC5Ty3zfDBeWUA==
+-----END CERTIFICATE-----
+
+# Issuer: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc.
+# Subject: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc.
+# Label: "DigiCert TLS RSA4096 Root G5"
+# Serial: 11930366277458970227240571539258396554
+# MD5 Fingerprint: ac:fe:f7:34:96:a9:f2:b3:b4:12:4b:e4:27:41:6f:e1
+# SHA1 Fingerprint: a7:88:49:dc:5d:7c:75:8c:8c:de:39:98:56:b3:aa:d0:b2:a5:71:35
+# SHA256 Fingerprint: 37:1a:00:dc:05:33:b3:72:1a:7e:eb:40:e8:41:9e:70:79:9d:2b:0a:0f:2c:1d:80:69:31:65:f7:ce:c4:ad:75
+-----BEGIN CERTIFICATE-----
+MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN
+MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT
+HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN
+NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs
+IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi
+MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+
+ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0
+2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp
+wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM
+pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD
+nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po
+sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx
+Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd
+Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX
+KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe
+XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL
+tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv
+TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN
+AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw
+GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H
+PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF
+O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ
+REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik
+AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv
+/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+
+p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw
+MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF
+qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK
+ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certainly Root R1 O=Certainly
+# Subject: CN=Certainly Root R1 O=Certainly
+# Label: "Certainly Root R1"
+# Serial: 188833316161142517227353805653483829216
+# MD5 Fingerprint: 07:70:d4:3e:82:87:a0:fa:33:36:13:f4:fa:33:e7:12
+# SHA1 Fingerprint: a0:50:ee:0f:28:71:f4:27:b2:12:6d:6f:50:96:25:ba:cc:86:42:af
+# SHA256 Fingerprint: 77:b8:2c:d8:64:4c:43:05:f7:ac:c5:cb:15:6b:45:67:50:04:03:3d:51:c6:0c:62:02:a8:e0:c3:34:67:d3:a0
+-----BEGIN CERTIFICATE-----
+MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw
+PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy
+dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9
+MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0
+YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2
+1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT
+vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed
+aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0
+1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5
+r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5
+cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ
+wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ
+6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA
+2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH
+Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR
+eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB
+/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u
+d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr
+PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d
+8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi
+1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd
+rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di
+taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7
+lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj
+yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn
+Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy
+yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n
+wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6
+OV+KmalBWQewLK8=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Certainly Root E1 O=Certainly
+# Subject: CN=Certainly Root E1 O=Certainly
+# Label: "Certainly Root E1"
+# Serial: 8168531406727139161245376702891150584
+# MD5 Fingerprint: 0a:9e:ca:cd:3e:52:50:c6:36:f3:4b:a3:ed:a7:53:e9
+# SHA1 Fingerprint: f9:e1:6d:dc:01:89:cf:d5:82:45:63:3e:c5:37:7d:c2:eb:93:6f:2b
+# SHA256 Fingerprint: b4:58:5f:22:e4:ac:75:6a:4e:86:12:a1:36:1c:5d:9d:03:1a:93:fd:84:fe:bb:77:8f:a3:06:8b:0f:c4:2d:c2
+-----BEGIN CERTIFICATE-----
+MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw
+CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu
+bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ
+BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s
+eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK
++IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2
+QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4
+hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm
+ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG
+BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR
+-----END CERTIFICATE-----
+
+# Issuer: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD.
+# Subject: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD.
+# Label: "Security Communication ECC RootCA1"
+# Serial: 15446673492073852651
+# MD5 Fingerprint: 7e:43:b0:92:68:ec:05:43:4c:98:ab:5d:35:2e:7e:86
+# SHA1 Fingerprint: b8:0e:26:a9:bf:d2:b2:3b:c0:ef:46:c9:ba:c7:bb:f6:1d:0d:41:41
+# SHA256 Fingerprint: e7:4f:bd:a5:5b:d5:64:c4:73:a3:6b:44:1a:a7:99:c8:a6:8e:07:74:40:e8:28:8b:9f:a1:e5:0e:4b:ba:ca:11
+-----BEGIN CERTIFICATE-----
+MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT
+AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD
+VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx
+NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT
+HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5
+IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi
+AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl
+dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK
+ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E
+BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu
+9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O
+be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k=
+-----END CERTIFICATE-----
+
+# Issuer: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY
+# Subject: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY
+# Label: "BJCA Global Root CA1"
+# Serial: 113562791157148395269083148143378328608
+# MD5 Fingerprint: 42:32:99:76:43:33:36:24:35:07:82:9b:28:f9:d0:90
+# SHA1 Fingerprint: d5:ec:8d:7b:4c:ba:79:f4:e7:e8:cb:9d:6b:ae:77:83:10:03:21:6a
+# SHA256 Fingerprint: f3:89:6f:88:fe:7c:0a:88:27:66:a7:fa:6a:d2:74:9f:b5:7a:7f:3e:98:fb:76:9c:1f:a7:b0:9c:2c:44:d5:ae
+-----BEGIN CERTIFICATE-----
+MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBU
+MQswCQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRI
+T1JJVFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAz
+MTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJF
+SUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2Jh
+bCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFmCL3Z
+xRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZ
+spDyRhySsTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O5
+58dnJCNPYwpj9mZ9S1WnP3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgR
+at7GGPZHOiJBhyL8xIkoVNiMpTAK+BcWyqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll
+5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRjeulumijWML3mG90Vr4Tq
+nMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNnMoH1V6XK
+V0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/
+pj+bOT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZO
+z2nxbkRs1CTqjSShGL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXn
+jSXWgXSHRtQpdaJCbPdzied9v3pKH9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+
+WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMBAAGjQjBAMB0GA1UdDgQWBBTF
+7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
+AwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4
+YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3Kli
+awLwQ8hOnThJdMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u
++2D2/VnGKhs/I0qUJDAnyIm860Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88
+X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuhTaRjAv04l5U/BXCga99igUOLtFkN
+SoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW4AB+dAb/OMRyHdOo
+P2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmpGQrI
++pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRz
+znfSxqxx4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9
+eVzYH6Eze9mCUAyTF6ps3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2
+YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4SSPfSKcOYKMryMguTjClPPGAyzQWWYezy
+r/6zcCwupvI=
+-----END CERTIFICATE-----
+
+# Issuer: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY
+# Subject: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY
+# Label: "BJCA Global Root CA2"
+# Serial: 58605626836079930195615843123109055211
+# MD5 Fingerprint: 5e:0a:f6:47:5f:a6:14:e8:11:01:95:3f:4d:01:eb:3c
+# SHA1 Fingerprint: f4:27:86:eb:6e:b8:6d:88:31:67:02:fb:ba:66:a4:53:00:aa:7a:a6
+# SHA256 Fingerprint: 57:4d:f6:93:1e:27:80:39:66:7b:72:0a:fd:c1:60:0f:c2:7e:b6:6d:d3:09:29:79:fb:73:85:64:87:21:28:82
+-----BEGIN CERTIFICATE-----
+MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQsw
+CQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJ
+VFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgy
+MVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJ
+TkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2JhbCBS
+b290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jlSR9B
+IgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK+
++kpRuDCK/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJK
+sVF/BvDRgh9Obl+rg/xI1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
+AgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA
+94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8gUXOQwKhbYdDFUDn9hf7B
+43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited
+# Subject: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited
+# Label: "Sectigo Public Server Authentication Root E46"
+# Serial: 88989738453351742415770396670917916916
+# MD5 Fingerprint: 28:23:f8:b2:98:5c:37:16:3b:3e:46:13:4e:b0:b3:01
+# SHA1 Fingerprint: ec:8a:39:6c:40:f0:2e:bc:42:75:d4:9f:ab:1c:1a:5b:67:be:d2:9a
+# SHA256 Fingerprint: c9:0f:26:f0:fb:1b:40:18:b2:22:27:51:9b:5c:a2:b5:3e:2c:a5:b3:be:5c:f1:8e:fe:1b:ef:47:38:0c:53:83
+-----BEGIN CERTIFICATE-----
+MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw
+CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T
+ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN
+MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG
+A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT
+ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA
+IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC
+WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+
+6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B
+Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa
+qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q
+4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited
+# Subject: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited
+# Label: "Sectigo Public Server Authentication Root R46"
+# Serial: 156256931880233212765902055439220583700
+# MD5 Fingerprint: 32:10:09:52:00:d5:7e:6c:43:df:15:c0:b1:16:93:e5
+# SHA1 Fingerprint: ad:98:f9:f3:e4:7d:75:3b:65:d4:82:b3:a4:52:17:bb:6e:f5:e4:38
+# SHA256 Fingerprint: 7b:b6:47:a6:2a:ee:ac:88:bf:25:7a:a5:22:d0:1f:fe:a3:95:e0:ab:45:c7:3f:93:f6:56:54:ec:38:f2:5a:06
+-----BEGIN CERTIFICATE-----
+MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf
+MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD
+Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw
+HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY
+MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp
+YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa
+ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz
+SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf
+iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X
+ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3
+IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS
+VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE
+SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu
++Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt
+8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L
+HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt
+zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P
+AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c
+mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ
+YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52
+gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA
+Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB
+JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX
+DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui
+TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5
+dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65
+LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp
+0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY
+QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL
+-----END CERTIFICATE-----
+
+# Issuer: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation
+# Subject: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation
+# Label: "SSL.com TLS RSA Root CA 2022"
+# Serial: 148535279242832292258835760425842727825
+# MD5 Fingerprint: d8:4e:c6:59:30:d8:fe:a0:d6:7a:5a:2c:2c:69:78:da
+# SHA1 Fingerprint: ec:2c:83:40:72:af:26:95:10:ff:0e:f2:03:ee:31:70:f6:78:9d:ca
+# SHA256 Fingerprint: 8f:af:7d:2e:2c:b4:70:9b:b8:e0:b3:36:66:bf:75:a5:dd:45:b5:de:48:0f:8e:a8:d4:bf:e6:be:bc:17:f2:ed
+-----BEGIN CERTIFICATE-----
+MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO
+MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD
+DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX
+DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw
+b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC
+AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP
+L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY
+t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins
+S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3
+PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO
+L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3
+R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w
+dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS
++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS
+d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG
+AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f
+gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j
+BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z
+NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt
+hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM
+QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf
+R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ
+DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW
+P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy
+lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq
+bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w
+AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q
+r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji
+Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU
+98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA=
+-----END CERTIFICATE-----
+
+# Issuer: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation
+# Subject: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation
+# Label: "SSL.com TLS ECC Root CA 2022"
+# Serial: 26605119622390491762507526719404364228
+# MD5 Fingerprint: 99:d7:5c:f1:51:36:cc:e9:ce:d9:19:2e:77:71:56:c5
+# SHA1 Fingerprint: 9f:5f:d9:1a:54:6d:f5:0c:71:f0:ee:7a:bd:17:49:98:84:73:e2:39
+# SHA256 Fingerprint: c3:2f:fd:9f:46:f9:36:d1:6c:36:73:99:09:59:43:4b:9a:d6:0a:af:bb:9e:7c:f3:36:54:f1:44:cc:1b:a1:43
+-----BEGIN CERTIFICATE-----
+MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw
+CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT
+U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2
+MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh
+dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG
+ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm
+acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN
+SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME
+GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW
+uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp
+15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN
+b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos
+# Subject: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos
+# Label: "Atos TrustedRoot Root CA ECC TLS 2021"
+# Serial: 81873346711060652204712539181482831616
+# MD5 Fingerprint: 16:9f:ad:f1:70:ad:79:d6:ed:29:b4:d1:c5:79:70:a8
+# SHA1 Fingerprint: 9e:bc:75:10:42:b3:02:f3:81:f4:f7:30:62:d4:8f:c3:a7:51:b2:dd
+# SHA256 Fingerprint: b2:fa:e5:3e:14:cc:d7:ab:92:12:06:47:01:ae:27:9c:1d:89:88:fa:cb:77:5f:a8:a0:08:91:4e:66:39:88:a8
+-----BEGIN CERTIFICATE-----
+MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w
+LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w
+CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0
+MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF
+Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI
+zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X
+tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4
+AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2
+KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD
+aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu
+CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo
+9H1/IISpQuQo
+-----END CERTIFICATE-----
+
+# Issuer: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos
+# Subject: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos
+# Label: "Atos TrustedRoot Root CA RSA TLS 2021"
+# Serial: 111436099570196163832749341232207667876
+# MD5 Fingerprint: d4:d3:46:b8:9a:c0:9c:76:5d:9e:3a:c3:b9:99:31:d2
+# SHA1 Fingerprint: 18:52:3b:0d:06:37:e4:d6:3a:df:23:e4:98:fb:5b:16:fb:86:74:48
+# SHA256 Fingerprint: 81:a9:08:8e:a5:9f:b3:64:c5:48:a6:f8:55:59:09:9b:6f:04:05:ef:bf:18:e5:32:4e:c9:f4:57:ba:00:11:2f
+-----BEGIN CERTIFICATE-----
+MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM
+MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx
+MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00
+MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD
+QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z
+4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv
+Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ
+kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs
+GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln
+nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh
+3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD
+0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy
+geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8
+ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB
+c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI
+pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
+dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB
+DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS
+4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs
+o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ
+qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw
+xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM
+rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4
+AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR
+0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY
+o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5
+dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE
+oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc.
+# Subject: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc.
+# Label: "TrustAsia Global Root CA G3"
+# Serial: 576386314500428537169965010905813481816650257167
+# MD5 Fingerprint: 30:42:1b:b7:bb:81:75:35:e4:16:4f:53:d2:94:de:04
+# SHA1 Fingerprint: 63:cf:b6:c1:27:2b:56:e4:88:8e:1c:23:9a:b6:2e:81:47:24:c3:c7
+# SHA256 Fingerprint: e0:d3:22:6a:eb:11:63:c2:e4:8f:f9:be:3b:50:b4:c6:43:1b:e7:bb:1e:ac:c5:c3:6b:5d:5e:c5:09:03:9a:08
+-----BEGIN CERTIFICATE-----
+MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM
+BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp
+ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe
+Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw
+IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU
+cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC
+DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS
+T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK
+AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1
+nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep
+qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA
+yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs
+hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX
+zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv
+kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT
+f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA
+uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB
+o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih
+MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E
+BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4
+wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2
+XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1
+JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j
+ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV
+VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx
+xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on
+AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d
+7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj
+gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV
++Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo
+FGWsJwt0ivKH
+-----END CERTIFICATE-----
+
+# Issuer: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc.
+# Subject: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc.
+# Label: "TrustAsia Global Root CA G4"
+# Serial: 451799571007117016466790293371524403291602933463
+# MD5 Fingerprint: 54:dd:b2:d7:5f:d8:3e:ed:7c:e0:0b:2e:cc:ed:eb:eb
+# SHA1 Fingerprint: 57:73:a5:61:5d:80:b2:e6:ac:38:82:fc:68:07:31:ac:9f:b5:92:5a
+# SHA256 Fingerprint: be:4b:56:cb:50:56:c0:13:6a:52:6d:f4:44:50:8d:aa:36:a0:b5:4f:42:e4:ac:38:f7:2a:f4:70:e4:79:65:4c
+-----BEGIN CERTIFICATE-----
+MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw
+WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs
+IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y
+MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD
+VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz
+dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx
+s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw
+LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij
+YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD
+pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE
+AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR
+UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj
+/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH
+# Subject: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH
+# Label: "Telekom Security TLS ECC Root 2020"
+# Serial: 72082518505882327255703894282316633856
+# MD5 Fingerprint: c1:ab:fe:6a:10:2c:03:8d:bc:1c:22:32:c0:85:a7:fd
+# SHA1 Fingerprint: c0:f8:96:c5:a9:3b:01:06:21:07:da:18:42:48:bc:e9:9d:88:d5:ec
+# SHA256 Fingerprint: 57:8a:f4:de:d0:85:3f:4e:59:98:db:4a:ea:f9:cb:ea:8d:94:5f:60:b6:20:a3:8d:1a:3c:13:b2:bc:7b:a8:e1
+-----BEGIN CERTIFICATE-----
+MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw
+CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH
+bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw
+MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx
+JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE
+AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49
+AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O
+tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP
+f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f
+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA
+MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di
+z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn
+27iQ7t0l
+-----END CERTIFICATE-----
+
+# Issuer: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH
+# Subject: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH
+# Label: "Telekom Security TLS RSA Root 2023"
+# Serial: 44676229530606711399881795178081572759
+# MD5 Fingerprint: bf:5b:eb:54:40:cd:48:71:c4:20:8d:7d:de:0a:42:f2
+# SHA1 Fingerprint: 54:d3:ac:b3:bd:57:56:f6:85:9d:ce:e5:c3:21:e2:d4:ad:83:d0:93
+# SHA256 Fingerprint: ef:c6:5c:ad:bb:59:ad:b6:ef:e8:4d:a2:23:11:b3:56:24:b7:1b:3b:1e:a0:da:8b:66:55:17:4e:c8:97:86:46
+-----BEGIN CERTIFICATE-----
+MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj
+MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0
+eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy
+MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC
+REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG
+A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9
+cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV
+cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA
+U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6
+Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug
+BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy
+8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J
+co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg
+8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8
+rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12
+mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg
++y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX
+gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2
+p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ
+pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm
+9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw
+M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd
+GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+
+CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t
+xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+
+w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK
+L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj
+X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q
+ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm
+dTdmQRCsu/WU48IxK63nI1bMNSWSs1A=
+-----END CERTIFICATE-----
+
+# Issuer: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA
+# Subject: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA
+# Label: "TWCA CYBER Root CA"
+# Serial: 85076849864375384482682434040119489222
+# MD5 Fingerprint: 0b:33:a0:97:52:95:d4:a9:fd:bb:db:6e:a3:55:5b:51
+# SHA1 Fingerprint: f6:b1:1c:1a:83:38:e9:7b:db:b3:a8:c8:33:24:e0:2d:9c:7f:26:66
+# SHA256 Fingerprint: 3f:63:bb:28:14:be:17:4e:c8:b6:43:9c:f0:8d:6d:56:f0:b7:c4:05:88:3a:56:48:a3:34:42:4d:6b:3e:c5:58
+-----BEGIN CERTIFICATE-----
+MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ
+MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290
+IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5
+WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO
+LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg
+Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P
+40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF
+avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/
+34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i
+JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu
+j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf
+Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP
+2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA
+S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA
+oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC
+kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW
+5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD
+VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd
+BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB
+AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t
+tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn
+68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn
+TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t
+RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx
+f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI
+Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz
+8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4
+NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX
+xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6
+t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X
+-----END CERTIFICATE-----
+
+# Issuer: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd.
+# Subject: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd.
+# Label: "SecureSign Root CA14"
+# Serial: 575790784512929437950770173562378038616896959179
+# MD5 Fingerprint: 71:0d:72:fa:92:19:65:5e:89:04:ac:16:33:f0:bc:d5
+# SHA1 Fingerprint: dd:50:c0:f7:79:b3:64:2e:74:a2:b8:9d:9f:d3:40:dd:bb:f0:f2:4f
+# SHA256 Fingerprint: 4b:00:9c:10:34:49:4f:9a:b5:6b:ba:3b:a1:d6:27:31:fc:4d:20:d8:95:5a:dc:ec:10:a9:25:60:72:61:e3:38
+-----BEGIN CERTIFICATE-----
+MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM
+BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u
+LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw
+NzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD
+eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS
+b290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/
+FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg
+vlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy
+6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo
+/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J
+kdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ
+0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib
+y8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac
+18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs
+0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB
+SMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL
+ApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD
+AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk
+86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E
+rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib
+ed87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT
+zfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS
+DCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4
+2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo
+FlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy
+K4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6
+dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl
+Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB
+365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c
+JRNItX+S
+-----END CERTIFICATE-----
+
+# Issuer: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd.
+# Subject: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd.
+# Label: "SecureSign Root CA15"
+# Serial: 126083514594751269499665114766174399806381178503
+# MD5 Fingerprint: 13:30:fc:c4:62:a6:a9:de:b5:c1:68:af:b5:d2:31:47
+# SHA1 Fingerprint: cb:ba:83:c8:c1:5a:5d:f1:f9:73:6f:ca:d7:ef:28:13:06:4a:07:7d
+# SHA256 Fingerprint: e7:78:f0:f0:95:fe:84:37:29:cd:1a:00:82:17:9e:53:14:a9:c2:91:44:28:05:e1:fb:1d:8f:b6:b8:88:6c:3a
+-----BEGIN CERTIFICATE-----
+MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw
+UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM
+dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy
+NTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl
+cnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290
+IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4
+wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR
+ZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB
+Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT
+9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp
+4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6
+bkU6iYAZezKYVWOr62Nuk22rGwlgMU4=
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST BR Root CA 2 2023 O=D-Trust GmbH
+# Subject: CN=D-TRUST BR Root CA 2 2023 O=D-Trust GmbH
+# Label: "D-TRUST BR Root CA 2 2023"
+# Serial: 153168538924886464690566649552453098598
+# MD5 Fingerprint: e1:09:ed:d3:60:d4:56:1b:47:1f:b7:0c:5f:1b:5f:85
+# SHA1 Fingerprint: 2d:b0:70:ee:71:94:af:69:68:17:db:79:ce:58:9f:a0:6b:96:f7:87
+# SHA256 Fingerprint: 05:52:e6:f8:3f:df:65:e8:fa:96:70:e6:66:df:28:a4:e2:13:40:b5:10:cb:e5:25:66:f9:7c:4f:b9:4b:2b:d1
+-----BEGIN CERTIFICATE-----
+MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI
+MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE
+LVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw
+OTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi
+MCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr
+i3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE
+gXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8
+k12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT
+Rphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl
+2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U
+cSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP
+/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS
+uREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+
+0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N
+DfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+
+XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61
+GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG
+OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y
+XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI
+FoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n
+riv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR
+VFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc
+LmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn
+4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD
+hQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG
+koHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46
+ls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS
+Ecr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80
+knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ
+hJ65bvspmZDogNOfJA==
+-----END CERTIFICATE-----
+
+# Issuer: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc.
+# Subject: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc.
+# Label: "TrustAsia TLS ECC Root CA"
+# Serial: 310892014698942880364840003424242768478804666567
+# MD5 Fingerprint: 09:48:04:77:d2:fc:65:93:71:66:b1:11:95:4f:06:8c
+# SHA1 Fingerprint: b5:ec:39:f3:a1:66:37:ae:c3:05:94:57:e2:be:11:be:b7:a1:7f:36
+# SHA256 Fingerprint: c0:07:6b:9e:f0:53:1f:b1:a6:56:d6:7c:4e:be:97:cd:5d:ba:a4:1e:f4:45:98:ac:c2:48:98:78:c9:2d:87:11
+-----BEGIN CERTIFICATE-----
+MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMw
+WDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs
+IEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQw
+NTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UE
+ChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RB
+c2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/pVs/
+AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDp
+guMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAw
+DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw
+DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01
+L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqR
+OkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc.
+# Subject: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc.
+# Label: "TrustAsia TLS RSA Root CA"
+# Serial: 160405846464868906657516898462547310235378010780
+# MD5 Fingerprint: 3b:9e:c3:86:0f:34:3c:6b:c5:46:c4:8e:1d:e7:19:12
+# SHA1 Fingerprint: a5:46:50:c5:62:ea:95:9a:1a:a7:04:6f:17:58:c7:29:53:3d:03:fa
+# SHA256 Fingerprint: 06:c0:8d:7d:af:d8:76:97:1e:b1:12:4f:e6:7f:84:7e:c0:c7:a1:58:d3:ea:53:cb:e9:40:e2:ea:97:91:f4:c3
+-----BEGIN CERTIFICATE-----
+MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEM
+BQAwWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp
+ZXMsIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcN
+MjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMG
+A1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1
+c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC
+AgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+
+NmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJ
+Q1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561
+HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32
+ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8Tb
+xNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeX
+i9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQ
+UNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+j
+TnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DT
+bE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8
+S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMBAAGjQjBA
+MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT
+MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3
+Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4
+iqME3mmL5Dw8veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt
+7DlK9RME7I10nYEKqG/odv6LTytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp
+2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHxtlotJnMnlvm5P1vQiJ3koP26TpUJ
+g3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp27RIGAAtvKLEiUUj
+pQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87qqA8M
+pugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongP
+XvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe
+SsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0
+ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy
+323imttUQ/hHWKNddBWcwauwxzQ=
+-----END CERTIFICATE-----
+
+# Issuer: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH
+# Subject: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH
+# Label: "D-TRUST EV Root CA 2 2023"
+# Serial: 139766439402180512324132425437959641711
+# MD5 Fingerprint: 96:b4:78:09:f0:09:cb:77:eb:bb:1b:4d:6f:36:bc:b6
+# SHA1 Fingerprint: a5:5b:d8:47:6c:8f:19:f7:4c:f4:6d:6b:b6:c2:79:82:22:df:54:8b
+# SHA256 Fingerprint: 8e:82:21:b2:e7:d4:00:78:36:a1:67:2f:0d:cc:29:9c:33:bc:07:d3:16:f1:32:fa:1a:20:6d:58:71:50:f1:ce
+-----BEGIN CERTIFICATE-----
+MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI
+MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE
+LVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw
+OTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi
+MCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN
+AQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK
+F8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE
+7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe
+EMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6
+lHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb
+RlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV
+jTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc
+jmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx
+TnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+
+ARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk
+hbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF
+NXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH
+kV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG
+OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y
+XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14
+QvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4
+pZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q
+3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU
+t7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX
+cI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8
+ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT
+2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs
+7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP
+gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst
+Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh
+XBxvWHZks/wCuPWdCg==
+-----END CERTIFICATE-----
+
+# Issuer: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG
+# Subject: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG
+# Label: "SwissSign RSA TLS Root CA 2022 - 1"
+# Serial: 388078645722908516278762308316089881486363258315
+# MD5 Fingerprint: 16:2e:e4:19:76:81:85:ba:8e:91:58:f1:15:ef:72:39
+# SHA1 Fingerprint: 81:34:0a:be:4c:cd:ce:cc:e7:7d:cc:8a:d4:57:e2:45:a0:77:5d:ce
+# SHA256 Fingerprint: 19:31:44:f4:31:e0:fd:db:74:07:17:d4:de:92:6a:57:11:33:88:4b:43:60:d3:0e:27:29:13:cb:e6:60:ce:41
+-----BEGIN CERTIFICATE-----
+MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL
+BQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE
+AxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx
+MTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT
+d2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg
+MjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX
+vDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7
+LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX
+5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE
+EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt
+/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x
+0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5
+KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM
+0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd
+OxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta
+clXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK
+wP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD
+AQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4
+DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL
+BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3
+10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz
+Hqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ
+iJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc
+gC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM
+ZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF
+LhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp
+zv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td
+Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0
+rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO
+gLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ
+-----END CERTIFICATE-----
+
+# Issuer: CN=OISTE Server Root ECC G1 O=OISTE Foundation
+# Subject: CN=OISTE Server Root ECC G1 O=OISTE Foundation
+# Label: "OISTE Server Root ECC G1"
+# Serial: 47819833811561661340092227008453318557
+# MD5 Fingerprint: 42:a7:d2:35:ae:02:92:db:19:76:08:de:2f:05:b4:d4
+# SHA1 Fingerprint: 3b:f6:8b:09:ae:2a:92:7b:ba:e3:8d:3f:11:95:d9:e6:44:0c:45:e2
+# SHA256 Fingerprint: ee:c9:97:c0:c3:0f:21:6f:7e:3b:8b:30:7d:2b:ae:42:41:2d:75:3f:c8:21:9d:af:d1:52:0b:25:72:85:0f:49
+-----BEGIN CERTIFICATE-----
+MIICNTCCAbqgAwIBAgIQI/nD1jWvjyhLH/BU6n6XnTAKBggqhkjOPQQDAzBLMQsw
+CQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UEAwwY
+T0lTVEUgU2VydmVyIFJvb3QgRUNDIEcxMB4XDTIzMDUzMTE0NDIyOFoXDTQ4MDUy
+NDE0NDIyN1owSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRp
+b24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IEVDQyBHMTB2MBAGByqGSM49
+AgEGBSuBBAAiA2IABBcv+hK8rBjzCvRE1nZCnrPoH7d5qVi2+GXROiFPqOujvqQy
+cvO2Ackr/XeFblPdreqqLiWStukhEaivtUwL85Zgmjvn6hp4LrQ95SjeHIC6XG4N
+2xml4z+cKrhAS93mT6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQ3
+TYhlz/w9itWj8UnATgwQb0K0nDAdBgNVHQ4EFgQUN02IZc/8PYrVo/FJwE4MEG9C
+tJwwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCpKjAd0MKfkFFR
+QD6VVCHNFmb3U2wIFjnQEnx/Yxvf4zgAOdktUyBFCxxgZzFDJe0CMQCSia7pXGKD
+YmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c=
+-----END CERTIFICATE-----
+
+# Issuer: CN=OISTE Server Root RSA G1 O=OISTE Foundation
+# Subject: CN=OISTE Server Root RSA G1 O=OISTE Foundation
+# Label: "OISTE Server Root RSA G1"
+# Serial: 113845518112613905024960613408179309848
+# MD5 Fingerprint: 23:a7:9e:d4:70:b8:b9:14:57:41:8a:7e:44:59:e2:68
+# SHA1 Fingerprint: f7:00:34:25:94:88:68:31:e4:34:87:3f:70:fe:86:b3:86:9f:f0:6e
+# SHA256 Fingerprint: 9a:e3:62:32:a5:18:9f:fd:db:35:3d:fd:26:52:0c:01:53:95:d2:27:77:da:c5:9d:b5:7b:98:c0:89:a6:51:e6
+-----BEGIN CERTIFICATE-----
+MIIFgzCCA2ugAwIBAgIQVaXZZ5Qoxu0M+ifdWwFNGDANBgkqhkiG9w0BAQwFADBL
+MQswCQYDVQQGEwJDSDEZMBcGA1UECgwQT0lTVEUgRm91bmRhdGlvbjEhMB8GA1UE
+AwwYT0lTVEUgU2VydmVyIFJvb3QgUlNBIEcxMB4XDTIzMDUzMTE0MzcxNloXDTQ4
+MDUyNDE0MzcxNVowSzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5k
+YXRpb24xITAfBgNVBAMMGE9JU1RFIFNlcnZlciBSb290IFJTQSBHMTCCAiIwDQYJ
+KoZIhvcNAQEBBQADggIPADCCAgoCggIBAKqu9KuCz/vlNwvn1ZatkOhLKdxVYOPM
+vLO8LZK55KN68YG0nnJyQ98/qwsmtO57Gmn7KNByXEptaZnwYx4M0rH/1ow00O7b
+rEi56rAUjtgHqSSY3ekJvqgiG1k50SeH3BzN+Puz6+mTeO0Pzjd8JnduodgsIUzk
+ik/HEzxux9UTl7Ko2yRpg1bTacuCErudG/L4NPKYKyqOBGf244ehHa1uzjZ0Dl4z
+O8vbUZeUapU8zhhabkvG/AePLhq5SvdkNCncpo1Q4Y2LS+VIG24ugBA/5J8bZT8R
+tOpXaZ+0AOuFJJkk9SGdl6r7NH8CaxWQrbueWhl/pIzY+m0o/DjH40ytas7ZTpOS
+jswMZ78LS5bOZmdTaMsXEY5Z96ycG7mOaES3GK/m5Q9l3JUJsJMStR8+lKXHiHUh
+sd4JJCpM4rzsTGdHwimIuQq6+cF0zowYJmXa92/GjHtoXAvuY8BeS/FOzJ8vD+Ho
+mnqT8eDI278n5mUpezbgMxVz8p1rhAhoKzYHKyfMeNhqhw5HdPSqoBNdZH702xSu
++zrkL8Fl47l6QGzwBrd7KJvX4V84c5Ss2XCTLdyEr0YconosP4EmQufU2MVshGYR
+i3drVByjtdgQ8K4p92cIiBdcuJd5z+orKu5YM+Vt6SmqZQENghPsJQtdLEByFSnT
+kCz3GkPVavBpAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
+8snBDw1jALvsRQ5KH7WxszbNDo0wHQYDVR0OBBYEFPLJwQ8NYwC77EUOSh+1sbM2
+zQ6NMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQwFAAOCAgEANGd5sjrG5T33
+I3K5Ce+SrScfoE4KsvXaFwyihdJ+klH9FWXXXGtkFu6KRcoMQzZENdl//nk6HOjG
+5D1rd9QhEOP28yBOqb6J8xycqd+8MDoX0TJD0KqKchxRKEzdNsjkLWd9kYccnbz8
+qyiWXmFcuCIzGEgWUOrKL+mlSdx/PKQZvDatkuK59EvV6wit53j+F8Bdh3foZ3dP
+AGav9LEDOr4SfEE15fSmG0eLy3n31r8Xbk5l8PjaV8GUgeV6Vg27Rn9vkf195hfk
+gSe7BYhW3SCl95gtkRlpMV+bMPKZrXJAlszYd2abtNUOshD+FKrDgHGdPY3ofRRs
+YWSGRqbXVMW215AWRqWFyp464+YTFrYVI8ypKVL9AMb2kI5Wj4kI3Zaq5tNqqYY1
+9tVFeEJKRvwDyF7YZvZFZSS0vod7VSCd9521Kvy5YhnLbDuv0204bKt7ph6N/Ome
+/msVuduCmsuY33OhkKCgxeDoAaijFJzIwZqsFVAzje18KotzlUBDJvyBpCpfOZC3
+J8tRd/iWkx7P8nd9H0aTolkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2
+wq1yVAb+axj5d9spLFKebXd7Yv0PTY6YMjAwcRLWJTXjn/hvnLXrahut6hDTlhZy
+BiElxky8j3C7DOReIoMt0r7+hVu05L0=
+-----END CERTIFICATE-----
+
+# Issuer: CN=e-Szigno TLS Root CA 2023 O=Microsec Ltd.
+# Subject: CN=e-Szigno TLS Root CA 2023 O=Microsec Ltd.
+# Label: "e-Szigno TLS Root CA 2023"
+# Serial: 71934828665710877219916191754
+# MD5 Fingerprint: 6a:e9:99:74:a5:da:5e:f1:d9:2e:f2:c8:d1:86:8b:71
+# SHA1 Fingerprint: 6f:9a:d5:d5:df:e8:2c:eb:be:37:07:ee:4f:4f:52:58:29:41:d1:fe
+# SHA256 Fingerprint: b4:91:41:50:2d:00:66:3d:74:0f:2e:7e:c3:40:c5:28:00:96:26:66:12:1a:36:d0:9c:f7:dd:2b:90:38:4f:b4
+-----BEGIN CERTIFICATE-----
+MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYD
+VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
+ZC4xFzAVBgNVBGEMDlZBVEhVLTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBU
+TFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0MDAwMFoXDTM4MDcxNzE0MDAwMFow
+dTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYwFAYDVQQKDA1NaWNy
+b3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZZS1T
+emlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE
+AGgP36J8PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFS
+AL/fjO1ZrTJlqwlZULUZwmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/v
+SzUaQ49CE0y5LBqcvjC2xN7cS53kpDzLLtmt3999Cd8ukv+ho2MwYTAPBgNVHRMB
+Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUWYQCYlpGePVd3I8K
+ECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0UwCgYIKoZI
+zj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpt
+y7Ve7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZl
+C9p2x1L/Cx6AcCIwwzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6
+uWWL
+-----END CERTIFICATE-----
+
+# Issuer: CN=SECOM TLS RSA Root CA 2024 O=SECOM Trust Systems Co., Ltd.
+# Subject: CN=SECOM TLS RSA Root CA 2024 O=SECOM Trust Systems Co., Ltd.
+# Label: "SECOM TLS RSA Root CA 2024"
+# Serial: 17188327524208271538
+# MD5 Fingerprint: d0:a4:db:32:eb:44:98:d2:62:0b:3e:bc:4d:7c:5c:e9
+# SHA1 Fingerprint: fb:97:96:7c:ef:8d:98:63:06:c0:3b:b6:11:f8:e0:13:97:a2:98:d3
+# SHA256 Fingerprint: 14:35:f2:25:c5:d2:52:d7:a2:19:48:cc:3c:e6:2a:ec:fa:88:00:1e:3d:d7:2d:1c:c3:55:51:00:eb:37:2f:93
+-----BEGIN CERTIFICATE-----
+MIIFmjCCA4KgAwIBAgIJAO6JNNDLgOCyMA0GCSqGSIb3DQEBDAUAMFoxCzAJBgNV
+BAYTAkpQMSYwJAYDVQQKEx1TRUNPTSBUcnVzdCBTeXN0ZW1zIENvLiwgTHRkLjEj
+MCEGA1UEAxMaU0VDT00gVExTIFJTQSBSb290IENBIDIwMjQwHhcNMjQwMTMxMDUx
+MTU1WhcNNDkwMTE0MDUxMTU1WjBaMQswCQYDVQQGEwJKUDEmMCQGA1UEChMdU0VD
+T00gVHJ1c3QgU3lzdGVtcyBDby4sIEx0ZC4xIzAhBgNVBAMTGlNFQ09NIFRMUyBS
+U0EgUm9vdCBDQSAyMDI0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
+4TjizUwzxbInq8Tx11gaFYNk5fO+34y7TyM4neh0UgL5JIZbJNLTz2x//L/B71+5
+m6X6nGIr7d4lFJBGtjO677hXOz93zkcWaUTm3VbOAjBlt4YWxlcccBHXuZ7o3Q+4
+R+ormrBdHeJ1CTUEG8ttQbKIl3G7OZYbnH8/pP8cjPub/0kDVNuMzp7xsVRROOis
+Qt53fMoJLlYgoebbuMphOqMCtjkJ7R6efEMfLp8UAVi9ZaLRn76ET/CJkk925ndu
+uufC4BatS4mnXFmxN0vUXb0ij9B8O/D8gixQEsVSD4GK8FWRPh3bVd/6bzdkHGJj
+y21XI0yejVomZUbRrOfNuz0boPGV1pt18fFC39IHQEth3OFqb5NDO3L+A9bNqTgA
+yUgRmIn4ucgDc/Ri/Km3V51ueZjy1/yk0qwJVadAVVrCt56iNeXOyEvzJADGgDQ8
+E1Pdaqct8Cynz/47ReQM62vFYO08wcQkrjmX/tesiko1V1yyaf6EfPzUFzmaGy9x
+vkCwdbm15EdTolOjE0H2Vb5/APDOyCFEokiYGmXTLdAUl0wKZ4IyjkHGzy0jhpaX
+EXE/GJcEvI6VzEchjaBL03EJ0h9pG4OqeIOycKvAo3A+TbetyfsrgYyHzU0a7/qU
+jGat1AAq1nVljMpKqpinPTsf/d9H39FTUeJL7TpzzjUCAwEAAaNjMGEwHQYDVR0O
+BBYEFCzrchKOWHdkNRVWNQFXB6l9DTbmMB8GA1UdIwQYMBaAFCzrchKOWHdkNRVW
+NQFXB6l9DTbmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
+SIb3DQEBDAUAA4ICAQAVwsvluSafaez5tFPR/hRTBzRxEyMMQF3XJXCVi3yegZyK
+oec7hmE6jx2ZM8KgM1kn2yJRwFXHX8zUW9nBLEDWc4wuE8LrlZqhGZM9pJQXGmGz
+ResDJV6JgRBna+j4sA1M7yIdlvL0sAfFXFCTRaWTD4E1V99RLrFzWfTcC+e180hD
+uNMpqOEo46+lMeW/Wvh7ifOQs+kiK0O2gHxQDNxslSavnCs4V7l8HRDJ2La10o70
+Bo7VLzf1W8MBvv0VTnxB+NjT5qTAbhGFh9Gvp4BaJpmdUf0C5CEP6dbQlfgxWfzY
+r69yVT6dPQB+GFEaY03IMY+AcBCs+om1fNxrQXt9zoofMBNFbLhvpNH/JsXWdGUz
+fNbO12uswTa5wah8LB18FTQN2/zPHYmvBEoLuyUgZ09VNLJo5YA0kXItVYkLjMe2
+SixzK4scUHv81IK99I91DWx7FwMVKw2xgFp+ZLYB2dnpQQrqwlW64glHUcK2N9BD
+snjLSxeZ+UPECh9RxH4WAcKiZW+cqaKMmhP2WBfR4IcR7NOL32ml11ds87hhV1CZ
+WWFCJAcCidYZz6CZa8exzHojP9SB5RH0/v1KdHAisqhSjtJl/UIAHIQ48elOn8wr
+TdFap4Yb5aHglmMeNx+fAIhDluWVfxTO7H4dTPU+SFVRMLAh+wwKZfqb94nMeQ==
+-----END CERTIFICATE-----
+
+# Issuer: CN=SECOM TLS ECC Root CA 2024 O=SECOM Trust Systems Co., Ltd.
+# Subject: CN=SECOM TLS ECC Root CA 2024 O=SECOM Trust Systems Co., Ltd.
+# Label: "SECOM TLS ECC Root CA 2024"
+# Serial: 9329818985461676612
+# MD5 Fingerprint: 99:d3:9d:e4:d2:b1:2d:f0:2a:04:67:85:f3:df:46:d6
+# SHA1 Fingerprint: 7a:1f:22:2d:72:b2:c3:19:87:44:db:61:69:e8:a6:4b:d7:0d:44:0e
+# SHA256 Fingerprint: 6a:b2:ab:75:f5:1c:b4:f4:f0:15:62:03:fb:f6:f6:46:23:2f:51:4b:e0:59:f6:28:33:30:8b:82:b4:d7:2d:b1
+-----BEGIN CERTIFICATE-----
+MIICTDCCAdGgAwIBAgIJAIF6LO+PI3pEMAoGCCqGSM49BAMDMFoxCzAJBgNVBAYT
+AkpQMSYwJAYDVQQKEx1TRUNPTSBUcnVzdCBTeXN0ZW1zIENvLiwgTHRkLjEjMCEG
+A1UEAxMaU0VDT00gVExTIEVDQyBSb290IENBIDIwMjQwHhcNMjQwMTMxMDU1MjM0
+WhcNNDkwMTE0MDU1MjM0WjBaMQswCQYDVQQGEwJKUDEmMCQGA1UEChMdU0VDT00g
+VHJ1c3QgU3lzdGVtcyBDby4sIEx0ZC4xIzAhBgNVBAMTGlNFQ09NIFRMUyBFQ0Mg
+Um9vdCBDQSAyMDI0MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7NzFMtu9dzQXSNC1
+2fabk0+GlC5finB3R7XaZonRUd20aFiWObtuNBCLUZSfk6QXAE55BjEXsXQ/NG8y
+UqicXjsu9ksDK3JZBgCwLOVh6+nwJXTvso/dEj/GUYH5mBdoo2MwYTAdBgNVHQ4E
+FgQUO3YReyl04k4GTFaCQNAhL3qzydUwHwYDVR0jBBgwFoAUO3YReyl04k4GTFaC
+QNAhL3qzydUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZI
+zj0EAwMDaQAwZgIxAN3ib8fi1pMYtAPjMilB5e5/H+t5CL0xPL+cZ5oTTZuSCjpA
+n1v7F/VAr8bFxQXAowIxAKsBVO1ACFp7skwzPvdv1EUY5a897WGLT4lb+bjxFAWy
+l8wDcZJdwGZ/pAHxt1AJ1g==
+-----END CERTIFICATE-----
+
+# Issuer: CN=Telia EC TLS Root CA v3 O=Telia Company AB
+# Subject: CN=Telia EC TLS Root CA v3 O=Telia Company AB
+# Label: "Telia EC TLS Root CA v3"
+# Serial: 8028200332079443110287896228123317
+# MD5 Fingerprint: b6:fa:6a:5c:42:fb:c5:67:72:c0:e0:2f:72:fb:5a:44
+# SHA1 Fingerprint: b4:d6:07:c2:a5:95:bc:5b:f4:67:4d:c9:dc:6f:6f:0a:00:7a:a5:35
+# SHA256 Fingerprint: 09:8e:08:a9:1d:bb:f7:74:78:b9:6c:ce:b8:9b:14:13:a5:da:37:b7:c8:62:60:6a:95:5d:eb:07:17:9f:43:26
+-----BEGIN CERTIFICATE-----
+MIICMjCCAbegAwIBAgIPAYvSIlRjTQSLbOVHH9K1MAoGCCqGSM49BAMDMEoxCzAJ
+BgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSAwHgYDVQQDDBdU
+ZWxpYSBFQyBUTFMgUm9vdCBDQSB2MzAeFw0yMzExMTUwODU1MjZaFw00ODA1MjMx
+MTAwMDBaMEoxCzAJBgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFC
+MSAwHgYDVQQDDBdUZWxpYSBFQyBUTFMgUm9vdCBDQSB2MzB2MBAGByqGSM49AgEG
+BSuBBAAiA2IABMHIlhVDLbmFKUpW0iK4dpryT6emYOeS31JPwWnWPmkWRrAkTbPX
+40sQfHI9mpR7Rbktu3ngg6W+BBSXSechtMCnBmWXj/EaVlmV5cY1jD2HoTfhBQ3A
+acpCNMLJK4NpZaNjMGEwHwYDVR0jBBgwFoAU1GToQ4g6cy/QGnGCNgtehd7H3kMw
+HQYDVR0OBBYEFNRk6EOIOnMv0BpxgjYLXoXex95DMA4GA1UdDwEB/wQEAwIBBjAP
+BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2kAMGYCMQCXAUdS/9bbJ8A1JYaG
+f/bWt/s7Ta0ot5Ulno8OjSNYRWQIlS4tVWldvTAVA7heOFgCMQCvKr8+Z2Rn+OBr
+5UHzlgBObpad1LuwNTRcdNgUJxIWadcki+UBLEi1/AURKV5md2M=
+-----END CERTIFICATE-----
+
+# Issuer: CN=Telia RSA TLS Root CA v3 O=Telia Company AB
+# Subject: CN=Telia RSA TLS Root CA v3 O=Telia Company AB
+# Label: "Telia RSA TLS Root CA v3"
+# Serial: 8028214673410753541795188766939845
+# MD5 Fingerprint: f4:8b:9c:f3:f8:63:cf:dc:25:8f:b4:bb:a2:e9:9d:e2
+# SHA1 Fingerprint: b5:2e:88:4e:40:c1:11:fb:50:c7:e2:4f:ac:18:2b:bd:68:15:d2:34
+# SHA256 Fingerprint: d1:3d:b1:29:4c:45:eb:c6:fc:86:c6:bb:f6:9f:a2:9b:df:e6:92:df:f7:c7:13:c2:43:c7:a9:56:c6:a2:28:4c
+-----BEGIN CERTIFICATE-----
+MIIFgjCCA2qgAwIBAgIPAYvSUKtCVSxHWr2h3BrFMA0GCSqGSIb3DQEBDAUAMEsx
+CzAJBgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSEwHwYDVQQD
+DBhUZWxpYSBSU0EgVExTIFJvb3QgQ0EgdjMwHhcNMjMxMTE1MDk0NzQyWhcNNDgw
+NTIzMTEwMDAwWjBLMQswCQYDVQQGEwJTRTEZMBcGA1UECgwQVGVsaWEgQ29tcGFu
+eSBBQjEhMB8GA1UEAwwYVGVsaWEgUlNBIFRMUyBSb290IENBIHYzMIICIjANBgkq
+hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsV89KG19hCf4S1Fvk8D3TyDERhmcvx8F
+7Kmb4WATx3ije1id3KHxRE0TKmcNCbAQ57bvHFEYa4hR2l20VjVadExqOW+2ld99
+MbEiO+jRVOz+BbxLxJnmGwCqI+BfuTjjVReDxsxjQvjgBsClaO/sm5i70nlZcWGR
+tIkvWDK3NNkT5RtwXc/O8NTFVpbUqT6cRjIj3olAblR+lRf4Ffy5o+Q9fabjYn9Z
+9S4itruElcEFf9Ljk7fwdTycT/rvJW9w/B3G2a3r0f/zXNOVruIBcqE6pkSospAC
+U2bG42fYKrbM/GWnp7u+p9Frz4jaNwpb4YHuEeS8BratNcP8X62jXIvvKHxlsMDJ
+Cnb4U8JzFOLsU6mohVY58BdZrvi0Gk9UOuqmgoG6dskHoksjZTlK61D/InzmEoA1
+yAYJFDVysjRxDUOu9cAwANbqmq77WIFL6BpnZgVqPtMfG6wN8BrTKdapvilVsYR5
+9BFgIsAVBMxrGh+W+QcvmJafUpASvlArKvVG2FI4i6PiLjSBT0+6F6EQLrYqefOQ
+F/fBNEXb+njUQ0SUVrAqtH4Y+OjCI/a4/JJQppxeemZcQ0SUShgiI5AM5xHO5iya
+UrTjYH4zxUz9j+1FEbDH/xpstr1gXBykspup+hRTaJcbA+UbpJqtWZndAPddJmt6
+YJQ+dU3pDu8CAwEAAaNjMGEwHwYDVR0jBBgwFoAUsMep0t2yKFZzBJSMFFxIbzdS
+kqgwHQYDVR0OBBYEFLDHqdLdsihWcwSUjBRcSG83UpKoMA4GA1UdDwEB/wQEAwIB
+BjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQBdYzFsNGDRk7bR
+/AgRKq+5637YuOW+w6uhpoS0VnKMUpyHCwku86hEvqivakPtfmlm4bFwt++sb/8O
+XsWBqtfbXMaBNDTZl8XRMJuLWOW2JrbKkRzgG0eBUcvsadG1rrhbmZqYvFXaAZO7
+o4TdOZzxhBB5GOAWWXB3IeraNP4J63zyo9n8Gqw3sJBG44em5hoYjBffP+npibys
+lnslRi4L6xHsCYj/Pab+OlqbMCB6v+sTCLeEIukRVzoR9aQ45pEK7Z1QBnSsbAKQ
+tss0JKD9d/mX143H1xePjPhTXlv5JCkhrcj+SShz0P9+EHoWe6m9lyUEOIVn0rp+
+yVJWNbmyDv3VkwFxHC1ApSQsgSimjGQ4wtr6cSmordYxkV+Ro8lOIIhRksXPyDk2
+7gW6IjUXCkZKpxFjkL3jiBSc8SkxnwCWtXg8xwNwdFVNBGLCCuJnsneYXjJNqzRq
+UcoGwzsvF3Qi/ZnHUNvISdevlgIAXL4Wvrxaqvoa01wB+GCfs57RTGE4TvAGhKNK
+us8K3hRT1BSpigzMIRzSxtAOrqPN6j//QSmW9f8Jcncri4j2ihSpVrFU0NdNkMhZ
+eAKidTFPsxCVFuW4Aniz7jqiw5sWtjbQrlW035izIEU4sYwQoC1Nx0Svy+mMTRai
+50LqFQ+A1/Hq6xHHDNx7CI83d23Erw==
+-----END CERTIFICATE-----

+ 83 - 0
vendor/certifi/core.py

@@ -0,0 +1,83 @@
+"""
+certifi.py
+~~~~~~~~~~
+
+This module returns the installation location of cacert.pem or its contents.
+"""
+import sys
+import atexit
+
+def exit_cacert_ctx() -> None:
+    _CACERT_CTX.__exit__(None, None, None)  # type: ignore[union-attr]
+
+
+if sys.version_info >= (3, 11):
+
+    from importlib.resources import as_file, files
+
+    _CACERT_CTX = None
+    _CACERT_PATH = None
+
+    def where() -> str:
+        # This is slightly terrible, but we want to delay extracting the file
+        # in cases where we're inside of a zipimport situation until someone
+        # actually calls where(), but we don't want to re-extract the file
+        # on every call of where(), so we'll do it once then store it in a
+        # global variable.
+        global _CACERT_CTX
+        global _CACERT_PATH
+        if _CACERT_PATH is None:
+            # This is slightly janky, the importlib.resources API wants you to
+            # manage the cleanup of this file, so it doesn't actually return a
+            # path, it returns a context manager that will give you the path
+            # when you enter it and will do any cleanup when you leave it. In
+            # the common case of not needing a temporary file, it will just
+            # return the file system location and the __exit__() is a no-op.
+            #
+            # We also have to hold onto the actual context manager, because
+            # it will do the cleanup whenever it gets garbage collected, so
+            # we will also store that at the global level as well.
+            _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem"))
+            _CACERT_PATH = str(_CACERT_CTX.__enter__())
+            atexit.register(exit_cacert_ctx)
+
+        return _CACERT_PATH
+
+    def contents() -> str:
+        return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii")
+
+else:
+
+    from importlib.resources import path as get_path, read_text
+
+    _CACERT_CTX = None
+    _CACERT_PATH = None
+
+    def where() -> str:
+        # This is slightly terrible, but we want to delay extracting the
+        # file in cases where we're inside of a zipimport situation until
+        # someone actually calls where(), but we don't want to re-extract
+        # the file on every call of where(), so we'll do it once then store
+        # it in a global variable.
+        global _CACERT_CTX
+        global _CACERT_PATH
+        if _CACERT_PATH is None:
+            # This is slightly janky, the importlib.resources API wants you
+            # to manage the cleanup of this file, so it doesn't actually
+            # return a path, it returns a context manager that will give
+            # you the path when you enter it and will do any cleanup when
+            # you leave it. In the common case of not needing a temporary
+            # file, it will just return the file system location and the
+            # __exit__() is a no-op.
+            #
+            # We also have to hold onto the actual context manager, because
+            # it will do the cleanup whenever it gets garbage collected, so
+            # we will also store that at the global level as well.
+            _CACERT_CTX = get_path("certifi", "cacert.pem")
+            _CACERT_PATH = str(_CACERT_CTX.__enter__())
+            atexit.register(exit_cacert_ctx)
+
+        return _CACERT_PATH
+
+    def contents() -> str:
+        return read_text("certifi", "cacert.pem", encoding="ascii")

+ 0 - 0
vendor/certifi/py.typed


+ 0 - 0
vendor/certifi/tests/__init__.py


+ 18 - 0
vendor/certifi/tests/test_certify.py

@@ -0,0 +1,18 @@
+import os
+import unittest
+
+import certifi
+
+
+class TestCertifi(unittest.TestCase):
+    def test_cabundle_exists(self) -> None:
+        assert os.path.exists(certifi.where())
+
+    def test_read_contents(self) -> None:
+        content = certifi.contents()
+        assert "-----BEGIN CERTIFICATE-----" in content
+
+    def test_py_typed_exists(self) -> None:
+        assert os.path.exists(
+            os.path.join(os.path.dirname(certifi.__file__), 'py.typed')
+        )

+ 131 - 1
vendor/chattolib/__init__.py

@@ -1 +1,131 @@
-# Vendored chattolib
+"""Async Python client library for the Chatto Connect API."""
+
+from chattolib.client import ChattoClient
+from chattolib.exceptions import ChattoAuthError, ChattoConnectError, ChattoError
+from chattolib.realtime import (
+    ChattoRealtimeCloseError,
+    ChattoRealtimeError,
+    RealtimeConnection,
+    RealtimeEvent,
+    ServerHello,
+    stream_events,
+)
+from chattolib.types import (
+    ActiveCall,
+    AdminMember,
+    AdminRole,
+    AdminRoomLayoutGroup,
+    AdminRoomLayoutItemKind,
+    Asset,
+    AssetUpload,
+    AssetUploadStatus,
+    AssetUrl,
+    CustomUserStatus,
+    DirectoryMember,
+    ExternalIdentityProvider,
+    FollowedThread,
+    FollowedThreadsPage,
+    ImageFitMode,
+    ImageTransformOptions,
+    LinkedExternalIdentity,
+    LinkPreview,
+    Message,
+    MessageAttachment,
+    MessageReaction,
+    Notification,
+    NotificationLevel,
+    NotificationPreference,
+    NotificationsPage,
+    Page,
+    PresenceStatus,
+    ProviderMetadata,
+    Role,
+    Room,
+    RoomBan,
+    RoomDirectoryScope,
+    RoomGroup,
+    RoomKind,
+    RoomSummary,
+    RoomViewerState,
+    RoomWithViewerState,
+    ServerConfig,
+    ServerLogin,
+    ServerProfile,
+    ServerRuntimeConfig,
+    SidebarLink,
+    ThreadSummary,
+    TimeFormat,
+    TimelineEvent,
+    TimelinePage,
+    User,
+    UserSettings,
+    VideoProcessing,
+    VideoProcessingStatus,
+    VideoVariant,
+    ViewerUser,
+)
+
+__all__ = [
+    "ChattoClient",
+    "ChattoError",
+    "ChattoConnectError",
+    "ChattoAuthError",
+    "ChattoRealtimeCloseError",
+    "ChattoRealtimeError",
+    "RealtimeConnection",
+    "RealtimeEvent",
+    "ServerHello",
+    "stream_events",
+    "ActiveCall",
+    "AdminMember",
+    "AdminRole",
+    "AdminRoomLayoutGroup",
+    "AdminRoomLayoutItemKind",
+    "Asset",
+    "AssetUpload",
+    "AssetUploadStatus",
+    "AssetUrl",
+    "CustomUserStatus",
+    "DirectoryMember",
+    "ExternalIdentityProvider",
+    "FollowedThread",
+    "FollowedThreadsPage",
+    "ImageFitMode",
+    "ImageTransformOptions",
+    "LinkedExternalIdentity",
+    "LinkPreview",
+    "Message",
+    "MessageAttachment",
+    "MessageReaction",
+    "Notification",
+    "NotificationLevel",
+    "NotificationPreference",
+    "NotificationsPage",
+    "Page",
+    "PresenceStatus",
+    "ProviderMetadata",
+    "Role",
+    "Room",
+    "RoomBan",
+    "RoomDirectoryScope",
+    "RoomGroup",
+    "RoomKind",
+    "RoomSummary",
+    "RoomViewerState",
+    "RoomWithViewerState",
+    "ServerConfig",
+    "ServerLogin",
+    "ServerProfile",
+    "ServerRuntimeConfig",
+    "SidebarLink",
+    "ThreadSummary",
+    "TimeFormat",
+    "TimelineEvent",
+    "TimelinePage",
+    "User",
+    "UserSettings",
+    "VideoProcessing",
+    "VideoProcessingStatus",
+    "VideoVariant",
+    "ViewerUser",
+]

+ 1 - 0
vendor/connectrpc-0.11.1.dist-info/INSTALLER

@@ -0,0 +1 @@
+uv

+ 188 - 0
vendor/connectrpc-0.11.1.dist-info/METADATA

@@ -0,0 +1,188 @@
+Metadata-Version: 2.4
+Name: connectrpc
+Version: 0.11.1
+Summary: Server and client runtime library for Connect RPC
+Keywords: connect,grpc,http,protobuf,rpc
+License-Expression: Apache-2.0
+License-File: LICENSE
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Classifier: Topic :: Internet :: WWW/HTTP
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Typing :: Typed
+Requires-Dist: protobuf-py==0.1.1
+Requires-Dist: pyqwest>=0.5.1
+Maintainer: Anuraag Agrawal, Spencer Nelson, Stefan VanBuren, Yasushi Itoh
+Maintainer-email: Anuraag Agrawal <anuraaga@gmail.com>, Spencer Nelson <spencer@firetiger.com>, Stefan VanBuren <stefan@vanburen.xyz>, Yasushi Itoh <i2y.may.roku@gmail.com>
+Requires-Python: >=3.10
+Project-URL: Documentation, https://connectrpc.com/docs/python/getting-started/
+Project-URL: Homepage, https://github.com/connectrpc/connect-py
+Project-URL: Issues, https://github.com/connectrpc/connect-py/issues
+Project-URL: Repository, https://github.com/connectrpc/connect-py
+Description-Content-Type: text/markdown
+
+<div align="center">
+
+![The Connect logo](https://raw.githubusercontent.com/connectrpc/connectrpc.com/12f7ad8e95c5f784700bc280708b27cd148d0cf1/public/img/logos/simple-connect.svg)
+
+# Connect for Python
+
+[![PyPI version](https://img.shields.io/pypi/v/connectrpc?style=flat-square)](https://pypi.org/project/connectrpc)
+[![License](https://img.shields.io/pypi/l/connectrpc?style=flat-square)](https://github.com/connectrpc/connect-py/blob/main/LICENSE)
+[![Slack](https://img.shields.io/badge/slack-buf-%23e01e5a?style=flat-square)](https://buf.build/links/slack)
+
+Connect is the **easiest way to build modern APIs**.
+
+[Docs](https://connectrpc.com/docs/python/getting-started/) •
+[Example](https://github.com/connectrpc/connect-py/tree/main/example) •
+[New to Connect?](https://connectrpc.com/)
+
+</div>
+
+## Why Connect?
+
+Building an API usually means writing the same types at least twice: once on the server, and once in every client. Connect makes this simple - define your API schema using Protobuf, and Connect generates type-safe server stubs and idiomatic client libraries in **every major language**, including for your frontend. All that's left to write is your business logic, in plain Python.
+
+Connect already works with your current tech stack:
+
+- Connect is built on top of HTTP and speaks both Protobuf and JSON, so `curl` works out of the box.
+- It interoperates seamlessly with gRPC and gRPC-Web, and supports streaming as a first-class feature.
+- It runs on WSGI/ASGI. Build Connect APIs *alongside* your current API framework - all in the same app.
+
+## Features
+
+- **Servers:** WSGI and ASGI-ready, use with any [compatible server](https://connectrpc.com/docs/python/deployment/)
+- **Clients:** Lightweight sync and async clients, backed by `pyqwest`
+- **Protocols:** Supports Connect, gRPC, and gRPC-Web (HTTP/1.1 and HTTP/2)
+- **Type safety:** Fully type-annotated generated code
+- **Streaming:** Full support for server, client, and bidirectional streaming
+- **Compression:** Built-in support for gzip, brotli, and zstd
+- **Middleware:** Server- and client-side interceptors for telemetry, logging, etc.
+- **Compliant:** Verified using the official [Connect conformance](https://github.com/connectrpc/conformance) test suite
+
+## Getting started
+
+Install the runtime library:
+
+```bash
+uv add connectrpc
+```
+
+For codegen, install [`buf`](https://github.com/bufbuild/buf) and create `buf.gen.yaml`:
+
+```yaml
+version: v2
+plugins:
+  - remote: buf.build/bufbuild/py
+    out: gen
+  - remote: buf.build/connectrpc/py
+    out: gen
+```
+
+<details>
+
+<summary><b>Local plugin setup</b></summary>
+
+The example above uses a Buf-hosted plugin server. To generate code entirely locally, install the relevant plugins:
+
+```bash
+uv add --dev protoc-gen-py protoc-gen-connectrpc
+```
+
+Now edit your `buf.gen.yaml`:
+
+```yaml
+version: v2
+plugins:
+  - local: .venv/bin/protoc-gen-py
+    out: gen
+  - local: .venv/bin/protoc-gen-connectrpc
+    out: gen
+```
+
+</details>
+
+<details>
+
+<summary><b>Compatibility with <code>google-protobuf</code></b></summary>
+
+Connect defaults to targeting [protobuf-py](https://protobufpy.com) as the Protocol Buffers implementation, but it also supports Google's Protocol Buffers for Python. Pass `protobuf=google` to the codegen plugin to use it.
+
+```yaml
+version: v2
+plugins:
+  - remote: buf.build/protocolbuffers/python
+    out: .
+  - remote: buf.build/protocolbuffers/pyi
+    out: .
+  - remote: buf.build/connectrpc/py
+    out: .
+    opt: protobuf=google
+```
+
+If configuring a client for JSON codec, make sure to pass `connectrpc.compat.google_protobuf_json_codec` instead of `connectrpc.codec.proto_json_codec`.
+
+</details>
+
+## Usage
+
+A basic Connect server is easy to set up. Just import the stubs, subclass the generated service, and serve:
+
+```python
+from connectrpc.request import RequestContext
+from your_service_pb import HelloRequest, HelloResponse
+from your_service_connect import HelloService, HelloServiceASGIApplication
+
+class MyHelloService(HelloService):
+    async def say_hello(self, request: HelloRequest, ctx: RequestContext) -> HelloResponse:
+        return HelloResponse(message=f"Hello, {request.name}!")
+
+# Create ASGI app
+app = HelloServiceASGIApplication(MyHelloService())
+
+# Run with any ASGI server, e.g. uvicorn:
+# uvicorn server:app --port 8080
+```
+
+Client libraries are automatically generated for you. Here's what the async client looks like:
+
+```python
+from your_service_pb import HelloRequest, HelloResponse
+from your_service_connect import HelloServiceClient
+
+async def main():
+    # Create async client
+    async with HelloServiceClient("https://api.example.com") as client:
+        response = await client.say_hello(HelloRequest(name="World"))
+        print(response.message)  # "Hello, World!"
+```
+
+And the sync client:
+
+```python
+from your_service_pb import HelloRequest
+from your_service_connect import HelloServiceClientSync
+
+def main():
+    # Create sync client
+    with HelloServiceClientSync("https://api.example.com") as client:
+        response = client.say_hello(HelloRequest(name="World"))
+        print(response.message)  # "Hello, World!"
+
+if __name__ == "__main__":
+    main()
+```
+
+Check out [the docs](https://connectrpc.com/docs/python/getting-started/) for more detailed usage.
+
+- [**Streaming:**](https://connectrpc.com/docs/python/streaming/) Connect supports server-side, client-side, and bidirectional streaming.
+- [**Interceptors:**](https://connectrpc.com/docs/python/interceptors/) Set up middleware for logging, observability, and metrics.
+- [**Other languages:**](https://connectrpc.com/docs/introduction/) Generate clients for use in other languages.

+ 46 - 0
vendor/connectrpc-0.11.1.dist-info/RECORD

@@ -0,0 +1,46 @@
+connectrpc-0.11.1.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
+connectrpc-0.11.1.dist-info/METADATA,sha256=q-Es8E6XOgZdMt3yqaFdN2RBLW5oI6R6mAUVY2OJyeY,7143
+connectrpc-0.11.1.dist-info/RECORD,,
+connectrpc-0.11.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+connectrpc-0.11.1.dist-info/WHEEL,sha256=CoDSoyhtC_eO_tlxRYzsTraPv1fPJRXFx91k6ISeAvA,81
+connectrpc-0.11.1.dist-info/licenses/LICENSE,sha256=8mANIla78Y2BKoEjfDX9aO9Xa-t5eFN2upJGI7Bcv3E,12583
+connectrpc/__init__.py,sha256=UW1Z2zrnvvysjz1HaJ_f9CIKMgtf_yDLlnoGXBDDX30,98
+connectrpc/_asyncio_timeout.py,sha256=YVymRrn0n2PhP0HKSNRUav2k03EzraSEeNJgjZPaYJA,5643
+connectrpc/_client_async.py,sha256=IBpuHfzEGZ3V28MmUCScf1eztOx63mLTIdm6AL_1SvY,17822
+connectrpc/_client_shared.py,sha256=iD35h3_J-OWEwB_Mt_DzRTaLWwsfe_bjx7n-a0cVXlg,2489
+connectrpc/_client_sync.py,sha256=kWSWnq-vLfbOvLamH-I-_p83cfxVwN3EJNX0ikJaSiQ,17944
+connectrpc/_codec.py,sha256=mMj9P6kCcq7hRyyR_VBzvTAdXWqNGnklrw_8hTaSjts,3035
+connectrpc/_compression.py,sha256=taIrudYarkjI3hdBNSEr7SK2e8mvdbYBUjOnDbrjbmw,1446
+connectrpc/_envelope.py,sha256=oUzv7SDTzu8RvFR0I5d9cjiay-i89XieUGANd1edQHo,4224
+connectrpc/_gen/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
+connectrpc/_gen/google/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
+connectrpc/_gen/google/rpc/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
+connectrpc/_gen/google/rpc/status_pb.py,sha256=5WslLPoQnlnVHaOd59oOcnyBlABCaJVnvWhB25xVYQ0,2799
+connectrpc/_headers.py,sha256=pvGRSvWGRVdEPQBxDksUvvq80XwNPKkupQjgZjPoTNM,4672
+connectrpc/_interceptor_async.py,sha256=HiEzK0Iz2yjKnO1rt2cQFjpaLQ9s2MYGoJ_hL8UxEDU,7810
+connectrpc/_interceptor_sync.py,sha256=t5-qk9FqoONXWX3j1ODcfzMP7I8rukVkzMTs0vBiehI,7659
+connectrpc/_protocol.py,sha256=hAo4eCXSRh3mF0IHHKWEnd3E8-Kc3mEja-tPvFxo4X8,10122
+connectrpc/_protocol_connect.py,sha256=C2Vj5fqItgB_-w-YncxeGqYJwiJ363ANFNHDGmP9QCs,12330
+connectrpc/_protocol_grpc.py,sha256=Y43q4veeRxGRY4xltPcGYBDhsODG_ZnE5nqYdEvhxUY,15662
+connectrpc/_protocol_server.py,sha256=eKZgByCX7hBwfuxAw5VyUO7yLVk9GH11WOTe_AKCDJo,931
+connectrpc/_response_metadata.py,sha256=VBU2oMSrGLXiBseaYRt-_Pf1eehqWEWt_gN3Pc0s7y4,3474
+connectrpc/_server_async.py,sha256=-17u9yBQZa_EvHJCXrc62LO7uq9v2mHV_WkdIjGRyeE,25560
+connectrpc/_server_shared.py,sha256=LswqCKWoXCzv9y3vuhAESlFvx6F10K7DtEO4OZzlYQU,4358
+connectrpc/_server_sync.py,sha256=duAbeftS57gCYSybHOyqcpkF3u8XtX8cR8J8MCnA8l4,25179
+connectrpc/_version.py,sha256=Qvv_qbKDWHivrwDqBFSkOorxzLriDfpbIzikCyFmaok,112
+connectrpc/client.py,sha256=p7wgc15jNINMiiiIILlS4JHrPiPcBlZyEDBVroRutjw,241
+connectrpc/code.py,sha256=V8-B9UotxsToYmG87MW4cnAPsCR0KyjPlNVFxs3QRec,2102
+connectrpc/codec.py,sha256=vP6OFdXcXeb3niFIG_UYK2-G3oD5_v_ApG4v5Ln5xzM,163
+connectrpc/compat/__init__.py,sha256=dhu7TDN_4jY5iqx_1CzmTkj4nPrlJ3bt7WgZXpCcMV0,269
+connectrpc/compat/_codec.py,sha256=6O0n3QHX0qL5SAvxromYTXIy0jaFoRHArNlK-VVRHu8,2018
+connectrpc/compression/__init__.py,sha256=o17OV74BKEhCYWIZojFZjiPi2FJCJADDdqDec5ZAIjk,1037
+connectrpc/compression/brotli.py,sha256=87S92Kyv6D8gL9zlOxUk97turPw6SIrm-cklZVFZg88,697
+connectrpc/compression/gzip.py,sha256=sFsG9_zfQVXIQ_ni5u3BpziKiYm9tcTbAUq7EvIYCHk,648
+connectrpc/compression/zstd.py,sha256=dy-A7sL9TyanY6r7gOXjbFV03OI6RfpCFpdXC6Z3i-U,857
+connectrpc/errors.py,sha256=r4hvgHp6qxKq3mOktjVN2r5QCTPeLAYbezVESGywAaw,3362
+connectrpc/interceptor.py,sha256=9f2N_A8h1Db4JJpCs-Erm4KiL3aEmf7s9T41QMEi0Qo,791
+connectrpc/method.py,sha256=mM7k5vzGj8zKw3nc9x067NMJt2ICAMZaJGtrm-WESXY,2534
+connectrpc/protocol.py,sha256=7_wD_XNOE4P3CZfr6mA1nT1-WVHiTQbvaykQpspU89c,272
+connectrpc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+connectrpc/request.py,sha256=6yrIU5SxH2GLds_eY1aYYxUubrEL_IgArgvZrXY1CvA,3563
+connectrpc/server.py,sha256=ch78mXS4enpcKIsNshn435Zac5xKVU1UJpjIVPcrp8A,298

+ 0 - 0
vendor/connectrpc-0.11.1.dist-info/REQUESTED


+ 4 - 0
vendor/connectrpc-0.11.1.dist-info/WHEEL

@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: uv 0.11.28
+Root-Is-Purelib: true
+Tag: py3-none-any

+ 219 - 0
vendor/connectrpc-0.11.1.dist-info/licenses/LICENSE

@@ -0,0 +1,219 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (which shall not include communications that are solely limited to
+      and are identified as being limited to a portion of the work).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based upon (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and derivative works thereof.
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been made to Licensor. For the
+      purposes of this definition, "Contribution" shall mean any work of
+      authorship, including the original version of the Work and any
+      modifications or additions to that Work or Derivative Works thereof,
+      that is intentionally submitted to Licensor for inclusion in the Work
+      by the copyright owner or by an individual or Legal Entity authorized
+      to submit on behalf of the copyright owner. For the purposes of this
+      definition, "submitted" means any form of electronic, verbal, or
+      written communication sent to the Licensor or its representatives,
+      including but not limited to communication on electronic mailing
+      lists, source code control systems, and issue tracking systems that
+      are managed by, or on behalf of, the Licensor for the purpose of
+      discussing and improving the Work, but excluding communication that
+      is conspicuously marked or otherwise designated in writing by the
+      copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been made to Licensor. For the
+      purposes of this definition, "Contribution" shall mean any work of
+      authorship, including the original version of the Work and any
+      modifications or additions to that Work or Derivative Works thereof,
+      that is intentionally submitted to Licensor for inclusion in the Work
+      by the copyright owner or by an individual or Legal Entity authorized
+      to submit on behalf of the copyright owner. For the purposes of this
+      definition, "submitted" means any form of electronic, verbal, or
+      written communication sent to the Licensor or its representatives,
+      including but not limited to communication on electronic mailing
+      lists, source code control systems, and issue tracking systems that
+      are managed by, or on behalf of, the Licensor for the purpose of
+      discussing and improving the Work, but excluding communication that
+      is conspicuously marked or otherwise designated in writing by the
+      copyright owner as "Not a Contribution."
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to use, reproduce, modify, distribute, and prepare
+      Derivative Works of, publicly display, publicly perform, sublicense,
+      and distribute the Work and such Derivative Works in source or object
+      form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in source or object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, trademark, patent,
+          attribution and other notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright notice to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Support. Unless agreed to otherwise,
+      no Contributor is required to provide support, warranties,
+      indemnities, or other obligations to You. However, if You elect to
+      provide support, warranties, indemnities, or other obligations to
+      one or more recipients of Covered Software, You may do so only on
+      Your own behalf, and not on behalf of any Contributor. You must make
+      it absolutely clear that any such warranty, support, indemnity, or
+      other obligation is offered by You alone, and You hereby agree to
+      indemnify every Contributor for any liability incurred by such
+      Contributor as a result of warranty, support, indemnity or other
+      obligation You offer.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2025 The Connect Authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 6 - 0
vendor/connectrpc/__init__.py

@@ -0,0 +1,6 @@
+from __future__ import annotations
+
+__all__ = ["__version__"]
+
+
+from ._version import __version__

+ 166 - 0
vendor/connectrpc/_asyncio_timeout.py

@@ -0,0 +1,166 @@
+# Copyright © 2001 Python Software Foundation. All rights reserved.
+# SPDX-License-Identifier: PSF-2.0
+
+# Backport of asyncio.timeout for Python 3.10
+from __future__ import annotations
+
+import enum
+import sys
+from asyncio import events, exceptions, tasks
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from types import TracebackType
+
+_HAX_EXCEPTION_GROUP = sys.version_info >= (3, 11)
+
+
+class _State(enum.Enum):
+    CREATED = "created"
+    ENTERED = "active"
+    EXPIRING = "expiring"
+    EXPIRED = "expired"
+    EXITED = "finished"
+
+
+# Modifications - we don't track task cancellation on enter.
+
+
+class Timeout:
+    """Asynchronous context manager for cancelling overdue coroutines.
+
+    Use `timeout()` or `timeout_at()` rather than instantiating this class directly.
+    """
+
+    def __init__(self, when: float | None) -> None:
+        """Schedule a timeout that will trigger at a given loop time.
+
+        - If `when` is `None`, the timeout will never trigger.
+        - If `when < loop.time()`, the timeout will trigger on the next
+          iteration of the event loop.
+        """
+        self._state = _State.CREATED
+
+        self._timeout_handler: events.Handle | None = None
+        self._task: tasks.Task | None = None
+        self._when = when
+
+    def when(self) -> float | None:
+        """Return the current deadline."""
+        return self._when
+
+    def reschedule(self, when: float | None) -> None:
+        """Reschedule the timeout."""
+        if self._state is not _State.ENTERED:
+            if self._state is _State.CREATED:
+                msg = "Timeout has not been entered"
+                raise RuntimeError(msg)
+            msg = f"Cannot change state of {self._state.value} Timeout"
+            raise RuntimeError(msg)
+
+        self._when = when
+
+        if self._timeout_handler is not None:
+            self._timeout_handler.cancel()
+
+        if when is None:
+            self._timeout_handler = None
+        else:
+            loop = events.get_running_loop()
+            if when <= loop.time():
+                self._timeout_handler = loop.call_soon(self._on_timeout)
+            else:
+                self._timeout_handler = loop.call_at(when, self._on_timeout)
+
+    def expired(self) -> bool:
+        """Is timeout expired during execution?"""
+        return self._state in (_State.EXPIRING, _State.EXPIRED)
+
+    def __repr__(self) -> str:
+        info = [""]
+        if self._state is _State.ENTERED:
+            when = round(self._when, 3) if self._when is not None else None
+            info.append(f"when={when}")
+        info_str = " ".join(info)
+        return f"<Timeout [{self._state.value}]{info_str}>"
+
+    async def __aenter__(self) -> Timeout:
+        if self._state is not _State.CREATED:
+            msg = "Timeout has already been entered"
+            raise RuntimeError(msg)
+        task = tasks.current_task()
+        if task is None:
+            msg = "Timeout should be used inside a task"
+            raise RuntimeError(msg)
+        self._state = _State.ENTERED
+        self._task = task
+        self.reschedule(self._when)
+        return self
+
+    async def __aexit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> bool | None:
+        assert self._state in (_State.ENTERED, _State.EXPIRING)  # noqa: S101
+
+        if self._timeout_handler is not None:
+            self._timeout_handler.cancel()
+            self._timeout_handler = None
+
+        if self._state is _State.EXPIRING:
+            self._state = _State.EXPIRED
+
+            if exc_type is not None:
+                # Since there are no new cancel requests, we're
+                # handling this.
+                if issubclass(exc_type, exceptions.CancelledError):
+                    raise TimeoutError from exc_val
+                if exc_val is not None:
+                    self._insert_timeout_error(exc_val)
+                    if _HAX_EXCEPTION_GROUP and isinstance(exc_val, ExceptionGroup):  # noqa: F821
+                        for exc in exc_val.exceptions:
+                            self._insert_timeout_error(exc)
+        elif self._state is _State.ENTERED:
+            self._state = _State.EXITED
+
+        return None
+
+    def _on_timeout(self) -> None:
+        assert self._state is _State.ENTERED  # noqa: S101
+        assert self._task is not None  # noqa: S101
+        self._task.cancel()
+        self._state = _State.EXPIRING
+        # drop the reference early
+        self._timeout_handler = None
+
+    @staticmethod
+    def _insert_timeout_error(exc_val: BaseException) -> None:
+        while exc_val.__context__ is not None:
+            if isinstance(exc_val.__context__, exceptions.CancelledError):
+                te = TimeoutError()
+                te.__context__ = te.__cause__ = exc_val.__context__
+                exc_val.__context__ = te
+                break
+            exc_val = exc_val.__context__
+
+
+def timeout(delay: float | None) -> Timeout:
+    """Timeout async context manager.
+
+    Useful in cases when you want to apply timeout logic around block
+    of code or in cases when asyncio.wait_for is not suitable. For example:
+
+    >>> async with asyncio.timeout(10):  # 10 seconds timeout
+    ...     await long_running_task()
+
+
+    delay - value in seconds or None to disable timeout logic
+
+    long_running_task() is interrupted by raising asyncio.CancelledError,
+    the top-most affected timeout() context manager converts CancelledError
+    into TimeoutError.
+    """
+    loop = events.get_running_loop()
+    return Timeout(loop.time() + delay if delay is not None else None)

+ 467 - 0
vendor/connectrpc/_client_async.py

@@ -0,0 +1,467 @@
+from __future__ import annotations
+
+import asyncio
+import functools
+import sys
+from asyncio import CancelledError, sleep, wait_for
+from typing import TYPE_CHECKING, Any, Protocol, TypeVar
+from urllib.parse import urlencode
+
+from pyqwest import Client as HTTPClient
+from pyqwest import FullResponse, Response
+from pyqwest import Headers as HTTPHeaders
+
+from . import _client_shared
+from ._codec import proto_binary_codec
+from ._compression import IdentityCompression, _gzip, resolve_compressions
+from ._interceptor_async import (
+    BidiStreamInterceptor,
+    ClientStreamInterceptor,
+    Interceptor,
+    ServerStreamInterceptor,
+    UnaryInterceptor,
+    resolve_interceptors,
+)
+from ._protocol import ConnectWireError
+from ._protocol_connect import ConnectClientProtocol, ConnectEnvelopeWriter
+from ._protocol_grpc import GRPCClientProtocol, GRPCWebClientProtocol
+from ._response_metadata import handle_response_headers
+from .code import Code
+from .errors import ConnectError
+from .protocol import ProtocolType
+
+if sys.version_info >= (3, 11):
+    from asyncio import timeout as asyncio_timeout
+else:
+    from ._asyncio_timeout import timeout as asyncio_timeout
+
+if TYPE_CHECKING:
+    from collections.abc import AsyncIterator, Iterable, Mapping
+    from types import TracebackType
+
+    from ._envelope import EnvelopeReader
+    from .codec import Codec
+    from .compression import Compression
+    from .method import MethodInfo
+    from .request import Headers, RequestContext
+
+    if sys.version_info >= (3, 11):
+        from typing import Self
+    else:
+        from typing_extensions import Self
+else:
+    Self = "Self"
+
+REQ = TypeVar("REQ")
+RES = TypeVar("RES")
+
+
+class _ExecuteUnary(Protocol[REQ, RES]):
+    async def __call__(self, request: REQ, ctx: RequestContext[REQ, RES]) -> RES: ...
+
+
+class _ExecuteClientStream(Protocol[REQ, RES]):
+    async def __call__(
+        self, request: AsyncIterator[REQ], ctx: RequestContext[REQ, RES]
+    ) -> RES: ...
+
+
+class _ExecuteServerStream(Protocol[REQ, RES]):
+    def __call__(
+        self, request: REQ, ctx: RequestContext[REQ, RES]
+    ) -> AsyncIterator[RES]: ...
+
+
+class _ExecuteBidiStream(Protocol[REQ, RES]):
+    def __call__(
+        self, request: AsyncIterator[REQ], ctx: RequestContext[REQ, RES]
+    ) -> AsyncIterator[RES]: ...
+
+
+class ConnectClient:
+    """An asynchronous client for the Connect protocol."""
+
+    _execute_unary: _ExecuteUnary
+    _execute_client_stream: _ExecuteClientStream
+    _execute_server_stream: _ExecuteServerStream
+    _execute_bidi_stream: _ExecuteBidiStream
+
+    def __init__(
+        self,
+        address: str,
+        *,
+        codec: Codec | None = None,
+        protocol: ProtocolType = ProtocolType.CONNECT,
+        accept_compression: Iterable[Compression] | None = None,
+        send_compression: Compression | None = _gzip,
+        timeout_ms: int | None = None,
+        read_max_bytes: int | None = None,
+        interceptors: Iterable[Interceptor] = (),
+        http_client: HTTPClient | None = None,
+    ) -> None:
+        """Creates a new asynchronous Connect client.
+
+        When providing an HTTP client, for example to configure TLS settings,
+        it is the caller's responsibility to close it.
+
+        Examples:
+            ```python
+            from pyqwest import Client
+            from my_service import MyServiceClient
+
+            async with (
+                Client() as http_client,
+                MyServiceClient("http://localhost:8000", http_client=http_client) as client,
+            ):
+                # Use the client!
+            ```
+
+        Args:
+            address: The address of the server to connect to, including scheme.
+            codec: The [Codec][] to use for requests. If unset, defaults to binary protobuf.
+                   For JSON encoding, use [proto_json_codec][connectrpc.codec.proto_json_codec].
+            protocol: The [ProtocolType][] to use for requests.
+            accept_compression: Compression algorithms to accept from the server. If unset,
+                                defaults to gzip. If set to empty, disables response compression.
+            send_compression: Compression algorithm to use for sending requests. If unset,
+                              defaults to gzip. If set to None, disables request compression.
+            timeout_ms: The timeout for requests in milliseconds.
+            read_max_bytes: The maximum number of bytes to read from the response.
+            interceptors: A list of interceptors to apply to requests.
+            http_client: A pyqwest Client to use for requests.
+        """
+        self._address = address
+        self._codec = codec or proto_binary_codec()
+        self._response_compressions = resolve_compressions(accept_compression)
+        self._accept_compression_header = ",".join(self._response_compressions.keys())
+        self._send_compression = send_compression or IdentityCompression()
+        self._timeout_ms = timeout_ms
+        self._read_max_bytes = read_max_bytes
+        if http_client:
+            self._http_client = http_client
+        else:
+            # Use shared default transport if not specified
+            self._http_client = HTTPClient()
+        self._closed = False
+
+        match protocol:
+            case ProtocolType.CONNECT:
+                self._protocol = ConnectClientProtocol()
+            case ProtocolType.GRPC:
+                self._protocol = GRPCClientProtocol()
+            case ProtocolType.GRPC_WEB:
+                self._protocol = GRPCWebClientProtocol()
+
+        interceptors = resolve_interceptors(interceptors)
+        execute_unary = self._send_request_unary
+        for interceptor in (
+            i for i in reversed(interceptors) if isinstance(i, UnaryInterceptor)
+        ):
+            execute_unary = functools.partial(
+                interceptor.intercept_unary, execute_unary
+            )
+        self._execute_unary = execute_unary
+
+        execute_client_stream = self._send_request_client_stream
+        for interceptor in (
+            i for i in reversed(interceptors) if isinstance(i, ClientStreamInterceptor)
+        ):
+            execute_client_stream = functools.partial(
+                interceptor.intercept_client_stream, execute_client_stream
+            )
+        self._execute_client_stream = execute_client_stream
+
+        execute_server_stream: _ExecuteServerStream = self._send_request_server_stream
+        for interceptor in (
+            i for i in reversed(interceptors) if isinstance(i, ServerStreamInterceptor)
+        ):
+            execute_server_stream = functools.partial(
+                interceptor.intercept_server_stream, execute_server_stream
+            )
+        self._execute_server_stream = execute_server_stream
+
+        execute_bidi_stream = self._send_request_bidi_stream
+        for interceptor in (
+            i for i in reversed(interceptors) if isinstance(i, BidiStreamInterceptor)
+        ):
+            execute_bidi_stream = functools.partial(
+                interceptor.intercept_bidi_stream, execute_bidi_stream
+            )
+        self._execute_bidi_stream = execute_bidi_stream
+
+    async def close(self) -> None:
+        """Close the client. After closing, the client cannot be used to make requests."""
+        if not self._closed:
+            self._closed = True
+
+    async def __aenter__(self) -> Self:
+        return self
+
+    async def __aexit__(
+        self,
+        _exc_type: type[BaseException] | None,
+        _exc_value: BaseException | None,
+        _traceback: TracebackType | None,
+    ) -> None:
+        await self.close()
+
+    async def execute_unary(
+        self,
+        *,
+        request: REQ,
+        method: MethodInfo[REQ, RES],
+        headers: Headers | Mapping[str, str] | None = None,
+        timeout_ms: int | None = None,
+        use_get: bool = False,
+    ) -> RES:
+        ctx = self._protocol.create_request_context(
+            method=method,
+            url=self._address,
+            http_method="GET" if use_get else "POST",
+            user_headers=headers,
+            timeout_ms=timeout_ms or self._timeout_ms,
+            codec=self._codec,
+            stream=False,
+            accept_compression=self._accept_compression_header,
+            send_compression=self._send_compression,
+        )
+        return await self._execute_unary(request, ctx)
+
+    async def execute_client_stream(
+        self,
+        *,
+        request: AsyncIterator[REQ],
+        method: MethodInfo[REQ, RES],
+        headers: Headers | Mapping[str, str] | None = None,
+        timeout_ms: int | None = None,
+    ) -> RES:
+        ctx = self._protocol.create_request_context(
+            method=method,
+            url=self._address,
+            http_method="POST",
+            user_headers=headers,
+            timeout_ms=timeout_ms or self._timeout_ms,
+            codec=self._codec,
+            stream=True,
+            accept_compression=self._accept_compression_header,
+            send_compression=self._send_compression,
+        )
+        return await self._execute_client_stream(request, ctx)
+
+    def execute_server_stream(
+        self,
+        *,
+        request: REQ,
+        method: MethodInfo[REQ, RES],
+        headers: Headers | Mapping[str, str] | None = None,
+        timeout_ms: int | None = None,
+    ) -> AsyncIterator[RES]:
+        ctx = self._protocol.create_request_context(
+            method=method,
+            url=self._address,
+            http_method="POST",
+            user_headers=headers,
+            timeout_ms=timeout_ms or self._timeout_ms,
+            codec=self._codec,
+            stream=True,
+            accept_compression=self._accept_compression_header,
+            send_compression=self._send_compression,
+        )
+        return self._execute_server_stream(request, ctx)
+
+    def execute_bidi_stream(
+        self,
+        *,
+        request: AsyncIterator[REQ],
+        method: MethodInfo[REQ, RES],
+        headers: Headers | Mapping[str, str] | None = None,
+        timeout_ms: int | None = None,
+    ) -> AsyncIterator[RES]:
+        ctx = self._protocol.create_request_context(
+            method=method,
+            url=self._address,
+            http_method="POST",
+            user_headers=headers,
+            timeout_ms=timeout_ms or self._timeout_ms,
+            codec=self._codec,
+            stream=True,
+            accept_compression=self._accept_compression_header,
+            send_compression=self._send_compression,
+        )
+        return self._execute_bidi_stream(request, ctx)
+
+    async def _send_request_unary(
+        self, request: REQ, ctx: RequestContext[REQ, RES]
+    ) -> RES:
+        if isinstance(self._protocol, GRPCClientProtocol):
+            return await _consume_single_response(
+                self._send_request_bidi_stream(_yield_single_message(request), ctx)
+            )
+
+        request_headers = HTTPHeaders(ctx.request_headers.allitems())
+        url = f"{self._address}/{ctx.method.service_name}/{ctx.method.name}"
+        if (timeout_ms := ctx.timeout_ms) is not None:
+            timeout_s = timeout_ms / 1000.0
+        else:
+            timeout_s = None
+
+        try:
+            request_data = self._codec.encode(request)
+            if self._send_compression:
+                request_data = self._send_compression.compress(request_data)
+
+            if ctx.http_method == "GET":
+                params = _client_shared.prepare_get_params(
+                    self._codec, request_data, request_headers
+                )
+                params_str = urlencode(params)
+                url = f"{url}?{params_str}"
+                request_headers.pop("content-type", None)
+                resp = await wait_for(
+                    self._http_client.get(url=url, headers=request_headers), timeout_s
+                )
+            else:
+                resp = await wait_for(
+                    self._http_client.post(
+                        url=url, headers=request_headers, content=request_data
+                    ),
+                    timeout_s,
+                )
+
+            self._protocol.validate_response(
+                self._codec.name(), resp.status, resp.headers.get("content-type", "")
+            )
+            # Decompression itself is handled by pyqwest, but we validate it
+            # by resolving it.
+            self._protocol.handle_response_compression(
+                resp.headers, self._response_compressions, stream=False
+            )
+            handle_response_headers(resp.headers)
+
+            if resp.status == 200:
+                if (
+                    self._read_max_bytes is not None
+                    and len(resp.content) > self._read_max_bytes
+                ):
+                    raise ConnectError(
+                        Code.RESOURCE_EXHAUSTED,
+                        f"message is larger than configured max {self._read_max_bytes}",
+                    )
+
+                return self._codec.decode(resp.content, ctx.method.output)
+            raise ConnectWireError.from_response(resp).to_exception()
+        except (TimeoutError, asyncio.TimeoutError) as e:
+            raise ConnectError(Code.DEADLINE_EXCEEDED, "Request timed out") from e
+        except ConnectError:
+            raise
+        except CancelledError as e:
+            raise ConnectError(Code.CANCELED, "Request was cancelled") from e
+        except Exception as e:
+            raise ConnectError(Code.UNAVAILABLE, str(e)) from e
+
+    async def _send_request_client_stream(
+        self, request: AsyncIterator[REQ], ctx: RequestContext[REQ, RES]
+    ) -> RES:
+        return await _consume_single_response(
+            self._send_request_bidi_stream(request, ctx)
+        )
+
+    def _send_request_server_stream(
+        self, request: REQ, ctx: RequestContext[REQ, RES]
+    ) -> AsyncIterator[RES]:
+        return self._send_request_bidi_stream(_yield_single_message(request), ctx)
+
+    async def _send_request_bidi_stream(
+        self, request: AsyncIterator[REQ], ctx: RequestContext[REQ, RES]
+    ) -> AsyncIterator[RES]:
+        request_headers = HTTPHeaders(ctx.request_headers.allitems())
+        url = f"{self._address}/{ctx.method.service_name}/{ctx.method.name}"
+        if (timeout_ms := ctx.timeout_ms) is not None:
+            timeout_s = timeout_ms / 1000.0
+        else:
+            timeout_s = None
+
+        reader: EnvelopeReader | None = None
+        resp: Response | None = None
+        try:
+            request_data = _streaming_request_content(
+                request, self._codec, self._send_compression
+            )
+
+            async with (
+                asyncio_timeout(timeout_s),
+                self._http_client.stream(
+                    "POST", url, headers=request_headers, content=request_data
+                ) as resp,
+            ):
+                handle_response_headers(resp.headers)
+                if resp.status == 200:
+                    self._protocol.validate_stream_response(
+                        self._codec.name(), resp.headers.get("content-type", "")
+                    )
+                    compression = self._protocol.handle_response_compression(
+                        resp.headers, self._response_compressions, stream=True
+                    )
+                    reader = self._protocol.create_envelope_reader(
+                        ctx.method.output,
+                        self._codec,
+                        compression,
+                        self._read_max_bytes,
+                    )
+                    async for chunk in resp.content:
+                        for message in reader.feed(bytes(chunk)):
+                            yield message
+                            # Check for cancellation each message. While this seems heavyweight,
+                            # conformance tests require it.
+                            await sleep(0)
+                    reader.handle_response_complete(resp)
+                else:
+                    content = bytearray()
+                    async for chunk in resp.content:
+                        content.extend(chunk)
+                    fres = FullResponse(
+                        status=resp.status,
+                        headers=resp.headers,
+                        content=bytes(content),
+                        trailers=resp.trailers,
+                    )
+                    raise ConnectWireError.from_response(fres).to_exception()
+        except (TimeoutError, asyncio.TimeoutError) as e:
+            raise ConnectError(Code.DEADLINE_EXCEEDED, "Request timed out") from e
+        except ConnectError:
+            raise
+        except CancelledError as e:
+            raise ConnectError(Code.CANCELED, "Request was cancelled") from e
+        except Exception as e:
+            if rst_err := _client_shared.maybe_map_stream_reset(e, ctx):
+                # It is possible for a reset to come with trailers which should
+                # be used.
+                if reader and resp:
+                    reader.handle_response_complete(resp, rst_err)
+                raise rst_err from e
+            raise ConnectError(Code.UNAVAILABLE, str(e)) from e
+
+
+async def _streaming_request_content(
+    msgs: AsyncIterator[Any], codec: Codec, compression: Compression | None
+) -> AsyncIterator[bytes]:
+    writer = ConnectEnvelopeWriter(codec, compression)
+    async for msg in msgs:
+        yield writer.write(msg)
+
+
+async def _yield_single_message(message: REQ) -> AsyncIterator[REQ]:
+    yield message
+
+
+async def _consume_single_response(stream: AsyncIterator[RES]) -> RES:
+    res = None
+    async for message in stream:
+        if res is not None:
+            raise ConnectError(
+                Code.UNIMPLEMENTED, "unary response has multiple messages"
+            )
+        res = message
+    if res is None:
+        raise ConnectError(Code.UNIMPLEMENTED, "unary response has zero messages")
+    return res

+ 69 - 0
vendor/connectrpc/_client_shared.py

@@ -0,0 +1,69 @@
+from __future__ import annotations
+
+import base64
+from typing import TYPE_CHECKING, TypeVar
+
+from pyqwest import Headers as HTTPHeaders
+from pyqwest import StreamError, StreamErrorCode
+
+from ._protocol_connect import CONNECT_PROTOCOL_VERSION
+from .code import Code
+from .errors import ConnectError
+
+if TYPE_CHECKING:
+    from ._codec import Codec
+    from .request import RequestContext
+
+
+REQ = TypeVar("REQ")
+RES = TypeVar("RES")
+
+
+def prepare_get_params(
+    codec: Codec, request_data: bytes, headers: HTTPHeaders
+) -> dict[str, str]:
+    # Follow order in spec: https://connectrpc.com/docs/protocol/#unary-get-request
+    params: dict[str, str] = {"connect": f"v{CONNECT_PROTOCOL_VERSION}", "base64": "1"}
+    if "content-encoding" in headers:
+        params["compression"] = headers.pop("content-encoding")
+    params["encoding"] = codec.name()
+    params["message"] = base64.urlsafe_b64encode(request_data).decode("ascii")
+    return params
+
+
+# https://github.com/connectrpc/connect-go/blob/59cc6973156cd9164d6bea493b1d106ed894f2df/error.go#L393
+def maybe_map_stream_reset(
+    e: Exception, ctx: RequestContext[REQ, RES]
+) -> ConnectError | None:
+    if not isinstance(e, StreamError):
+        return None
+
+    msg = str(e)
+    match e.code:
+        case (
+            StreamErrorCode.NO_ERROR
+            | StreamErrorCode.PROTOCOL_ERROR
+            | StreamErrorCode.INTERNAL_ERROR
+            | StreamErrorCode.FLOW_CONTROL_ERROR
+            | StreamErrorCode.SETTINGS_TIMEOUT
+            | StreamErrorCode.FRAME_SIZE_ERROR
+            | StreamErrorCode.COMPRESSION_ERROR
+            | StreamErrorCode.CONNECT_ERROR
+        ):
+            return ConnectError(Code.INTERNAL, msg)
+        case StreamErrorCode.REFUSED_STREAM:
+            return ConnectError(Code.UNAVAILABLE, msg)
+        case StreamErrorCode.CANCEL:
+            # Some servers use CANCEL when deadline expires. We can't differentiate
+            # that from normal cancel without checking our own deadline.
+            if (t := ctx.timeout_ms) is not None and t <= 0:
+                return ConnectError(Code.DEADLINE_EXCEEDED, msg)
+            return ConnectError(Code.CANCELED, msg)
+        case StreamErrorCode.ENHANCE_YOUR_CALM:
+            return ConnectError(Code.RESOURCE_EXHAUSTED, f"Bandwidth exhausted: {msg}")
+        case StreamErrorCode.INADEQUATE_SECURITY:
+            return ConnectError(
+                Code.PERMISSION_DENIED, f"Transport protocol insecure: {msg}"
+            )
+
+    return None

+ 472 - 0
vendor/connectrpc/_client_sync.py

@@ -0,0 +1,472 @@
+from __future__ import annotations
+
+import functools
+from typing import TYPE_CHECKING, Any, Protocol, TypeVar
+from urllib.parse import urlencode
+
+from pyqwest import FullResponse, SyncClient, SyncResponse
+from pyqwest import Headers as HTTPHeaders
+
+from connectrpc._protocol_grpc import GRPCClientProtocol, GRPCWebClientProtocol
+
+from . import _client_shared
+from ._codec import proto_binary_codec
+from ._compression import IdentityCompression, _gzip, resolve_compressions
+from ._interceptor_sync import (
+    BidiStreamInterceptorSync,
+    ClientStreamInterceptorSync,
+    InterceptorSync,
+    ServerStreamInterceptorSync,
+    UnaryInterceptorSync,
+    resolve_interceptors,
+)
+from ._protocol import ConnectWireError
+from ._protocol_connect import ConnectClientProtocol, ConnectEnvelopeWriter
+from ._response_metadata import handle_response_headers
+from .code import Code
+from .errors import ConnectError
+from .protocol import ProtocolType
+
+if TYPE_CHECKING:
+    import sys
+    from collections.abc import Iterable, Iterator, Mapping
+    from types import TracebackType
+
+    from ._envelope import EnvelopeReader
+    from .codec import Codec
+    from .compression import Compression
+    from .method import MethodInfo
+    from .request import Headers, RequestContext
+
+    if sys.version_info >= (3, 11):
+        from typing import Self
+    else:
+        from typing_extensions import Self
+else:
+    Self = "Self"
+
+REQ = TypeVar("REQ")
+RES = TypeVar("RES")
+
+
+class _ExecuteUnary(Protocol[REQ, RES]):
+    def __call__(self, request: REQ, ctx: RequestContext[REQ, RES]) -> RES: ...
+
+
+class _ExecuteClientStream(Protocol[REQ, RES]):
+    def __call__(
+        self, request: Iterator[REQ], ctx: RequestContext[REQ, RES]
+    ) -> RES: ...
+
+
+class _ExecuteServerStream(Protocol[REQ, RES]):
+    def __call__(
+        self, request: REQ, ctx: RequestContext[REQ, RES]
+    ) -> Iterator[RES]: ...
+
+
+class _ExecuteBidiStream(Protocol[REQ, RES]):
+    def __call__(
+        self, request: Iterator[REQ], ctx: RequestContext[REQ, RES]
+    ) -> Iterator[RES]: ...
+
+
+class ConnectClientSync:
+    """A synchronous client for the Connect protocol."""
+
+    _execute_unary: _ExecuteUnary
+    _execute_client_stream: _ExecuteClientStream
+    _execute_server_stream: _ExecuteServerStream
+    _execute_bidi_stream: _ExecuteBidiStream
+
+    def __init__(
+        self,
+        address: str,
+        *,
+        codec: Codec | None = None,
+        protocol: ProtocolType = ProtocolType.CONNECT,
+        accept_compression: Iterable[Compression] | None = None,
+        send_compression: Compression | None = _gzip,
+        timeout_ms: int | None = None,
+        read_max_bytes: int | None = None,
+        interceptors: Iterable[InterceptorSync] = (),
+        http_client: SyncClient | None = None,
+    ) -> None:
+        """Creates a new synchronous Connect client.
+
+        When providing an HTTP client, for example to configure TLS settings,
+        it is the caller's responsibility to close it.
+
+        Examples:
+            ```python
+            from pyqwest import SyncClient
+            from my_service import MyServiceClientSync
+
+            with (
+                SyncClient() as http_client,
+                MyServiceClientSync("http://localhost:8000", http_client=http_client) as client,
+            ):
+                # Use the client!
+            ```
+
+        Args:
+            address: The address of the server to connect to, including scheme.
+            codec: The [Codec][] to use for requests. If unset, defaults to binary protobuf.
+                   For JSON encoding, use [proto_json_codec][connectrpc.codec.proto_json_codec].
+            protocol: The [ProtocolType][] to use for requests.
+            accept_compression: Compression algorithms to accept from the server. If unset,
+                                defaults to gzip. If set to empty, disables response compression.
+            send_compression: Compression algorithm to use for sending requests. If unset,
+                              defaults to gzip. If set to None, disables request compression.
+            timeout_ms: The timeout for requests in milliseconds.
+            read_max_bytes: The maximum number of bytes to read from the response.
+            interceptors: A list of interceptors to apply to requests.
+            http_client: A pyqwest SyncClient to use for requests.
+        """
+        self._address = address
+        self._codec = codec or proto_binary_codec()
+        self._timeout_ms = timeout_ms
+        self._read_max_bytes = read_max_bytes
+        self._response_compressions = resolve_compressions(accept_compression)
+        self._accept_compression_header = ",".join(self._response_compressions.keys())
+        self._send_compression = send_compression or IdentityCompression()
+        if http_client:
+            self._http_client = http_client
+        else:
+            # Use shared default transport if not specified
+            self._http_client = SyncClient()
+        self._closed = False
+
+        match protocol:
+            case ProtocolType.CONNECT:
+                self._protocol = ConnectClientProtocol()
+            case ProtocolType.GRPC:
+                self._protocol = GRPCClientProtocol()
+            case ProtocolType.GRPC_WEB:
+                self._protocol = GRPCWebClientProtocol()
+
+        interceptors = resolve_interceptors(interceptors)
+        execute_unary = self._send_request_unary
+        for interceptor in (
+            i for i in reversed(interceptors) if isinstance(i, UnaryInterceptorSync)
+        ):
+            execute_unary = functools.partial(
+                interceptor.intercept_unary_sync, execute_unary
+            )
+        self._execute_unary = execute_unary
+
+        execute_client_stream = self._send_request_client_stream
+        for interceptor in (
+            i
+            for i in reversed(interceptors)
+            if isinstance(i, ClientStreamInterceptorSync)
+        ):
+            execute_client_stream = functools.partial(
+                interceptor.intercept_client_stream_sync, execute_client_stream
+            )
+        self._execute_client_stream = execute_client_stream
+
+        execute_server_stream: _ExecuteServerStream = self._send_request_server_stream
+        for interceptor in (
+            i
+            for i in reversed(interceptors)
+            if isinstance(i, ServerStreamInterceptorSync)
+        ):
+            execute_server_stream = functools.partial(
+                interceptor.intercept_server_stream_sync, execute_server_stream
+            )
+        self._execute_server_stream = execute_server_stream
+
+        execute_bidi_stream = self._send_request_bidi_stream
+        for interceptor in (
+            i
+            for i in reversed(interceptors)
+            if isinstance(i, BidiStreamInterceptorSync)
+        ):
+            execute_bidi_stream = functools.partial(
+                interceptor.intercept_bidi_stream_sync, execute_bidi_stream
+            )
+        self._execute_bidi_stream = execute_bidi_stream
+
+    def close(self) -> None:
+        """Close the client. After closing, the client cannot be used to make requests."""
+        if not self._closed:
+            self._closed = True
+
+    def __enter__(self) -> Self:
+        return self
+
+    def __exit__(
+        self,
+        _exc_type: type[BaseException] | None,
+        _exc_value: BaseException | None,
+        _traceback: TracebackType | None,
+    ) -> None:
+        self.close()
+
+    def execute_unary(
+        self,
+        *,
+        request: REQ,
+        method: MethodInfo[REQ, RES],
+        headers: Headers | Mapping[str, str] | None = None,
+        timeout_ms: int | None = None,
+        use_get: bool = False,
+    ) -> RES:
+        ctx = self._protocol.create_request_context(
+            method=method,
+            url=self._address,
+            http_method="GET" if use_get else "POST",
+            user_headers=headers,
+            timeout_ms=timeout_ms or self._timeout_ms,
+            codec=self._codec,
+            stream=False,
+            accept_compression=self._accept_compression_header,
+            send_compression=self._send_compression,
+        )
+        return self._execute_unary(request, ctx)
+
+    def execute_client_stream(
+        self,
+        *,
+        request: Iterator[REQ],
+        method: MethodInfo[REQ, RES],
+        headers: Headers | Mapping[str, str] | None = None,
+        timeout_ms: int | None = None,
+    ) -> RES:
+        ctx = self._protocol.create_request_context(
+            method=method,
+            url=self._address,
+            http_method="POST",
+            user_headers=headers,
+            timeout_ms=timeout_ms or self._timeout_ms,
+            codec=self._codec,
+            stream=True,
+            accept_compression=self._accept_compression_header,
+            send_compression=self._send_compression,
+        )
+        return self._execute_client_stream(request, ctx)
+
+    def execute_server_stream(
+        self,
+        *,
+        request: REQ,
+        method: MethodInfo[REQ, RES],
+        headers: Headers | Mapping[str, str] | None = None,
+        timeout_ms: int | None = None,
+    ) -> Iterator[RES]:
+        ctx = self._protocol.create_request_context(
+            method=method,
+            url=self._address,
+            http_method="POST",
+            user_headers=headers,
+            timeout_ms=timeout_ms or self._timeout_ms,
+            codec=self._codec,
+            stream=True,
+            accept_compression=self._accept_compression_header,
+            send_compression=self._send_compression,
+        )
+        return self._execute_server_stream(request, ctx)
+
+    def execute_bidi_stream(
+        self,
+        *,
+        request: Iterator[REQ],
+        method: MethodInfo[REQ, RES],
+        headers: Headers | Mapping[str, str] | None = None,
+        timeout_ms: int | None = None,
+    ) -> Iterator[RES]:
+        ctx = self._protocol.create_request_context(
+            method=method,
+            url=self._address,
+            http_method="POST",
+            user_headers=headers,
+            timeout_ms=timeout_ms or self._timeout_ms,
+            codec=self._codec,
+            stream=True,
+            accept_compression=self._accept_compression_header,
+            send_compression=self._send_compression,
+        )
+        return self._execute_bidi_stream(request, ctx)
+
+    def _send_request_unary(self, request: REQ, ctx: RequestContext[REQ, RES]) -> RES:
+        if isinstance(self._protocol, GRPCClientProtocol):
+            return _consume_single_response(
+                self._send_request_bidi_stream(iter([request]), ctx)
+            )
+
+        request_headers = HTTPHeaders(ctx.request_headers.allitems())
+        url = f"{self._address}/{ctx.method.service_name}/{ctx.method.name}"
+        if (timeout_ms := ctx.timeout_ms) is not None:
+            timeout_s = timeout_ms / 1000.0
+        else:
+            timeout_s = None
+
+        try:
+            request_data = self._codec.encode(request)
+            if self._send_compression:
+                request_data = self._send_compression.compress(request_data)
+
+            if ctx.http_method == "GET":
+                params = _client_shared.prepare_get_params(
+                    self._codec, request_data, request_headers
+                )
+                params_str = urlencode(params)
+                url = f"{url}?{params_str}"
+                request_headers.pop("content-type", None)
+                resp = self._http_client.get(
+                    url=url, headers=request_headers, timeout=timeout_s
+                )
+            else:
+                resp = self._http_client.post(
+                    url=url,
+                    headers=request_headers,
+                    content=request_data,
+                    timeout=timeout_s,
+                )
+
+            self._protocol.validate_response(
+                self._codec.name(), resp.status, resp.headers.get("content-type", "")
+            )
+            # Decompression itself is handled by pyqwest, but we validate it
+            # by resolving it.
+            self._protocol.handle_response_compression(
+                resp.headers, self._response_compressions, stream=False
+            )
+            handle_response_headers(resp.headers)
+
+            if resp.status == 200:
+                if (
+                    self._read_max_bytes is not None
+                    and len(resp.content) > self._read_max_bytes
+                ):
+                    raise ConnectError(
+                        Code.RESOURCE_EXHAUSTED,
+                        f"message is larger than configured max {self._read_max_bytes}",
+                    )
+
+                return self._codec.decode(resp.content, ctx.method.output)
+            raise ConnectWireError.from_response(resp).to_exception()
+        except TimeoutError as e:
+            raise ConnectError(Code.DEADLINE_EXCEEDED, "Request timed out") from e
+        except ConnectError:
+            raise
+        except Exception as e:
+            raise ConnectError(Code.UNAVAILABLE, str(e)) from e
+
+    def _send_request_client_stream(
+        self, request: Iterator[REQ], ctx: RequestContext[REQ, RES]
+    ) -> RES:
+        return _consume_single_response(self._send_request_bidi_stream(request, ctx))
+
+    def _send_request_server_stream(
+        self, request: REQ, ctx: RequestContext[REQ, RES]
+    ) -> Iterator[RES]:
+        return self._send_request_bidi_stream(iter([request]), ctx)
+
+    def _send_request_bidi_stream(
+        self, request: Iterator[REQ], ctx: RequestContext[REQ, RES]
+    ) -> Iterator[RES]:
+        request_headers = HTTPHeaders(ctx.request_headers.allitems())
+        url = f"{self._address}/{ctx.method.service_name}/{ctx.method.name}"
+        if (timeout_ms := ctx.timeout_ms) is not None:
+            timeout_s = timeout_ms / 1000.0
+        else:
+            timeout_s = None
+
+        stream_error: Exception | None = None
+        reader: EnvelopeReader | None = None
+        resp: SyncResponse | None = None
+        try:
+            request_data = _streaming_request_content(
+                request, self._codec, self._send_compression
+            )
+
+            with self._http_client.stream(
+                method="POST",
+                url=url,
+                headers=request_headers,
+                content=request_data,
+                timeout=timeout_s,
+            ) as resp:
+                handle_response_headers(resp.headers)
+
+                if resp.status == 200:
+                    self._protocol.validate_stream_response(
+                        self._codec.name(), resp.headers.get("content-type", "")
+                    )
+                    compression = self._protocol.handle_response_compression(
+                        resp.headers, self._response_compressions, stream=True
+                    )
+                    reader = self._protocol.create_envelope_reader(
+                        ctx.method.output,
+                        self._codec,
+                        compression,
+                        self._read_max_bytes,
+                    )
+                    try:
+                        for chunk in resp.content:
+                            yield from reader.feed(chunk)
+                    except ConnectError as e:
+                        stream_error = e
+                        raise
+                    # For sync, we rely on the HTTP client to handle timeout, but
+                    # currently the one we use for gRPC does not propagate RST_STREAM
+                    # correctly which is used for server timeouts. We go ahead and check
+                    # the timeout ourselves too.
+                    # https://github.com/hyperium/hyper/issues/3681#issuecomment-3734084436
+                    if (t := ctx.timeout_ms) is not None and t <= 0:
+                        raise TimeoutError
+
+                    reader.handle_response_complete(resp)
+                else:
+                    content = bytearray()
+                    for chunk in resp.content:
+                        content.extend(chunk)
+                    fres = FullResponse(
+                        status=resp.status,
+                        headers=resp.headers,
+                        content=bytes(content),
+                        trailers=resp.trailers,
+                    )
+                    raise ConnectWireError.from_response(fres).to_exception()
+        except TimeoutError as e:
+            raise ConnectError(Code.DEADLINE_EXCEEDED, "Request timed out") from e
+        except ConnectError:
+            raise
+        except Exception as e:
+            # If a context manager's exit raises an exception, it overwrites any raised
+            # by our own stream handling. This seems to happen when we end the response without
+            # fully consuming it due to message limits. It should always be fine to prioritize
+            # the stream error here.
+            if stream_error is not None:
+                raise stream_error from None
+
+            if rst_err := _client_shared.maybe_map_stream_reset(e, ctx):
+                # It is possible for a reset to come with trailers which should
+                # be used.
+                if reader and resp:
+                    reader.handle_response_complete(resp, rst_err)
+                raise rst_err from e
+            raise ConnectError(Code.UNAVAILABLE, str(e)) from e
+
+
+def _streaming_request_content(
+    msgs: Iterator[Any], codec: Codec, compression: Compression | None
+) -> Iterator[bytes]:
+    writer = ConnectEnvelopeWriter(codec, compression)
+    for msg in msgs:
+        yield writer.write(msg)
+
+
+def _consume_single_response(stream: Iterator[RES]) -> RES:
+    res = None
+    for message in stream:
+        if res is not None:
+            raise ConnectError(
+                Code.UNIMPLEMENTED, "unary response has multiple messages"
+            )
+        res = message
+    if res is None:
+        raise ConnectError(Code.UNIMPLEMENTED, "unary response has zero messages")
+    return res

+ 118 - 0
vendor/connectrpc/_codec.py

@@ -0,0 +1,118 @@
+from __future__ import annotations
+
+from typing import Protocol, TypeVar
+
+from protobuf import Message, Registry
+from protobuf.wkt import (
+    api_pb,
+    cpp_features_pb,
+    descriptor_pb,
+    duration_pb,
+    empty_pb,
+    field_mask_pb,
+    go_features_pb,
+    java_features_pb,
+    source_context_pb,
+    struct_pb,
+    timestamp_pb,
+    type_pb,
+    wrappers_pb,
+)
+
+CODEC_NAME_PROTO = "proto"
+CODEC_NAME_JSON = "json"
+
+
+DEFAULT_REGISTRY = Registry(
+    api_pb.desc(),
+    cpp_features_pb.desc(),
+    descriptor_pb.desc(),
+    duration_pb.desc(),
+    empty_pb.desc(),
+    field_mask_pb.desc(),
+    go_features_pb.desc(),
+    java_features_pb.desc(),
+    source_context_pb.desc(),
+    struct_pb.desc(),
+    timestamp_pb.desc(),
+    type_pb.desc(),
+    wrappers_pb.desc(),
+)
+
+
+T_contra = TypeVar("T_contra", contravariant=True)
+U = TypeVar("U")
+V = TypeVar("V", bound=Message)
+
+
+class Codec(Protocol[T_contra, U]):
+    def name(self) -> str:
+        """Returns the name of the codec.
+
+        This corresponds to the content-type used in requests.
+        """
+        ...
+
+    def encode(self, message: T_contra) -> bytes:
+        """Marshals the given message."""
+        ...
+
+    def decode(self, data: bytes | bytearray, message_class: type[U]) -> U:
+        """Unmarshals the given message."""
+        ...
+
+
+class ProtoBinaryCodec(Codec[Message, V]):
+    """Codec for the Protocol Buffers binary format."""
+
+    def name(self) -> str:
+        return "proto"
+
+    def encode(self, message: Message) -> bytes:
+        return message.to_binary()
+
+    def decode(self, data: bytes | bytearray, message_class: type[V]) -> V:
+        return message_class.from_binary(data)
+
+
+class ProtoJSONCodec(Codec[Message, V]):
+    """Codec for the Protocol Buffers JSON format."""
+
+    def __init__(self, name: str = "json", registry: Registry | None = None) -> None:
+        self._name = name
+        self._registry = registry or DEFAULT_REGISTRY
+
+    def name(self) -> str:
+        return self._name
+
+    def encode(self, message: Message) -> bytes:
+        return message.to_json(registry=self._registry).encode()
+
+    def decode(self, data: bytes | bytearray, message_class: type[V]) -> V:
+        return message_class.from_json(data, registry=self._registry)
+
+
+_proto_binary_codec = ProtoBinaryCodec()
+_proto_json_codec = ProtoJSONCodec()
+_default_codecs: list[Codec] = [_proto_binary_codec, _proto_json_codec]
+
+
+def get_default_codecs() -> list[Codec]:
+    return _default_codecs
+
+
+def proto_binary_codec() -> Codec:
+    """Returns the Protocol Buffers binary codec."""
+    return _proto_binary_codec
+
+
+def proto_json_codec(registry: Registry | None = None) -> Codec:
+    """Returns the Protocol Buffers JSON codec.
+
+    Args:
+        registry: An optional protobuf Registry to use for marshaling Any and extensions in messages.
+                  If not provided, a default registry containing WKTs will be used.
+    """
+    if registry:
+        return ProtoJSONCodec(name=CODEC_NAME_JSON, registry=registry)
+    return _proto_json_codec

+ 50 - 0
vendor/connectrpc/_compression.py

@@ -0,0 +1,50 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from connectrpc.compression.gzip import GzipCompression
+
+from .compression import Compression
+
+if TYPE_CHECKING:
+    from collections.abc import Iterable
+
+
+class IdentityCompression(Compression):
+    def name(self) -> str:
+        return "identity"
+
+    def compress(self, data: bytes | bytearray | memoryview) -> bytes:
+        """Return data as-is without compression."""
+        return data if isinstance(data, bytes) else bytes(data)
+
+    def decompress(self, data: bytes | bytearray | memoryview) -> bytes:
+        """Return data as-is without decompression."""
+        return data if isinstance(data, bytes) else bytes(data)
+
+
+_identity = IdentityCompression()
+
+_gzip = GzipCompression()
+_default_compressions: dict[str, Compression] = {"gzip": _gzip, "identity": _identity}
+
+
+def resolve_compressions(
+    compressions: Iterable[Compression] | None,
+) -> dict[str, Compression]:
+    if compressions is None:
+        return _default_compressions
+    res = {comp.name(): comp for comp in compressions}
+    # identity is always supported
+    res["identity"] = _identity
+    return res
+
+
+def negotiate_compression(
+    accept_encoding: str, compressions: dict[str, Compression]
+) -> Compression:
+    for accept in accept_encoding.split(","):
+        compression = compressions.get(accept.strip())
+        if compression:
+            return compression
+    return _identity

+ 121 - 0
vendor/connectrpc/_envelope.py

@@ -0,0 +1,121 @@
+from __future__ import annotations
+
+import struct
+from abc import ABC, abstractmethod
+from typing import TYPE_CHECKING, Any, Generic, TypeVar
+
+from ._compression import Compression, IdentityCompression
+from .code import Code
+from .errors import ConnectError
+
+if TYPE_CHECKING:
+    from collections.abc import Iterator
+
+    from pyqwest import Response, SyncResponse
+
+    from ._codec import Codec
+    from ._protocol import ConnectWireError
+    from .request import Headers
+
+_RES = TypeVar("_RES")
+_T = TypeVar("_T")
+
+
+class EnvelopeReader(Generic[_RES]):
+    _next_message_length: int | None
+
+    def __init__(
+        self,
+        message_class: type[_RES],
+        codec: Codec,
+        compression: Compression,
+        read_max_bytes: int | None,
+    ) -> None:
+        self._buffer = bytearray()
+        self._message_class = message_class
+        self._codec = codec
+        self._compression = compression
+        self._read_max_bytes = read_max_bytes
+
+        self._next_message_length = None
+
+    def feed(self, data: bytes | memoryview | bytearray) -> Iterator[_RES]:
+        self._buffer.extend(data)
+        return self._read_messages()
+
+    def _read_messages(self) -> Iterator[_RES]:
+        while self._buffer:
+            if self._next_message_length is not None:
+                if len(self._buffer) < self._next_message_length + 5:
+                    return
+
+                prefix_byte = self._buffer[0]
+                compressed = prefix_byte & 0b01 != 0
+
+                message_data = self._buffer[5 : 5 + self._next_message_length]
+                self._buffer = self._buffer[5 + self._next_message_length :]
+                self._next_message_length = None
+                if compressed:
+                    if isinstance(self._compression, IdentityCompression):
+                        raise ConnectError(
+                            Code.INTERNAL,
+                            "protocol error: sent compressed message without compression support",
+                        )
+                    message_data = self._compression.decompress(message_data)
+
+                if (
+                    self._read_max_bytes is not None
+                    and len(message_data) > self._read_max_bytes
+                ):
+                    raise ConnectError(
+                        Code.RESOURCE_EXHAUSTED,
+                        f"message is larger than configured max {self._read_max_bytes}",
+                    )
+
+                if self.handle_end_message(prefix_byte, message_data):
+                    return
+
+                res = self._codec.decode(message_data, self._message_class)
+                yield res
+
+            if len(self._buffer) < 5:
+                return
+
+            self._next_message_length = int.from_bytes(self._buffer[1:5], "big")
+
+    def handle_end_message(
+        self, prefix_byte: int, message_data: bytes | bytearray
+    ) -> bool:
+        """For client protocols with an end message like Connect and gRPC-Web, handle the end message.
+        Returns True if the end message was handled, False otherwise.
+        """
+        return False
+
+    def handle_response_complete(
+        self, response: Response | SyncResponse, e: ConnectError | None = None
+    ) -> None:
+        """Handle any client finalization needed when the response is complete.
+        This is typically used to process trailers for gRPC.
+        """
+
+
+class EnvelopeWriter(ABC, Generic[_T]):
+    def __init__(self, codec: Codec[_T, Any], compression: Compression | None) -> None:
+        self._codec = codec
+        self._compression = compression
+        self._prefix = (
+            0 if not compression or isinstance(compression, IdentityCompression) else 1
+        )
+
+    def write(self, message: _T) -> bytes:
+        data = self._codec.encode(message)
+        if self._compression:
+            data = self._compression.compress(data)
+        # This copies data into the final envelope, but it is still better than issuing
+        # I/O multiple times for small prefix / length elements.
+        return struct.pack(">BI", self._prefix, len(data)) + data
+
+    @abstractmethod
+    def end(
+        self, user_trailers: Headers, error: ConnectWireError | None
+    ) -> bytes | Headers: ...

+ 1 - 0
vendor/connectrpc/_gen/__init__.py

@@ -0,0 +1 @@
+from __future__ import annotations

+ 1 - 0
vendor/connectrpc/_gen/google/__init__.py

@@ -0,0 +1 @@
+from __future__ import annotations

+ 1 - 0
vendor/connectrpc/_gen/google/rpc/__init__.py

@@ -0,0 +1 @@
+from __future__ import annotations

+ 93 - 0
vendor/connectrpc/_gen/google/rpc/status_pb.py

@@ -0,0 +1,93 @@
+# Generated from google/rpc/status.proto. DO NOT EDIT.
+# Generated by protoc-gen-py v0.1.1 with parameter "".
+# ruff: noqa: PGH004
+# ruff: noqa
+# fmt: off
+
+from __future__ import annotations
+
+from typing import Literal, TYPE_CHECKING, TypeAlias
+
+from protobuf import Message
+from protobuf._codegen import file_desc
+from protobuf.wkt import any_pb
+
+if TYPE_CHECKING:
+    from protobuf import DescFile
+    from protobuf.wkt import Any
+
+
+_StatusFields: TypeAlias = Literal["code", "message", "details"]
+
+class Status(Message[_StatusFields]):
+    """
+    The `Status` type defines a logical error model that is suitable for
+    different programming environments, including REST APIs and RPC APIs. It is
+    used by [gRPC](https://github.com/grpc). Each `Status` message contains
+    three pieces of data: error code, error message, and error details.
+
+    You can find out more about this error model and how to work with it in the
+    [API Design Guide](https://cloud.google.com/apis/design/errors).
+
+    ```proto
+    message google.rpc.Status
+    ```
+
+    Attributes:
+        code:
+            The status code, which should be an enum value of
+            [google.rpc.Code][google.rpc.Code].
+
+            ```proto
+            int32 code = 1;
+            ```
+        message:
+            A developer-facing error message, which should be in English. Any
+            user-facing error message should be localized and sent in the
+            [google.rpc.Status.details][google.rpc.Status.details] field, or localized
+            by the client.
+
+            ```proto
+            string message = 2;
+            ```
+        details:
+            A list of messages that carry the error details.  There is a common set of
+            message types for APIs to use.
+
+            ```proto
+            repeated google.protobuf.Any details = 3;
+            ```
+    """
+
+    __slots__ = ("code", "message", "details")
+
+    if TYPE_CHECKING:
+
+        def __init__(
+            self,
+            *,
+            code: int = 0,
+            message: str = "",
+            details: list[Any] | None = None,
+        ) -> None:
+            pass
+
+        code: int
+        message: str
+        details: list[Any]
+
+
+_DESC = file_desc(
+    b'\n\x17google/rpc/status.proto\x12\ngoogle.rpc\x1a\x19google/protobuf/any.proto"f\n\x06Status\x12\x12\n\x04code\x18\x01 \x01(\x05R\x04code\x12\x18\n\x07message\x18\x02 \x01(\tR\x07message\x12.\n\x07details\x18\x03 \x03(\x0b2\x14.google.protobuf.AnyR\x07detailsB^\n\x0ecom.google.rpcB\x0bStatusProtoP\x01Z7google.golang.org/genproto/googleapis/rpc/status;status\xa2\x02\x03RPCb\x06proto3',
+    [
+        any_pb.desc(),
+    ],
+    {
+        "Status": Status,
+    },
+)
+
+
+def desc() -> DescFile:
+    """Returns the descriptor for the file `google/rpc/status.proto`."""
+    return _DESC

+ 148 - 0
vendor/connectrpc/_headers.py

@@ -0,0 +1,148 @@
+from __future__ import annotations
+
+from collections.abc import (
+    ItemsView,
+    Iterator,
+    KeysView,
+    Mapping,
+    MutableMapping,
+    Sequence,
+    ValuesView,
+)
+from typing import cast
+
+
+class Headers(MutableMapping[str, str]):
+    """Container of HTTP headers.
+
+    This class behaves like a dictionary with case-insensitive keys and
+    string values. It additionally can be used to store multiple values for
+    the same key by using the `add` method, and retrieve a view including
+    duplicates using `allitems`.
+    """
+
+    _store: dict[str, str]
+    _extra: dict[str, list[str]] | None
+
+    def __init__(
+        self, items: Mapping[str, str] | Sequence[tuple[str, str]] = ()
+    ) -> None:
+        self._extra = None
+        if isinstance(items, Mapping):
+            # ty does not preserve type parameters when narrowing a union via
+            # `isinstance(x, Mapping)`, so we re-assert `Mapping[str, str]`.
+            # See https://github.com/astral-sh/ty/issues/456.
+            items = cast("Mapping[str, str]", items)
+            self._store = {k.lower(): v for k, v in items.items()}
+        else:
+            self._store = {}
+            for k, v in items:
+                self.add(k, v)
+
+    def __getitem__(self, key: str) -> str:
+        key = key.lower()
+        return self._store[key]
+
+    def __setitem__(self, key: str, value: str) -> None:
+        key = key.lower()
+        self._store[key] = value
+        if self._extra is not None:
+            self._extra.pop(key, None)
+
+    def __delitem__(self, key: str) -> None:
+        key = key.lower()
+        del self._store[key]
+        # If value wasn't in store, it's not in extra
+        # so no need to worry about exception.
+        if self._extra is not None:
+            self._extra.pop(key, None)
+
+    def __iter__(self) -> Iterator[str]:
+        return iter(self._store)
+
+    def __len__(self) -> int:
+        return len(self._store)
+
+    def __repr__(self) -> str:
+        return repr(list(self.allitems()))
+
+    def add(self, key: str, value: str) -> None:
+        """Add a header, appending to existing values without overwriting.
+
+        To overwrite an existing value, use `self[key] = value` instead.
+        """
+        key = key.lower()
+        if key in self._store:
+            if self._extra is None:
+                self._extra = {}
+            self._extra.setdefault(key, []).append(value)
+            return
+        self._store[key] = value
+
+    def clear(self) -> None:
+        """Clear all headers."""
+        self._store.clear()
+        self._extra = None
+
+    def getall(self, key: str) -> Sequence[str]:
+        """Get all values for a header key, including duplicates."""
+        key = key.lower()
+        if key not in self._store:
+            return ()
+        res = [self._store[key]]
+        if self._extra is not None:
+            res.extend(self._extra.get(key, ()))
+        return res
+
+    def allitems(self) -> ItemsView[str, str]:
+        """Return an iterable view of all header items, including duplicates."""
+        if self._extra is None:
+            return self._store.items()
+        return _AllItemsView(self._store, self._extra)
+
+    # Commonly used functions that delegate to _store for performance vs the base class
+    # implementations that rely on dunder methods.
+
+    def items(self) -> ItemsView[str, str]:
+        """Return an iterable view of the headers, without duplicates."""
+        return self._store.items()
+
+    def keys(self) -> KeysView[str]:
+        """Return an iterable view of the header keys."""
+        return self._store.keys()
+
+    def values(self) -> ValuesView[str]:
+        """Return an iterable view of the header values, without duplicates."""
+        return self._store.values()
+
+    def __contains__(self, key: object) -> bool:
+        if not isinstance(key, str):
+            return False
+        key = key.lower()
+        return key in self._store
+
+
+class _AllItemsView(ItemsView[str, str]):
+    """An iterable view of all header items, including duplicates."""
+
+    _store: dict[str, str]
+    _extra: dict[str, list[str]] | None
+
+    def __init__(
+        self, store: dict[str, str], extra: dict[str, list[str]] | None
+    ) -> None:
+        self._store = store
+        self._extra = extra
+
+    def __iter__(self) -> Iterator[tuple[str, str]]:
+        for key, v in self._store.items():
+            yield (key, v)
+            if self._extra:
+                for vv in self._extra.get(key, ()):
+                    yield (key, vv)
+
+    def __len__(self) -> int:
+        size = len(self._store)
+        if self._extra:
+            size += sum(len(v) for v in self._extra.values())
+        return size

+ 242 - 0
vendor/connectrpc/_interceptor_async.py

@@ -0,0 +1,242 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Generic, Protocol, TypeVar, runtime_checkable
+
+if TYPE_CHECKING:
+    from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence
+
+    from .request import RequestContext
+
+REQ = TypeVar("REQ")
+RES = TypeVar("RES")
+T = TypeVar("T")
+
+
+@runtime_checkable
+class UnaryInterceptor(Protocol):
+    """An interceptor of an asynchronous unary RPC method."""
+
+    async def intercept_unary(
+        self,
+        call_next: Callable[[REQ, RequestContext], Awaitable[RES]],
+        request: REQ,
+        ctx: RequestContext,
+    ) -> RES:
+        """Intercepts a unary RPC.
+
+        Args:
+            call_next: A callable to invoke to continue processing, either to another
+                interceptor or the actual RPC. Generally will be called with the same
+                request the interceptor received but the request can be replaced as
+                needed. Can be skipped if returning a response from the interceptor
+                directly.
+            request: The request message.
+            ctx: The request context.
+
+        Returns:
+            The response message.
+        """
+        ...
+
+
+@runtime_checkable
+class ClientStreamInterceptor(Protocol):
+    """An interceptor of an asynchronous client-streaming RPC method."""
+
+    async def intercept_client_stream(
+        self,
+        call_next: Callable[[AsyncIterator[REQ], RequestContext], Awaitable[RES]],
+        request: AsyncIterator[REQ],
+        ctx: RequestContext,
+    ) -> RES:
+        """Intercepts a client-streaming RPC.
+
+        Args:
+            call_next: A callable to invoke to continue processing, either to another
+                interceptor or the actual RPC. Generally will be called with the same
+                request the interceptor received but the request can be replaced as
+                needed. Can be skipped if returning a response from the interceptor
+                directly.
+            request: The request message iterator.
+            ctx: The request context.
+
+        Returns:
+            The response message.
+        """
+        ...
+
+
+@runtime_checkable
+class ServerStreamInterceptor(Protocol):
+    """An interceptor of an asynchronous server-streaming RPC method."""
+
+    def intercept_server_stream(
+        self,
+        call_next: Callable[[REQ, RequestContext], AsyncIterator[RES]],
+        request: REQ,
+        ctx: RequestContext,
+    ) -> AsyncIterator[RES]:
+        """Intercepts a server-streaming RPC.
+
+        Args:
+            call_next: A callable to invoke to continue processing, either to another
+                interceptor or the actual RPC. Generally will be called with the same
+                request the interceptor received but the request can be replaced as
+                needed. Can be skipped if returning a response from the interceptor
+                directly.
+            request: The request message.
+            ctx: The request context.
+
+        Returns:
+            The response message iterator.
+        """
+        ...
+
+
+@runtime_checkable
+class BidiStreamInterceptor(Protocol):
+    """An interceptor of an asynchronous bidirectional-streaming RPC method."""
+
+    def intercept_bidi_stream(
+        self,
+        call_next: Callable[[AsyncIterator[REQ], RequestContext], AsyncIterator[RES]],
+        request: AsyncIterator[REQ],
+        ctx: RequestContext,
+    ) -> AsyncIterator[RES]:
+        """Intercepts a bidirectional-streaming RPC.
+
+        Args:
+            call_next: A callable to invoke to continue processing, either to another
+                interceptor or the actual RPC. Generally will be called with the same
+                request the interceptor received but the request can be replaced as
+                needed. Can be skipped if returning a response from the interceptor
+                directly.
+            request: The request message iterator.
+            ctx: The request context.
+
+        Returns:
+            The response message iterator.
+        """
+        ...
+
+
+@runtime_checkable
+class MetadataInterceptor(Protocol[T]):
+    """An interceptor that can be applied to any type of method, only having
+    access to metadata such as headers and trailers.
+
+    To access request and response bodies of a method, instead use an interceptor
+    corresponding to the type of method such as [UnaryInterceptor][].
+    """
+
+    async def on_start(self, ctx: RequestContext) -> T:
+        """Called when the RPC starts. The return value will be passed to [on_end][] as-is.
+        For example, if measuring RPC invocation time, on_start may return the current
+        time. If a return value isn't needed or [on_end][] won't be used, return None.
+        """
+        ...
+
+    async def on_end(
+        self, token: T, ctx: RequestContext, error: Exception | None
+    ) -> None:
+        """Called when the RPC ends."""
+        return
+
+
+Interceptor = (
+    UnaryInterceptor
+    | ClientStreamInterceptor
+    | ServerStreamInterceptor
+    | BidiStreamInterceptor
+    | MetadataInterceptor
+)
+"""An interceptor to apply to an asynchronous RPC server or client."""
+
+
+class MetadataInterceptorInvoker(Generic[T]):
+    _delegate: MetadataInterceptor[T]
+
+    def __init__(self, delegate: MetadataInterceptor[T]) -> None:
+        self._delegate = delegate
+
+    async def intercept_unary(
+        self,
+        call_next: Callable[[REQ, RequestContext], Awaitable[RES]],
+        request: REQ,
+        ctx: RequestContext,
+    ) -> RES:
+        token = await self._delegate.on_start(ctx)
+        error: Exception | None = None
+        try:
+            return await call_next(request, ctx)
+        except Exception as e:
+            error = e
+            raise
+        finally:
+            await self._delegate.on_end(token, ctx, error)
+
+    async def intercept_client_stream(
+        self,
+        call_next: Callable[[AsyncIterator[REQ], RequestContext], Awaitable[RES]],
+        request: AsyncIterator[REQ],
+        ctx: RequestContext,
+    ) -> RES:
+        token = await self._delegate.on_start(ctx)
+        error: Exception | None = None
+        try:
+            return await call_next(request, ctx)
+        except Exception as e:
+            error = e
+            raise
+        finally:
+            await self._delegate.on_end(token, ctx, error)
+
+    async def intercept_server_stream(
+        self,
+        call_next: Callable[[REQ, RequestContext], AsyncIterator[RES]],
+        request: REQ,
+        ctx: RequestContext,
+    ) -> AsyncIterator[RES]:
+        token = await self._delegate.on_start(ctx)
+        error: Exception | None = None
+        try:
+            async for response in call_next(request, ctx):
+                yield response
+        except Exception as e:
+            error = e
+            raise
+        finally:
+            await self._delegate.on_end(token, ctx, error)
+
+    async def intercept_bidi_stream(
+        self,
+        call_next: Callable[[AsyncIterator[REQ], RequestContext], AsyncIterator[RES]],
+        request: AsyncIterator[REQ],
+        ctx: RequestContext,
+    ) -> AsyncIterator[RES]:
+        token = await self._delegate.on_start(ctx)
+        error: Exception | None = None
+        try:
+            async for response in call_next(request, ctx):
+                yield response
+        except Exception as e:
+            error = e
+            raise
+        finally:
+            await self._delegate.on_end(token, ctx, error)
+
+
+def resolve_interceptors(
+    interceptors: Iterable[Interceptor],
+) -> Sequence[
+    UnaryInterceptor
+    | ClientStreamInterceptor
+    | ServerStreamInterceptor
+    | BidiStreamInterceptor
+]:
+    return [
+        MetadataInterceptorInvoker(interceptor)
+        if isinstance(interceptor, MetadataInterceptor)
+        else interceptor
+        for interceptor in interceptors
+    ]

+ 240 - 0
vendor/connectrpc/_interceptor_sync.py

@@ -0,0 +1,240 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Generic, Protocol, TypeVar, runtime_checkable
+
+if TYPE_CHECKING:
+    from collections.abc import Callable, Iterable, Iterator, Sequence
+
+    from .request import RequestContext
+
+REQ = TypeVar("REQ")
+RES = TypeVar("RES")
+T = TypeVar("T")
+
+
+@runtime_checkable
+class UnaryInterceptorSync(Protocol):
+    """An interceptor of a synchronous unary RPC method."""
+
+    def intercept_unary_sync(
+        self,
+        call_next: Callable[[REQ, RequestContext], RES],
+        request: REQ,
+        ctx: RequestContext,
+    ) -> RES:
+        """Intercepts a unary RPC.
+
+        Args:
+            call_next: A callable to invoke to continue processing, either to another
+                interceptor or the actual RPC. Generally will be called with the same
+                request the interceptor received but the request can be replaced as
+                needed. Can be skipped if returning a response from the interceptor
+                directly.
+            request: The request message.
+            ctx: The request context.
+
+        Returns:
+            The response message.
+        """
+        ...
+
+
+@runtime_checkable
+class ClientStreamInterceptorSync(Protocol):
+    """An interceptor of a synchronous client-streaming RPC method."""
+
+    def intercept_client_stream_sync(
+        self,
+        call_next: Callable[[Iterator[REQ], RequestContext], RES],
+        request: Iterator[REQ],
+        ctx: RequestContext,
+    ) -> RES:
+        """Intercepts a client-streaming RPC.
+
+        Args:
+            call_next: A callable to invoke to continue processing, either to another
+                interceptor or the actual RPC. Generally will be called with the same
+                request the interceptor received but the request can be replaced as
+                needed. Can be skipped if returning a response from the interceptor
+                directly.
+            request: The request message iterator.
+            ctx: The request context.
+
+        Returns:
+            The response message.
+        """
+        ...
+
+
+@runtime_checkable
+class ServerStreamInterceptorSync(Protocol):
+    """An interceptor of a synchronous server-streaming RPC method."""
+
+    def intercept_server_stream_sync(
+        self,
+        call_next: Callable[[REQ, RequestContext], Iterator[RES]],
+        request: REQ,
+        ctx: RequestContext,
+    ) -> Iterator[RES]:
+        """Intercepts a server-streaming RPC.
+
+        Args:
+            call_next: A callable to invoke to continue processing, either to another
+                interceptor or the actual RPC. Generally will be called with the same
+                request the interceptor received but the request can be replaced as
+                needed. Can be skipped if returning a response from the interceptor
+                directly.
+            request: The request message.
+            ctx: The request context.
+
+        Returns:
+            The response message iterator.
+        """
+        ...
+
+
+@runtime_checkable
+class BidiStreamInterceptorSync(Protocol):
+    """An interceptor of a synchronous bidirectional-streaming RPC method."""
+
+    def intercept_bidi_stream_sync(
+        self,
+        call_next: Callable[[Iterator[REQ], RequestContext], Iterator[RES]],
+        request: Iterator[REQ],
+        ctx: RequestContext,
+    ) -> Iterator[RES]:
+        """Intercepts a bidirectional-streaming RPC.
+
+        Args:
+            call_next: A callable to invoke to continue processing, either to another
+                interceptor or the actual RPC. Generally will be called with the same
+                request the interceptor received but the request can be replaced as
+                needed. Can be skipped if returning a response from the interceptor
+                directly.
+            request: The request message iterator.
+            ctx: The request context.
+
+        Returns:
+            The response message iterator.
+        """
+        ...
+
+
+@runtime_checkable
+class MetadataInterceptorSync(Protocol[T]):
+    """An interceptor that can be applied to any type of method, only having
+    access to metadata such as headers and trailers.
+
+    To access request and response bodies of a method, instead use an interceptor
+    corresponding to the type of method such as [UnaryInterceptorSync][].
+    """
+
+    def on_start_sync(self, ctx: RequestContext) -> T:
+        """Called when the RPC starts. The return value will be passed to [on_end_sync][] as-is.
+        For example, if measuring RPC invocation time, on_start_sync may return the current
+        time. If a return value isn't needed or [on_end_sync][] won't be used, return None.
+        """
+        ...
+
+    def on_end_sync(
+        self, token: T, ctx: RequestContext, error: Exception | None
+    ) -> None:
+        """Called when the RPC ends."""
+        return
+
+
+InterceptorSync = (
+    UnaryInterceptorSync
+    | ClientStreamInterceptorSync
+    | ServerStreamInterceptorSync
+    | BidiStreamInterceptorSync
+    | MetadataInterceptorSync
+)
+"""An interceptor to apply to a synchronous RPC server or client."""
+
+
+class MetadataInterceptorInvokerSync(Generic[T]):
+    _delegate: MetadataInterceptorSync[T]
+
+    def __init__(self, delegate: MetadataInterceptorSync[T]) -> None:
+        self._delegate = delegate
+
+    def intercept_unary_sync(
+        self,
+        call_next: Callable[[REQ, RequestContext], RES],
+        request: REQ,
+        ctx: RequestContext,
+    ) -> RES:
+        token = self._delegate.on_start_sync(ctx)
+        error: Exception | None = None
+        try:
+            return call_next(request, ctx)
+        except Exception as e:
+            error = e
+            raise
+        finally:
+            self._delegate.on_end_sync(token, ctx, error)
+
+    def intercept_client_stream_sync(
+        self,
+        call_next: Callable[[Iterator[REQ], RequestContext], RES],
+        request: Iterator[REQ],
+        ctx: RequestContext,
+    ) -> RES:
+        token = self._delegate.on_start_sync(ctx)
+        error: Exception | None = None
+        try:
+            return call_next(request, ctx)
+        except Exception as e:
+            error = e
+            raise
+        finally:
+            self._delegate.on_end_sync(token, ctx, error)
+
+    def intercept_server_stream_sync(
+        self,
+        call_next: Callable[[REQ, RequestContext], Iterator[RES]],
+        request: REQ,
+        ctx: RequestContext,
+    ) -> Iterator[RES]:
+        token = self._delegate.on_start_sync(ctx)
+        error: Exception | None = None
+        try:
+            yield from call_next(request, ctx)
+        except Exception as e:
+            error = e
+            raise
+        finally:
+            self._delegate.on_end_sync(token, ctx, error)
+
+    def intercept_bidi_stream_sync(
+        self,
+        call_next: Callable[[Iterator[REQ], RequestContext], Iterator[RES]],
+        request: Iterator[REQ],
+        ctx: RequestContext,
+    ) -> Iterator[RES]:
+        token = self._delegate.on_start_sync(ctx)
+        error: Exception | None = None
+        try:
+            yield from call_next(request, ctx)
+        except Exception as e:
+            error = e
+            raise
+        finally:
+            self._delegate.on_end_sync(token, ctx, error)
+
+
+def resolve_interceptors(
+    interceptors: Iterable[InterceptorSync],
+) -> Sequence[
+    UnaryInterceptorSync
+    | ClientStreamInterceptorSync
+    | ServerStreamInterceptorSync
+    | BidiStreamInterceptorSync
+]:
+    return [
+        MetadataInterceptorInvokerSync(interceptor)
+        if isinstance(interceptor, MetadataInterceptorSync)
+        else interceptor
+        for interceptor in interceptors
+    ]

+ 304 - 0
vendor/connectrpc/_protocol.py

@@ -0,0 +1,304 @@
+from __future__ import annotations
+
+import json
+from base64 import b64decode, b64encode
+from dataclasses import dataclass
+from http import HTTPStatus
+from typing import TYPE_CHECKING, Protocol, TypeVar, cast
+
+from protobuf import message_to_json_value
+from protobuf.wkt import Any
+
+from ._compression import Compression
+from .code import Code
+from .errors import ConnectError, ErrorDetail
+
+if TYPE_CHECKING:
+    from collections.abc import Mapping, Sequence
+
+    from pyqwest import FullResponse
+    from pyqwest import Headers as HTTPHeaders
+
+    from ._codec import Codec
+    from ._compression import Compression
+    from ._envelope import EnvelopeReader, EnvelopeWriter
+    from .method import MethodInfo
+    from .request import Headers, RequestContext
+
+REQ = TypeVar("REQ")
+RES = TypeVar("RES")
+T = TypeVar("T")
+
+
+# Define a custom class for HTTP Status to allow adding 499 status code
+@dataclass(frozen=True)
+class ExtendedHTTPStatus:
+    code: int
+    reason: str
+
+    @staticmethod
+    def from_http_status(status: HTTPStatus) -> ExtendedHTTPStatus:
+        return ExtendedHTTPStatus(code=status.value, reason=status.phrase)
+
+
+# Dedupe statuses that are mapped multiple times
+_BAD_REQUEST = ExtendedHTTPStatus.from_http_status(HTTPStatus.BAD_REQUEST)
+_CONFLICT = ExtendedHTTPStatus.from_http_status(HTTPStatus.CONFLICT)
+_INTERNAL_SERVER_ERROR = ExtendedHTTPStatus.from_http_status(
+    HTTPStatus.INTERNAL_SERVER_ERROR
+)
+
+_error_to_http_status = {
+    Code.CANCELED: ExtendedHTTPStatus(499, "Client Closed Request"),
+    Code.UNKNOWN: _INTERNAL_SERVER_ERROR,
+    Code.INVALID_ARGUMENT: _BAD_REQUEST,
+    Code.DEADLINE_EXCEEDED: ExtendedHTTPStatus.from_http_status(
+        HTTPStatus.GATEWAY_TIMEOUT
+    ),
+    Code.NOT_FOUND: ExtendedHTTPStatus.from_http_status(HTTPStatus.NOT_FOUND),
+    Code.ALREADY_EXISTS: _CONFLICT,
+    Code.PERMISSION_DENIED: ExtendedHTTPStatus.from_http_status(HTTPStatus.FORBIDDEN),
+    Code.RESOURCE_EXHAUSTED: ExtendedHTTPStatus.from_http_status(
+        HTTPStatus.TOO_MANY_REQUESTS
+    ),
+    Code.FAILED_PRECONDITION: _BAD_REQUEST,
+    Code.ABORTED: _CONFLICT,
+    Code.OUT_OF_RANGE: _BAD_REQUEST,
+    Code.UNIMPLEMENTED: ExtendedHTTPStatus.from_http_status(HTTPStatus.NOT_IMPLEMENTED),
+    Code.INTERNAL: _INTERNAL_SERVER_ERROR,
+    Code.UNAVAILABLE: ExtendedHTTPStatus.from_http_status(
+        HTTPStatus.SERVICE_UNAVAILABLE
+    ),
+    Code.DATA_LOSS: _INTERNAL_SERVER_ERROR,
+    Code.UNAUTHENTICATED: ExtendedHTTPStatus.from_http_status(HTTPStatus.UNAUTHORIZED),
+}
+
+
+_http_status_code_to_error = {
+    400: Code.INTERNAL,
+    401: Code.UNAUTHENTICATED,
+    403: Code.PERMISSION_DENIED,
+    404: Code.UNIMPLEMENTED,
+    429: Code.UNAVAILABLE,
+    502: Code.UNAVAILABLE,
+    503: Code.UNAVAILABLE,
+    504: Code.UNAVAILABLE,
+}
+
+
+@dataclass(frozen=True)
+class ConnectWireError:
+    code: Code
+    message: str
+    details: Sequence[ErrorDetail]
+
+    @staticmethod
+    def from_exception(exc: Exception) -> ConnectWireError:
+        if isinstance(exc, ConnectError):
+            return ConnectWireError(exc.code, exc.message, exc.details)
+        return ConnectWireError(Code.UNKNOWN, str(exc), details=())
+
+    @staticmethod
+    def from_response(response: FullResponse) -> ConnectWireError:
+        try:
+            data = response.json()
+        except Exception:
+            data = None
+        if isinstance(data, dict):
+            return ConnectWireError.from_dict(data, response.status, Code.UNAVAILABLE)
+        return ConnectWireError.from_http_status(response.status)
+
+    @staticmethod
+    def from_dict(
+        data: dict, http_status: int, unexpected_code: Code
+    ) -> ConnectWireError:
+        code_str = data.get("code")
+        if code_str:
+            try:
+                code = Code(code_str)
+            except ValueError:
+                code = unexpected_code
+        else:
+            code = _http_status_code_to_error.get(http_status, Code.UNKNOWN)
+        message = data.get("message", "")
+        details: Sequence[ErrorDetail] = ()
+        details_json = cast("list[dict[str, str]] | None", data.get("details"))
+        if details_json:
+            details = []
+            for detail in details_json:
+                detail_type = detail.get("type")
+                detail_value = detail.get("value")
+                if detail_type is None or detail_value is None:
+                    # Ignore malformed details
+                    continue
+                details.append(
+                    ErrorDetail(
+                        Any(
+                            type_url="type.googleapis.com/" + detail_type,
+                            value=b64decode(detail_value + "==="),
+                        )
+                    )
+                )
+        return ConnectWireError(code, message, details)
+
+    @staticmethod
+    def from_http_status(status_code: int) -> ConnectWireError:
+        code = _http_status_code_to_error.get(status_code, Code.UNKNOWN)
+        try:
+            http_status = HTTPStatus(status_code)
+            message = http_status.phrase
+        except ValueError:
+            message = "Client Closed Request" if status_code == 499 else ""
+        return ConnectWireError(code, message, details=())
+
+    def to_exception(self) -> ConnectError:
+        return ConnectError(self.code, self.message, details=self.details)
+
+    def to_http_status(self) -> ExtendedHTTPStatus:
+        return _error_to_http_status.get(self.code, _INTERNAL_SERVER_ERROR)
+
+    def to_dict(self) -> dict:
+        data: dict = {"code": self.code.value, "message": self.message}
+        if self.details:
+            details: list[dict] = []
+            for detail in self.details:
+                detail_dict: dict = {
+                    "type": detail.type_name,
+                    # Connect requires unpadded base64
+                    "value": b64encode(detail.message_bytes)
+                    .decode("utf-8")
+                    .rstrip("="),
+                }
+                # Try to produce debug info, but expect failure when we don't
+                # have descriptors for the message type.
+                if debug := detail.value():
+                    try:
+                        debug_value = message_to_json_value(debug)
+                    except Exception:  # noqa: S110
+                        pass
+                    else:
+                        detail_dict["debug"] = debug_value
+                details.append(detail_dict)
+            data["details"] = details
+        return data
+
+    def to_json_bytes(self) -> bytes:
+        return json.dumps(self.to_dict()).encode("utf-8")
+
+
+class ServerProtocol(Protocol):
+    def create_request_context(
+        self,
+        method: MethodInfo[REQ, RES],
+        http_method: str,
+        http_scheme: str,
+        headers: Headers,
+        client_address: str | None = None,
+    ) -> RequestContext[REQ, RES]:
+        """Creates a RequestContext from the HTTP method and headers."""
+        ...
+
+    def create_envelope_writer(
+        self, codec: Codec[T, Any], compression: Compression | None
+    ) -> EnvelopeWriter[T]:
+        """Creates the EnvelopeWriter to write response messages."""
+        ...
+
+    def uses_trailers(self) -> bool:
+        """Returns whether the protocol uses trailers for status reporting."""
+        ...
+
+    def content_type(self, codec: Codec) -> str:
+        """Returns the content type for the given codec."""
+        ...
+
+    def compression_header_name(self) -> str:
+        """Returns the compression header name and value."""
+        ...
+
+    def codec_name_from_content_type(self, content_type: str, *, stream: bool) -> str:
+        """Extracts the codec name from the content type."""
+        ...
+
+    def negotiate_stream_compression(
+        self, headers: Headers, compressions: dict[str, Compression]
+    ) -> tuple[Compression | None, Compression]:
+        """Negotiates request and response compression based on headers."""
+        ...
+
+
+class ClientProtocol(Protocol):
+    def create_request_context(
+        self,
+        *,
+        method: MethodInfo[REQ, RES],
+        address: str,
+        http_method: str,
+        user_headers: Headers | Mapping[str, str] | None,
+        timeout_ms: int | None,
+        codec: Codec,
+        stream: bool,
+        accept_compression: str,
+        send_compression: Compression | None,
+    ) -> RequestContext[REQ, RES]:
+        """Creates a RequestContext for the given method and headers."""
+        ...
+
+    def validate_response(
+        self, request_codec_name: str, status_code: int, response_content_type: str
+    ) -> None:
+        """Validates a unary response"""
+        ...
+
+    def validate_stream_response(
+        self, request_codec_name: str, response_content_type: str
+    ) -> None:
+        """Validates a streaming response"""
+        ...
+
+    def handle_response_compression(
+        self, headers: HTTPHeaders, *, stream: bool
+    ) -> Compression:
+        """Handles response compression based on the response headers."""
+        ...
+
+    def create_envelope_reader(
+        self,
+        message_class: type[RES],
+        codec: Codec,
+        compression: Compression,
+        read_max_bytes: int | None,
+    ) -> EnvelopeReader[RES]:
+        """Creates the EnvelopeReader to read response messages."""
+        ...
+
+
+class HTTPException(Exception):
+    """An HTTP exception returned directly before starting the connect protocol."""
+
+    def __init__(self, status: HTTPStatus, headers: list[tuple[str, str]]) -> None:
+        self.status = status
+        self.headers = headers
+
+
+def host_to_server_address(host: str | None, http_scheme: str) -> str | None:
+    if host is None:
+        return None
+    if ":" not in host:
+        match http_scheme:
+            case "https":
+                host += ":443"
+            case "http":
+                host += ":80"
+    return host
+
+
+def url_to_server_address(address: str) -> str | None:
+    if address.startswith("https://"):
+        scheme = "https"
+        address = address[len("https://") :]
+    else:
+        scheme = "http"
+        address = address[len("http://") :]
+
+    return host_to_server_address(address, scheme)

+ 332 - 0
vendor/connectrpc/_protocol_connect.py

@@ -0,0 +1,332 @@
+from __future__ import annotations
+
+import json
+import struct
+from http import HTTPStatus
+from typing import TYPE_CHECKING, Any, TypeVar
+
+from ._codec import CODEC_NAME_JSON, Codec
+from ._compression import IdentityCompression, negotiate_compression
+from ._envelope import EnvelopeReader, EnvelopeWriter
+from ._protocol import (
+    ConnectWireError,
+    HTTPException,
+    host_to_server_address,
+    url_to_server_address,
+)
+from ._response_metadata import handle_response_trailers
+from ._version import __version__
+from .code import Code
+from .errors import ConnectError
+from .method import IdempotencyLevel, MethodInfo
+from .request import Headers, RequestContext
+
+if TYPE_CHECKING:
+    from collections.abc import Mapping
+
+    import pyqwest
+
+    from ._codec import Codec
+    from ._compression import Compression
+
+REQ = TypeVar("REQ")
+RES = TypeVar("RES")
+
+CONNECT_HEADER_PROTOCOL_VERSION = "connect-protocol-version"
+CONNECT_PROTOCOL_VERSION = "1"
+CONNECT_HEADER_TIMEOUT = "connect-timeout-ms"
+CONNECT_UNARY_CONTENT_TYPE_PREFIX = "application/"
+CONNECT_UNARY_CONTENT_TYPE_JSON = (
+    f"{CONNECT_UNARY_CONTENT_TYPE_PREFIX}{CODEC_NAME_JSON}"
+)
+CONNECT_STREAMING_CONTENT_TYPE_PREFIX = "application/connect+"
+CONNECT_UNARY_HEADER_COMPRESSION = "content-encoding"
+CONNECT_UNARY_HEADER_ACCEPT_COMPRESSION = "accept-encoding"
+CONNECT_STREAMING_HEADER_COMPRESSION = "connect-content-encoding"
+CONNECT_STREAMING_HEADER_ACCEPT_COMPRESSION = "connect-accept-encoding"
+
+
+_DEFAULT_CONNECT_USER_AGENT = f"connectrpc/{__version__}"
+
+
+def _normalize_content_type(content_type: str) -> str:
+    # content-type can have parameters, most commonly charset. Our supported codecs,
+    # binary and JSON are always either non-text or utf-8 and the parameters are not
+    # important for matching to a codec. A custom codec could conceivably need to
+    # match on parameters, but we will reconsider that if it is ever asked for.
+    return content_type.partition(";")[0].strip().lower()
+
+
+def codec_name_from_content_type(content_type: str, *, stream: bool) -> str:
+    content_type = _normalize_content_type(content_type)
+    prefix = (
+        CONNECT_STREAMING_CONTENT_TYPE_PREFIX
+        if stream
+        else CONNECT_UNARY_CONTENT_TYPE_PREFIX
+    )
+    if content_type.startswith(prefix):
+        return content_type[len(prefix) :]
+    # Follow connect-go behavior for malformed content type. If the content type misses the prefix,
+    # it will still be coincidentally handled.
+    return content_type
+
+
+class ConnectServerProtocol:
+    def create_request_context(
+        self,
+        method: MethodInfo[REQ, RES],
+        http_method: str,
+        http_scheme: str,
+        headers: Headers,
+        client_address: str | None = None,
+    ) -> RequestContext[REQ, RES]:
+        if method.idempotency_level == IdempotencyLevel.NO_SIDE_EFFECTS:
+            if http_method not in ("GET", "POST"):
+                raise HTTPException(
+                    HTTPStatus.METHOD_NOT_ALLOWED, [("allow", "GET, POST")]
+                )
+        elif http_method != "POST":
+            raise HTTPException(HTTPStatus.METHOD_NOT_ALLOWED, [("allow", "POST")])
+
+        # We don't require connect-protocol-version header. connect-go provides an option
+        # to require it but it's almost never used in practice.
+        connect_protocol_version = headers.get(
+            CONNECT_HEADER_PROTOCOL_VERSION, CONNECT_PROTOCOL_VERSION
+        )
+        if connect_protocol_version != CONNECT_PROTOCOL_VERSION:
+            raise ConnectError(
+                Code.INVALID_ARGUMENT,
+                f"connect-protocol-version must be '1': got '{connect_protocol_version}'",
+            )
+
+        timeout_header = headers.get(CONNECT_HEADER_TIMEOUT)
+        if timeout_header:
+            if len(timeout_header) > 10:
+                raise ConnectError(
+                    Code.INVALID_ARGUMENT,
+                    f"Invalid timeout header: '{timeout_header} has >10 digits",
+                )
+            try:
+                timeout_ms = int(timeout_header)
+            except ValueError as e:
+                raise ConnectError(
+                    Code.INVALID_ARGUMENT, f"Invalid timeout header: '{timeout_header}'"
+                ) from e
+        else:
+            timeout_ms = None
+
+        server_address = host_to_server_address(headers.get("host"), http_scheme)
+
+        return RequestContext(
+            method=method,
+            http_method=http_method,
+            request_headers=headers,
+            timeout_ms=timeout_ms,
+            server_address=server_address,
+            client_address=client_address,
+        )
+
+    def create_envelope_writer(
+        self, codec: Codec[RES, Any], compression: Compression | None
+    ) -> EnvelopeWriter[RES]:
+        return ConnectEnvelopeWriter(codec, compression)
+
+    def uses_trailers(self) -> bool:
+        return False
+
+    def content_type(self, codec: Codec) -> str:
+        return f"{CONNECT_STREAMING_CONTENT_TYPE_PREFIX}{codec.name()}"
+
+    def compression_header_name(self) -> str:
+        return CONNECT_STREAMING_HEADER_COMPRESSION
+
+    def codec_name_from_content_type(self, content_type: str, *, stream: bool) -> str:
+        return codec_name_from_content_type(content_type, stream=stream)
+
+    def negotiate_stream_compression(
+        self, headers: Headers, compressions: dict[str, Compression]
+    ) -> tuple[Compression, Compression]:
+        req_compression_name = headers.get(
+            CONNECT_STREAMING_HEADER_COMPRESSION, "identity"
+        )
+        req_compression = (
+            compressions.get(req_compression_name) or IdentityCompression()
+        )
+        accept_compression = headers.get(
+            CONNECT_STREAMING_HEADER_ACCEPT_COMPRESSION, ""
+        )
+        resp_compression = negotiate_compression(accept_compression, compressions)
+        return req_compression, resp_compression
+
+
+class ConnectEnvelopeWriter(EnvelopeWriter):
+    def end(self, user_trailers: Headers, error: ConnectWireError | None) -> bytes:
+        end_message = {}
+        if user_trailers:
+            metadata: dict[str, list[str]] = {}
+            for key, value in user_trailers.allitems():
+                metadata.setdefault(key, []).append(value)
+            end_message["metadata"] = metadata
+        if error:
+            end_message["error"] = error.to_dict()
+        data = json.dumps(end_message).encode()
+        if self._compression:
+            data = self._compression.compress(data)
+        return struct.pack(">BI", self._prefix | 0b10, len(data)) + data
+
+
+class ConnectClientProtocol:
+    def create_request_context(
+        self,
+        *,
+        method: MethodInfo[REQ, RES],
+        url: str,
+        http_method: str,
+        user_headers: Headers | Mapping[str, str] | None,
+        timeout_ms: int | None,
+        codec: Codec,
+        stream: bool,
+        accept_compression: str,
+        send_compression: Compression | None,
+    ) -> RequestContext[REQ, RES]:
+        match user_headers:
+            case Headers():
+                # Copy to prevent modification if user keeps reference
+                # TODO: Optimize
+                headers = Headers(tuple(user_headers.allitems()))
+            case None:
+                headers = Headers()
+            case _:
+                headers = Headers(user_headers)
+
+        if "user-agent" not in headers:
+            headers["user-agent"] = _DEFAULT_CONNECT_USER_AGENT
+        headers["connect-protocol-version"] = CONNECT_PROTOCOL_VERSION
+
+        compression_header = (
+            CONNECT_STREAMING_HEADER_COMPRESSION
+            if stream
+            else CONNECT_UNARY_HEADER_COMPRESSION
+        )
+        accept_compression_header = (
+            CONNECT_STREAMING_HEADER_ACCEPT_COMPRESSION
+            if stream
+            else CONNECT_UNARY_HEADER_ACCEPT_COMPRESSION
+        )
+
+        headers[accept_compression_header] = accept_compression
+        if send_compression is not None:
+            headers[compression_header] = send_compression.name()
+        else:
+            headers.pop(compression_header, None)
+        headers["content-type"] = (
+            f"{CONNECT_STREAMING_CONTENT_TYPE_PREFIX if stream else CONNECT_UNARY_CONTENT_TYPE_PREFIX}{codec.name()}"
+        )
+        if timeout_ms is not None:
+            headers["connect-timeout-ms"] = str(timeout_ms)
+
+        server_address = url_to_server_address(url)
+
+        return RequestContext(
+            method=method,
+            http_method=http_method,
+            request_headers=headers,
+            timeout_ms=timeout_ms,
+            server_address=server_address,
+        )
+
+    def validate_response(
+        self, request_codec_name: str, status_code: int, response_content_type: str
+    ) -> None:
+        response_content_type = _normalize_content_type(response_content_type)
+        if status_code != HTTPStatus.OK:
+            # Error responses must be JSON-encoded
+            if response_content_type == CONNECT_UNARY_CONTENT_TYPE_JSON:
+                return
+            raise ConnectWireError.from_http_status(status_code).to_exception()
+
+        if not response_content_type.startswith(CONNECT_UNARY_CONTENT_TYPE_PREFIX):
+            raise ConnectError(
+                Code.UNKNOWN,
+                f"invalid content-type: '{response_content_type}'; expecting '{CONNECT_UNARY_CONTENT_TYPE_PREFIX}{request_codec_name}'",
+            )
+
+        response_codec_name = codec_name_from_content_type(
+            response_content_type, stream=False
+        )
+        if response_codec_name == request_codec_name:
+            return
+
+        raise ConnectError(
+            Code.INTERNAL,
+            f"invalid content-type: '{response_content_type}'; expecting '{CONNECT_UNARY_CONTENT_TYPE_PREFIX}{request_codec_name}'",
+        )
+
+    def validate_stream_response(
+        self, request_codec_name: str, response_content_type: str
+    ) -> None:
+        if not response_content_type.startswith(CONNECT_STREAMING_CONTENT_TYPE_PREFIX):
+            raise ConnectError(
+                Code.UNKNOWN,
+                f"invalid content-type: '{response_content_type}'; expecting '{CONNECT_STREAMING_CONTENT_TYPE_PREFIX}{request_codec_name}'",
+            )
+
+        response_codec_name = response_content_type[
+            len(CONNECT_STREAMING_CONTENT_TYPE_PREFIX) :
+        ]
+        if response_codec_name != request_codec_name:
+            raise ConnectError(
+                Code.INTERNAL,
+                f"invalid content-type: '{response_content_type}'; expecting '{CONNECT_STREAMING_CONTENT_TYPE_PREFIX}{request_codec_name}'",
+            )
+
+    def handle_response_compression(
+        self,
+        headers: pyqwest.Headers,
+        compressions: dict[str, Compression],
+        *,
+        stream: bool,
+    ) -> Compression:
+        compression_header = (
+            CONNECT_STREAMING_HEADER_COMPRESSION
+            if stream
+            else CONNECT_UNARY_HEADER_COMPRESSION
+        )
+        encoding = headers.get(compression_header)
+        if not encoding:
+            return IdentityCompression()
+        res = compressions.get(encoding)
+        if not res:
+            raise ConnectError(
+                Code.INTERNAL,
+                f"unknown encoding '{encoding}'; accepted encodings are {', '.join(compressions.keys())}",
+            )
+        return res
+
+    def create_envelope_reader(
+        self,
+        message_class: type[RES],
+        codec: Codec,
+        compression: Compression,
+        read_max_bytes: int | None,
+    ) -> EnvelopeReader[RES]:
+        return ConnectEnvelopeReader(message_class, codec, compression, read_max_bytes)
+
+
+class ConnectEnvelopeReader(EnvelopeReader[RES]):
+    def handle_end_message(
+        self, prefix_byte: int, message_data: bytes | bytearray
+    ) -> bool:
+        end_stream = prefix_byte & 0b10 != 0
+        if not end_stream:
+            return False
+        end_stream_message: dict = json.loads(message_data)
+        metadata = end_stream_message.get("metadata")
+        if metadata:
+            handle_response_trailers(metadata)
+        error = end_stream_message.get("error")
+        if error:
+            # Most likely a bug in the protocol, handling of unknown code is different for unary
+            # and streaming.
+            raise ConnectWireError.from_dict(error, 500, Code.UNKNOWN).to_exception()
+        return True

+ 443 - 0
vendor/connectrpc/_protocol_grpc.py

@@ -0,0 +1,443 @@
+from __future__ import annotations
+
+import struct
+import sys
+import urllib.parse
+from base64 import b64decode, b64encode
+from http import HTTPStatus
+from typing import TYPE_CHECKING, Any, TypeVar
+
+from pyqwest import Headers as HTTPHeaders
+
+from ._compression import IdentityCompression, negotiate_compression
+from ._envelope import EnvelopeReader, EnvelopeWriter
+from ._gen.google.rpc.status_pb import Status
+from ._protocol import (
+    ConnectWireError,
+    HTTPException,
+    host_to_server_address,
+    url_to_server_address,
+)
+from ._response_metadata import handle_response_trailers
+from ._version import __version__
+from .code import Code
+from .errors import ConnectError
+from .request import Headers, RequestContext
+
+if TYPE_CHECKING:
+    from collections.abc import Mapping
+
+    from pyqwest import Response, SyncResponse
+
+    from ._codec import Codec
+    from ._compression import Compression
+    from .method import MethodInfo
+
+REQ = TypeVar("REQ")
+RES = TypeVar("RES")
+
+GRPC_CONTENT_TYPE_DEFAULT = "application/grpc"
+GRPC_CONTENT_TYPE_PREFIX = f"{GRPC_CONTENT_TYPE_DEFAULT}+"
+GRPC_WEB_CONTENT_TYPE_DEFAULT = "application/grpc-web"
+GRPC_WEB_CONTENT_TYPE_PREFIX = f"{GRPC_WEB_CONTENT_TYPE_DEFAULT}+"
+
+GRPC_HEADER_TIMEOUT = "grpc-timeout"
+GRPC_HEADER_COMPRESSION = "grpc-encoding"
+GRPC_HEADER_ACCEPT_COMPRESSION = "grpc-accept-encoding"
+
+_DEFAULT_GRPC_USER_AGENT = f"grpc-python-connect/{__version__} ({sys.version})"
+
+
+class GRPCServerProtocol:
+    def create_request_context(
+        self,
+        method: MethodInfo[REQ, RES],
+        http_method: str,
+        http_scheme: str,
+        headers: Headers,
+        client_address: str | None = None,
+    ) -> RequestContext[REQ, RES]:
+        if http_method != "POST":
+            raise HTTPException(HTTPStatus.METHOD_NOT_ALLOWED, [("allow", "POST")])
+
+        timeout_header = headers.get(GRPC_HEADER_TIMEOUT)
+        timeout_ms = _parse_timeout(timeout_header) if timeout_header else None
+
+        server_address = host_to_server_address(headers.get("host"), http_scheme)
+
+        return RequestContext(
+            method=method,
+            http_method=http_method,
+            request_headers=headers,
+            timeout_ms=timeout_ms,
+            server_address=server_address,
+            client_address=client_address,
+        )
+
+    def create_envelope_writer(
+        self, codec: Codec[RES, Any], compression: Compression | None
+    ) -> EnvelopeWriter[RES]:
+        return GRPCEnvelopeWriter(codec, compression)
+
+    def uses_trailers(self) -> bool:
+        return True
+
+    def content_type(self, codec: Codec) -> str:
+        return f"{GRPC_CONTENT_TYPE_PREFIX}{codec.name()}"
+
+    def compression_header_name(self) -> str:
+        return GRPC_HEADER_COMPRESSION
+
+    def codec_name_from_content_type(self, content_type: str, *, stream: bool) -> str:
+        if content_type.startswith(GRPC_CONTENT_TYPE_PREFIX):
+            return content_type[len(GRPC_CONTENT_TYPE_PREFIX) :]
+        return "proto"
+
+    def negotiate_stream_compression(
+        self, headers: Headers, compressions: dict[str, Compression]
+    ) -> tuple[Compression | None, Compression]:
+        req_compression_name = headers.get(GRPC_HEADER_COMPRESSION, "identity")
+        req_compression = compressions.get(req_compression_name)
+        accept_compression = headers.get(GRPC_HEADER_ACCEPT_COMPRESSION, "")
+        resp_compression = negotiate_compression(accept_compression, compressions)
+        return req_compression, resp_compression
+
+
+class GRPCWebServerProtocol(GRPCServerProtocol):
+    def uses_trailers(self) -> bool:
+        return False
+
+    def content_type(self, codec: Codec) -> str:
+        return f"{GRPC_WEB_CONTENT_TYPE_PREFIX}{codec.name()}"
+
+    def codec_name_from_content_type(self, content_type: str, *, stream: bool) -> str:
+        if content_type.startswith(GRPC_WEB_CONTENT_TYPE_PREFIX):
+            return content_type[len(GRPC_WEB_CONTENT_TYPE_PREFIX) :]
+        return "proto"
+
+    def create_envelope_writer(
+        self, codec: Codec[RES, Any], compression: Compression | None
+    ) -> EnvelopeWriter[RES]:
+        return GRPCWebEnvelopeWriter(codec, compression)
+
+
+def _parse_timeout(timeout: str) -> int:
+    # We normalize to int milliseconds matching connect's timeout header.
+    value_to_ms = _lookup_timeout_unit(timeout[-1])
+    try:
+        value = int(timeout[:-1])
+    except ValueError as e:
+        msg = f"protocol error: invalid timeout '{timeout}'"
+        raise ValueError(msg) from e
+
+    # timeout must be ASCII string of at most 8 digits
+    if value > 99999999:
+        msg = f"protocol error: timeout '{timeout}' is too long"
+        raise ValueError(msg)
+
+    return int(value * value_to_ms)
+
+
+def _lookup_timeout_unit(unit: str) -> float:
+    match unit:
+        case "H":
+            return 60 * 60 * 1000
+        case "M":
+            return 60 * 1000
+        case "S":
+            return 1 * 1000
+        case "m":
+            return 1
+        case "u":
+            return 1 / 1000
+        case "n":
+            return 1 / 1000 / 1000
+        case _:
+            msg = f"protocol error: timeout has invalid unit '{unit}'"
+            raise ValueError(msg)
+
+
+class GRPCEnvelopeWriter(EnvelopeWriter):
+    def end(self, user_trailers: Headers, error: ConnectWireError | None) -> Headers:
+        trailers = Headers(list(user_trailers.allitems()))
+        if error:
+            status = _connect_status_to_grpc[error.code]
+            trailers["grpc-status"] = status
+            message = error.message
+            if message:
+                message = urllib.parse.quote(message, safe="")
+                trailers["grpc-message"] = message
+            if error.details:
+                grpc_status = Status(
+                    code=int(status),
+                    message=error.message,
+                    details=[d._any for d in error.details],  # noqa: SLF001
+                )
+                grpc_status_bin = (
+                    b64encode(grpc_status.to_binary()).decode().rstrip("=")
+                )
+                trailers["grpc-status-details-bin"] = grpc_status_bin
+        else:
+            trailers["grpc-status"] = "0"
+        return trailers
+
+
+class GRPCWebEnvelopeWriter(GRPCEnvelopeWriter):
+    def end(self, user_trailers: Headers, error: ConnectWireError | None) -> bytes:  # ty: ignore[invalid-method-override]
+        trailers = super().end(user_trailers, error)
+        data = "".join(f"{k}: {v}\r\n" for k, v in trailers.allitems()).encode()
+        if self._compression:
+            data = self._compression.compress(data)
+        prefix = self._prefix | 0b10000000
+        return struct.pack(">BI", prefix, len(data)) + data
+
+
+class GRPCClientProtocol:
+    def __init__(self) -> None:
+        self._content_type = GRPC_CONTENT_TYPE_DEFAULT
+        self._content_type_prefix = GRPC_CONTENT_TYPE_PREFIX
+
+    def create_request_context(
+        self,
+        *,
+        method: MethodInfo[REQ, RES],
+        url: str,
+        http_method: str,
+        user_headers: Headers | Mapping[str, str] | None,
+        timeout_ms: int | None,
+        codec: Codec,
+        stream: bool,
+        accept_compression: str,
+        send_compression: Compression | None,
+    ) -> RequestContext[REQ, RES]:
+        match user_headers:
+            case Headers():
+                # Copy to prevent modification if user keeps reference
+                # TODO: Optimize
+                headers = Headers(tuple(user_headers.allitems()))
+            case None:
+                headers = Headers()
+            case _:
+                headers = Headers(user_headers)
+        headers["te"] = "trailers"
+        if "user-agent" not in headers:
+            headers["user-agent"] = _DEFAULT_GRPC_USER_AGENT
+
+        headers[GRPC_HEADER_ACCEPT_COMPRESSION] = accept_compression
+        if send_compression is not None:
+            headers[GRPC_HEADER_COMPRESSION] = send_compression.name()
+        else:
+            headers.pop(GRPC_HEADER_COMPRESSION, None)
+        headers["content-type"] = f"{self._content_type_prefix}{codec.name()}"
+        if timeout_ms is not None:
+            headers[GRPC_HEADER_TIMEOUT] = _serialize_timeout(timeout_ms)
+
+        server_address = url_to_server_address(url)
+
+        return RequestContext(
+            method=method,
+            http_method=http_method,
+            request_headers=headers,
+            timeout_ms=timeout_ms,
+            server_address=server_address,
+        )
+
+    def validate_response(
+        self, request_codec_name: str, status_code: int, response_content_type: str
+    ) -> None:
+        raise NotImplementedError
+
+    def validate_stream_response(
+        self, request_codec_name: str, response_content_type: str
+    ) -> None:
+        if not (
+            response_content_type == self._content_type
+            or response_content_type.startswith(self._content_type_prefix)
+        ):
+            raise ConnectError(
+                Code.UNKNOWN,
+                f"invalid content-type: '{response_content_type}'; expecting '{self._content_type_prefix}{request_codec_name}'",
+            )
+        if response_content_type.startswith(self._content_type_prefix):
+            response_codec_name = response_content_type[
+                len(self._content_type_prefix) :
+            ]
+        else:
+            response_codec_name = "proto"
+        if response_codec_name != request_codec_name:
+            raise ConnectError(
+                Code.INTERNAL,
+                f"invalid content-type: '{response_content_type}'; expecting '{self._content_type_prefix}{request_codec_name}'",
+            )
+
+    def handle_response_compression(
+        self, headers: HTTPHeaders, compressions: dict[str, Compression], stream: bool
+    ) -> Compression:
+        encoding = headers.get(GRPC_HEADER_COMPRESSION)
+        if not encoding:
+            return IdentityCompression()
+        res = compressions.get(encoding)
+        if not res:
+            raise ConnectError(
+                Code.INTERNAL,
+                f"unknown encoding '{encoding}'; accepted encodings are {', '.join(compressions.keys())}",
+            )
+        return res
+
+    def create_envelope_reader(
+        self,
+        message_class: type[RES],
+        codec: Codec,
+        compression: Compression,
+        read_max_bytes: int | None,
+    ) -> EnvelopeReader[RES]:
+        return GRPCEnvelopeReader(message_class, codec, compression, read_max_bytes)
+
+
+class GRPCWebClientProtocol(GRPCClientProtocol):
+    def __init__(self) -> None:
+        self._content_type = GRPC_WEB_CONTENT_TYPE_DEFAULT
+        self._content_type_prefix = GRPC_WEB_CONTENT_TYPE_PREFIX
+
+    def create_envelope_reader(
+        self,
+        message_class: type[RES],
+        codec: Codec,
+        compression: Compression,
+        read_max_bytes: int | None,
+    ) -> EnvelopeReader[RES]:
+        return GRPCWebEnvelopeReader(message_class, codec, compression, read_max_bytes)
+
+
+class GRPCEnvelopeReader(EnvelopeReader[RES]):
+    def __init__(
+        self,
+        message_class: type[RES],
+        codec: Codec,
+        compression: Compression,
+        read_max_bytes: int | None,
+    ) -> None:
+        super().__init__(message_class, codec, compression, read_max_bytes)
+        self._read_message = False
+
+    def handle_end_message(
+        self, prefix_byte: int, message_data: bytes | bytearray
+    ) -> bool:
+        # It's coincidence that this method is called when there is a body and not
+        # when there isn't. Somewhat hacky, but easiest way to handle the case
+        # where there is a body and no trailers, which conformance tests verify.
+        self._read_message = True
+        return False
+
+    def get_response_trailers(self, response: Response | SyncResponse) -> HTTPHeaders:
+        return response.trailers
+
+    def handle_response_complete(
+        self, response: Response | SyncResponse, e: ConnectError | None = None
+    ) -> None:
+        # Get the actual HTTP trailers
+        trailers = self.get_response_trailers(response)
+
+        # gRPC trailers are either the HTTP trailers if there was body present
+        # or HTTP headers if there was no body.
+        grpc_status = trailers.get("grpc-status")
+        if grpc_status is None:
+            # If there was a body message, we do not read response headers
+            if self._read_message:
+                raise e or ConnectError(Code.INTERNAL, "missing grpc-status trailer")
+            trailers = response.headers
+
+        handle_response_trailers(trailers)
+
+        grpc_status = trailers.get("grpc-status")
+        if grpc_status is None:
+            raise e or ConnectError(Code.INTERNAL, "missing grpc-status trailer")
+
+        # e is present for RST_STREAM. We prioritize its code while reading message and details
+        # from trailers when available.
+        code = e.code if e else None
+        if grpc_status != "0":
+            message = trailers.get("grpc-message", "")
+            if grpc_status_details := trailers.get("grpc-status-details-bin"):
+                status = Status.from_binary(b64decode(grpc_status_details + "==="))
+                connect_code = code or _grpc_status_to_connect.get(
+                    str(status.code), Code.UNKNOWN
+                )
+                raise ConnectError(connect_code, status.message, details=status.details)
+            connect_code = code or _grpc_status_to_connect.get(
+                grpc_status, Code.UNKNOWN
+            )
+            raise ConnectError(connect_code, urllib.parse.unquote(message))
+
+
+class GRPCWebEnvelopeReader(GRPCEnvelopeReader):
+    def __init__(
+        self,
+        message_class: type[RES],
+        codec: Codec,
+        compression: Compression,
+        read_max_bytes: int | None,
+    ) -> None:
+        super().__init__(message_class, codec, compression, read_max_bytes)
+        self._trailers = HTTPHeaders()
+
+    def handle_end_message(
+        self, prefix_byte: int, message_data: bytes | bytearray
+    ) -> bool:
+        super().handle_end_message(prefix_byte, message_data)
+        end_stream = prefix_byte & 0b10000000 != 0
+        if not end_stream:
+            return False
+        for line in message_data.split(b"\r\n"):
+            if not line:
+                continue
+            key, value = line.split(b":", 1)
+            self._trailers.add(key.decode().strip(), value.decode().strip())
+        return True
+
+    def get_response_trailers(self, response: Response | SyncResponse) -> HTTPHeaders:
+        return self._trailers
+
+
+_GRPC_TIMEOUT_MAX_VALUE = 1e8
+
+
+def _serialize_timeout(timeout_ms: int) -> str:
+    if timeout_ms <= 0:
+        return "0n"
+
+    # The gRPC protocol limits timeouts to 8 characters (not counting the unit),
+    # so timeouts must be strictly less than 1e8 of the appropriate unit.
+
+    if timeout_ms < _GRPC_TIMEOUT_MAX_VALUE:
+        size, unit = 1, "m"
+    elif timeout_ms < _GRPC_TIMEOUT_MAX_VALUE * 1000:
+        size, unit = 1 * 1000, "S"
+    elif timeout_ms < _GRPC_TIMEOUT_MAX_VALUE * 60 * 1000:
+        size, unit = 60 * 1000, "M"
+    else:
+        size, unit = 60 * 60 * 1000, "H"
+
+    return f"{timeout_ms // size}{unit}"
+
+
+_connect_status_to_grpc = {
+    Code.CANCELED: "1",
+    Code.UNKNOWN: "2",
+    Code.INVALID_ARGUMENT: "3",
+    Code.DEADLINE_EXCEEDED: "4",
+    Code.NOT_FOUND: "5",
+    Code.ALREADY_EXISTS: "6",
+    Code.PERMISSION_DENIED: "7",
+    Code.RESOURCE_EXHAUSTED: "8",
+    Code.FAILED_PRECONDITION: "9",
+    Code.ABORTED: "10",
+    Code.OUT_OF_RANGE: "11",
+    Code.UNIMPLEMENTED: "12",
+    Code.INTERNAL: "13",
+    Code.UNAVAILABLE: "14",
+    Code.DATA_LOSS: "15",
+    Code.UNAUTHENTICATED: "16",
+}
+
+_grpc_status_to_connect = {v: k for k, v in _connect_status_to_grpc.items()}

+ 33 - 0
vendor/connectrpc/_protocol_server.py

@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from ._protocol_connect import ConnectServerProtocol
+from ._protocol_grpc import (
+    GRPC_CONTENT_TYPE_DEFAULT,
+    GRPC_CONTENT_TYPE_PREFIX,
+    GRPC_WEB_CONTENT_TYPE_DEFAULT,
+    GRPC_WEB_CONTENT_TYPE_PREFIX,
+    GRPCServerProtocol,
+    GRPCWebServerProtocol,
+)
+
+if TYPE_CHECKING:
+    from ._protocol import ServerProtocol
+
+
+_CONNECT_PROTOCOL = ConnectServerProtocol()
+_GRPC_PROTOCOL = GRPCServerProtocol()
+_GRPC_WEB_PROTOCOL = GRPCWebServerProtocol()
+
+
+def negotiate_server_protocol(content_type: str) -> ServerProtocol:
+    if content_type == GRPC_CONTENT_TYPE_DEFAULT or content_type.startswith(
+        GRPC_CONTENT_TYPE_PREFIX
+    ):
+        return _GRPC_PROTOCOL
+    if content_type == GRPC_WEB_CONTENT_TYPE_DEFAULT or content_type.startswith(
+        GRPC_WEB_CONTENT_TYPE_PREFIX
+    ):
+        return _GRPC_WEB_PROTOCOL
+    return _CONNECT_PROTOCOL

+ 110 - 0
vendor/connectrpc/_response_metadata.py

@@ -0,0 +1,110 @@
+from __future__ import annotations
+
+import contextlib
+from contextvars import ContextVar, Token
+from typing import TYPE_CHECKING
+
+from .request import Headers
+
+if TYPE_CHECKING:
+    from collections.abc import Mapping, Sequence
+    from types import TracebackType
+
+    from pyqwest import Headers as HTTPHeaders
+
+
+_current_response = ContextVar["ResponseMetadata"]("connectrpc_current_response")
+
+
+def handle_response_headers(headers: HTTPHeaders) -> None:
+    response = _current_response.get(None)
+    if not response:
+        return
+
+    response_headers: Headers = Headers()
+    response_trailers: Headers = Headers()
+    for key, value in headers.items():
+        if key.startswith("trailer-"):
+            normalized_key = key[len("trailer-") :]
+            obj = response_trailers
+        else:
+            normalized_key = key
+            obj = response_headers
+        obj.add(normalized_key, value)
+    if response_headers:
+        response._headers = response_headers  # noqa: SLF001
+    if response_trailers:
+        response._trailers = response_trailers  # noqa: SLF001
+
+
+def handle_response_trailers(
+    trailers: HTTPHeaders | Mapping[str, Sequence[str]],
+) -> None:
+    response = _current_response.get(None)
+    if not response:
+        return
+    response_trailers = response.trailers
+    for key, value in trailers.items():
+        if isinstance(value, str):
+            response_trailers.add(key, value)
+        else:
+            for v in value:
+                response_trailers.add(key, v)
+    if response_trailers:
+        response._trailers = response_trailers  # noqa: SLF001
+
+
+class ResponseMetadata:
+    """
+    Response metadata separate from the message payload.
+
+    Commonly, RPC client invocations only need the message payload and do not need to
+    directly read other data such as headers or trailers. In cases where they are needed,
+    initialize this class in a context manager to access the response headers and trailers
+    for the invocation made within the context.
+
+    Example:
+        ```python
+        with ResponseMetadata() as resp_data:
+            resp = client.MakeHat(Size(inches=10))
+            do_something_with_response_payload(resp)
+            check_response_headers(resp_data.headers())
+            check_response_trailers(resp_data.trailers())
+        ```
+    """
+
+    _headers: Headers | None = None
+    _trailers: Headers | None = None
+    _token: Token[ResponseMetadata] | None = None
+
+    def __enter__(self) -> ResponseMetadata:
+        self._token = _current_response.set(self)
+        return self
+
+    def __exit__(
+        self,
+        _exc_type: type[BaseException] | None,
+        _exc_value: BaseException | None,
+        _traceback: TracebackType | None,
+    ) -> None:
+        if self._token:
+            # Normal usage with context manager will always work but it is
+            # theoretically possible for user to move to another thread
+            # and this fails, it is fine to ignore it.
+            with contextlib.suppress(Exception):
+                _current_response.reset(self._token)
+        self._token = None
+
+    @property
+    def headers(self) -> Headers:
+        """Returns the response headers."""
+        if self._headers is None:
+            return Headers()
+        return self._headers
+
+    @property
+    def trailers(self) -> Headers:
+        """Returns the response trailers."""
+        if self._trailers is None:
+            return Headers()
+        return self._trailers

+ 665 - 0
vendor/connectrpc/_server_async.py

@@ -0,0 +1,665 @@
+from __future__ import annotations
+
+import base64
+import contextlib
+import functools
+import inspect
+from abc import ABC, abstractmethod
+from asyncio import CancelledError, Event, create_task, sleep
+from dataclasses import replace
+from http import HTTPStatus
+from typing import TYPE_CHECKING, Generic, TypeVar, cast
+from urllib.parse import parse_qs
+
+from ._codec import Codec, get_default_codecs
+from ._compression import negotiate_compression, resolve_compressions
+from ._envelope import EnvelopeReader
+from ._interceptor_async import (
+    BidiStreamInterceptor,
+    ClientStreamInterceptor,
+    Interceptor,
+    ServerStreamInterceptor,
+    UnaryInterceptor,
+    resolve_interceptors,
+)
+from ._protocol import ConnectWireError, HTTPException, ServerProtocol
+from ._protocol_connect import CONNECT_UNARY_CONTENT_TYPE_PREFIX, ConnectServerProtocol
+from ._protocol_server import negotiate_server_protocol
+from ._server_shared import (
+    EndpointBidiStream,
+    EndpointClientStream,
+    EndpointServerStream,
+    EndpointUnary,
+)
+from .code import Code
+from .errors import ConnectError
+from .request import Headers, RequestContext
+
+if TYPE_CHECKING:
+    # We don't use asgiref code so only import from it for type checking
+    from collections.abc import (
+        AsyncGenerator,
+        AsyncIterator,
+        Callable,
+        Iterable,
+        Mapping,
+        Sequence,
+    )
+
+    from asgiref.typing import ASGIReceiveCallable, ASGISendCallable, HTTPScope, Scope
+
+    from . import _server_shared
+    from .compression import Compression
+else:
+    ASGIReceiveCallable = "asgiref.typing.ASGIReceiveCallable"
+    ASGISendCallable = "asgiref.typing.ASGISendCallable"
+    HTTPScope = "asgiref.typing.HTTPScope"
+    Scope = "asgiref.typing.Scope"
+
+
+_SVC = TypeVar("_SVC")
+_REQ = TypeVar("_REQ")
+_RES = TypeVar("_RES")
+
+# We don't mutate query params so use a singleton for when they're not set.
+_UNSET_QUERY_PARAMS: dict[str, list[str]] = {}
+
+# While _server_shared.Endpoint is a closed type, we can't indicate that to Python so define
+# a more precise type here.
+Endpoint = (
+    EndpointBidiStream[_REQ, _RES]
+    | EndpointClientStream[_REQ, _RES]
+    | EndpointServerStream[_REQ, _RES]
+    | EndpointUnary[_REQ, _RES]
+)
+
+
+class ConnectASGIApplication(ABC, Generic[_SVC]):
+    """An ASGI application for the Connect protocol."""
+
+    _resolved_endpoints: Mapping[str, Endpoint] | None
+
+    @property
+    @abstractmethod
+    def path(self) -> str: ...
+
+    def __init__(
+        self,
+        *,
+        service: _SVC | AsyncGenerator[_SVC],
+        endpoints: Callable[[_SVC], Mapping[str, Endpoint]],
+        interceptors: Iterable[Interceptor] = (),
+        read_max_bytes: int | None = None,
+        compressions: Iterable[Compression] | None = None,
+        codecs: Iterable[Codec] | None = None,
+    ) -> None:
+        """Initialize the ASGI application.
+
+        Args:
+            service: The service instance or async generator that yields the service during lifespan.
+            endpoints: A callable that takes the service instance and returns a mapping of URL
+                paths to endpoints. Typically provided directly by generated code from the
+                Connect Python plugin.
+            interceptors: A sequence of interceptors to apply to the endpoints.
+            read_max_bytes: Maximum size of request messages.
+            compressions: Supported compression algorithms. If unset, defaults to gzip.
+                          If set to empty, disables compression.
+            codecs: The codecs supported by the server. If unset, defaults to Protocol Buffers
+                    binary and JSON codecs.
+        """
+        super().__init__()
+        self._service = service
+        self._endpoints = endpoints
+        self._interceptors = interceptors
+        self._resolved_endpoints = None
+        self._read_max_bytes = read_max_bytes
+        self._compressions = resolve_compressions(compressions)
+        codecs = codecs if codecs is not None else get_default_codecs()
+        self._codecs = {codec.name(): codec for codec in codecs}
+
+    async def __call__(
+        self, scope: Scope, receive: ASGIReceiveCallable, send: ASGISendCallable
+    ) -> None:
+        if scope["type"] == "websocket":
+            msg = "connect does not support websockets"
+            raise RuntimeError(msg)
+
+        if scope["type"] == "lifespan":
+            service_iter = None
+            while True:
+                msg = await receive()
+                match msg["type"]:
+                    case "lifespan.startup":
+                        # Need to cast since type checking doesn't seem to narrow well with isasyncgen
+                        if inspect.isasyncgen(self._service):
+                            service_iter = cast(
+                                "AsyncGenerator[_SVC, None]", self._service
+                            )
+                            try:
+                                service = await anext(service_iter)
+                            except Exception as e:
+                                await send(
+                                    {
+                                        "type": "lifespan.startup.failed",
+                                        "message": str(e),
+                                    }
+                                )
+                                return None
+                        else:
+                            service = cast("_SVC", self._service)
+                        self._resolved_endpoints = self._resolve_endpoints(service)
+                        await send({"type": "lifespan.startup.complete"})
+                    case "lifespan.shutdown":
+                        if service_iter is not None:
+                            try:
+                                await service_iter.aclose()
+                            except Exception as e:
+                                await send(
+                                    {
+                                        "type": "lifespan.shutdown.failed",
+                                        "message": str(e),
+                                    }
+                                )
+                                return None
+                        await send({"type": "lifespan.shutdown.complete"})
+                        return None
+
+        if not self._resolved_endpoints:
+            if inspect.isasyncgen(self._service):
+                msg = "ASGI server does not support lifespan but async generator passed for service. Enable lifespan support."
+                raise RuntimeError(msg)
+
+            self._resolved_endpoints = self._resolve_endpoints(
+                cast("_SVC", self._service)
+            )
+        endpoints = self._resolved_endpoints
+
+        ctx: RequestContext | None = None
+        try:
+            path = scope["path"]
+            endpoint = endpoints.get(path)
+            if not endpoint and scope["root_path"]:
+                # The application was mounted at some root so try stripping the prefix.
+                path = path.removeprefix(scope["root_path"])
+                endpoint = endpoints.get(path)
+            if not endpoint:
+                raise HTTPException(HTTPStatus.NOT_FOUND, [])
+
+            http_method = scope["method"]
+            http_scheme = scope.get("scheme", "http")
+            headers = _process_headers(scope.get("headers", ()))
+            client_address = f"{ca[0]}:{ca[1]}" if (ca := scope.get("client")) else None
+
+            content_type = headers.get("content-type", "")
+            protocol = negotiate_server_protocol(content_type)
+            if protocol.uses_trailers() and "http.response.trailers" not in cast(
+                "dict", scope.get("extensions", {})
+            ):
+                msg = f"ASGI server does not support ASGI trailers extension but protocol for content-type '{content_type}' requires trailers"
+                raise RuntimeError(msg)
+
+            ctx = protocol.create_request_context(
+                endpoint.method, http_method, http_scheme, headers, client_address
+            )
+
+            is_unary = isinstance(endpoint, EndpointUnary)
+
+            if http_method == "GET":
+                query_string = scope.get("query_string", b"").decode("utf-8")
+                query_params = parse_qs(query_string, keep_blank_values=True)
+                codec_name = query_params.get("encoding", ("",))[0]
+            else:
+                query_params = _UNSET_QUERY_PARAMS
+                codec_name = protocol.codec_name_from_content_type(
+                    headers.get("content-type", ""), stream=not is_unary
+                )
+            codec = self._codecs.get(codec_name)
+            if not codec:
+                raise HTTPException(
+                    HTTPStatus.UNSUPPORTED_MEDIA_TYPE,
+                    [("Accept-Post", "application/json, application/proto")],
+                )
+
+            if is_unary and isinstance(protocol, ConnectServerProtocol):
+                return await self._handle_unary_connect(
+                    http_method,
+                    headers,
+                    codec,
+                    query_params,
+                    endpoint,
+                    receive,
+                    send,
+                    ctx,
+                )
+        except Exception as e:
+            await self._handle_error(e, ctx, send)
+            if not isinstance(e, (ConnectError, HTTPException)):
+                raise
+            return None
+
+        # Streams have their own error handling so move out of the try block.
+        return await self._handle_stream(
+            receive, send, protocol, endpoint, codec, headers, ctx
+        )
+
+    async def _handle_unary_connect(
+        self,
+        http_method: str,
+        headers: Headers,
+        codec: Codec,
+        query_params: dict[str, list[str]],
+        endpoint: EndpointUnary[_REQ, _RES],
+        receive: ASGIReceiveCallable,
+        send: ASGISendCallable,
+        ctx: RequestContext,
+    ) -> None:
+        accept_encoding = headers.get("accept-encoding", "")
+        compression = negotiate_compression(accept_encoding, self._compressions)
+
+        if http_method == "GET":
+            request = await self._read_get_request(endpoint, codec, query_params)
+        else:
+            request = await self._read_post_request(endpoint, receive, codec, headers)
+
+        response_data = await endpoint.function(request, ctx)
+
+        res_bytes = codec.encode(response_data)
+        response_headers: list[tuple[bytes, bytes]] = [
+            (
+                b"content-type",
+                f"{CONNECT_UNARY_CONTENT_TYPE_PREFIX}{codec.name()}".encode(),
+            )
+        ]
+        res_bytes = compression.compress(res_bytes)
+        response_headers.append((b"content-encoding", compression.name().encode()))
+        response_headers.append((b"vary", b"Accept-Encoding"))
+
+        _add_context_headers(response_headers, ctx)
+
+        await send(
+            {
+                "type": "http.response.start",
+                "status": 200,
+                "headers": response_headers,
+                "trailers": False,
+            }
+        )
+        await send(
+            {"type": "http.response.body", "body": res_bytes, "more_body": False}
+        )
+
+    async def _read_get_request(
+        self,
+        endpoint: EndpointUnary[_REQ, _RES],
+        codec: Codec,
+        params: dict[str, list[str]],
+    ) -> _REQ:
+        """Handle GET request with query parameters."""
+        # Validation
+        if "message" not in params:
+            raise ConnectError(
+                Code.INVALID_ARGUMENT,
+                "'message' parameter is required for GET requests",
+            )
+
+        # Get and decode message
+        message = params["message"][0]
+        is_base64 = "base64" in params and params["base64"][0] == "1"
+
+        if is_base64:
+            try:
+                message = base64.urlsafe_b64decode(message + "===")
+            except Exception as e:
+                raise ConnectError(
+                    Code.INVALID_ARGUMENT, "Invalid base64 encoding"
+                ) from e
+        else:
+            message = message.encode("utf-8")
+
+        # Handle compression
+        compression_name = params.get("compression", ["identity"])[0]
+        compression = self._compressions.get(compression_name)
+        if not compression:
+            raise ConnectError(
+                Code.UNIMPLEMENTED,
+                f"unknown compression: '{compression_name}': supported encodings are {', '.join(self._compressions.keys())}",
+            )
+
+        # Decompress and decode message
+        if message:  # Don't decompress empty messages
+            message = compression.decompress(message)
+
+        # Get the appropriate decoder for the endpoint
+        return codec.decode(message, endpoint.method.input)
+
+    async def _read_post_request(
+        self,
+        endpoint: Endpoint[_REQ, _RES],
+        receive: ASGIReceiveCallable,
+        codec: Codec,
+        headers: Headers,
+    ) -> _REQ:
+        """Handle POST request with body."""
+
+        # Get request body
+        chunks: list[bytes] = [chunk async for chunk in _read_body(receive)]
+        req_body = b"".join(chunks)
+
+        # Handle compression if specified
+        compression_name = headers.get("content-encoding", "identity").lower()
+        compression = self._compressions.get(compression_name)
+        if not compression:
+            raise ConnectError(
+                Code.UNIMPLEMENTED,
+                f"unknown compression: '{compression_name}': supported encodings are {', '.join(self._compressions.keys())}",
+            )
+
+        if req_body:  # Don't decompress empty body
+            req_body = compression.decompress(req_body)
+
+        if self._read_max_bytes is not None and len(req_body) > self._read_max_bytes:
+            raise ConnectError(
+                Code.RESOURCE_EXHAUSTED,
+                f"message is larger than configured max {self._read_max_bytes}",
+            )
+
+        return codec.decode(req_body, endpoint.method.input)
+
+    async def _handle_stream(
+        self,
+        receive: ASGIReceiveCallable,
+        send: ASGISendCallable,
+        protocol: ServerProtocol,
+        endpoint: Endpoint[_REQ, _RES],
+        codec: Codec,
+        headers: Headers,
+        ctx: _server_shared.RequestContext,
+    ) -> None:
+        req_compression, resp_compression = protocol.negotiate_stream_compression(
+            headers, self._compressions
+        )
+
+        writer = protocol.create_envelope_writer(codec, resp_compression)
+
+        error: Exception | None = None
+        sent_headers = False
+        try:
+            if not req_compression:
+                raise ConnectError(
+                    Code.UNIMPLEMENTED, "Unrecognized request compression"
+                )
+            request_stream = _request_stream(
+                receive,
+                endpoint.method.input,
+                codec,
+                req_compression,
+                self._read_max_bytes,
+            )
+
+            disconnect_detected: Event | None = None
+            monitor_task = None
+
+            match endpoint:
+                case EndpointUnary():
+                    request = await _consume_single_request(request_stream)
+                    response = await endpoint.function(request, ctx)
+                    response_stream = _yield_single_response(response)
+                case EndpointClientStream():
+                    response = await endpoint.function(request_stream, ctx)
+                    response_stream = _yield_single_response(response)
+                case EndpointServerStream():
+                    request = await _consume_single_request(request_stream)
+                    response_stream = endpoint.function(request, ctx)
+
+                    # The request has been fully consumed; monitor receive() for a
+                    # client disconnect so we can stop streaming promptly.
+                    disconnect_detected = Event()
+
+                    async def _watch_for_disconnect() -> None:
+                        while True:
+                            msg = await receive()
+                            if msg["type"] == "http.disconnect":
+                                disconnect_detected.set()
+                                return
+
+                    monitor_task = create_task(_watch_for_disconnect())
+                case EndpointBidiStream():
+                    response_stream = endpoint.function(request_stream, ctx)
+
+            try:
+                async for message in response_stream:
+                    if disconnect_detected is not None and disconnect_detected.is_set():
+                        raise ConnectError(Code.CANCELED, "Client disconnected")
+                    # Don't send headers until the first message to allow logic a chance to add
+                    # response headers.
+                    if not sent_headers:
+                        await _send_stream_response_headers(
+                            send, protocol, codec, resp_compression.name(), ctx
+                        )
+                        sent_headers = True
+
+                    body = writer.write(message)
+                    await send(
+                        {"type": "http.response.body", "body": body, "more_body": True}
+                    )
+            finally:
+                # Cancel the monitor first so a throwing generator finally-block
+                # doesn't leak the task.
+                if monitor_task is not None:
+                    monitor_task.cancel()
+                    with contextlib.suppress(CancelledError):
+                        await monitor_task
+                # Explicitly close the stream so that any generator finally-blocks
+                # run promptly (Python defers async-generator cleanup to GC otherwise).
+                aclose = getattr(response_stream, "aclose", None)
+                if aclose is not None:
+                    await aclose()
+        except CancelledError as e:
+            raise ConnectError(Code.CANCELED, "Request was cancelled") from e
+        except Exception as e:
+            error = e
+        finally:
+            end_message = writer.end(
+                ctx.response_trailers,
+                ConnectWireError.from_exception(error) if error else None,
+            )
+            if not sent_headers:
+                # Exception before any response message is returned
+                await _send_stream_response_headers(
+                    send, protocol, codec, resp_compression.name(), ctx
+                )
+            if isinstance(end_message, bytes):
+                await send(
+                    {
+                        "type": "http.response.body",
+                        "body": end_message,
+                        "more_body": False,
+                    }
+                )
+            else:
+                await send(
+                    {"type": "http.response.body", "body": b"", "more_body": False}
+                )
+                await send(
+                    {
+                        "type": "http.response.trailers",
+                        "headers": [
+                            (k.encode(), v.encode()) for k, v in end_message.allitems()
+                        ],
+                        "more_trailers": False,
+                    }
+                )
+            if error and not isinstance(error, ConnectError):
+                raise error
+
+    async def _handle_error(
+        self, exc: Exception, ctx: RequestContext | None, send: ASGISendCallable
+    ) -> None:
+        """Handle errors that occur during request processing."""
+        headers: list[tuple[bytes, bytes]]
+        body: bytes
+        status: int
+        if isinstance(exc, HTTPException):
+            status = exc.status.value
+            headers = [(k.encode("utf-8"), v.encode("utf-8")) for k, v in exc.headers]
+            body = b""
+        else:
+            wire_error = ConnectWireError.from_exception(exc)
+            status = wire_error.to_http_status().code
+            headers = [(b"content-type", b"application/json")]
+            body = wire_error.to_json_bytes()
+
+        if ctx:
+            _add_context_headers(headers, ctx)
+
+        await send(
+            {
+                "type": "http.response.start",
+                "status": status,
+                "headers": headers,
+                "trailers": False,
+            }
+        )
+        await send({"type": "http.response.body", "body": body, "more_body": False})
+
+    def _resolve_endpoints(self, service: _SVC) -> Mapping[str, Endpoint]:
+        resolved_endpoints = self._endpoints(service)
+        if self._interceptors:
+            resolved_endpoints = {
+                path: _apply_interceptors(
+                    endpoint, resolve_interceptors(self._interceptors)
+                )
+                for path, endpoint in resolved_endpoints.items()
+            }
+        return resolved_endpoints
+
+
+async def _send_stream_response_headers(
+    send: ASGISendCallable,
+    protocol: ServerProtocol,
+    codec: Codec,
+    compression_name: str,
+    ctx: RequestContext,
+) -> None:
+    response_headers = [
+        (b"content-type", protocol.content_type(codec).encode()),
+        (protocol.compression_header_name().encode(), compression_name.encode()),
+    ]
+    response_headers.extend(
+        (key.encode(), value.encode()) for key, value in ctx.response_headers.allitems()
+    )
+    await send(
+        {
+            "type": "http.response.start",
+            "status": 200,
+            "headers": response_headers,
+            "trailers": protocol.uses_trailers(),
+        }
+    )
+
+
+async def _request_stream(
+    receive: ASGIReceiveCallable,
+    request_class: type[_REQ],
+    codec: Codec,
+    compression: Compression,
+    read_max_bytes: int | None = None,
+) -> AsyncIterator[_REQ]:
+    reader = EnvelopeReader(request_class, codec, compression, read_max_bytes)
+    try:
+        async for chunk in _read_body(receive):
+            for message in reader.feed(chunk):
+                yield message
+                # Check for cancellation each message. While this seems heavyweight,
+                # conformance tests require it.
+                await sleep(0)
+    except CancelledError as e:
+        raise ConnectError(Code.CANCELED, "Request was cancelled") from e
+
+
+async def _read_body(receive: ASGIReceiveCallable) -> AsyncIterator[bytes]:
+    """Read the body of the request."""
+    while True:
+        message = await receive()
+        match message["type"]:
+            case "http.request":
+                body = message.get("body", b"")
+                yield body
+                if not message.get("more_body", False):
+                    return
+            case "http.disconnect":
+                raise ConnectError(
+                    Code.CANCELED, "Client disconnected before request completion"
+                )
+            case _:
+                raise ConnectError(Code.UNKNOWN, "Unexpected message type")
+
+
+async def _consume_single_request(stream: AsyncIterator[_REQ]) -> _REQ:
+    req = None
+    async for message in stream:
+        if req is not None:
+            raise ConnectError(
+                Code.UNIMPLEMENTED, "unary request has multiple messages"
+            )
+        req = message
+    if req is None:
+        raise ConnectError(Code.UNIMPLEMENTED, "unary request has zero messages")
+    return req
+
+
+async def _yield_single_response(response: _RES) -> AsyncIterator[_RES]:
+    yield response
+
+
+def _process_headers(iterable: Iterable[tuple[bytes, bytes]]) -> Headers:
+    result = Headers()
+    for key, value in iterable:
+        result.add(key.decode(), value.decode())
+    return result
+
+
+def _add_context_headers(
+    headers: list[tuple[bytes, bytes]], ctx: RequestContext
+) -> None:
+    headers.extend(
+        (key.encode(), value.encode()) for key, value in ctx.response_headers.allitems()
+    )
+    headers.extend(
+        (f"trailer-{key}".encode(), value.encode())
+        for key, value in ctx.response_trailers.allitems()
+    )
+
+
+def _apply_interceptors(
+    endpoint: Endpoint[_REQ, _RES], interceptors: Sequence[Interceptor]
+) -> Endpoint[_REQ, _RES]:
+    match endpoint:
+        case EndpointUnary():
+            func = endpoint.function
+            for interceptor in reversed(interceptors):
+                if not isinstance(interceptor, UnaryInterceptor):
+                    continue
+                func = functools.partial(interceptor.intercept_unary, func)
+            return replace(endpoint, function=func)
+        case EndpointClientStream():
+            func = endpoint.function
+            for interceptor in reversed(interceptors):
+                if not isinstance(interceptor, ClientStreamInterceptor):
+                    continue
+                func = functools.partial(interceptor.intercept_client_stream, func)
+            return replace(endpoint, function=func)
+        case EndpointServerStream():
+            func = endpoint.function
+            for interceptor in reversed(interceptors):
+                if not isinstance(interceptor, ServerStreamInterceptor):
+                    continue
+                func = functools.partial(interceptor.intercept_server_stream, func)
+            return replace(endpoint, function=func)
+        case EndpointBidiStream():
+            func = endpoint.function
+            for interceptor in reversed(interceptors):
+                if not isinstance(interceptor, BidiStreamInterceptor):
+                    continue
+                func = functools.partial(interceptor.intercept_bidi_stream, func)
+            return replace(endpoint, function=func)

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません