crackerjack 0.33.0__py3-none-any.whl → 0.33.2__py3-none-any.whl
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.
Potentially problematic release.
This version of crackerjack might be problematic. Click here for more details.
- crackerjack/__main__.py +1350 -34
- crackerjack/adapters/__init__.py +17 -0
- crackerjack/adapters/lsp_client.py +358 -0
- crackerjack/adapters/rust_tool_adapter.py +194 -0
- crackerjack/adapters/rust_tool_manager.py +193 -0
- crackerjack/adapters/skylos_adapter.py +231 -0
- crackerjack/adapters/zuban_adapter.py +560 -0
- crackerjack/agents/base.py +7 -3
- crackerjack/agents/coordinator.py +271 -33
- crackerjack/agents/documentation_agent.py +9 -15
- crackerjack/agents/dry_agent.py +3 -15
- crackerjack/agents/formatting_agent.py +1 -1
- crackerjack/agents/import_optimization_agent.py +36 -180
- crackerjack/agents/performance_agent.py +17 -98
- crackerjack/agents/performance_helpers.py +7 -31
- crackerjack/agents/proactive_agent.py +1 -3
- crackerjack/agents/refactoring_agent.py +16 -85
- crackerjack/agents/refactoring_helpers.py +7 -42
- crackerjack/agents/security_agent.py +9 -48
- crackerjack/agents/test_creation_agent.py +356 -513
- crackerjack/agents/test_specialist_agent.py +0 -4
- crackerjack/api.py +6 -25
- crackerjack/cli/cache_handlers.py +204 -0
- crackerjack/cli/cache_handlers_enhanced.py +683 -0
- crackerjack/cli/facade.py +100 -0
- crackerjack/cli/handlers.py +224 -9
- crackerjack/cli/interactive.py +6 -4
- crackerjack/cli/options.py +642 -55
- crackerjack/cli/utils.py +2 -1
- crackerjack/code_cleaner.py +58 -117
- crackerjack/config/global_lock_config.py +8 -48
- crackerjack/config/hooks.py +53 -62
- crackerjack/core/async_workflow_orchestrator.py +24 -34
- crackerjack/core/autofix_coordinator.py +3 -17
- crackerjack/core/enhanced_container.py +4 -13
- crackerjack/core/file_lifecycle.py +12 -89
- crackerjack/core/performance.py +2 -2
- crackerjack/core/performance_monitor.py +15 -55
- crackerjack/core/phase_coordinator.py +104 -204
- crackerjack/core/resource_manager.py +14 -90
- crackerjack/core/service_watchdog.py +62 -95
- crackerjack/core/session_coordinator.py +149 -0
- crackerjack/core/timeout_manager.py +14 -72
- crackerjack/core/websocket_lifecycle.py +13 -78
- crackerjack/core/workflow_orchestrator.py +171 -174
- crackerjack/docs/INDEX.md +11 -0
- crackerjack/docs/generated/api/API_REFERENCE.md +10895 -0
- crackerjack/docs/generated/api/CLI_REFERENCE.md +109 -0
- crackerjack/docs/generated/api/CROSS_REFERENCES.md +1755 -0
- crackerjack/docs/generated/api/PROTOCOLS.md +3 -0
- crackerjack/docs/generated/api/SERVICES.md +1252 -0
- crackerjack/documentation/__init__.py +31 -0
- crackerjack/documentation/ai_templates.py +756 -0
- crackerjack/documentation/dual_output_generator.py +765 -0
- crackerjack/documentation/mkdocs_integration.py +518 -0
- crackerjack/documentation/reference_generator.py +977 -0
- crackerjack/dynamic_config.py +55 -50
- crackerjack/executors/async_hook_executor.py +10 -15
- crackerjack/executors/cached_hook_executor.py +117 -43
- crackerjack/executors/hook_executor.py +8 -34
- crackerjack/executors/hook_lock_manager.py +26 -183
- crackerjack/executors/individual_hook_executor.py +13 -11
- crackerjack/executors/lsp_aware_hook_executor.py +270 -0
- crackerjack/executors/tool_proxy.py +417 -0
- crackerjack/hooks/lsp_hook.py +79 -0
- crackerjack/intelligence/adaptive_learning.py +25 -10
- crackerjack/intelligence/agent_orchestrator.py +2 -5
- crackerjack/intelligence/agent_registry.py +34 -24
- crackerjack/intelligence/agent_selector.py +5 -7
- crackerjack/interactive.py +17 -6
- crackerjack/managers/async_hook_manager.py +0 -1
- crackerjack/managers/hook_manager.py +79 -1
- crackerjack/managers/publish_manager.py +44 -8
- crackerjack/managers/test_command_builder.py +1 -15
- crackerjack/managers/test_executor.py +1 -3
- crackerjack/managers/test_manager.py +98 -7
- crackerjack/managers/test_manager_backup.py +10 -9
- crackerjack/mcp/cache.py +2 -2
- crackerjack/mcp/client_runner.py +1 -1
- crackerjack/mcp/context.py +191 -68
- crackerjack/mcp/dashboard.py +7 -5
- crackerjack/mcp/enhanced_progress_monitor.py +31 -28
- crackerjack/mcp/file_monitor.py +30 -23
- crackerjack/mcp/progress_components.py +31 -21
- crackerjack/mcp/progress_monitor.py +50 -53
- crackerjack/mcp/rate_limiter.py +6 -6
- crackerjack/mcp/server_core.py +17 -16
- crackerjack/mcp/service_watchdog.py +2 -1
- crackerjack/mcp/state.py +4 -7
- crackerjack/mcp/task_manager.py +11 -9
- crackerjack/mcp/tools/core_tools.py +173 -32
- crackerjack/mcp/tools/error_analyzer.py +3 -2
- crackerjack/mcp/tools/execution_tools.py +8 -10
- crackerjack/mcp/tools/execution_tools_backup.py +42 -30
- crackerjack/mcp/tools/intelligence_tool_registry.py +7 -5
- crackerjack/mcp/tools/intelligence_tools.py +5 -2
- crackerjack/mcp/tools/monitoring_tools.py +33 -70
- crackerjack/mcp/tools/proactive_tools.py +24 -11
- crackerjack/mcp/tools/progress_tools.py +5 -8
- crackerjack/mcp/tools/utility_tools.py +20 -14
- crackerjack/mcp/tools/workflow_executor.py +62 -40
- crackerjack/mcp/websocket/app.py +8 -0
- crackerjack/mcp/websocket/endpoints.py +352 -357
- crackerjack/mcp/websocket/jobs.py +40 -57
- crackerjack/mcp/websocket/monitoring_endpoints.py +2935 -0
- crackerjack/mcp/websocket/server.py +7 -25
- crackerjack/mcp/websocket/websocket_handler.py +6 -17
- crackerjack/mixins/__init__.py +0 -2
- crackerjack/mixins/error_handling.py +1 -70
- crackerjack/models/config.py +12 -1
- crackerjack/models/config_adapter.py +49 -1
- crackerjack/models/protocols.py +122 -122
- crackerjack/models/resource_protocols.py +55 -210
- crackerjack/monitoring/ai_agent_watchdog.py +13 -13
- crackerjack/monitoring/metrics_collector.py +426 -0
- crackerjack/monitoring/regression_prevention.py +8 -8
- crackerjack/monitoring/websocket_server.py +643 -0
- crackerjack/orchestration/advanced_orchestrator.py +11 -6
- crackerjack/orchestration/coverage_improvement.py +3 -3
- crackerjack/orchestration/execution_strategies.py +26 -6
- crackerjack/orchestration/test_progress_streamer.py +8 -5
- crackerjack/plugins/base.py +2 -2
- crackerjack/plugins/hooks.py +7 -0
- crackerjack/plugins/managers.py +11 -8
- crackerjack/security/__init__.py +0 -1
- crackerjack/security/audit.py +6 -35
- crackerjack/services/anomaly_detector.py +392 -0
- crackerjack/services/api_extractor.py +615 -0
- crackerjack/services/backup_service.py +2 -2
- crackerjack/services/bounded_status_operations.py +15 -152
- crackerjack/services/cache.py +127 -1
- crackerjack/services/changelog_automation.py +395 -0
- crackerjack/services/config.py +15 -9
- crackerjack/services/config_merge.py +19 -80
- crackerjack/services/config_template.py +506 -0
- crackerjack/services/contextual_ai_assistant.py +48 -22
- crackerjack/services/coverage_badge_service.py +171 -0
- crackerjack/services/coverage_ratchet.py +27 -25
- crackerjack/services/debug.py +3 -3
- crackerjack/services/dependency_analyzer.py +460 -0
- crackerjack/services/dependency_monitor.py +14 -11
- crackerjack/services/documentation_generator.py +491 -0
- crackerjack/services/documentation_service.py +675 -0
- crackerjack/services/enhanced_filesystem.py +6 -5
- crackerjack/services/enterprise_optimizer.py +865 -0
- crackerjack/services/error_pattern_analyzer.py +676 -0
- crackerjack/services/file_hasher.py +1 -1
- crackerjack/services/git.py +8 -25
- crackerjack/services/health_metrics.py +10 -8
- crackerjack/services/heatmap_generator.py +735 -0
- crackerjack/services/initialization.py +11 -30
- crackerjack/services/input_validator.py +5 -97
- crackerjack/services/intelligent_commit.py +327 -0
- crackerjack/services/log_manager.py +15 -12
- crackerjack/services/logging.py +4 -3
- crackerjack/services/lsp_client.py +628 -0
- crackerjack/services/memory_optimizer.py +19 -87
- crackerjack/services/metrics.py +42 -33
- crackerjack/services/parallel_executor.py +9 -67
- crackerjack/services/pattern_cache.py +1 -1
- crackerjack/services/pattern_detector.py +6 -6
- crackerjack/services/performance_benchmarks.py +18 -59
- crackerjack/services/performance_cache.py +20 -81
- crackerjack/services/performance_monitor.py +27 -95
- crackerjack/services/predictive_analytics.py +510 -0
- crackerjack/services/quality_baseline.py +234 -0
- crackerjack/services/quality_baseline_enhanced.py +646 -0
- crackerjack/services/quality_intelligence.py +785 -0
- crackerjack/services/regex_patterns.py +618 -524
- crackerjack/services/regex_utils.py +43 -123
- crackerjack/services/secure_path_utils.py +5 -164
- crackerjack/services/secure_status_formatter.py +30 -141
- crackerjack/services/secure_subprocess.py +11 -92
- crackerjack/services/security.py +9 -41
- crackerjack/services/security_logger.py +12 -24
- crackerjack/services/server_manager.py +124 -16
- crackerjack/services/status_authentication.py +16 -159
- crackerjack/services/status_security_manager.py +4 -131
- crackerjack/services/thread_safe_status_collector.py +19 -125
- crackerjack/services/unified_config.py +21 -13
- crackerjack/services/validation_rate_limiter.py +5 -54
- crackerjack/services/version_analyzer.py +459 -0
- crackerjack/services/version_checker.py +1 -1
- crackerjack/services/websocket_resource_limiter.py +10 -144
- crackerjack/services/zuban_lsp_service.py +390 -0
- crackerjack/slash_commands/__init__.py +2 -7
- crackerjack/slash_commands/run.md +2 -2
- crackerjack/tools/validate_input_validator_patterns.py +14 -40
- crackerjack/tools/validate_regex_patterns.py +19 -48
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/METADATA +196 -25
- crackerjack-0.33.2.dist-info/RECORD +229 -0
- crackerjack/CLAUDE.md +0 -207
- crackerjack/RULES.md +0 -380
- crackerjack/py313.py +0 -234
- crackerjack-0.33.0.dist-info/RECORD +0 -187
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/WHEEL +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.33.0.dist-info → crackerjack-0.33.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
WebSocket Resource Limiter to prevent resource exhaustion attacks.
|
|
3
|
-
|
|
4
|
-
Provides comprehensive resource monitoring and limiting for WebSocket
|
|
5
|
-
connections, including connection limits, message limits, memory usage
|
|
6
|
-
tracking, and DoS prevention.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
1
|
import asyncio
|
|
10
2
|
import time
|
|
11
3
|
import typing as t
|
|
@@ -18,8 +10,6 @@ from .security_logger import SecurityEventLevel, SecurityEventType, get_security
|
|
|
18
10
|
|
|
19
11
|
@dataclass
|
|
20
12
|
class ConnectionMetrics:
|
|
21
|
-
"""Metrics for individual WebSocket connections."""
|
|
22
|
-
|
|
23
13
|
client_id: str
|
|
24
14
|
connect_time: float = field(default_factory=time.time)
|
|
25
15
|
message_count: int = 0
|
|
@@ -29,70 +19,44 @@ class ConnectionMetrics:
|
|
|
29
19
|
|
|
30
20
|
@property
|
|
31
21
|
def connection_duration(self) -> float:
|
|
32
|
-
"""Get connection duration in seconds."""
|
|
33
22
|
return time.time() - self.connect_time
|
|
34
23
|
|
|
35
24
|
@property
|
|
36
25
|
def idle_time(self) -> float:
|
|
37
|
-
"""Get idle time since last activity."""
|
|
38
26
|
return time.time() - self.last_activity
|
|
39
27
|
|
|
40
28
|
|
|
41
29
|
@dataclass
|
|
42
30
|
class ResourceLimits:
|
|
43
|
-
"""Resource limits configuration."""
|
|
44
|
-
|
|
45
31
|
max_connections: int = 50
|
|
46
32
|
max_connections_per_ip: int = 10
|
|
47
|
-
max_message_size: int = 64 * 1024
|
|
33
|
+
max_message_size: int = 64 * 1024
|
|
48
34
|
max_messages_per_minute: int = 100
|
|
49
35
|
max_messages_per_connection: int = 10000
|
|
50
|
-
max_connection_duration: float = 3600.0
|
|
51
|
-
max_idle_time: float = 300.0
|
|
36
|
+
max_connection_duration: float = 3600.0
|
|
37
|
+
max_idle_time: float = 300.0
|
|
52
38
|
max_memory_usage_mb: int = 100
|
|
53
39
|
connection_timeout: float = 30.0
|
|
54
40
|
message_timeout: float = 10.0
|
|
55
41
|
|
|
56
42
|
|
|
57
43
|
class ResourceExhaustedError(Exception):
|
|
58
|
-
"""Raised when resource limits are exceeded."""
|
|
59
|
-
|
|
60
44
|
pass
|
|
61
45
|
|
|
62
46
|
|
|
63
47
|
class WebSocketResourceLimiter:
|
|
64
|
-
"""
|
|
65
|
-
Resource limiter for WebSocket connections.
|
|
66
|
-
|
|
67
|
-
Features:
|
|
68
|
-
- Connection count and per-IP limits
|
|
69
|
-
- Message size and rate limiting
|
|
70
|
-
- Memory usage monitoring
|
|
71
|
-
- Connection duration limits
|
|
72
|
-
- Idle connection cleanup
|
|
73
|
-
- DoS attack prevention
|
|
74
|
-
"""
|
|
75
|
-
|
|
76
48
|
def __init__(self, limits: ResourceLimits | None = None):
|
|
77
|
-
"""
|
|
78
|
-
Initialize WebSocket resource limiter.
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
limits: Resource limits configuration
|
|
82
|
-
"""
|
|
83
49
|
self.limits = limits or ResourceLimits()
|
|
84
50
|
self.security_logger = get_security_logger()
|
|
85
51
|
|
|
86
52
|
self._setup_limiter_components()
|
|
87
53
|
|
|
88
54
|
def _setup_limiter_components(self) -> None:
|
|
89
|
-
"""Set up all limiter components in initialization."""
|
|
90
55
|
self._initialize_connection_tracking()
|
|
91
56
|
self._initialize_metrics_tracking()
|
|
92
57
|
self._initialize_cleanup_system()
|
|
93
58
|
|
|
94
59
|
def _initialize_connection_tracking(self) -> None:
|
|
95
|
-
"""Initialize thread-safe connection tracking structures."""
|
|
96
60
|
self._lock = RLock()
|
|
97
61
|
self._connections: dict[str, ConnectionMetrics] = {}
|
|
98
62
|
self._ip_connections: dict[str, set[str]] = defaultdict(set)
|
|
@@ -102,20 +66,16 @@ class WebSocketResourceLimiter:
|
|
|
102
66
|
self._blocked_ips: dict[str, float] = {}
|
|
103
67
|
|
|
104
68
|
def _initialize_metrics_tracking(self) -> None:
|
|
105
|
-
"""Initialize metrics tracking with proper typing."""
|
|
106
69
|
self._connection_metrics: dict[str, ConnectionMetrics] = {}
|
|
107
70
|
self._message_queues: dict[str, deque[bytes]] = defaultdict(deque)
|
|
108
71
|
self._resource_usage: dict[str, dict[str, t.Any]] = {}
|
|
109
72
|
self._memory_usage: int = 0
|
|
110
73
|
|
|
111
74
|
def _initialize_cleanup_system(self) -> None:
|
|
112
|
-
"""Initialize the background cleanup system."""
|
|
113
75
|
self._cleanup_task: asyncio.Task[t.Any] | None = None
|
|
114
76
|
self._shutdown_event = asyncio.Event()
|
|
115
77
|
|
|
116
78
|
async def start(self) -> None:
|
|
117
|
-
"""Start the resource limiter with background cleanup."""
|
|
118
|
-
|
|
119
79
|
if self._cleanup_task is None:
|
|
120
80
|
self._cleanup_task = asyncio.create_task(self._cleanup_loop())
|
|
121
81
|
|
|
@@ -127,8 +87,6 @@ class WebSocketResourceLimiter:
|
|
|
127
87
|
)
|
|
128
88
|
|
|
129
89
|
async def stop(self) -> None:
|
|
130
|
-
"""Stop the resource limiter and cleanup resources."""
|
|
131
|
-
|
|
132
90
|
self._shutdown_event.set()
|
|
133
91
|
|
|
134
92
|
if self._cleanup_task:
|
|
@@ -143,7 +101,6 @@ class WebSocketResourceLimiter:
|
|
|
143
101
|
|
|
144
102
|
self._cleanup_task = None
|
|
145
103
|
|
|
146
|
-
# Force cleanup all connections
|
|
147
104
|
with self._lock:
|
|
148
105
|
self._connections.clear()
|
|
149
106
|
self._ip_connections.clear()
|
|
@@ -157,17 +114,6 @@ class WebSocketResourceLimiter:
|
|
|
157
114
|
)
|
|
158
115
|
|
|
159
116
|
def validate_new_connection(self, client_id: str, client_ip: str) -> None:
|
|
160
|
-
"""
|
|
161
|
-
Validate if a new connection can be accepted.
|
|
162
|
-
|
|
163
|
-
Args:
|
|
164
|
-
client_id: Unique client identifier
|
|
165
|
-
client_ip: Client IP address
|
|
166
|
-
|
|
167
|
-
Raises:
|
|
168
|
-
ResourceExhaustedError: If connection limits exceeded
|
|
169
|
-
"""
|
|
170
|
-
|
|
171
117
|
with self._lock:
|
|
172
118
|
current_time = time.time()
|
|
173
119
|
|
|
@@ -176,7 +122,6 @@ class WebSocketResourceLimiter:
|
|
|
176
122
|
self._check_per_ip_connection_limit(client_id, client_ip, current_time)
|
|
177
123
|
|
|
178
124
|
def _check_ip_blocking_status(self, client_ip: str, current_time: float) -> None:
|
|
179
|
-
"""Check if IP is currently blocked."""
|
|
180
125
|
if client_ip in self._blocked_ips:
|
|
181
126
|
if current_time < self._blocked_ips[client_ip]:
|
|
182
127
|
raise ResourceExhaustedError(f"IP {client_ip} is temporarily blocked")
|
|
@@ -184,7 +129,6 @@ class WebSocketResourceLimiter:
|
|
|
184
129
|
del self._blocked_ips[client_ip]
|
|
185
130
|
|
|
186
131
|
def _check_total_connection_limit(self, client_id: str, client_ip: str) -> None:
|
|
187
|
-
"""Check if total connection limit is exceeded."""
|
|
188
132
|
if len(self._connections) >= self.limits.max_connections:
|
|
189
133
|
self.security_logger.log_security_event(
|
|
190
134
|
event_type=SecurityEventType.RATE_LIMIT_EXCEEDED,
|
|
@@ -201,7 +145,6 @@ class WebSocketResourceLimiter:
|
|
|
201
145
|
def _check_per_ip_connection_limit(
|
|
202
146
|
self, client_id: str, client_ip: str, current_time: float
|
|
203
147
|
) -> None:
|
|
204
|
-
"""Check if per-IP connection limit is exceeded."""
|
|
205
148
|
ip_connection_count = len(self._ip_connections[client_ip])
|
|
206
149
|
if ip_connection_count >= self.limits.max_connections_per_ip:
|
|
207
150
|
self.security_logger.log_security_event(
|
|
@@ -213,22 +156,13 @@ class WebSocketResourceLimiter:
|
|
|
213
156
|
additional_data={"client_ip": client_ip},
|
|
214
157
|
)
|
|
215
158
|
|
|
216
|
-
|
|
217
|
-
self._blocked_ips[client_ip] = current_time + 300.0 # 5 minute block
|
|
159
|
+
self._blocked_ips[client_ip] = current_time + 300.0
|
|
218
160
|
|
|
219
161
|
raise ResourceExhaustedError(
|
|
220
162
|
f"Maximum connections per IP exceeded: {ip_connection_count}"
|
|
221
163
|
)
|
|
222
164
|
|
|
223
165
|
def register_connection(self, client_id: str, client_ip: str) -> None:
|
|
224
|
-
"""
|
|
225
|
-
Register a new WebSocket connection.
|
|
226
|
-
|
|
227
|
-
Args:
|
|
228
|
-
client_id: Unique client identifier
|
|
229
|
-
client_ip: Client IP address
|
|
230
|
-
"""
|
|
231
|
-
|
|
232
166
|
with self._lock:
|
|
233
167
|
metrics = ConnectionMetrics(client_id=client_id)
|
|
234
168
|
self._connections[client_id] = metrics
|
|
@@ -244,14 +178,6 @@ class WebSocketResourceLimiter:
|
|
|
244
178
|
)
|
|
245
179
|
|
|
246
180
|
def unregister_connection(self, client_id: str, client_ip: str) -> None:
|
|
247
|
-
"""
|
|
248
|
-
Unregister a WebSocket connection.
|
|
249
|
-
|
|
250
|
-
Args:
|
|
251
|
-
client_id: Unique client identifier
|
|
252
|
-
client_ip: Client IP address
|
|
253
|
-
"""
|
|
254
|
-
|
|
255
181
|
with self._lock:
|
|
256
182
|
if client_id in self._connections:
|
|
257
183
|
metrics = self._connections[client_id]
|
|
@@ -272,28 +198,15 @@ class WebSocketResourceLimiter:
|
|
|
272
198
|
},
|
|
273
199
|
)
|
|
274
200
|
|
|
275
|
-
# Remove from IP tracking
|
|
276
201
|
if client_ip in self._ip_connections:
|
|
277
202
|
self._ip_connections[client_ip].discard(client_id)
|
|
278
203
|
if not self._ip_connections[client_ip]:
|
|
279
204
|
del self._ip_connections[client_ip]
|
|
280
205
|
|
|
281
|
-
# Clear message history
|
|
282
206
|
if client_id in self._message_history:
|
|
283
207
|
del self._message_history[client_id]
|
|
284
208
|
|
|
285
209
|
def validate_message(self, client_id: str, message_size: int) -> None:
|
|
286
|
-
"""
|
|
287
|
-
Validate if a message can be processed.
|
|
288
|
-
|
|
289
|
-
Args:
|
|
290
|
-
client_id: Client identifier
|
|
291
|
-
message_size: Size of the message in bytes
|
|
292
|
-
|
|
293
|
-
Raises:
|
|
294
|
-
ResourceExhaustedError: If message limits exceeded
|
|
295
|
-
"""
|
|
296
|
-
|
|
297
210
|
with self._lock:
|
|
298
211
|
self._check_message_size(client_id, message_size)
|
|
299
212
|
metrics = self._get_connection_metrics(client_id)
|
|
@@ -301,7 +214,6 @@ class WebSocketResourceLimiter:
|
|
|
301
214
|
self._check_message_rate(client_id)
|
|
302
215
|
|
|
303
216
|
def _check_message_size(self, client_id: str, message_size: int) -> None:
|
|
304
|
-
"""Check if message size exceeds limits."""
|
|
305
217
|
if message_size > self.limits.max_message_size:
|
|
306
218
|
self.security_logger.log_security_event(
|
|
307
219
|
event_type=SecurityEventType.RATE_LIMIT_EXCEEDED,
|
|
@@ -313,13 +225,11 @@ class WebSocketResourceLimiter:
|
|
|
313
225
|
raise ResourceExhaustedError(f"Message too large: {message_size} bytes")
|
|
314
226
|
|
|
315
227
|
def _get_connection_metrics(self, client_id: str) -> ConnectionMetrics:
|
|
316
|
-
"""Get connection metrics, raising error if connection doesn't exist."""
|
|
317
228
|
if client_id not in self._connections:
|
|
318
229
|
raise ResourceExhaustedError(f"Connection not registered: {client_id}")
|
|
319
230
|
return self._connections[client_id]
|
|
320
231
|
|
|
321
232
|
def _check_message_count(self, client_id: str, metrics: ConnectionMetrics) -> None:
|
|
322
|
-
"""Check if total message count per connection exceeds limits."""
|
|
323
233
|
if metrics.message_count >= self.limits.max_messages_per_connection:
|
|
324
234
|
self.security_logger.log_security_event(
|
|
325
235
|
event_type=SecurityEventType.RATE_LIMIT_EXCEEDED,
|
|
@@ -333,11 +243,9 @@ class WebSocketResourceLimiter:
|
|
|
333
243
|
)
|
|
334
244
|
|
|
335
245
|
def _check_message_rate(self, client_id: str) -> None:
|
|
336
|
-
"""Check if message rate exceeds per-minute limits."""
|
|
337
246
|
current_time = time.time()
|
|
338
247
|
message_times = self._message_history[client_id]
|
|
339
248
|
|
|
340
|
-
# Remove old messages (older than 1 minute)
|
|
341
249
|
while message_times and current_time - message_times[0] > 60.0:
|
|
342
250
|
message_times.popleft()
|
|
343
251
|
|
|
@@ -356,15 +264,6 @@ class WebSocketResourceLimiter:
|
|
|
356
264
|
def track_message(
|
|
357
265
|
self, client_id: str, message_size: int, is_sent: bool = True
|
|
358
266
|
) -> None:
|
|
359
|
-
"""
|
|
360
|
-
Track a processed message.
|
|
361
|
-
|
|
362
|
-
Args:
|
|
363
|
-
client_id: Client identifier
|
|
364
|
-
message_size: Size of the message in bytes
|
|
365
|
-
is_sent: True if message was sent, False if received
|
|
366
|
-
"""
|
|
367
|
-
|
|
368
267
|
with self._lock:
|
|
369
268
|
current_time = time.time()
|
|
370
269
|
|
|
@@ -378,20 +277,9 @@ class WebSocketResourceLimiter:
|
|
|
378
277
|
else:
|
|
379
278
|
metrics.bytes_received += message_size
|
|
380
279
|
|
|
381
|
-
# Track message timing for rate limiting
|
|
382
280
|
self._message_history[client_id].append(current_time)
|
|
383
281
|
|
|
384
282
|
async def check_connection_limits(self, client_id: str) -> None:
|
|
385
|
-
"""
|
|
386
|
-
Check if connection has exceeded limits.
|
|
387
|
-
|
|
388
|
-
Args:
|
|
389
|
-
client_id: Client identifier
|
|
390
|
-
|
|
391
|
-
Raises:
|
|
392
|
-
ResourceExhaustedError: If connection limits exceeded
|
|
393
|
-
"""
|
|
394
|
-
|
|
395
283
|
with self._lock:
|
|
396
284
|
if client_id not in self._connections:
|
|
397
285
|
return
|
|
@@ -399,45 +287,40 @@ class WebSocketResourceLimiter:
|
|
|
399
287
|
metrics = self._connections[client_id]
|
|
400
288
|
time.time()
|
|
401
289
|
|
|
402
|
-
# Check connection duration
|
|
403
290
|
if metrics.connection_duration > self.limits.max_connection_duration:
|
|
404
291
|
self.security_logger.log_security_event(
|
|
405
292
|
event_type=SecurityEventType.CONNECTION_TIMEOUT,
|
|
406
293
|
level=SecurityEventLevel.WARNING,
|
|
407
|
-
message=f"Connection duration exceeded: {metrics.connection_duration
|
|
294
|
+
message=f"Connection duration exceeded: {metrics.connection_duration: .1f}s",
|
|
408
295
|
client_id=client_id,
|
|
409
296
|
operation="connection_limit_check",
|
|
410
297
|
)
|
|
411
298
|
raise ResourceExhaustedError(
|
|
412
|
-
f"Connection duration exceeded: {metrics.connection_duration
|
|
299
|
+
f"Connection duration exceeded: {metrics.connection_duration: .1f}s"
|
|
413
300
|
)
|
|
414
301
|
|
|
415
|
-
# Check idle time
|
|
416
302
|
if metrics.idle_time > self.limits.max_idle_time:
|
|
417
303
|
self.security_logger.log_security_event(
|
|
418
304
|
event_type=SecurityEventType.CONNECTION_IDLE,
|
|
419
305
|
level=SecurityEventLevel.INFO,
|
|
420
|
-
message=f"Connection idle timeout: {metrics.idle_time
|
|
306
|
+
message=f"Connection idle timeout: {metrics.idle_time: .1f}s",
|
|
421
307
|
client_id=client_id,
|
|
422
308
|
operation="connection_limit_check",
|
|
423
309
|
)
|
|
424
310
|
raise ResourceExhaustedError(
|
|
425
|
-
f"Connection idle timeout: {metrics.idle_time
|
|
311
|
+
f"Connection idle timeout: {metrics.idle_time: .1f}s"
|
|
426
312
|
)
|
|
427
313
|
|
|
428
314
|
async def _cleanup_loop(self) -> None:
|
|
429
|
-
"""Background cleanup loop for expired connections and resources."""
|
|
430
|
-
|
|
431
315
|
while not self._shutdown_event.is_set():
|
|
432
316
|
try:
|
|
433
317
|
await asyncio.wait_for(
|
|
434
318
|
self._shutdown_event.wait(),
|
|
435
|
-
timeout=30.0,
|
|
319
|
+
timeout=30.0,
|
|
436
320
|
)
|
|
437
|
-
break
|
|
321
|
+
break
|
|
438
322
|
|
|
439
323
|
except TimeoutError:
|
|
440
|
-
# Perform cleanup
|
|
441
324
|
await self._perform_cleanup()
|
|
442
325
|
|
|
443
326
|
self.security_logger.log_security_event(
|
|
@@ -448,7 +331,6 @@ class WebSocketResourceLimiter:
|
|
|
448
331
|
)
|
|
449
332
|
|
|
450
333
|
async def _perform_cleanup(self) -> None:
|
|
451
|
-
"""Perform resource cleanup."""
|
|
452
334
|
current_time = time.time()
|
|
453
335
|
|
|
454
336
|
with self._lock:
|
|
@@ -459,7 +341,6 @@ class WebSocketResourceLimiter:
|
|
|
459
341
|
self._log_cleanup_results(cleanup_count)
|
|
460
342
|
|
|
461
343
|
def _cleanup_expired_connections(self, current_time: float) -> int:
|
|
462
|
-
"""Clean up expired connections and return count of cleaned connections."""
|
|
463
344
|
expired_connections = self._find_expired_connections()
|
|
464
345
|
cleanup_count = 0
|
|
465
346
|
|
|
@@ -470,7 +351,6 @@ class WebSocketResourceLimiter:
|
|
|
470
351
|
return cleanup_count
|
|
471
352
|
|
|
472
353
|
def _find_expired_connections(self) -> list[str]:
|
|
473
|
-
"""Find connections that have exceeded duration or idle time limits."""
|
|
474
354
|
return [
|
|
475
355
|
client_id
|
|
476
356
|
for client_id, metrics in self._connections.items()
|
|
@@ -481,31 +361,25 @@ class WebSocketResourceLimiter:
|
|
|
481
361
|
]
|
|
482
362
|
|
|
483
363
|
def _remove_expired_connection(self, client_id: str) -> bool:
|
|
484
|
-
"""Remove a specific expired connection and its associated data."""
|
|
485
364
|
if client_id not in self._connections:
|
|
486
365
|
return False
|
|
487
366
|
|
|
488
|
-
# Remove connection metrics
|
|
489
367
|
del self._connections[client_id]
|
|
490
368
|
|
|
491
|
-
# Clean up IP tracking
|
|
492
369
|
for client_set in self._ip_connections.values():
|
|
493
370
|
client_set.discard(client_id)
|
|
494
371
|
|
|
495
|
-
# Clean up message history
|
|
496
372
|
if client_id in self._message_history:
|
|
497
373
|
del self._message_history[client_id]
|
|
498
374
|
|
|
499
375
|
return True
|
|
500
376
|
|
|
501
377
|
def _cleanup_empty_ip_entries(self) -> None:
|
|
502
|
-
"""Remove IP entries that no longer have any connections."""
|
|
503
378
|
empty_ips = [ip for ip, clients in self._ip_connections.items() if not clients]
|
|
504
379
|
for ip in empty_ips:
|
|
505
380
|
del self._ip_connections[ip]
|
|
506
381
|
|
|
507
382
|
def _cleanup_expired_ip_blocks(self, current_time: float) -> None:
|
|
508
|
-
"""Remove IP blocks that have expired."""
|
|
509
383
|
expired_blocks = [
|
|
510
384
|
ip
|
|
511
385
|
for ip, block_until in self._blocked_ips.items()
|
|
@@ -515,7 +389,6 @@ class WebSocketResourceLimiter:
|
|
|
515
389
|
del self._blocked_ips[ip]
|
|
516
390
|
|
|
517
391
|
def _log_cleanup_results(self, cleanup_count: int) -> None:
|
|
518
|
-
"""Log cleanup results if any connections were cleaned up."""
|
|
519
392
|
if cleanup_count > 0:
|
|
520
393
|
self.security_logger.log_security_event(
|
|
521
394
|
event_type=SecurityEventType.RESOURCE_CLEANUP,
|
|
@@ -525,8 +398,6 @@ class WebSocketResourceLimiter:
|
|
|
525
398
|
)
|
|
526
399
|
|
|
527
400
|
def get_resource_status(self) -> dict[str, t.Any]:
|
|
528
|
-
"""Get current resource usage status."""
|
|
529
|
-
|
|
530
401
|
with self._lock:
|
|
531
402
|
return {
|
|
532
403
|
"connections": {
|
|
@@ -551,21 +422,16 @@ class WebSocketResourceLimiter:
|
|
|
551
422
|
}
|
|
552
423
|
|
|
553
424
|
def get_connection_metrics(self, client_id: str) -> ConnectionMetrics | None:
|
|
554
|
-
"""Get metrics for a specific connection."""
|
|
555
|
-
|
|
556
425
|
with self._lock:
|
|
557
426
|
return self._connections.get(client_id)
|
|
558
427
|
|
|
559
428
|
|
|
560
|
-
# Global singleton instance
|
|
561
429
|
_resource_limiter: WebSocketResourceLimiter | None = None
|
|
562
430
|
|
|
563
431
|
|
|
564
432
|
def get_websocket_resource_limiter(
|
|
565
433
|
limits: ResourceLimits | None = None,
|
|
566
434
|
) -> WebSocketResourceLimiter:
|
|
567
|
-
"""Get the global WebSocket resource limiter instance."""
|
|
568
|
-
|
|
569
435
|
global _resource_limiter
|
|
570
436
|
if _resource_limiter is None:
|
|
571
437
|
_resource_limiter = WebSocketResourceLimiter(limits)
|