CrackNuts 0.0.1.dev2__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.
cracknuts/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ __version__ = "0.0.1.dev2"
2
+
3
+
4
+ def version():
5
+ return __version__
cracknuts/__main__.py ADDED
@@ -0,0 +1,33 @@
1
+ import logging
2
+
3
+ import click
4
+
5
+ import cracknuts
6
+ from cracknuts.cracker import protocol
7
+ from cracknuts.cracker.mock_cracker import MockCracker
8
+
9
+
10
+ @click.group(help="A library for cracker device.")
11
+ @click.version_option(version=cracknuts.__version__, message="%(version)s")
12
+ def main(): ...
13
+
14
+
15
+ @main.command(help="Start a mock cracker.")
16
+ @click.option("--host", default="127.0.0.1", show_default=True, help="The host to attach to.")
17
+ @click.option("--port", default=protocol.DEFAULT_PORT, show_default=True, help="The port to attach to.", type=int)
18
+ @click.option(
19
+ "--logging_level",
20
+ default="INFO",
21
+ show_default=True,
22
+ help="The logging level of mock cracker.",
23
+ type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]),
24
+ )
25
+ def start_mock_cracker(
26
+ host: str = "127.0.0.1", port: int = protocol.DEFAULT_PORT, logging_level: str | int = logging.INFO
27
+ ):
28
+ mock_cracker = MockCracker(host, port, logging_level)
29
+ mock_cracker.start()
30
+
31
+
32
+ if __name__ == "__main__":
33
+ main()
@@ -0,0 +1,3 @@
1
+ from cracknuts.acquisition.acquisition import Acquisition, AcquisitionBuilder
2
+
3
+ __all__ = ["Acquisition", "AcquisitionBuilder"]
@@ -0,0 +1,554 @@
1
+ import abc
2
+ import datetime
3
+ import threading
4
+ import time
5
+ import typing
6
+
7
+ import numpy as np
8
+
9
+ from cracknuts import logger
10
+ from cracknuts.cracker.stateful_cracker import StatefulCracker
11
+ from cracknuts.solver.trace import ScarrTraceDataset, NumpyTraceDataset
12
+
13
+
14
+ class AcqProgress:
15
+ def __init__(self, finished, total):
16
+ self.finished: int = finished
17
+ self.total: int = total
18
+
19
+
20
+ class Acquisition(abc.ABC):
21
+ STATUS_STOPPED: int = 0
22
+ STATUS_TESTING: int = 1
23
+ STATUS_RUNNING: int = 2
24
+ _STATUS_PAUSED_SWITCH: int = -1
25
+
26
+ DO_ERROR_HANDLER_STRATEGY_EXIT: int = 0
27
+ DO_ERROR_HANDLER_STRATEGY_CONTINUE_UNTIL_MAX_ERROR_COUNT: int = 1
28
+
29
+ def __init__(
30
+ self,
31
+ cracker: StatefulCracker,
32
+ trace_count: int = 1000,
33
+ sample_length: int = 1024,
34
+ sample_offset: int = 0,
35
+ data_length: int = 0,
36
+ trigger_judge_wait_time: float = 0.05,
37
+ trigger_judge_timeout: float = 1.0,
38
+ do_error_handler_strategy: int = DO_ERROR_HANDLER_STRATEGY_EXIT,
39
+ do_error_max_count: int = -1,
40
+ file_format: str = "scarr",
41
+ file_path: str = "auto",
42
+ ):
43
+ self._logger = logger.get_logger(self)
44
+ self._last_wave: dict[int, np.ndarray] | None = {1: np.zeros(1)}
45
+ self._status: int = self.STATUS_STOPPED
46
+ self._current_trace_count = 1
47
+ self._run_thread_pause_event: threading.Event = threading.Event()
48
+
49
+ self.cracker: StatefulCracker = cracker
50
+ self.trace_count: int = trace_count
51
+ self.sample_length = sample_length
52
+ self.sample_offset: int = sample_offset
53
+ self.data_length: int = data_length
54
+ self.trigger_judge_wait_time: float = trigger_judge_wait_time # second
55
+ self.trigger_judge_timeout: float = trigger_judge_timeout # second
56
+ self.do_error_handler_strategy: int = do_error_handler_strategy
57
+ self.do_error_max_count: int = do_error_max_count # -1 never exit
58
+ self.file_format: str = file_format
59
+ self.file_path: str = file_path
60
+
61
+ self._on_wave_loaded_callback: typing.Callable[[np.ndarray], None] | None = None
62
+ self._on_status_change_listeners: list[typing.Callable[[int], None]] = []
63
+ self._on_run_progress_changed_listeners: list[typing.Callable[[dict], None]] = []
64
+
65
+ def get_status(self):
66
+ return self._status
67
+
68
+ def set_cracker(self, cracker: StatefulCracker):
69
+ self.cracker = cracker
70
+
71
+ def on_status_changed(self, callback: typing.Callable[[int], None]) -> None:
72
+ """
73
+ User should not use this function. If you need to perform actions when the ACQ state changes,
74
+ please use the node functions in the ACQ lifecycle point: `init`, `do`, and `finish`.
75
+
76
+ status: 0 stopped, 1 testing, 2 running 3 paused
77
+ """
78
+ self._on_status_change_listeners.append(callback)
79
+
80
+ def on_run_progress_changed(self, callback: typing.Callable[[dict], None]) -> None:
81
+ self._on_run_progress_changed_listeners.append(callback)
82
+
83
+ def on_wave_loaded(self, callback: typing.Callable[[np.ndarray], None]) -> None:
84
+ self._on_wave_loaded_callback = callback
85
+
86
+ def run(
87
+ self,
88
+ count: int = 1,
89
+ sample_length: int = None,
90
+ sample_offset: int = None,
91
+ data_length: int = None,
92
+ trigger_judge_wait_time: float | None = None,
93
+ trigger_judge_timeout: float | None = None,
94
+ do_error_max_count: int | None = None,
95
+ do_error_handler_strategy: int | None = None,
96
+ file_format: str | None = "scarr",
97
+ file_path: str | None = "auto",
98
+ ):
99
+ if self._status < 0:
100
+ self.resume()
101
+ else:
102
+ self._run(
103
+ test=False,
104
+ count=count,
105
+ sample_length=sample_length,
106
+ sample_offset=sample_offset,
107
+ data_length=data_length,
108
+ trigger_judge_wait_time=trigger_judge_wait_time,
109
+ trigger_judge_timeout=trigger_judge_timeout,
110
+ do_error_max_count=do_error_max_count,
111
+ do_error_handler_strategy=do_error_handler_strategy,
112
+ file_format=file_format,
113
+ file_path=file_path,
114
+ )
115
+
116
+ def run_sync(
117
+ self,
118
+ count=1,
119
+ sample_length: int = None,
120
+ sample_offset: int = None,
121
+ trigger_judge_wait_time: float | None = None,
122
+ trigger_judge_timeout: float | None = None,
123
+ do_error_max_count: int | None = None,
124
+ do_error_handler_strategy: int | None = None,
125
+ file_format: str | None = "scarr",
126
+ ):
127
+ try:
128
+ self._do_run(
129
+ test=False,
130
+ count=count,
131
+ sample_length=sample_length,
132
+ sample_offset=sample_offset,
133
+ trigger_judge_wait_time=trigger_judge_wait_time,
134
+ trigger_judge_timeout=trigger_judge_timeout,
135
+ do_error_max_count=do_error_max_count,
136
+ do_error_handler_strategy=do_error_handler_strategy,
137
+ file_format=file_format,
138
+ )
139
+ self._logger.debug("stop by timeout.")
140
+ except KeyboardInterrupt:
141
+ self._logger.debug("stop by interrupted.")
142
+ self.stop()
143
+
144
+ def test(
145
+ self,
146
+ count=-1,
147
+ sample_length: int = None,
148
+ sample_offset: int = None,
149
+ trigger_judge_wait_time: float | None = None,
150
+ trigger_judge_timeout: float | None = None,
151
+ do_error_max_count: int | None = None,
152
+ do_error_handler_strategy: int | None = None,
153
+ ):
154
+ if self._status < 0:
155
+ self._logger.debug("Resume to test mode.")
156
+ self.resume()
157
+ else:
158
+ self._logger.debug("Start test mode.")
159
+ self._run(
160
+ test=True,
161
+ count=count,
162
+ sample_length=sample_length,
163
+ sample_offset=sample_offset,
164
+ trigger_judge_wait_time=trigger_judge_wait_time,
165
+ trigger_judge_timeout=trigger_judge_timeout,
166
+ do_error_max_count=do_error_max_count,
167
+ do_error_handler_strategy=do_error_handler_strategy,
168
+ )
169
+
170
+ def test_sync(
171
+ self,
172
+ count=-1,
173
+ sample_length: int = None,
174
+ sample_offset: int = None,
175
+ trigger_judge_wait_time: float | None = None,
176
+ trigger_judge_timeout: float | None = None,
177
+ do_error_max_count: int | None = None,
178
+ do_error_handler_strategy: int | None = None,
179
+ ):
180
+ try:
181
+ self._do_run(
182
+ test=True,
183
+ count=count,
184
+ sample_length=sample_length,
185
+ sample_offset=sample_offset,
186
+ trigger_judge_wait_time=trigger_judge_wait_time,
187
+ trigger_judge_timeout=trigger_judge_timeout,
188
+ do_error_max_count=do_error_max_count,
189
+ do_error_handler_strategy=do_error_handler_strategy,
190
+ )
191
+ except KeyboardInterrupt:
192
+ self._logger.debug("stop by interrupted.")
193
+ self.stop()
194
+
195
+ def pause(self):
196
+ if self._status > 0:
197
+ self._run_thread_pause_event.clear()
198
+ self._status = self._status * self._STATUS_PAUSED_SWITCH
199
+
200
+ def resume(self):
201
+ if self._status < 0:
202
+ self._run_thread_pause_event.set()
203
+ self._status = self._status * self._STATUS_PAUSED_SWITCH
204
+
205
+ def stop(self):
206
+ if not self._run_thread_pause_event.is_set():
207
+ self._run_thread_pause_event.set()
208
+ self._status = self.STATUS_STOPPED
209
+
210
+ def _run(
211
+ self,
212
+ test: bool = True,
213
+ count: int = 1,
214
+ sample_length: int = None,
215
+ sample_offset: int = None,
216
+ data_length: int = None,
217
+ trigger_judge_wait_time: float | None = None,
218
+ trigger_judge_timeout: float | None = None,
219
+ do_error_max_count: int | None = None,
220
+ do_error_handler_strategy: int | None = None,
221
+ file_format: str | None = "scarr",
222
+ file_path: str | None = "auto",
223
+ ):
224
+ self._run_thread_pause_event.set()
225
+ threading.Thread(
226
+ target=self._do_run,
227
+ kwargs={
228
+ "test": test,
229
+ "count": count,
230
+ "sample_length": sample_length,
231
+ "sample_offset": sample_offset,
232
+ "data_length": data_length,
233
+ "trigger_judge_wait_time": trigger_judge_wait_time,
234
+ "trigger_judge_timeout": trigger_judge_timeout,
235
+ "do_error_max_count": do_error_max_count,
236
+ "do_error_handler_strategy": do_error_handler_strategy,
237
+ "file_format": file_format,
238
+ "file_path": file_path,
239
+ },
240
+ ).start()
241
+
242
+ def _do_run(
243
+ self,
244
+ test: bool = True,
245
+ count: int = 1,
246
+ sample_length: int = 1024,
247
+ sample_offset: int = None,
248
+ data_length: int = None,
249
+ trigger_judge_wait_time: float | None = None,
250
+ trigger_judge_timeout: float | None = None,
251
+ do_error_max_count: int | None = None,
252
+ do_error_handler_strategy: int | None = None,
253
+ file_format: str | None = "scarr",
254
+ file_path: str | None = "auto",
255
+ ):
256
+ if self._status > 0:
257
+ raise Exception(f'AcquisitionTemplate is already running in {'run' if self._status == 2 else 'test'} mode.')
258
+
259
+ if not test:
260
+ self._status = self.STATUS_RUNNING
261
+ self._status_changed()
262
+ else:
263
+ self._status = self.STATUS_TESTING
264
+ self._status_changed()
265
+
266
+ self.trace_count = count
267
+ if sample_length is not None:
268
+ self.sample_length = sample_length
269
+ if sample_offset is not None:
270
+ self.sample_offset = sample_offset
271
+ if data_length is not None:
272
+ self.data_length = data_length
273
+ if trigger_judge_wait_time is not None:
274
+ self.trigger_judge_wait_time = trigger_judge_wait_time
275
+ if trigger_judge_timeout is not None:
276
+ self.trigger_judge_timeout = trigger_judge_timeout
277
+ if do_error_max_count is not None:
278
+ self.do_error_max_count = do_error_max_count
279
+ if do_error_handler_strategy is not None:
280
+ self.do_error_handler_strategy = do_error_handler_strategy
281
+ if file_format is not None:
282
+ self.file_format = file_format
283
+ if file_path is not None:
284
+ self.file_path = file_path
285
+ self.pre_init()
286
+ try:
287
+ self.init()
288
+ except Exception as e:
289
+ self._logger.error("Initialization error: %s", e.args)
290
+ return
291
+ self._post_init()
292
+ self._loop(not test, self.file_path, self.file_format)
293
+ self._pre_finish()
294
+ self._finish()
295
+ self._post_finish()
296
+
297
+ def _status_changed(self):
298
+ for listener in self._on_status_change_listeners:
299
+ listener(self._status)
300
+
301
+ def _loop(self, persistent: bool = True, file_path: str = None, file_format: str = "scarr"):
302
+ do_error_count = 0
303
+ trace_index = 0
304
+ self._progress_changed(AcqProgress(trace_index, self.trace_count))
305
+
306
+ cracker_version = self.cracker.get_version()
307
+ if persistent:
308
+ if file_path is None or file_path == "auto":
309
+ file_path = "./dataset/" + datetime.datetime.now().strftime("%Y%m%d%H%M%S")
310
+ if file_format == "scarr":
311
+ file_path += ".zarr"
312
+ elif file_format == "numpy":
313
+ file_path += ".npy"
314
+
315
+ if file_format == "scarr":
316
+ dataset = ScarrTraceDataset.new(
317
+ file_path,
318
+ 1,
319
+ self.trace_count,
320
+ self.sample_length,
321
+ self.data_length,
322
+ version=f"({cracker_version}, {cracker_version})",
323
+ ) # todo channel
324
+ elif file_format == "numpy":
325
+ dataset = NumpyTraceDataset.new(
326
+ file_path,
327
+ 1,
328
+ self.trace_count,
329
+ self.sample_length,
330
+ self.data_length,
331
+ version=f"({cracker_version}, {cracker_version})",
332
+ ) # todo channel
333
+ else:
334
+ self._logger.warning(f"Unsupported file format: {file_format}")
335
+ else:
336
+ dataset = None
337
+
338
+ while self._status != 0 and self.trace_count - trace_index != 0:
339
+ if self._status < 0:
340
+ self._status_changed()
341
+ self._run_thread_pause_event.wait()
342
+ self._status_changed()
343
+ self._logger.debug("Get wave data: %s", trace_index)
344
+ self.pre_do()
345
+ start = time.time()
346
+ try:
347
+ data = self.do()
348
+ except Exception as e:
349
+ self._logger.error("Do error: %s", e.args)
350
+ do_error_count += 1
351
+ if self.do_error_handler_strategy == self.DO_ERROR_HANDLER_STRATEGY_EXIT:
352
+ self._logger.error("Exit with do error")
353
+ elif do_error_count > self.do_error_max_count:
354
+ self._logger.error(
355
+ f"Exit with do function get error count: {do_error_count} is grate than do_error_max_count"
356
+ )
357
+ break
358
+ else:
359
+ self._logger.error(f"Do function get error count: {do_error_count}")
360
+ continue
361
+ self._logger.debug(f"count: {trace_index} delay: {time.time() - start}")
362
+ trigger_judge_start_time = time.time()
363
+ while self._status != 0:
364
+ trigger_judge_time = time.time() - trigger_judge_start_time
365
+ if trigger_judge_time > self.trigger_judge_timeout:
366
+ self._logger.error(
367
+ "Triggered judge timeout and will get next waves, " "judge time: %s and timeout is %s",
368
+ trigger_judge_time,
369
+ self.trigger_judge_timeout,
370
+ )
371
+ break
372
+ self._logger.debug("Judge trigger status.")
373
+ # if self._is_triggered():
374
+ if True:
375
+ self._last_wave = self._get_waves(self.sample_offset, self.sample_length)
376
+ if self._last_wave is not None:
377
+ self._logger.debug(
378
+ "Got wave: %s.",
379
+ {k: v.shape for k, v in self._last_wave.items()},
380
+ )
381
+ # self._logger.debug('Got wave: ')
382
+ if self._on_wave_loaded_callback and callable(self._on_wave_loaded_callback):
383
+ try:
384
+ self._on_wave_loaded_callback(self._last_wave)
385
+ except Exception as e:
386
+ self._logger.error("Wave loaded event callback error: %s", e.args)
387
+ break
388
+
389
+ time.sleep(self.trigger_judge_wait_time)
390
+ if dataset is not None:
391
+ dataset.set_trace(0, trace_index, self._last_wave[1], data)
392
+ # dataset.set_trace(1, trace_index, self._last_wave[2], data) # todo second channel
393
+ self._post_do()
394
+ trace_index += 1
395
+ self._current_trace_count = trace_index
396
+ self._progress_changed(AcqProgress(trace_index, self.trace_count))
397
+
398
+ if dataset is not None:
399
+ dataset.dump()
400
+ self._status = self.STATUS_STOPPED
401
+ self._status_changed()
402
+
403
+ def _progress_changed(self, progress: "AcqProgress"):
404
+ for listener in self._on_run_progress_changed_listeners:
405
+ listener({"finished": progress.finished, "total": progress.total})
406
+
407
+ def _loop_without_log(self):
408
+ ...
409
+ # for i in range(self.trace_count):
410
+ # # print('yes', self._logger.handlers[0].stream)
411
+ # # self._logger.debug('Get wave data: %s', i)
412
+ # self.pre_do()
413
+ # self.do()
414
+ # trigger_judge_start_time = time.time()
415
+ # while self._status:
416
+ # trigger_judge_time = time.time() - trigger_judge_start_time
417
+ # if trigger_judge_time > self.trigger_judge_timeout:
418
+ # # self._logger.error("Triggered judge timeout and will get next waves, "
419
+ # # "judge time: %s and timeout is %s",
420
+ # # trigger_judge_time, self._trigger_judge_timeout)
421
+ # break
422
+ # # self._logger.debug('Judge trigger status.')
423
+ # if self._is_triggered():
424
+ # self._last_wave = self._get_waves()
425
+ # if self._last_wave is not None:
426
+ # ...
427
+ # # self._logger.debug('Got wave: %s.', self._last_wave.shape)
428
+ # if self._on_wave_loaded_callback and callable(self._on_wave_loaded_callback):
429
+ # try:
430
+ # self._on_wave_loaded_callback(self._last_wave)
431
+ # except Exception as e:
432
+ # ...
433
+ # # self._logger.error('Wave loaded event callback error: %s', e.args)
434
+ # time.sleep(1)
435
+ # break
436
+ # time.sleep(self.trigger_judge_wait_time)
437
+ # self._save_wave()
438
+ # self._post_do()
439
+
440
+ def connect_scrat(self):
441
+ """
442
+ Connect to scrat device
443
+ :return:
444
+ """
445
+ ...
446
+
447
+ def connect_cracker(self):
448
+ """
449
+ Connect to cracker device.
450
+ :return:
451
+ """
452
+ self.cracker.connect()
453
+
454
+ def connect_net(self): ...
455
+
456
+ def config_scrat(self): ...
457
+
458
+ def config_cracker(self): ...
459
+
460
+ def pre_init(self):
461
+ self.cracker.sync_config_to_cracker()
462
+
463
+ @abc.abstractmethod
464
+ def init(self): ...
465
+
466
+ def _post_init(self): ...
467
+
468
+ def transfer(self): ...
469
+
470
+ def pre_do(self):
471
+ self.cracker.osc_single()
472
+
473
+ @abc.abstractmethod
474
+ def do(self): ...
475
+
476
+ def _do(self):
477
+ try:
478
+ self.do()
479
+ return True
480
+ except Exception as e:
481
+ self._logger.error("Do error: %s", e.args)
482
+ return False
483
+
484
+ def _post_do(self): ...
485
+
486
+ def _is_triggered(self):
487
+ return self.cracker.osc_is_triggered()
488
+
489
+ def _get_waves(self, offset: int, sample_count: int) -> dict[int, np.ndarray]:
490
+ enable_channels = [k for k, v in self.cracker.get_current_config().osc_analog_channel_enable.items() if v]
491
+ wave_dict = {}
492
+ for c in enable_channels:
493
+ status, wave_dict[c] = self.cracker.osc_get_analog_wave(c, offset, sample_count)
494
+ return wave_dict
495
+
496
+ def save_data(self, data_type: str = "zarr"):
497
+ """
498
+ Save wave, key and other info to acquisition dataset file.
499
+ """
500
+ # if data_type == 'zarr':
501
+ # zarr.create()
502
+ ...
503
+
504
+ def _pre_finish(self): ...
505
+
506
+ def _post_finish(self): ...
507
+
508
+ def _save_dataset(self): ...
509
+
510
+ def _finish(self):
511
+ pass
512
+
513
+ def get_last_wave(self) -> dict[int, np.ndarray]:
514
+ return self._last_wave
515
+
516
+ @staticmethod
517
+ def builder():
518
+ return AcquisitionBuilder()
519
+
520
+
521
+ class AcquisitionBuilder:
522
+ def __init__(self):
523
+ self._cracker = None
524
+ self._do_function = lambda _: ...
525
+ self._init_function = lambda _: ...
526
+
527
+ def cracker(self, cracker: StatefulCracker):
528
+ self._cracker = cracker
529
+ return self
530
+
531
+ def init(self, init_function: typing.Callable[[StatefulCracker], None]):
532
+ if init_function:
533
+ self._init_function = init_function
534
+ return self
535
+
536
+ def do(self, do_function: typing.Callable[[StatefulCracker], None]):
537
+ if do_function:
538
+ self._do_function = do_function
539
+ return self
540
+
541
+ def build(self, **kwargs):
542
+ if self._cracker is None:
543
+ raise ValueError("No cracker")
544
+
545
+ builder_self = self
546
+
547
+ class AnonymousAcquisition(Acquisition):
548
+ def init(self):
549
+ builder_self._init_function(self.cracker)
550
+
551
+ def do(self):
552
+ return builder_self._do_function(self.cracker)
553
+
554
+ return AnonymousAcquisition(builder_self._cracker, **kwargs)
File without changes