oscura 0.0.1__py3-none-any.whl → 0.1.0__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 (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.0.dist-info/METADATA +300 -0
  460. oscura-0.1.0.dist-info/RECORD +463 -0
  461. oscura-0.1.0.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,923 @@
1
+ """Checksum and CRC field detection and identification.
2
+
3
+
4
+ This module provides tools for detecting checksum and CRC fields in binary
5
+ messages by analyzing field correlations and testing common algorithms.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import TYPE_CHECKING, Any, Literal, Union
10
+
11
+ import numpy as np
12
+
13
+ if TYPE_CHECKING:
14
+ from numpy.typing import NDArray
15
+
16
+ # Type alias for input data
17
+ DataType = Union[bytes, bytearray, "NDArray[np.uint8]"]
18
+
19
+
20
+ @dataclass
21
+ class ChecksumCandidate:
22
+ """Candidate checksum field.
23
+
24
+ Attributes:
25
+ offset: Byte offset in message
26
+ size: Field size in bytes (1, 2, or 4)
27
+ position: Location in message structure
28
+ correlation: Correlation with content (0-1)
29
+ likely_scope: Byte range likely covered by checksum (start, end)
30
+ """
31
+
32
+ offset: int
33
+ size: int
34
+ position: Literal["header", "trailer"]
35
+ correlation: float
36
+ likely_scope: tuple[int, int]
37
+
38
+
39
+ @dataclass
40
+ class ChecksumMatch:
41
+ """Identified checksum algorithm.
42
+
43
+ Attributes:
44
+ algorithm: Algorithm name
45
+ offset: Field offset in message
46
+ size: Field size in bytes
47
+ scope_start: Start of checksummed region
48
+ scope_end: End of checksummed region
49
+ match_rate: Fraction of messages that match (0-1)
50
+ polynomial: CRC polynomial (for CRC algorithms)
51
+ init_value: Initial value (for CRC algorithms)
52
+ xor_out: Final XOR value (for CRC algorithms)
53
+ """
54
+
55
+ algorithm: str
56
+ offset: int
57
+ size: int
58
+ scope_start: int
59
+ scope_end: int
60
+ match_rate: float
61
+ polynomial: int | None = None
62
+ init_value: int | None = None
63
+ xor_out: int | None = None
64
+
65
+
66
+ def detect_checksum_fields(
67
+ messages: list[DataType], candidate_offsets: list[int] | None = None
68
+ ) -> list[ChecksumCandidate]:
69
+ """Detect fields that are correlated with message content.
70
+
71
+ : Checksum and CRC Field Detection
72
+
73
+ Analyzes message fields to find those that vary consistently with
74
+ content changes, indicating potential checksum/CRC fields.
75
+
76
+ Args:
77
+ messages: List of messages to analyze
78
+ candidate_offsets: Optional list of specific offsets to check
79
+
80
+ Returns:
81
+ List of checksum candidates sorted by correlation
82
+
83
+ Example:
84
+ >>> msgs = [b'\\x00\\x00DATA', b'\\x01\\x00DATA']
85
+ >>> candidates = detect_checksum_fields(msgs)
86
+ >>> len(candidates) >= 0
87
+ True
88
+ """
89
+ if not messages:
90
+ return []
91
+
92
+ # Convert all messages to bytes
93
+ byte_messages = []
94
+ for msg in messages:
95
+ if isinstance(msg, np.ndarray):
96
+ byte_messages.append(msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten()))
97
+ else:
98
+ byte_messages.append(bytes(msg))
99
+
100
+ # Find minimum message length
101
+ min_len = min(len(msg) for msg in byte_messages)
102
+
103
+ if min_len < 2:
104
+ return []
105
+
106
+ # Determine candidate positions
107
+ if candidate_offsets is None:
108
+ # Check header (first 16 bytes) and trailer (last 16 bytes)
109
+ header_positions = list(range(min(16, min_len - 1)))
110
+ trailer_start = max(0, min_len - 16)
111
+ trailer_positions = list(range(trailer_start, min_len - 1))
112
+ candidate_offsets = list(set(header_positions + trailer_positions))
113
+
114
+ candidates = []
115
+
116
+ # Test each candidate offset for different field sizes
117
+ for offset in candidate_offsets:
118
+ for size in [1, 2, 4]:
119
+ if offset + size > min_len:
120
+ continue
121
+
122
+ # Extract field values and content
123
+ field_values = []
124
+ content_hashes = []
125
+
126
+ for msg in byte_messages:
127
+ if len(msg) < offset + size:
128
+ continue
129
+
130
+ # Extract field value
131
+ field_bytes = msg[offset : offset + size]
132
+ field_value = int.from_bytes(field_bytes, byteorder="big")
133
+ field_values.append(field_value)
134
+
135
+ # Hash content (excluding the field itself)
136
+ content = msg[:offset] + msg[offset + size :]
137
+ content_hash = hash(content)
138
+ content_hashes.append(content_hash)
139
+
140
+ if len(field_values) < 2:
141
+ continue
142
+
143
+ # Calculate correlation between field and content
144
+ # If field varies with content, it's a good candidate
145
+ unique_content = len(set(content_hashes))
146
+ unique_fields = len(set(field_values))
147
+
148
+ if unique_content > 1:
149
+ # Correlation estimate: how much field varies with content
150
+ correlation = min(1.0, unique_fields / unique_content)
151
+ else:
152
+ correlation = 0.0
153
+
154
+ # Skip if correlation is too low
155
+ if correlation < 0.3:
156
+ continue
157
+
158
+ # Determine position (header vs trailer)
159
+ position: Literal["header", "trailer"] = (
160
+ "header" if offset < min_len // 2 else "trailer"
161
+ )
162
+
163
+ # Estimate likely scope
164
+ if position == "header":
165
+ likely_scope = (offset + size, min_len)
166
+ else:
167
+ likely_scope = (0, offset)
168
+
169
+ candidates.append(
170
+ ChecksumCandidate(
171
+ offset=offset,
172
+ size=size,
173
+ position=position,
174
+ correlation=correlation,
175
+ likely_scope=likely_scope,
176
+ )
177
+ )
178
+
179
+ # Sort by correlation descending
180
+ candidates.sort(key=lambda c: c.correlation, reverse=True)
181
+
182
+ return candidates
183
+
184
+
185
+ def identify_checksum_algorithm(
186
+ messages: list[DataType], field_offset: int, field_size: int | None = None
187
+ ) -> ChecksumMatch | None:
188
+ """Identify which checksum algorithm is used.
189
+
190
+ : Checksum and CRC Field Detection
191
+
192
+ Tests common checksum algorithms to identify which one matches
193
+ the observed field values.
194
+
195
+ Args:
196
+ messages: List of messages to analyze
197
+ field_offset: Offset of checksum field
198
+ field_size: Size of field (1, 2, or 4 bytes), auto-detect if None
199
+
200
+ Returns:
201
+ ChecksumMatch if algorithm identified, None otherwise
202
+
203
+ Example:
204
+ >>> msgs = [b'\\x41ABC', b'\\x42BCD'] # XOR checksum
205
+ >>> match = identify_checksum_algorithm(msgs, 0, 1)
206
+ >>> match is not None
207
+ True
208
+ """
209
+ if not messages:
210
+ return None
211
+
212
+ # Convert messages to bytes
213
+ byte_messages = []
214
+ for msg in messages:
215
+ if isinstance(msg, np.ndarray):
216
+ byte_messages.append(msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten()))
217
+ else:
218
+ byte_messages.append(bytes(msg))
219
+
220
+ # Determine field size if not specified
221
+ if field_size is None:
222
+ field_sizes = [1, 2, 4]
223
+ else:
224
+ field_sizes = [field_size]
225
+
226
+ best_match = None
227
+ best_rate = 0.0
228
+
229
+ # Try each field size
230
+ for size in field_sizes:
231
+ if any(len(msg) < field_offset + size for msg in byte_messages):
232
+ continue
233
+
234
+ # Define algorithm tests based on field size
235
+ if size == 1:
236
+ algorithms = ["xor", "sum8"]
237
+ elif size == 2:
238
+ # Include both big and little endian CRC variants
239
+ algorithms = [
240
+ "sum16_big",
241
+ "sum16_little",
242
+ "crc16_ccitt",
243
+ "crc16_ibm",
244
+ "crc16",
245
+ "checksum",
246
+ ]
247
+ elif size == 4:
248
+ algorithms = ["crc32"]
249
+ else:
250
+ continue
251
+
252
+ # Test each algorithm
253
+ for algo in algorithms:
254
+ # Map algorithm names to computation functions
255
+ actual_algo = algo
256
+ if algo == "crc16":
257
+ actual_algo = "crc16_ccitt"
258
+ elif algo == "checksum":
259
+ actual_algo = "sum16_big"
260
+
261
+ # For CRC algorithms, try different init values
262
+ init_values: list[int | None] = [None]
263
+ if actual_algo in ["crc16_ccitt", "crc16_ibm"]:
264
+ init_values = [0x0000, 0xFFFF]
265
+
266
+ for init_val in init_values:
267
+ # Try different scopes
268
+ for scope_start in [0, field_offset + size]:
269
+ for scope_end in [field_offset, len(byte_messages[0])]:
270
+ if scope_end <= scope_start:
271
+ continue
272
+
273
+ # Test algorithm on all messages
274
+ matches = 0
275
+ total = 0
276
+
277
+ for msg in byte_messages:
278
+ if len(msg) < scope_end:
279
+ continue
280
+
281
+ # Try both big and little endian for field extraction
282
+ endian_val: Literal["big", "little"]
283
+ for endian_val in ("big", "little"): # type: ignore[assignment]
284
+ expected = int.from_bytes(
285
+ msg[field_offset : field_offset + size], byteorder=endian_val
286
+ )
287
+
288
+ # Extract data to checksum
289
+ if scope_start < field_offset < scope_end:
290
+ # Exclude checksum field from data
291
+ data = (
292
+ msg[scope_start:field_offset]
293
+ + msg[field_offset + size : scope_end]
294
+ )
295
+ else:
296
+ data = msg[scope_start:scope_end]
297
+
298
+ # Compute checksum
299
+ try:
300
+ if init_val is not None:
301
+ computed = compute_checksum(
302
+ data, actual_algo, init=init_val
303
+ )
304
+ else:
305
+ computed = compute_checksum(data, actual_algo)
306
+ if computed == expected:
307
+ matches += 1
308
+ break # Found match with this endian
309
+ except Exception:
310
+ pass
311
+
312
+ total += 1
313
+
314
+ if total == 0:
315
+ continue
316
+
317
+ match_rate = matches / total
318
+
319
+ # Consider it a match if >= 80% of messages match
320
+ if match_rate >= 0.8 and match_rate > best_rate:
321
+ best_rate = match_rate
322
+ best_match = ChecksumMatch(
323
+ algorithm=algo,
324
+ offset=field_offset,
325
+ size=size,
326
+ scope_start=scope_start,
327
+ scope_end=scope_end,
328
+ match_rate=match_rate,
329
+ init_value=init_val,
330
+ )
331
+
332
+ return best_match
333
+
334
+
335
+ def verify_checksums(
336
+ messages: list[DataType],
337
+ algorithm: str,
338
+ field_offset: int,
339
+ scope_start: int = 0,
340
+ scope_end: int | None = None,
341
+ init_value: int | None = None,
342
+ ) -> tuple[int, int]:
343
+ """Verify checksums using identified algorithm.
344
+
345
+ : Checksum and CRC Field Detection
346
+
347
+ Validates checksums across multiple messages using the specified algorithm.
348
+
349
+ Args:
350
+ messages: List of messages to verify
351
+ algorithm: Checksum algorithm name
352
+ field_offset: Offset of checksum field
353
+ scope_start: Start of checksummed data (default: 0)
354
+ scope_end: End of checksummed data (None = message end)
355
+ init_value: Initial value for CRC algorithms (None = use default)
356
+
357
+ Returns:
358
+ Tuple of (passed, failed) counts
359
+
360
+ Example:
361
+ >>> msgs = [b'\\x41ABC']
362
+ >>> passed, failed = verify_checksums(msgs, 'xor', 0, 1)
363
+ >>> passed + failed == len(msgs)
364
+ True
365
+ """
366
+ if not messages:
367
+ return (0, 0)
368
+
369
+ passed = 0
370
+ failed = 0
371
+
372
+ # Determine field size from algorithm
373
+ if algorithm in ["xor", "sum8"]:
374
+ field_size = 1
375
+ elif algorithm.startswith("sum16") or algorithm.startswith("crc16"):
376
+ field_size = 2
377
+ elif algorithm == "crc32":
378
+ field_size = 4
379
+ else:
380
+ # Try to infer from first message
381
+ field_size = 1
382
+
383
+ for msg in messages:
384
+ if isinstance(msg, np.ndarray):
385
+ msg = msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten())
386
+ else:
387
+ msg = bytes(msg)
388
+
389
+ msg_scope_end = scope_end if scope_end is not None else len(msg)
390
+
391
+ if len(msg) < field_offset + field_size or len(msg) < msg_scope_end:
392
+ failed += 1
393
+ continue
394
+
395
+ # Try both endiannesses
396
+ matched = False
397
+ endian_val2: Literal["big", "little"]
398
+ for endian_val2 in ("big", "little"): # type: ignore[assignment]
399
+ expected = int.from_bytes(
400
+ msg[field_offset : field_offset + field_size], byteorder=endian_val2
401
+ )
402
+
403
+ # Extract data to checksum
404
+ if scope_start < field_offset < msg_scope_end:
405
+ data = (
406
+ msg[scope_start:field_offset] + msg[field_offset + field_size : msg_scope_end]
407
+ )
408
+ else:
409
+ data = msg[scope_start:msg_scope_end]
410
+
411
+ # Compute checksum
412
+ try:
413
+ if init_value is not None:
414
+ computed = compute_checksum(data, algorithm, init=init_value)
415
+ else:
416
+ computed = compute_checksum(data, algorithm)
417
+ if computed == expected:
418
+ matched = True
419
+ break
420
+ except Exception:
421
+ pass
422
+
423
+ if matched:
424
+ passed += 1
425
+ else:
426
+ failed += 1
427
+
428
+ return (passed, failed)
429
+
430
+
431
+ def compute_checksum(data: bytes, algorithm: str, **kwargs: Any) -> int:
432
+ """Compute checksum using specified algorithm.
433
+
434
+ : Checksum and CRC Field Detection
435
+
436
+ Args:
437
+ data: Data to checksum
438
+ algorithm: Algorithm name
439
+ **kwargs: Algorithm-specific parameters
440
+
441
+ Returns:
442
+ Computed checksum value
443
+
444
+ Raises:
445
+ ValueError: If algorithm is unknown
446
+
447
+ Example:
448
+ >>> compute_checksum(b'ABC', 'xor')
449
+ 2
450
+ """
451
+ if algorithm == "xor":
452
+ return xor_checksum(data)
453
+ elif algorithm == "sum8":
454
+ return sum8(data)
455
+ elif algorithm == "sum16_big":
456
+ return sum16(data, endian="big")
457
+ elif algorithm == "sum16_little":
458
+ return sum16(data, endian="little")
459
+ elif algorithm == "crc8":
460
+ poly = kwargs.get("poly", 0x07)
461
+ init = kwargs.get("init", 0x00)
462
+ return crc8(data, poly=poly, init=init)
463
+ elif algorithm == "crc16_ccitt":
464
+ init = kwargs.get("init", 0xFFFF)
465
+ return crc16_ccitt(data, init=init)
466
+ elif algorithm == "crc16_ibm":
467
+ init = kwargs.get("init", 0x0000)
468
+ return crc16_ibm(data, init=init)
469
+ elif algorithm == "crc32":
470
+ return crc32(data)
471
+ else:
472
+ raise ValueError(f"Unknown checksum algorithm: {algorithm}")
473
+
474
+
475
+ def crc8(data: bytes, poly: int = 0x07, init: int = 0x00) -> int:
476
+ """Calculate CRC-8.
477
+
478
+ : Checksum and CRC Field Detection
479
+
480
+ Standard CRC-8 with configurable polynomial.
481
+
482
+ Args:
483
+ data: Data to checksum
484
+ poly: Polynomial (default: 0x07)
485
+ init: Initial value (default: 0x00)
486
+
487
+ Returns:
488
+ CRC-8 value (0-255)
489
+
490
+ Example:
491
+ >>> crc8(b'123456789')
492
+ 244
493
+ """
494
+ crc = init
495
+ for byte in data:
496
+ crc ^= byte
497
+ for _ in range(8):
498
+ if crc & 0x80:
499
+ crc = (crc << 1) ^ poly
500
+ else:
501
+ crc = crc << 1
502
+ crc &= 0xFF
503
+ return crc
504
+
505
+
506
+ def crc16_ccitt(data: bytes, init: int = 0xFFFF) -> int:
507
+ """Calculate CRC-16-CCITT.
508
+
509
+ : Checksum and CRC Field Detection
510
+
511
+ CCITT polynomial: 0x1021
512
+
513
+ Args:
514
+ data: Data to checksum
515
+ init: Initial value (default: 0xFFFF)
516
+
517
+ Returns:
518
+ CRC-16 value (0-65535)
519
+
520
+ Example:
521
+ >>> crc16_ccitt(b'123456789')
522
+ 10673
523
+ """
524
+ poly = 0x1021
525
+ crc = init
526
+
527
+ for byte in data:
528
+ crc ^= byte << 8
529
+ for _ in range(8):
530
+ if crc & 0x8000:
531
+ crc = (crc << 1) ^ poly
532
+ else:
533
+ crc = crc << 1
534
+ crc &= 0xFFFF
535
+
536
+ return crc
537
+
538
+
539
+ def crc16_ibm(data: bytes, init: int = 0x0000) -> int:
540
+ """Calculate CRC-16-IBM (also known as CRC-16-ANSI).
541
+
542
+ : Checksum and CRC Field Detection
543
+
544
+ IBM polynomial: 0x8005 (reversed: 0xA001)
545
+
546
+ Args:
547
+ data: Data to checksum
548
+ init: Initial value (default: 0x0000)
549
+
550
+ Returns:
551
+ CRC-16 value (0-65535)
552
+
553
+ Example:
554
+ >>> crc16_ibm(b'123456789')
555
+ 47933
556
+ """
557
+ poly = 0xA001 # Reversed polynomial for LSB-first
558
+ crc = init
559
+
560
+ for byte in data:
561
+ crc ^= byte
562
+ for _ in range(8):
563
+ if crc & 0x0001:
564
+ crc = (crc >> 1) ^ poly
565
+ else:
566
+ crc = crc >> 1
567
+
568
+ return crc
569
+
570
+
571
+ def crc32(data: bytes) -> int:
572
+ """Calculate CRC-32 (IEEE 802.3).
573
+
574
+ : Checksum and CRC Field Detection
575
+
576
+ Standard CRC-32 as used in Ethernet, ZIP, etc.
577
+
578
+ Args:
579
+ data: Data to checksum
580
+
581
+ Returns:
582
+ CRC-32 value (0-4294967295)
583
+
584
+ Example:
585
+ >>> crc32(b'123456789')
586
+ 3421780262
587
+ """
588
+ poly = 0xEDB88320 # Reversed polynomial
589
+ crc = 0xFFFFFFFF
590
+
591
+ for byte in data:
592
+ crc ^= byte
593
+ for _ in range(8):
594
+ if crc & 0x00000001:
595
+ crc = (crc >> 1) ^ poly
596
+ else:
597
+ crc = crc >> 1
598
+
599
+ return crc ^ 0xFFFFFFFF
600
+
601
+
602
+ def sum8(data: bytes) -> int:
603
+ """Calculate 8-bit sum checksum.
604
+
605
+ : Checksum and CRC Field Detection
606
+
607
+ Simple sum of all bytes, modulo 256.
608
+
609
+ Args:
610
+ data: Data to checksum
611
+
612
+ Returns:
613
+ Sum modulo 256 (0-255)
614
+
615
+ Example:
616
+ >>> sum8(b'ABC')
617
+ 198
618
+ """
619
+ return sum(data) & 0xFF
620
+
621
+
622
+ def sum16(data: bytes, endian: Literal["big", "little"] = "big") -> int:
623
+ """Calculate 16-bit sum checksum.
624
+
625
+ : Checksum and CRC Field Detection
626
+
627
+ Sum of 16-bit words with configurable endianness.
628
+
629
+ Args:
630
+ data: Data to checksum
631
+ endian: Byte order ('big' or 'little', default: 'big')
632
+
633
+ Returns:
634
+ Sum modulo 65536 (0-65535)
635
+
636
+ Example:
637
+ >>> sum16(b'ABCD', endian='big')
638
+ 33923
639
+ """
640
+ total = 0
641
+
642
+ # Process 16-bit words
643
+ for i in range(0, len(data) - 1, 2):
644
+ if endian == "big":
645
+ word = (data[i] << 8) | data[i + 1]
646
+ else:
647
+ word = (data[i + 1] << 8) | data[i]
648
+ total += word
649
+
650
+ # Handle odd byte
651
+ if len(data) % 2 == 1:
652
+ if endian == "big":
653
+ total += data[-1] << 8
654
+ else:
655
+ total += data[-1]
656
+
657
+ return total & 0xFFFF
658
+
659
+
660
+ def xor_checksum(data: bytes) -> int:
661
+ """Calculate XOR checksum.
662
+
663
+ : Checksum and CRC Field Detection
664
+
665
+ XOR of all bytes.
666
+
667
+ Args:
668
+ data: Data to checksum
669
+
670
+ Returns:
671
+ XOR result (0-255)
672
+
673
+ Example:
674
+ >>> xor_checksum(b'ABC')
675
+ 2
676
+ """
677
+ result = 0
678
+ for byte in data:
679
+ result ^= byte
680
+ return result
681
+
682
+
683
+ @dataclass
684
+ class ChecksumDetectionResult:
685
+ """Result of checksum detection.
686
+
687
+ Attributes:
688
+ has_checksum: Whether a checksum was detected.
689
+ offset: Byte offset of the checksum field (None if not detected).
690
+ size: Size of the checksum field in bytes (None if not detected).
691
+ algorithm: Identified algorithm name (None if not identified).
692
+ confidence: Detection confidence (0-1).
693
+ candidates: All candidate positions found.
694
+ scope_start: Start of checksummed region (None if not identified).
695
+ scope_end: End of checksummed region (None if not identified).
696
+ init_value: Initial value for CRC algorithms (None if not applicable).
697
+ """
698
+
699
+ has_checksum: bool
700
+ offset: int | None = None
701
+ size: int | None = None
702
+ algorithm: str | None = None
703
+ confidence: float = 0.0
704
+ candidates: list[ChecksumCandidate] = field(default_factory=list)
705
+ scope_start: int | None = None
706
+ scope_end: int | None = None
707
+ init_value: int | None = None
708
+
709
+
710
+ class ChecksumDetector:
711
+ """Object-oriented wrapper for checksum detection functionality.
712
+
713
+ Provides a class-based interface for checksum detection operations,
714
+ wrapping the functional API for consistency with test expectations.
715
+
716
+
717
+
718
+ Example:
719
+ >>> detector = ChecksumDetector()
720
+ >>> result = detector.detect_checksum_field(messages)
721
+ >>> if result.has_checksum:
722
+ ... print(f"Checksum at offset {result.offset}")
723
+ """
724
+
725
+ def __init__(self, correlation_threshold: float = 0.5):
726
+ """Initialize checksum detector.
727
+
728
+ Args:
729
+ correlation_threshold: Minimum correlation for detection.
730
+ """
731
+ self.correlation_threshold = correlation_threshold
732
+ self._detection_result: ChecksumDetectionResult | None = None
733
+ self._messages: list[DataType] = []
734
+
735
+ def detect_checksum_field(self, messages: list[DataType]) -> ChecksumDetectionResult:
736
+ """Detect checksum field in messages.
737
+
738
+ Args:
739
+ messages: List of messages to analyze.
740
+
741
+ Returns:
742
+ ChecksumDetectionResult with detection results.
743
+
744
+ Example:
745
+ >>> detector = ChecksumDetector()
746
+ >>> result = detector.detect_checksum_field(messages)
747
+ """
748
+ self._messages = messages
749
+ candidates = detect_checksum_fields(messages)
750
+
751
+ if not candidates:
752
+ self._detection_result = ChecksumDetectionResult(has_checksum=False, confidence=0.0)
753
+ return self._detection_result
754
+
755
+ # Filter by correlation threshold
756
+ good_candidates = [c for c in candidates if c.correlation >= self.correlation_threshold]
757
+
758
+ if not good_candidates:
759
+ # Report no checksum with low confidence if candidates exist but none pass threshold
760
+ max_correlation = max(c.correlation for c in candidates) if candidates else 0.0
761
+ self._detection_result = ChecksumDetectionResult(
762
+ has_checksum=False, candidates=candidates, confidence=max_correlation
763
+ )
764
+ return self._detection_result
765
+
766
+ # Use best candidate, preferring trailer checksums when correlation is similar
767
+ best = good_candidates[0]
768
+
769
+ # Check if there's a trailer checksum with similar correlation
770
+ for candidate in good_candidates[1:]:
771
+ if candidate.position == "trailer" and best.position == "header":
772
+ # Prefer trailer if correlation is within 5% of header
773
+ if candidate.correlation >= best.correlation * 0.95:
774
+ best = candidate
775
+ break
776
+
777
+ # Try to identify algorithm for best candidate
778
+ algorithm_match = identify_checksum_algorithm(messages, best.offset, best.size)
779
+
780
+ # If algorithm identification fails, try other high-correlation candidates
781
+ if algorithm_match is None and len(good_candidates) > 1:
782
+ for candidate in good_candidates[1:]:
783
+ # Skip if correlation is too much lower
784
+ if candidate.correlation < best.correlation * 0.9:
785
+ break
786
+
787
+ alt_match = identify_checksum_algorithm(messages, candidate.offset, candidate.size)
788
+ if alt_match is not None:
789
+ # Found a candidate with identifiable algorithm
790
+ best = candidate
791
+ algorithm_match = alt_match
792
+ break
793
+
794
+ # Reduce confidence if algorithm couldn't be identified
795
+ # High correlation but no identifiable algorithm suggests false positive
796
+ final_confidence = best.correlation
797
+ if algorithm_match is None:
798
+ final_confidence = best.correlation * 0.3 # Penalize unidentified algorithms
799
+
800
+ self._detection_result = ChecksumDetectionResult(
801
+ has_checksum=True,
802
+ offset=best.offset,
803
+ size=best.size,
804
+ algorithm=algorithm_match.algorithm if algorithm_match else None,
805
+ confidence=final_confidence,
806
+ candidates=good_candidates,
807
+ scope_start=algorithm_match.scope_start if algorithm_match else None,
808
+ scope_end=algorithm_match.scope_end if algorithm_match else None,
809
+ init_value=algorithm_match.init_value if algorithm_match else None,
810
+ )
811
+ return self._detection_result
812
+
813
+ def identify_algorithm(
814
+ self, messages: list[DataType], offset: int, size: int | None = None
815
+ ) -> ChecksumMatch | None:
816
+ """Identify checksum algorithm at given offset.
817
+
818
+ Args:
819
+ messages: List of messages.
820
+ offset: Checksum field offset.
821
+ size: Field size (auto-detect if None).
822
+
823
+ Returns:
824
+ ChecksumMatch or None if no match found.
825
+ """
826
+ return identify_checksum_algorithm(messages, offset, size)
827
+
828
+ def verify(
829
+ self, messages: list[DataType], algorithm: str, offset: int, **kwargs: Any
830
+ ) -> tuple[int, int]:
831
+ """Verify checksums in messages.
832
+
833
+ Args:
834
+ messages: List of messages.
835
+ algorithm: Checksum algorithm name.
836
+ offset: Checksum field offset.
837
+ **kwargs: Algorithm-specific parameters.
838
+
839
+ Returns:
840
+ Tuple of (passed, failed) counts.
841
+ """
842
+ return verify_checksums(messages, algorithm, offset, **kwargs)
843
+
844
+ def verify_checksum(self, message: DataType) -> bool:
845
+ """Verify checksum for a single message.
846
+
847
+ Uses previously detected checksum parameters if available.
848
+
849
+ Args:
850
+ message: Single message to verify.
851
+
852
+ Returns:
853
+ True if checksum is valid, False otherwise.
854
+
855
+ Example:
856
+ >>> detector = ChecksumDetector()
857
+ >>> detector.detect_checksum_field(messages)
858
+ >>> is_valid = detector.verify_checksum(messages[0])
859
+ """
860
+ if self._detection_result is None or not self._detection_result.has_checksum:
861
+ # Try to detect checksum from the single message
862
+ return False
863
+
864
+ offset = self._detection_result.offset
865
+ size = self._detection_result.size
866
+
867
+ if offset is None or size is None:
868
+ return False
869
+
870
+ # Convert message to bytes
871
+ if isinstance(message, np.ndarray):
872
+ msg = message.tobytes() if message.dtype == np.uint8 else bytes(message.flatten())
873
+ else:
874
+ msg = bytes(message)
875
+
876
+ if self._detection_result.algorithm is None:
877
+ # No algorithm identified - try common ones
878
+ if size == 1:
879
+ algorithms = ["xor", "sum8"]
880
+ elif size == 2:
881
+ algorithms = ["crc16_ccitt", "crc16_ibm", "sum16_big", "sum16_little"]
882
+ elif size == 4:
883
+ algorithms = ["crc32"]
884
+ else:
885
+ algorithms = ["xor", "sum8", "crc16_ccitt", "crc16_ibm", "sum16_big", "crc32"]
886
+
887
+ # Try each algorithm
888
+ for algo in algorithms:
889
+ passed, _failed = verify_checksums([msg], algo, offset)
890
+ if passed == 1:
891
+ return True
892
+
893
+ return False
894
+
895
+ # Use identified algorithm
896
+ passed, _failed = verify_checksums(
897
+ [msg],
898
+ self._detection_result.algorithm,
899
+ self._detection_result.offset or 0,
900
+ scope_start=self._detection_result.scope_start or 0,
901
+ scope_end=self._detection_result.scope_end,
902
+ init_value=self._detection_result.init_value,
903
+ )
904
+ return passed == 1
905
+
906
+
907
+ __all__ = [
908
+ "ChecksumCandidate",
909
+ "ChecksumDetectionResult",
910
+ "ChecksumDetector",
911
+ "ChecksumMatch",
912
+ "compute_checksum",
913
+ "crc8",
914
+ "crc16_ccitt",
915
+ "crc16_ibm",
916
+ "crc32",
917
+ "detect_checksum_fields",
918
+ "identify_checksum_algorithm",
919
+ "sum8",
920
+ "sum16",
921
+ "verify_checksums",
922
+ "xor_checksum",
923
+ ]