iotsploit-core 0.0.6__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.
Files changed (41) hide show
  1. iotsploit_core/__init__.py +7 -0
  2. iotsploit_core/context.py +27 -0
  3. iotsploit_core/core/__init__.py +0 -0
  4. iotsploit_core/core/base_plugin.py +290 -0
  5. iotsploit_core/core/device_config.py +105 -0
  6. iotsploit_core/core/device_manager.py +942 -0
  7. iotsploit_core/core/device_registry.py +118 -0
  8. iotsploit_core/core/device_scanner.py +74 -0
  9. iotsploit_core/core/device_spec.py +105 -0
  10. iotsploit_core/core/device_store.py +86 -0
  11. iotsploit_core/core/execution_backend.py +464 -0
  12. iotsploit_core/core/execution_queue.py +456 -0
  13. iotsploit_core/core/exploit_manager.py +728 -0
  14. iotsploit_core/core/exploit_spec.py +73 -0
  15. iotsploit_core/core/stream_manager.py +157 -0
  16. iotsploit_core/core/tool_config.py +430 -0
  17. iotsploit_core/core/tool_manager.py +1166 -0
  18. iotsploit_core/core/tool_service.py +936 -0
  19. iotsploit_core/domain/__init__.py +3 -0
  20. iotsploit_core/domain/device.py +70 -0
  21. iotsploit_core/domain/execution_plan.py +29 -0
  22. iotsploit_core/domain/plugin.py +17 -0
  23. iotsploit_core/domain/stream.py +63 -0
  24. iotsploit_core/domain/target.py +243 -0
  25. iotsploit_core/platforms/__init__.py +19 -0
  26. iotsploit_core/platforms/consts.py +25 -0
  27. iotsploit_core/ports/__init__.py +6 -0
  28. iotsploit_core/ports/driver_state_repo.py +15 -0
  29. iotsploit_core/ports/plugin_repo.py +26 -0
  30. iotsploit_core/ports/stream_backend.py +28 -0
  31. iotsploit_core/ports/task_runner.py +22 -0
  32. iotsploit_core/ports/wifi_backend.py +148 -0
  33. iotsploit_core/py.typed +2 -0
  34. iotsploit_core/utils/__init__.py +53 -0
  35. iotsploit_core/utils/exceptions.py +152 -0
  36. iotsploit_core/utils/helpers.py +65 -0
  37. iotsploit_core/utils/iots_logger.py +164 -0
  38. iotsploit_core/utils/result.py +41 -0
  39. iotsploit_core-0.0.6.dist-info/METADATA +117 -0
  40. iotsploit_core-0.0.6.dist-info/RECORD +41 -0
  41. iotsploit_core-0.0.6.dist-info/WHEEL +4 -0
@@ -0,0 +1,456 @@
1
+ #!/usr/bin/env python3
2
+ """Execution queue and scheduler for tool execution."""
3
+
4
+ import time
5
+ import threading
6
+ import queue
7
+ import logging
8
+ from abc import ABC, abstractmethod
9
+ from typing import Dict, List, Optional, Any, Callable, Union
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum, IntEnum
12
+ from concurrent.futures import ThreadPoolExecutor, Future
13
+ import uuid
14
+
15
+ from .execution_backend import ExecutionResult, get_execution_backend_manager
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ class TaskPriority(IntEnum):
20
+ """Task priority levels (lower number = higher priority)"""
21
+ CRITICAL = 0
22
+ HIGH = 1
23
+ NORMAL = 2
24
+ LOW = 3
25
+ BACKGROUND = 4
26
+
27
+ class TaskStatus(Enum):
28
+ """Task execution status"""
29
+ PENDING = "pending"
30
+ QUEUED = "queued"
31
+ RUNNING = "running"
32
+ COMPLETED = "completed"
33
+ FAILED = "failed"
34
+ CANCELLED = "cancelled"
35
+ TIMEOUT = "timeout"
36
+
37
+ @dataclass
38
+ class ExecutionTask:
39
+ """Represents a tool execution task"""
40
+ task_id: str
41
+ tool_name: str
42
+ tool_path: str
43
+ args: List[str]
44
+ priority: TaskPriority = TaskPriority.NORMAL
45
+ timeout: Optional[int] = None
46
+ working_dir: Optional[str] = None
47
+ env: Optional[Dict[str, str]] = None
48
+ backend: Optional[str] = None
49
+ callback: Optional[Callable] = None
50
+ metadata: Dict[str, Any] = field(default_factory=dict)
51
+
52
+ # Status tracking
53
+ status: TaskStatus = TaskStatus.PENDING
54
+ created_at: float = field(default_factory=time.time)
55
+ started_at: Optional[float] = None
56
+ completed_at: Optional[float] = None
57
+ result: Optional[ExecutionResult] = None
58
+ error: Optional[str] = None
59
+
60
+ @dataclass
61
+ class QueueStats:
62
+ """Execution queue statistics"""
63
+ total_tasks: int = 0
64
+ pending_tasks: int = 0
65
+ running_tasks: int = 0
66
+ completed_tasks: int = 0
67
+ failed_tasks: int = 0
68
+ cancelled_tasks: int = 0
69
+ average_execution_time: float = 0.0
70
+ queue_size: int = 0
71
+
72
+ class ExecutionQueue:
73
+ """Priority-based execution queue with scheduling"""
74
+
75
+ def __init__(self, max_workers: int = 4, max_queue_size: int = 1000):
76
+ self.max_workers = max_workers
77
+ self.max_queue_size = max_queue_size
78
+
79
+ # Task storage
80
+ self.tasks: Dict[str, ExecutionTask] = {}
81
+ self.task_queue = queue.PriorityQueue(maxsize=max_queue_size)
82
+
83
+ # Execution management
84
+ self.executor = ThreadPoolExecutor(max_workers=max_workers)
85
+ self.running_tasks: Dict[str, Future] = {}
86
+
87
+ # Synchronization
88
+ self._lock = threading.RLock()
89
+ self._shutdown = False
90
+
91
+ # Statistics
92
+ self.stats = QueueStats()
93
+
94
+ # Backend manager
95
+ self.backend_manager = get_execution_backend_manager()
96
+
97
+ logger.info(f"Execution queue initialized with {max_workers} workers")
98
+
99
+ def submit_task(self, tool_name: str, tool_path: str, args: List[str],
100
+ priority: TaskPriority = TaskPriority.NORMAL,
101
+ timeout: Optional[int] = None,
102
+ working_dir: Optional[str] = None,
103
+ env: Optional[Dict[str, str]] = None,
104
+ backend: Optional[str] = None,
105
+ callback: Optional[Callable] = None,
106
+ metadata: Optional[Dict[str, Any]] = None) -> str:
107
+ """Submit a task for execution"""
108
+
109
+ if self._shutdown:
110
+ raise RuntimeError("Execution queue is shutdown")
111
+
112
+ if self.task_queue.qsize() >= self.max_queue_size:
113
+ raise RuntimeError("Execution queue is full")
114
+
115
+ # Create task
116
+ task_id = str(uuid.uuid4())
117
+ task = ExecutionTask(
118
+ task_id=task_id,
119
+ tool_name=tool_name,
120
+ tool_path=tool_path,
121
+ args=args,
122
+ priority=priority,
123
+ timeout=timeout,
124
+ working_dir=working_dir,
125
+ env=env,
126
+ backend=backend,
127
+ callback=callback,
128
+ metadata=metadata or {}
129
+ )
130
+
131
+ with self._lock:
132
+ # Store task
133
+ self.tasks[task_id] = task
134
+ task.status = TaskStatus.QUEUED
135
+
136
+ # Add to priority queue (priority, creation_time, task_id)
137
+ self.task_queue.put((priority.value, task.created_at, task_id))
138
+
139
+ # Update stats
140
+ self.stats.total_tasks += 1
141
+ self.stats.pending_tasks += 1
142
+ self.stats.queue_size = self.task_queue.qsize()
143
+
144
+ logger.debug(f"Task {task_id} queued with priority {priority.name}")
145
+
146
+
147
+ self._process_queue()
148
+
149
+ return task_id
150
+
151
+ def _process_queue(self):
152
+ """Process queued tasks"""
153
+ with self._lock:
154
+ # Check if we can start more tasks
155
+ while (len(self.running_tasks) < self.max_workers and
156
+ not self.task_queue.empty() and
157
+ not self._shutdown):
158
+
159
+ try:
160
+ # Get highest priority task
161
+ priority, created_at, task_id = self.task_queue.get_nowait()
162
+
163
+ if task_id not in self.tasks:
164
+ continue # Task was cancelled
165
+
166
+ task = self.tasks[task_id]
167
+
168
+ # Submit for execution
169
+ future = self.executor.submit(self._execute_task, task)
170
+ self.running_tasks[task_id] = future
171
+
172
+ # Update task status
173
+ task.status = TaskStatus.RUNNING
174
+ task.started_at = time.time()
175
+
176
+ # Update stats
177
+ self.stats.pending_tasks -= 1
178
+ self.stats.running_tasks += 1
179
+ self.stats.queue_size = self.task_queue.qsize()
180
+
181
+ logger.debug(f"Started execution of task {task_id}")
182
+
183
+ except queue.Empty:
184
+ break
185
+
186
+ def _execute_task(self, task: ExecutionTask) -> ExecutionResult:
187
+ """Execute a single task"""
188
+ try:
189
+ logger.info(f"Executing task {task.task_id}: {task.tool_name}")
190
+
191
+ # Execute using backend manager
192
+ result = self.backend_manager.execute(
193
+ tool_path=task.tool_path,
194
+ args=task.args,
195
+ backend=task.backend,
196
+ working_dir=task.working_dir,
197
+ env=task.env,
198
+ timeout=task.timeout
199
+ )
200
+
201
+ # Update task
202
+ with self._lock:
203
+ task.result = result
204
+ task.completed_at = time.time()
205
+ task.status = TaskStatus.COMPLETED if result.success else TaskStatus.FAILED
206
+
207
+ # Remove from running tasks
208
+ if task.task_id in self.running_tasks:
209
+ del self.running_tasks[task.task_id]
210
+
211
+ # Update stats
212
+ self.stats.running_tasks -= 1
213
+ if result.success:
214
+ self.stats.completed_tasks += 1
215
+ else:
216
+ self.stats.failed_tasks += 1
217
+
218
+ # Update average execution time
219
+ self._update_average_execution_time(result.execution_time)
220
+
221
+ # Call callback if provided
222
+ if task.callback:
223
+ try:
224
+ task.callback(task, result)
225
+ except Exception as e:
226
+ logger.error(f"Task callback failed: {e}")
227
+
228
+ logger.info(f"Task {task.task_id} completed: {result.success}")
229
+
230
+ # Process more tasks
231
+ self._process_queue()
232
+
233
+ return result
234
+
235
+ except Exception as e:
236
+ logger.error(f"Task {task.task_id} execution failed: {e}")
237
+
238
+ # Update task
239
+ with self._lock:
240
+ task.error = str(e)
241
+ task.completed_at = time.time()
242
+ task.status = TaskStatus.FAILED
243
+
244
+ # Remove from running tasks
245
+ if task.task_id in self.running_tasks:
246
+ del self.running_tasks[task.task_id]
247
+
248
+ # Update stats
249
+ self.stats.running_tasks -= 1
250
+ self.stats.failed_tasks += 1
251
+
252
+ # Process more tasks
253
+ self._process_queue()
254
+
255
+ raise
256
+
257
+ def _update_average_execution_time(self, execution_time: float):
258
+ """Update average execution time"""
259
+ completed = self.stats.completed_tasks
260
+ if completed == 1:
261
+ self.stats.average_execution_time = execution_time
262
+ else:
263
+ # Running average
264
+ current_avg = self.stats.average_execution_time
265
+ self.stats.average_execution_time = (
266
+ (current_avg * (completed - 1) + execution_time) / completed
267
+ )
268
+
269
+ def get_task(self, task_id: str) -> Optional[ExecutionTask]:
270
+ """Get task by ID"""
271
+ with self._lock:
272
+ return self.tasks.get(task_id)
273
+
274
+ def cancel_task(self, task_id: str) -> bool:
275
+ """Cancel a task"""
276
+ with self._lock:
277
+ task = self.tasks.get(task_id)
278
+ if not task:
279
+ return False
280
+
281
+ if task.status == TaskStatus.RUNNING:
282
+ # Try to cancel running task
283
+ future = self.running_tasks.get(task_id)
284
+ if future and future.cancel():
285
+ task.status = TaskStatus.CANCELLED
286
+ task.completed_at = time.time()
287
+ del self.running_tasks[task_id]
288
+ self.stats.running_tasks -= 1
289
+ self.stats.cancelled_tasks += 1
290
+ return True
291
+ return False
292
+
293
+ elif task.status in [TaskStatus.PENDING, TaskStatus.QUEUED]:
294
+ # Cancel queued task
295
+ task.status = TaskStatus.CANCELLED
296
+ task.completed_at = time.time()
297
+ self.stats.pending_tasks -= 1
298
+ self.stats.cancelled_tasks += 1
299
+ return True
300
+
301
+ return False
302
+
303
+ def wait_for_task(self, task_id: str, timeout: Optional[float] = None) -> Optional[ExecutionResult]:
304
+ """Wait for task completion"""
305
+ task = self.get_task(task_id)
306
+ if not task:
307
+ return None
308
+
309
+ start_time = time.time()
310
+ while task.status in [TaskStatus.PENDING, TaskStatus.QUEUED, TaskStatus.RUNNING]:
311
+ if timeout and (time.time() - start_time) > timeout:
312
+ return None
313
+ time.sleep(0.1)
314
+
315
+ return task.result
316
+
317
+ def list_tasks(self, status: Optional[TaskStatus] = None) -> List[ExecutionTask]:
318
+ """List tasks, optionally filtered by status"""
319
+ with self._lock:
320
+ tasks = list(self.tasks.values())
321
+ if status:
322
+ tasks = [t for t in tasks if t.status == status]
323
+ return sorted(tasks, key=lambda t: t.created_at)
324
+
325
+ def get_stats(self) -> QueueStats:
326
+ """Get queue statistics"""
327
+ with self._lock:
328
+ stats = QueueStats(
329
+ total_tasks=self.stats.total_tasks,
330
+ pending_tasks=self.stats.pending_tasks,
331
+ running_tasks=self.stats.running_tasks,
332
+ completed_tasks=self.stats.completed_tasks,
333
+ failed_tasks=self.stats.failed_tasks,
334
+ cancelled_tasks=self.stats.cancelled_tasks,
335
+ average_execution_time=self.stats.average_execution_time,
336
+ queue_size=self.task_queue.qsize()
337
+ )
338
+ return stats
339
+
340
+ def clear_completed_tasks(self, older_than: Optional[float] = None):
341
+ """Clear completed tasks"""
342
+ with self._lock:
343
+ current_time = time.time()
344
+ to_remove = []
345
+
346
+ for task_id, task in self.tasks.items():
347
+ if task.status in [TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.CANCELLED]:
348
+ if older_than is None or (task.completed_at and
349
+ (current_time - task.completed_at) > older_than):
350
+ to_remove.append(task_id)
351
+
352
+ for task_id in to_remove:
353
+ del self.tasks[task_id]
354
+
355
+ logger.info(f"Cleared {len(to_remove)} completed tasks")
356
+
357
+ def shutdown(self, wait: bool = True):
358
+ """Shutdown the execution queue"""
359
+ logger.info("Shutting down execution queue...")
360
+
361
+ with self._lock:
362
+ self._shutdown = True
363
+
364
+ if wait:
365
+ # Cancel all pending tasks
366
+ for task_id, task in list(self.tasks.items()):
367
+ if task.status in [TaskStatus.PENDING, TaskStatus.QUEUED]:
368
+ self.cancel_task(task_id)
369
+
370
+ # Shutdown executor
371
+ self.executor.shutdown(wait=wait)
372
+ logger.info("Execution queue shutdown complete")
373
+
374
+ class TaskScheduler:
375
+ """Task scheduler with simple cron-like capabilities"""
376
+
377
+ def __init__(self, execution_queue: ExecutionQueue):
378
+ self.execution_queue = execution_queue
379
+ self.scheduled_tasks: Dict[str, Dict] = {}
380
+ self._scheduler_thread = None
381
+ self._shutdown = False
382
+ self._lock = threading.Lock()
383
+
384
+ def schedule_task(self, schedule_id: str, tool_name: str, tool_path: str,
385
+ args: List[str], cron_expression: str, **kwargs) -> str:
386
+ """Schedule a recurring task"""
387
+ with self._lock:
388
+ self.scheduled_tasks[schedule_id] = {
389
+ 'tool_name': tool_name,
390
+ 'tool_path': tool_path,
391
+ 'args': args,
392
+ 'cron_expression': cron_expression,
393
+ 'kwargs': kwargs,
394
+ 'last_run': None,
395
+ 'next_run': self._calculate_next_run(cron_expression)
396
+ }
397
+
398
+ if not self._scheduler_thread:
399
+ self._start_scheduler()
400
+
401
+ return schedule_id
402
+
403
+ def _calculate_next_run(self, cron_expression: str) -> float:
404
+ """Calculate next run time (simplified)"""
405
+ return time.time() + 60
406
+
407
+ def _start_scheduler(self):
408
+ """Start the scheduler thread"""
409
+ self._scheduler_thread = threading.Thread(target=self._scheduler_loop, daemon=True)
410
+ self._scheduler_thread.start()
411
+
412
+ def _scheduler_loop(self):
413
+ """Main scheduler loop"""
414
+ while not self._shutdown:
415
+ current_time = time.time()
416
+
417
+ with self._lock:
418
+ for schedule_id, task_info in self.scheduled_tasks.items():
419
+ if current_time >= task_info['next_run']:
420
+ self.execution_queue.submit_task(
421
+ tool_name=task_info['tool_name'],
422
+ tool_path=task_info['tool_path'],
423
+ args=task_info['args'],
424
+ **task_info['kwargs']
425
+ )
426
+
427
+ # Update next run
428
+ task_info['last_run'] = current_time
429
+ task_info['next_run'] = self._calculate_next_run(
430
+ task_info['cron_expression']
431
+ )
432
+
433
+ time.sleep(1)
434
+
435
+ def shutdown(self):
436
+ """Shutdown the scheduler"""
437
+ self._shutdown = True
438
+ if self._scheduler_thread:
439
+ self._scheduler_thread.join()
440
+
441
+ _execution_queue = None
442
+ _task_scheduler = None
443
+
444
+ def get_execution_queue() -> ExecutionQueue:
445
+ """Get singleton execution queue"""
446
+ global _execution_queue
447
+ if _execution_queue is None:
448
+ _execution_queue = ExecutionQueue()
449
+ return _execution_queue
450
+
451
+ def get_task_scheduler() -> TaskScheduler:
452
+ """Get singleton task scheduler"""
453
+ global _task_scheduler
454
+ if _task_scheduler is None:
455
+ _task_scheduler = TaskScheduler(get_execution_queue())
456
+ return _task_scheduler