tigrcorn-protocols 0.3.16.dev12__tar.gz → 0.3.17.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/PKG-INFO +6 -6
  2. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/pyproject.toml +6 -6
  3. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/__init__.py +8 -0
  4. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/client_session_coverage/__init__.py +9 -0
  5. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/client_session_coverage/matrix.py +36 -0
  6. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/client_session_coverage/models.py +104 -0
  7. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/client_session_coverage/robustness.py +136 -0
  8. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/client_session_coverage/topology.py +130 -0
  9. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/client_session_coverage/validation.py +76 -0
  10. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http1/parser/__init__.py +14 -0
  11. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http1/parser/body.py +79 -0
  12. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http1/parser/core.py +43 -0
  13. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http1/parser/head.py +189 -0
  14. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http1/parser/models.py +39 -0
  15. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http1/parser/target.py +33 -0
  16. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http1/parser/validation.py +21 -0
  17. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/__init__.py +5 -0
  18. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/connect.py +176 -0
  19. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/core.py +88 -0
  20. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/flow_control.py +153 -0
  21. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/frames.py +149 -0
  22. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/headers.py +273 -0
  23. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/imports.py +69 -0
  24. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/io.py +77 -0
  25. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/requests.py +203 -0
  26. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/responses.py +233 -0
  27. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/streams.py +157 -0
  28. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http2/handler/websocket_support.py +46 -0
  29. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http3/__init__.py +12 -0
  30. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http3/codec.py +69 -0
  31. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/connect.py +269 -0
  32. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/core.py +59 -0
  33. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/custom.py +53 -0
  34. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/imports.py +58 -0
  35. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/lifecycle.py +126 -0
  36. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/outbound.py +171 -0
  37. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/packet.py +400 -0
  38. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/requests.py +292 -0
  39. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/responses.py +86 -0
  40. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/session.py +38 -0
  41. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/websocket_runtime.py +206 -0
  42. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http3/handler/webtransport.py +7 -1
  43. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/webtransport_datagrams.py +111 -0
  44. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/webtransport_streams.py +296 -0
  45. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/handler/webtransport_support.py +101 -0
  46. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/qpack/__init__.py +71 -0
  47. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/qpack/decoder.py +249 -0
  48. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/qpack/encoder.py +253 -0
  49. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/qpack/model.py +197 -0
  50. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/qpack/static_table.py +118 -0
  51. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/qpack/wire.py +60 -0
  52. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/streams/__init__.py +29 -0
  53. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/streams/connection.py +284 -0
  54. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/streams/constants.py +17 -0
  55. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/http3/streams/request.py +354 -0
  56. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/resume.py +202 -0
  57. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/websocket/handler/__init__.py +7 -0
  58. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/websocket/handler/app_send.py +183 -0
  59. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/websocket/handler/core.py +314 -0
  60. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/websocket/handler/errors.py +8 -0
  61. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/webtransport/__init__.py +61 -0
  62. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/webtransport/governance.py +257 -0
  63. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols/webtransport/wire.py +851 -0
  64. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols.egg-info/PKG-INFO +6 -6
  65. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols.egg-info/SOURCES.txt +57 -7
  66. tigrcorn_protocols-0.3.17.dev1/src/tigrcorn_protocols.egg-info/requires.txt +5 -0
  67. tigrcorn_protocols-0.3.16.dev12/src/tigrcorn_protocols/__init__.py +0 -1
  68. tigrcorn_protocols-0.3.16.dev12/src/tigrcorn_protocols/http1/parser.py +0 -481
  69. tigrcorn_protocols-0.3.16.dev12/src/tigrcorn_protocols/http2/handler.py +0 -1499
  70. tigrcorn_protocols-0.3.16.dev12/src/tigrcorn_protocols/http3/handler/core.py +0 -1958
  71. tigrcorn_protocols-0.3.16.dev12/src/tigrcorn_protocols/http3/handler.py +0 -3
  72. tigrcorn_protocols-0.3.16.dev12/src/tigrcorn_protocols/http3/qpack.py +0 -843
  73. tigrcorn_protocols-0.3.16.dev12/src/tigrcorn_protocols/http3/streams.py +0 -657
  74. tigrcorn_protocols-0.3.16.dev12/src/tigrcorn_protocols/websocket/handler.py +0 -462
  75. tigrcorn_protocols-0.3.16.dev12/src/tigrcorn_protocols.egg-info/requires.txt +0 -5
  76. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/LICENSE +0 -0
  77. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/README.md +0 -0
  78. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/setup.cfg +0 -0
  79. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/_compression.py +0 -0
  80. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/connect.py +0 -0
  81. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/content_coding.py +0 -0
  82. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/custom/__init__.py +0 -0
  83. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/custom/adapters.py +0 -0
  84. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/custom/registry.py +0 -0
  85. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/flow/__init__.py +0 -0
  86. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/flow/backpressure.py +0 -0
  87. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/flow/buffers.py +0 -0
  88. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/flow/credits.py +0 -0
  89. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/flow/keepalive.py +0 -0
  90. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/flow/timeouts.py +0 -0
  91. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/flow/watermarks.py +0 -0
  92. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http1/__init__.py +0 -0
  93. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http1/keepalive.py +0 -0
  94. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http1/serializer.py +0 -0
  95. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http1/state.py +0 -0
  96. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http2/__init__.py +0 -0
  97. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http2/codec.py +0 -0
  98. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http2/flow.py +0 -0
  99. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http2/hpack.py +0 -0
  100. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http2/state.py +0 -0
  101. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http2/streams.py +0 -0
  102. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http2/websocket.py +0 -0
  103. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http3/handler/__init__.py +0 -0
  104. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http3/state.py +0 -0
  105. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/http3/websocket.py +0 -0
  106. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/lifespan/__init__.py +0 -0
  107. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/lifespan/driver.py +0 -0
  108. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/py.typed +0 -0
  109. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/rawframed/__init__.py +0 -0
  110. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/rawframed/codec.py +0 -0
  111. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/rawframed/frames.py +0 -0
  112. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/rawframed/handler.py +0 -0
  113. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/rawframed/state.py +0 -0
  114. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/registry.py +0 -0
  115. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/scheduler/__init__.py +0 -0
  116. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/scheduler/cancellation.py +0 -0
  117. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/scheduler/dispatch.py +0 -0
  118. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/scheduler/fairness.py +0 -0
  119. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/scheduler/policy.py +0 -0
  120. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/scheduler/priorities.py +0 -0
  121. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/scheduler/quotas.py +0 -0
  122. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/scheduler/runtime.py +0 -0
  123. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/scheduler/tasks.py +0 -0
  124. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/sessions/__init__.py +0 -0
  125. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/sessions/base.py +0 -0
  126. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/sessions/connection.py +0 -0
  127. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/sessions/limits.py +0 -0
  128. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/sessions/manager.py +0 -0
  129. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/sessions/metadata.py +0 -0
  130. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/sessions/quic.py +0 -0
  131. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/streams/__init__.py +0 -0
  132. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/streams/base.py +0 -0
  133. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/streams/ids.py +0 -0
  134. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/streams/multiplex.py +0 -0
  135. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/streams/registry.py +0 -0
  136. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/streams/singleplex.py +0 -0
  137. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/websocket/__init__.py +0 -0
  138. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/websocket/codec.py +0 -0
  139. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/websocket/extensions.py +0 -0
  140. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/websocket/frames.py +0 -0
  141. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/websocket/handshake.py +0 -0
  142. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols/websocket/state.py +0 -0
  143. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols.egg-info/dependency_links.txt +0 -0
  144. {tigrcorn_protocols-0.3.16.dev12 → tigrcorn_protocols-0.3.17.dev1}/src/tigrcorn_protocols.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tigrcorn-protocols
3
- Version: 0.3.16.dev12
3
+ Version: 0.3.17.dev1
4
4
  Summary: Protocol handlers for Tigrcorn HTTP/1.1, HTTP/2, HTTP/3, QUIC, WebSocket, WebTransport, lifespan, and ASGI3 traffic.
5
5
  Author-email: Jacob Stewart <jacob@swarmauri.com>
6
6
  License: Apache License
@@ -187,11 +187,11 @@ Classifier: Typing :: Typed
187
187
  Requires-Python: <3.15,>=3.10
188
188
  Description-Content-Type: text/markdown
189
189
  License-File: LICENSE
190
- Requires-Dist: tigrcorn-core==0.3.16.dev12
191
- Requires-Dist: tigrcorn-config==0.3.16.dev12
192
- Requires-Dist: tigrcorn-asgi==0.3.16.dev12
193
- Requires-Dist: tigrcorn-http==0.3.16.dev12
194
- Requires-Dist: tigrcorn-transports==0.3.16.dev12
190
+ Requires-Dist: tigrcorn-core==0.3.17.dev1
191
+ Requires-Dist: tigrcorn-config==0.3.17.dev1
192
+ Requires-Dist: tigrcorn-asgi==0.3.17.dev1
193
+ Requires-Dist: tigrcorn-http==0.3.17.dev1
194
+ Requires-Dist: tigrcorn-transports==0.3.17.dev1
195
195
  Dynamic: license-file
196
196
 
197
197
  <div align="center">
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tigrcorn-protocols"
7
- version = "0.3.16.dev12"
7
+ version = "0.3.17.dev1"
8
8
  description = "Protocol handlers for Tigrcorn HTTP/1.1, HTTP/2, HTTP/3, QUIC, WebSocket, WebTransport, lifespan, and ASGI3 traffic."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10,<3.15"
@@ -30,11 +30,11 @@ classifiers = [
30
30
  "Typing :: Typed",
31
31
  ]
32
32
  dependencies = [
33
- "tigrcorn-core==0.3.16.dev12",
34
- "tigrcorn-config==0.3.16.dev12",
35
- "tigrcorn-asgi==0.3.16.dev12",
36
- "tigrcorn-http==0.3.16.dev12",
37
- "tigrcorn-transports==0.3.16.dev12",
33
+ "tigrcorn-core==0.3.17.dev1",
34
+ "tigrcorn-config==0.3.17.dev1",
35
+ "tigrcorn-asgi==0.3.17.dev1",
36
+ "tigrcorn-http==0.3.17.dev1",
37
+ "tigrcorn-transports==0.3.17.dev1",
38
38
  ]
39
39
 
40
40
  [tool.setuptools]
@@ -0,0 +1,8 @@
1
+ """Protocol implementations and protocol registries."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "client_session_coverage",
7
+ "resume",
8
+ ]
@@ -0,0 +1,9 @@
1
+ from __future__ import annotations
2
+
3
+ from .models import *
4
+ from .validation import *
5
+ from .matrix import *
6
+ from .topology import *
7
+ from .robustness import *
8
+
9
+ __all__ = [name for name in globals() if not name.startswith("_")]
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from .models import ClientTopology, CoverageDisposition, ProtocolCarrier, SessionScope
6
+ from .validation import _enum_value, _normalized, classify_default_session_scope, validate_matrix_row
7
+
8
+
9
+ def build_matrix_row(
10
+ *,
11
+ protocol_carrier: ProtocolCarrier | str,
12
+ client_topology: ClientTopology | str,
13
+ session_scope: SessionScope | str | None = None,
14
+ disposition: CoverageDisposition | str = CoverageDisposition.REQUIRED,
15
+ lifecycle_behavior: CoverageDisposition | str = CoverageDisposition.REQUIRED,
16
+ identity_isolation: CoverageDisposition | str = CoverageDisposition.REQUIRED,
17
+ ordering_behavior: CoverageDisposition | str = CoverageDisposition.REQUIRED,
18
+ pressure_mode: CoverageDisposition | str = CoverageDisposition.REQUIRED,
19
+ fault_mode: CoverageDisposition | str = CoverageDisposition.REQUIRED,
20
+ **identifiers: Any,
21
+ ) -> dict[str, Any]:
22
+ carrier = _normalized(_enum_value(protocol_carrier))
23
+ row: dict[str, Any] = {
24
+ "protocol_carrier": carrier,
25
+ "client_topology": _enum_value(client_topology),
26
+ "session_scope": _enum_value(session_scope or classify_default_session_scope(carrier)),
27
+ "lifecycle_behavior": _enum_value(lifecycle_behavior),
28
+ "identity_isolation": _enum_value(identity_isolation),
29
+ "ordering_behavior": _enum_value(ordering_behavior),
30
+ "pressure_mode": _enum_value(pressure_mode),
31
+ "fault_mode": _enum_value(fault_mode),
32
+ "disposition": _enum_value(disposition),
33
+ }
34
+ row.update(identifiers)
35
+ validate_matrix_row(row)
36
+ return row
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+
6
+
7
+ class ProtocolCarrier(str, Enum):
8
+ HTTP1 = "http1"
9
+ HTTP2 = "http2"
10
+ HTTP3_QUIC = "http3_quic"
11
+ WEBSOCKET_H1 = "websocket_h1"
12
+ WEBSOCKET_H2 = "websocket_h2"
13
+ WEBSOCKET_H3 = "websocket_h3"
14
+ WEBTRANSPORT_H3_QUIC = "webtransport_h3_quic"
15
+
16
+
17
+ class ClientTopology(str, Enum):
18
+ SINGLE_CLIENT = "single_client"
19
+ SEQUENTIAL_CLIENTS = "sequential_clients"
20
+ BOUNDED_INTERLEAVED_CLIENTS = "bounded_interleaved_clients"
21
+ CONCURRENT_CLIENTS = "concurrent_clients"
22
+ CHURN_CLIENTS = "churn_clients"
23
+
24
+
25
+ class SessionScope(str, Enum):
26
+ REQUEST_SCOPED = "request_scoped"
27
+ TCP_CONNECTION_SCOPED = "tcp_connection_scoped"
28
+ H2_CONNECTION_SCOPED = "h2_connection_scoped"
29
+ H2_STREAM_SCOPED = "h2_stream_scoped"
30
+ QUIC_CONNECTION_SCOPED = "quic_connection_scoped"
31
+ H3_STREAM_SCOPED = "h3_stream_scoped"
32
+ WEBSOCKET_CONNECTION_SCOPED = "websocket_connection_scoped"
33
+ WEBTRANSPORT_SESSION_SCOPED = "webtransport_session_scoped"
34
+ WEBTRANSPORT_STREAM_SCOPED = "webtransport_stream_scoped"
35
+ WEBTRANSPORT_DATAGRAM_SCOPED = "webtransport_datagram_scoped"
36
+
37
+
38
+ class CoverageDisposition(str, Enum):
39
+ COVERED = "covered"
40
+ REQUIRED = "required"
41
+ PLANNED = "planned"
42
+ NOT_APPLICABLE = "not_applicable"
43
+ FAIL_CLOSED = "fail_closed"
44
+
45
+
46
+ class BehaviorAxis(str, Enum):
47
+ LIFECYCLE = "lifecycle_behavior"
48
+ IDENTITY_ISOLATION = "identity_isolation"
49
+ ORDERING = "ordering_behavior"
50
+ PRESSURE = "pressure_mode"
51
+ FAULT = "fault_mode"
52
+
53
+
54
+ PROTOCOL_CARRIER_VALUES = frozenset(item.value for item in ProtocolCarrier)
55
+ CLIENT_TOPOLOGY_VALUES = frozenset(item.value for item in ClientTopology)
56
+ SESSION_SCOPE_VALUES = frozenset(item.value for item in SessionScope)
57
+ DISPOSITION_VALUES = frozenset(item.value for item in CoverageDisposition)
58
+ BEHAVIOR_AXIS_VALUES = frozenset(item.value for item in BehaviorAxis)
59
+
60
+ GOVERNED_IDENTIFIER_FIELDS = frozenset(
61
+ {
62
+ "client_id",
63
+ "connection_id",
64
+ "session_id",
65
+ "stream_id",
66
+ "datagram_id",
67
+ }
68
+ )
69
+ INTERNAL_ONLY_FIELDS = frozenset({"lane"})
70
+
71
+ REQUIRED_MATRIX_AXES = frozenset(
72
+ {
73
+ "protocol_carrier",
74
+ "client_topology",
75
+ "session_scope",
76
+ "lifecycle_behavior",
77
+ "identity_isolation",
78
+ "ordering_behavior",
79
+ "pressure_mode",
80
+ "fault_mode",
81
+ "disposition",
82
+ }
83
+ )
84
+
85
+ PROTOCOL_CARRIER_DEFAULT_SCOPES: dict[str, SessionScope] = {
86
+ ProtocolCarrier.HTTP1.value: SessionScope.REQUEST_SCOPED,
87
+ ProtocolCarrier.HTTP2.value: SessionScope.H2_STREAM_SCOPED,
88
+ ProtocolCarrier.HTTP3_QUIC.value: SessionScope.H3_STREAM_SCOPED,
89
+ ProtocolCarrier.WEBSOCKET_H1.value: SessionScope.WEBSOCKET_CONNECTION_SCOPED,
90
+ ProtocolCarrier.WEBSOCKET_H2.value: SessionScope.WEBSOCKET_CONNECTION_SCOPED,
91
+ ProtocolCarrier.WEBSOCKET_H3.value: SessionScope.WEBSOCKET_CONNECTION_SCOPED,
92
+ ProtocolCarrier.WEBTRANSPORT_H3_QUIC.value: SessionScope.WEBTRANSPORT_SESSION_SCOPED,
93
+ }
94
+
95
+
96
+ @dataclass
97
+ class ClientSession:
98
+ client_id: str
99
+ connection_id: str
100
+ session_id: str
101
+ closed: bool = False
102
+ payloads: list[str] = field(default_factory=list)
103
+ streams_seen: set[str] = field(default_factory=set)
104
+ datagrams_seen: set[str] = field(default_factory=set)
@@ -0,0 +1,136 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from .matrix import build_matrix_row
6
+ from .models import ClientSession, ClientTopology, CoverageDisposition, ProtocolCarrier, SessionScope
7
+
8
+
9
+ class ClientSessionRobustnessHarness:
10
+ """Carrier-aware runtime harness for T2 pressure, fault, and cleanup proof."""
11
+
12
+ def __init__(
13
+ self,
14
+ carrier: ProtocolCarrier,
15
+ scope: SessionScope | None = None,
16
+ *,
17
+ max_sessions: int = 32,
18
+ max_streams: int = 32,
19
+ max_queue: int = 32,
20
+ max_message_size: int = 65536,
21
+ max_datagram_size: int = 1200,
22
+ ) -> None:
23
+ self.carrier = carrier
24
+ self.scope = scope
25
+ self.max_sessions = max_sessions
26
+ self.max_streams = max_streams
27
+ self.max_queue = max_queue
28
+ self.max_message_size = max_message_size
29
+ self.max_datagram_size = max_datagram_size
30
+ self.sessions: dict[str, ClientSession] = {}
31
+ self.errors: list[dict[str, Any]] = []
32
+
33
+ def open(self, client_id: str, connection_id: str, session_id: str) -> None:
34
+ if len([session for session in self.sessions.values() if not session.closed]) >= self.max_sessions:
35
+ self.fail_closed(client_id, connection_id, session_id, ClientTopology.CONCURRENT_CLIENTS, "max_sessions_exceeded")
36
+ raise BufferError("session pressure budget exceeded")
37
+ if session_id in self.sessions and not self.sessions[session_id].closed:
38
+ self.fail_closed(client_id, connection_id, session_id, ClientTopology.CONCURRENT_CLIENTS, "duplicate_session")
39
+ raise ValueError("duplicate session rejected")
40
+ self.sessions[session_id] = ClientSession(client_id=client_id, connection_id=connection_id, session_id=session_id)
41
+
42
+ def send(
43
+ self,
44
+ client_id: str,
45
+ connection_id: str,
46
+ session_id: str,
47
+ topology: ClientTopology,
48
+ payload: object,
49
+ *,
50
+ stream_id: str | int | None = None,
51
+ datagram_id: str | int | None = None,
52
+ ) -> None:
53
+ session = self.session_for(client_id, connection_id, session_id, topology)
54
+ identifiers = {"stream_id": stream_id, "datagram_id": datagram_id}
55
+ if session.closed:
56
+ self.fail_closed(client_id, connection_id, session_id, topology, "post_close_send", **identifiers)
57
+ raise RuntimeError("post-close send rejected")
58
+ if not isinstance(payload, (str, bytes)) or not payload:
59
+ self.fail_closed(client_id, connection_id, session_id, topology, "malformed_payload", **identifiers)
60
+ raise ValueError("malformed payload rejected")
61
+ payload_size = len(payload)
62
+ if payload_size > self.max_message_size:
63
+ self.fail_closed(client_id, connection_id, session_id, topology, "message_pressure_budget_exceeded", **identifiers)
64
+ raise BufferError("message pressure budget exceeded")
65
+ if datagram_id is not None and payload_size > self.max_datagram_size:
66
+ self.fail_closed(client_id, connection_id, session_id, topology, "datagram_pressure_budget_exceeded", **identifiers)
67
+ raise BufferError("datagram pressure budget exceeded")
68
+ if len(session.payloads) >= self.max_queue:
69
+ self.fail_closed(client_id, connection_id, session_id, topology, "queue_pressure_budget_exceeded", **identifiers)
70
+ raise BufferError("queue pressure budget exceeded")
71
+ if stream_id is not None:
72
+ session.streams_seen.add(str(stream_id))
73
+ if len(session.streams_seen) > self.max_streams:
74
+ self.fail_closed(client_id, connection_id, session_id, topology, "max_streams_exceeded", **identifiers)
75
+ session.streams_seen.remove(str(stream_id))
76
+ raise BufferError("stream pressure budget exceeded")
77
+ if datagram_id is not None:
78
+ session.datagrams_seen.add(str(datagram_id))
79
+ session.payloads.append(payload if isinstance(payload, str) else payload.decode("latin1"))
80
+
81
+ def close(self, client_id: str, connection_id: str, session_id: str, topology: ClientTopology) -> None:
82
+ self.session_for(client_id, connection_id, session_id, topology).closed = True
83
+
84
+ def cancel(self, client_id: str, connection_id: str, session_id: str, topology: ClientTopology) -> None:
85
+ self.session_for(client_id, connection_id, session_id, topology).closed = True
86
+ self.fail_closed(client_id, connection_id, session_id, topology, "cancelled")
87
+
88
+ def timeout(self, client_id: str, connection_id: str, session_id: str, topology: ClientTopology) -> None:
89
+ self.session_for(client_id, connection_id, session_id, topology).closed = True
90
+ self.fail_closed(client_id, connection_id, session_id, topology, "timeout")
91
+
92
+ def session_for(
93
+ self,
94
+ client_id: str,
95
+ connection_id: str,
96
+ session_id: str,
97
+ topology: ClientTopology,
98
+ ) -> ClientSession:
99
+ try:
100
+ session = self.sessions[session_id]
101
+ except KeyError as exc:
102
+ self.fail_closed(client_id, connection_id, session_id, topology, "unknown_session")
103
+ raise KeyError("unknown session rejected") from exc
104
+ if session.client_id != client_id or session.connection_id != connection_id:
105
+ self.fail_closed(client_id, connection_id, session_id, topology, "cross_client_session_access")
106
+ raise PermissionError("cross-client or cross-connection session access rejected")
107
+ return session
108
+
109
+ def fail_closed(
110
+ self,
111
+ client_id: str,
112
+ connection_id: str,
113
+ session_id: str,
114
+ topology: ClientTopology,
115
+ error_kind: str,
116
+ **identifiers: Any,
117
+ ) -> dict[str, Any]:
118
+ identifiers = {key: value for key, value in identifiers.items() if value is not None}
119
+ row = build_matrix_row(
120
+ protocol_carrier=self.carrier,
121
+ client_topology=topology,
122
+ session_scope=self.scope,
123
+ disposition=CoverageDisposition.FAIL_CLOSED,
124
+ lifecycle_behavior=CoverageDisposition.COVERED,
125
+ identity_isolation=CoverageDisposition.COVERED,
126
+ ordering_behavior=CoverageDisposition.COVERED,
127
+ pressure_mode=CoverageDisposition.COVERED,
128
+ fault_mode=CoverageDisposition.COVERED,
129
+ client_id=client_id,
130
+ connection_id=connection_id,
131
+ session_id=session_id,
132
+ error_kind=error_kind,
133
+ **identifiers,
134
+ )
135
+ self.errors.append(row)
136
+ return row
@@ -0,0 +1,130 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import Any
5
+
6
+ from .matrix import build_matrix_row
7
+ from .models import ClientSession, ClientTopology, CoverageDisposition, ProtocolCarrier, SessionScope
8
+
9
+
10
+ class ClientSessionTopologyHarness:
11
+ """Runtime-facing carrier topology recorder for governed proof rows."""
12
+
13
+ def __init__(self, carrier: ProtocolCarrier, scope: SessionScope | None = None) -> None:
14
+ self.carrier = carrier
15
+ self.scope = scope
16
+ self.sessions: dict[str, ClientSession] = {}
17
+ self.events: list[dict[str, Any]] = []
18
+
19
+ def open(self, client_id: str, connection_id: str, session_id: str, topology: ClientTopology) -> None:
20
+ self.sessions[session_id] = ClientSession(
21
+ client_id=client_id,
22
+ connection_id=connection_id,
23
+ session_id=session_id,
24
+ )
25
+ self.events.append(self.record("open", client_id, connection_id, session_id, topology))
26
+
27
+ def send(
28
+ self,
29
+ client_id: str,
30
+ connection_id: str,
31
+ session_id: str,
32
+ topology: ClientTopology,
33
+ payload: str,
34
+ **identifiers: Any,
35
+ ) -> None:
36
+ session = self.session_for(client_id, connection_id, session_id)
37
+ session.payloads.append(payload)
38
+ self.events.append(
39
+ self.record(
40
+ "send",
41
+ client_id,
42
+ connection_id,
43
+ session_id,
44
+ topology,
45
+ payload=payload,
46
+ **identifiers,
47
+ )
48
+ )
49
+
50
+ async def send_async(
51
+ self,
52
+ client_id: str,
53
+ connection_id: str,
54
+ session_id: str,
55
+ topology: ClientTopology,
56
+ payload: str,
57
+ delay: float = 0.0,
58
+ **identifiers: Any,
59
+ ) -> None:
60
+ await asyncio.sleep(delay)
61
+ self.send(client_id, connection_id, session_id, topology, payload, **identifiers)
62
+
63
+ def close(self, client_id: str, connection_id: str, session_id: str, topology: ClientTopology) -> None:
64
+ session = self.session_for(client_id, connection_id, session_id)
65
+ session.closed = True
66
+ self.events.append(self.record("close", client_id, connection_id, session_id, topology))
67
+
68
+ def record(
69
+ self,
70
+ subevent: str,
71
+ client_id: str,
72
+ connection_id: str,
73
+ session_id: str,
74
+ topology: ClientTopology,
75
+ **extra: Any,
76
+ ) -> dict[str, Any]:
77
+ return build_matrix_row(
78
+ protocol_carrier=self.carrier,
79
+ client_topology=topology,
80
+ session_scope=self.scope,
81
+ disposition=CoverageDisposition.COVERED,
82
+ lifecycle_behavior=CoverageDisposition.COVERED,
83
+ identity_isolation=CoverageDisposition.COVERED,
84
+ ordering_behavior=CoverageDisposition.COVERED,
85
+ pressure_mode=CoverageDisposition.REQUIRED,
86
+ fault_mode=CoverageDisposition.REQUIRED,
87
+ client_id=client_id,
88
+ connection_id=connection_id,
89
+ session_id=session_id,
90
+ subevent=subevent,
91
+ **extra,
92
+ )
93
+
94
+ def session_for(self, client_id: str, connection_id: str, session_id: str) -> ClientSession:
95
+ session = self.sessions[session_id]
96
+ if session.client_id != client_id or session.connection_id != connection_id:
97
+ raise PermissionError("cross-client or cross-connection session access rejected")
98
+ if session.closed:
99
+ raise RuntimeError("post-close send rejected")
100
+ return session
101
+
102
+
103
+ def sequential_pair(carrier: ProtocolCarrier, scope: SessionScope | None = None) -> ClientSessionTopologyHarness:
104
+ topology = ClientTopology.SEQUENTIAL_CLIENTS
105
+ harness = ClientSessionTopologyHarness(carrier, scope)
106
+ harness.open("client-a", "conn-a", "session-a", topology)
107
+ harness.send("client-a", "conn-a", "session-a", topology, "a-1")
108
+ harness.close("client-a", "conn-a", "session-a", topology)
109
+ harness.open("client-b", "conn-b", "session-b", topology)
110
+ harness.send("client-b", "conn-b", "session-b", topology, "b-1")
111
+ harness.close("client-b", "conn-b", "session-b", topology)
112
+ return harness
113
+
114
+
115
+ def bounded_interleaved_pair(
116
+ carrier: ProtocolCarrier,
117
+ scope: SessionScope | None = None,
118
+ ) -> ClientSessionTopologyHarness:
119
+ topology = ClientTopology.BOUNDED_INTERLEAVED_CLIENTS
120
+ harness = ClientSessionTopologyHarness(carrier, scope)
121
+ harness.open("client-a", "conn-a", "session-a", topology)
122
+ harness.open("client-b", "conn-b", "session-b", topology)
123
+ for client_id, connection_id, session_id, payload in (
124
+ ("client-a", "conn-a", "session-a", "a-1"),
125
+ ("client-b", "conn-b", "session-b", "b-1"),
126
+ ("client-a", "conn-a", "session-a", "a-2"),
127
+ ("client-b", "conn-b", "session-b", "b-2"),
128
+ ):
129
+ harness.send(client_id, connection_id, session_id, topology, payload)
130
+ return harness
@@ -0,0 +1,76 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+ from typing import Any, Mapping
5
+
6
+ from .models import (
7
+ BEHAVIOR_AXIS_VALUES,
8
+ CLIENT_TOPOLOGY_VALUES,
9
+ DISPOSITION_VALUES,
10
+ GOVERNED_IDENTIFIER_FIELDS,
11
+ INTERNAL_ONLY_FIELDS,
12
+ PROTOCOL_CARRIER_DEFAULT_SCOPES,
13
+ PROTOCOL_CARRIER_VALUES,
14
+ REQUIRED_MATRIX_AXES,
15
+ SESSION_SCOPE_VALUES,
16
+ BehaviorAxis,
17
+ SessionScope,
18
+ )
19
+
20
+
21
+ def _enum_value(value: object) -> str:
22
+ if isinstance(value, Enum):
23
+ return str(value.value)
24
+ return str(value)
25
+
26
+
27
+ def _normalized(value: object) -> str:
28
+ return str(value).strip().lower().replace("-", "_").replace(".", "_")
29
+
30
+
31
+ def classify_default_session_scope(protocol_carrier: object) -> SessionScope:
32
+ carrier = _normalized(_enum_value(protocol_carrier))
33
+ try:
34
+ return PROTOCOL_CARRIER_DEFAULT_SCOPES[carrier]
35
+ except KeyError as exc:
36
+ raise ValueError(f"unknown protocol carrier: {protocol_carrier!r}") from exc
37
+
38
+
39
+ def validate_no_internal_lane(record: Mapping[str, Any]) -> None:
40
+ if INTERNAL_ONLY_FIELDS.intersection(record):
41
+ raise ValueError("lane is protocol-internal and not a governed proof field")
42
+
43
+
44
+ def validate_governed_identifiers(record: Mapping[str, Any]) -> None:
45
+ validate_no_internal_lane(record)
46
+ for field in GOVERNED_IDENTIFIER_FIELDS:
47
+ if field in record and record[field] is None:
48
+ raise ValueError(f"{field} must be omitted or populated")
49
+
50
+
51
+ def validate_matrix_row(row: Mapping[str, Any]) -> None:
52
+ validate_governed_identifiers(row)
53
+ missing = sorted(REQUIRED_MATRIX_AXES.difference(row))
54
+ if missing:
55
+ raise ValueError(f"client-session matrix row missing axes: {missing}")
56
+
57
+ carrier = _normalized(row["protocol_carrier"])
58
+ if carrier not in PROTOCOL_CARRIER_VALUES:
59
+ raise ValueError(f"unknown protocol carrier: {row['protocol_carrier']!r}")
60
+
61
+ topology = _enum_value(row["client_topology"])
62
+ if topology not in CLIENT_TOPOLOGY_VALUES:
63
+ raise ValueError(f"unknown client topology: {topology!r}")
64
+
65
+ scope = _enum_value(row["session_scope"])
66
+ if scope not in SESSION_SCOPE_VALUES:
67
+ raise ValueError(f"unknown session scope: {scope!r}")
68
+
69
+ disposition = _enum_value(row["disposition"])
70
+ if disposition not in DISPOSITION_VALUES:
71
+ raise ValueError(f"unknown disposition: {disposition!r}")
72
+
73
+ for axis in BehaviorAxis:
74
+ axis_value = _enum_value(row[axis.value])
75
+ if axis_value not in DISPOSITION_VALUES:
76
+ raise ValueError(f"{axis.value} must be a coverage disposition")
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from .models import *
4
+ from .validation import *
5
+ from .validation import _is_token, _validate_header_name, _validate_header_value
6
+ from .target import *
7
+ from .target import _parse_request_target
8
+ from .body import *
9
+ from .body import _read_chunked_body, _read_line, _read_request_head_until_terminator, _readexactly
10
+ from .head import *
11
+ from .head import _parse_request_head_bytes, _parse_transfer_encoding
12
+ from .core import *
13
+
14
+ __all__ = [name for name in globals() if not name.startswith("__")]
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+
5
+ from tigrcorn_core.errors import ProtocolError
6
+ from tigrcorn_core.types import StreamReaderLike
7
+
8
+ from .validation import _validate_header_name, _validate_header_value
9
+
10
+
11
+ async def _read_line(reader: StreamReaderLike) -> bytes:
12
+ try:
13
+ return await reader.readuntil(b"\r\n")
14
+ except asyncio.IncompleteReadError as exc:
15
+ raise ProtocolError('unexpected EOF while reading HTTP/1.1 body') from exc
16
+
17
+
18
+ async def _readexactly(reader: StreamReaderLike, amount: int) -> bytes:
19
+ try:
20
+ return await reader.readexactly(amount)
21
+ except asyncio.IncompleteReadError as exc:
22
+ raise ProtocolError('unexpected EOF while reading HTTP/1.1 body') from exc
23
+
24
+
25
+ async def _read_request_head_until_terminator(
26
+ reader: StreamReaderLike,
27
+ *,
28
+ limit: int,
29
+ buffer_size: int,
30
+ ) -> bytes:
31
+ limited_readuntil = getattr(reader, 'readuntil_limited', None)
32
+ if callable(limited_readuntil):
33
+ try:
34
+ return await limited_readuntil(b"\r\n\r\n", limit=limit, read_chunk_size=buffer_size)
35
+ except TypeError:
36
+ return await limited_readuntil(b"\r\n\r\n", limit=limit)
37
+ head = await reader.readuntil(b"\r\n\r\n")
38
+ if len(head) > limit:
39
+ raise asyncio.LimitOverrunError('request head exceeds configured HTTP/1.1 request-head limit', consumed=len(head))
40
+ return head
41
+
42
+
43
+ async def _consume_chunked_trailers(reader: StreamReaderLike) -> None:
44
+ while True:
45
+ trailer = await _read_line(reader)
46
+ if trailer == b"\r\n":
47
+ return
48
+ if trailer[:1] in {b' ', b'\t'}:
49
+ raise ProtocolError('obsolete line folding is not supported')
50
+ if b':' not in trailer[:-2]:
51
+ raise ProtocolError('malformed chunk trailer line')
52
+ name, value = trailer[:-2].split(b':', 1)
53
+ _validate_header_name(name.strip().lower())
54
+ _validate_header_value(value.strip())
55
+
56
+
57
+ async def _read_chunked_body(reader: StreamReaderLike, *, max_body_size: int) -> bytes:
58
+ parts: list[bytes] = []
59
+ total = 0
60
+ while True:
61
+ line = await _read_line(reader)
62
+ size_token = line[:-2].split(b';', 1)[0].strip()
63
+ try:
64
+ size = int(size_token, 16)
65
+ except ValueError as exc:
66
+ raise ProtocolError('invalid chunk size') from exc
67
+ if size < 0:
68
+ raise ProtocolError('invalid chunk size')
69
+ if size == 0:
70
+ await _consume_chunked_trailers(reader)
71
+ return b''.join(parts)
72
+ chunk = await _readexactly(reader, size)
73
+ terminator = await _readexactly(reader, 2)
74
+ if terminator != b"\r\n":
75
+ raise ProtocolError('invalid chunk terminator')
76
+ total += size
77
+ if total > max_body_size:
78
+ raise ProtocolError('request body exceeds configured max_body_size')
79
+ parts.append(chunk)