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,634 @@
1
+ """State machine inference using RPNI algorithm.
2
+
3
+ Requirements addressed: PSI-002
4
+
5
+ This module infers protocol state machines from observed message sequences using
6
+ passive learning algorithms (no system interaction required).
7
+
8
+ Key capabilities:
9
+ - RPNI algorithm for passive DFA learning
10
+ - State merging to minimize automaton
11
+ - Export to DOT format for visualization
12
+ - Export to NetworkX graph for analysis
13
+ """
14
+
15
+ from copy import deepcopy
16
+ from dataclasses import dataclass
17
+ from typing import Any
18
+
19
+
20
+ @dataclass
21
+ class State:
22
+ """A state in the inferred automaton.
23
+
24
+ : State representation.
25
+
26
+ Attributes:
27
+ id: Unique state identifier
28
+ name: Human-readable state name
29
+ is_initial: Whether this is the initial state
30
+ is_accepting: Whether this is an accepting state
31
+ """
32
+
33
+ id: int
34
+ name: str
35
+ is_initial: bool = False
36
+ is_accepting: bool = False
37
+
38
+
39
+ @dataclass
40
+ class Transition:
41
+ """A transition in the automaton.
42
+
43
+ : Transition representation.
44
+
45
+ Attributes:
46
+ source: Source state ID
47
+ target: Target state ID
48
+ symbol: Transition label/symbol
49
+ count: Number of times observed
50
+ """
51
+
52
+ source: int # State ID
53
+ target: int # State ID
54
+ symbol: str # Transition label
55
+ count: int = 1 # Number of observations
56
+
57
+
58
+ @dataclass
59
+ class FiniteAutomaton:
60
+ """An inferred finite automaton.
61
+
62
+ : Complete automaton representation with export capabilities.
63
+
64
+ Attributes:
65
+ states: List of all states
66
+ transitions: List of all transitions
67
+ alphabet: Set of all symbols
68
+ initial_state: Initial state ID
69
+ accepting_states: Set of accepting state IDs
70
+ """
71
+
72
+ states: list[State]
73
+ transitions: list[Transition]
74
+ alphabet: set[str]
75
+ initial_state: int
76
+ accepting_states: set[int]
77
+
78
+ def to_dot(self) -> str:
79
+ """Export to DOT format for Graphviz.
80
+
81
+ : DOT format export for visualization.
82
+
83
+ Returns:
84
+ DOT format string
85
+ """
86
+ lines = ["digraph finite_automaton {", " rankdir=LR;", " node [shape=circle];"]
87
+
88
+ # Mark accepting states
89
+ if self.accepting_states:
90
+ accepting_names = [s.name for s in self.states if s.id in self.accepting_states]
91
+ lines.append(f" node [shape=doublecircle]; {' '.join(accepting_names)};")
92
+ lines.append(" node [shape=circle];")
93
+
94
+ # Add invisible start node for initial state
95
+ initial_state = next(s for s in self.states if s.id == self.initial_state)
96
+ lines.append(' __start__ [shape=none, label=""];')
97
+ lines.append(f" __start__ -> {initial_state.name};")
98
+
99
+ # Add transitions
100
+ for trans in self.transitions:
101
+ src_state = next(s for s in self.states if s.id == trans.source)
102
+ tgt_state = next(s for s in self.states if s.id == trans.target)
103
+ label = trans.symbol
104
+ if trans.count > 1:
105
+ label = f"{trans.symbol} ({trans.count})"
106
+ lines.append(f' {src_state.name} -> {tgt_state.name} [label="{label}"];')
107
+
108
+ lines.append("}")
109
+ return "\n".join(lines)
110
+
111
+ def to_networkx(self) -> Any:
112
+ """Export to NetworkX graph.
113
+
114
+ : NetworkX export for programmatic analysis.
115
+
116
+ Returns:
117
+ NetworkX MultiDiGraph (supports multiple edges between same nodes)
118
+
119
+ Raises:
120
+ ImportError: If NetworkX is not installed.
121
+ """
122
+ try:
123
+ import networkx as nx # type: ignore[import-untyped]
124
+ except ImportError as err:
125
+ raise ImportError("NetworkX is required for graph export") from err
126
+
127
+ # Use MultiDiGraph to support multiple transitions between same states
128
+ G = nx.MultiDiGraph()
129
+
130
+ # Add nodes
131
+ for state in self.states:
132
+ G.add_node(
133
+ state.id,
134
+ name=state.name,
135
+ is_initial=state.is_initial,
136
+ is_accepting=state.is_accepting,
137
+ )
138
+
139
+ # Add edges
140
+ for trans in self.transitions:
141
+ G.add_edge(trans.source, trans.target, symbol=trans.symbol, count=trans.count)
142
+
143
+ return G
144
+
145
+ def accepts(self, sequence: list[str]) -> bool:
146
+ """Check if automaton accepts sequence.
147
+
148
+ : Sequence acceptance checking.
149
+
150
+ Args:
151
+ sequence: List of symbols
152
+
153
+ Returns:
154
+ True if sequence is accepted
155
+ """
156
+ current_state = self.initial_state
157
+
158
+ for symbol in sequence:
159
+ # Find transition with this symbol
160
+ trans = None
161
+ for t in self.transitions:
162
+ if t.source == current_state and t.symbol == symbol:
163
+ trans = t
164
+ break
165
+
166
+ if trans is None:
167
+ return False # No valid transition
168
+
169
+ current_state = trans.target
170
+
171
+ # Check if we ended in accepting state
172
+ return current_state in self.accepting_states
173
+
174
+ def get_successors(self, state_id: int) -> dict[str, int]:
175
+ """Get successor states from given state.
176
+
177
+ : State successor lookup.
178
+
179
+ Args:
180
+ state_id: State ID to query
181
+
182
+ Returns:
183
+ Dictionary mapping symbols to target state IDs
184
+ """
185
+ successors = {}
186
+ for trans in self.transitions:
187
+ if trans.source == state_id:
188
+ successors[trans.symbol] = trans.target
189
+ return successors
190
+
191
+
192
+ class StateMachineInferrer:
193
+ """Infer state machines using passive learning.
194
+
195
+ : RPNI algorithm for DFA inference.
196
+
197
+ The RPNI (Regular Positive and Negative Inference) algorithm:
198
+ 1. Build Prefix Tree Acceptor from positive samples
199
+ 2. Iteratively merge compatible state pairs
200
+ 3. Validate against negative samples
201
+ 4. Converge to minimal consistent DFA
202
+ """
203
+
204
+ def __init__(self) -> None:
205
+ """Initialize inferrer."""
206
+ self._next_state_id = 0
207
+
208
+ def infer(
209
+ self,
210
+ positive_traces: list[list[str]] | None = None,
211
+ negative_traces: list[list[str]] | None = None,
212
+ positive_samples: list[list[str]] | None = None,
213
+ negative_samples: list[list[str]] | None = None,
214
+ ) -> FiniteAutomaton:
215
+ """Infer DFA from traces (alias for infer_rpni).
216
+
217
+ Args:
218
+ positive_traces: List of accepted sequences.
219
+ negative_traces: List of rejected sequences (optional).
220
+ positive_samples: Alias for positive_traces (deprecated).
221
+ negative_samples: Alias for negative_traces (deprecated).
222
+
223
+ Returns:
224
+ Inferred FiniteAutomaton.
225
+
226
+ Raises:
227
+ ValueError: If no positive traces provided.
228
+ """
229
+ # Handle parameter aliases
230
+ pos = positive_traces if positive_traces is not None else positive_samples
231
+ neg = negative_traces if negative_traces is not None else negative_samples
232
+
233
+ if pos is None:
234
+ raise ValueError("Must provide either positive_traces or positive_samples")
235
+
236
+ return self.infer_rpni(pos, neg)
237
+
238
+ def infer_rpni(
239
+ self, positive_traces: list[list[str]], negative_traces: list[list[str]] | None = None
240
+ ) -> FiniteAutomaton:
241
+ """Infer DFA using RPNI (Regular Positive and Negative Inference).
242
+
243
+ : Complete RPNI algorithm.
244
+
245
+ Args:
246
+ positive_traces: List of accepted sequences (list of symbols)
247
+ negative_traces: List of rejected sequences (optional)
248
+
249
+ Returns:
250
+ Inferred FiniteAutomaton
251
+
252
+ Raises:
253
+ ValueError: If no positive traces provided.
254
+ """
255
+ if not positive_traces:
256
+ raise ValueError("Need at least one positive trace")
257
+
258
+ # Build alphabet from all traces
259
+ alphabet: set[str] = set()
260
+ neg_traces = negative_traces if negative_traces is not None else []
261
+ for trace in positive_traces + neg_traces:
262
+ alphabet.update(trace)
263
+
264
+ # Build Prefix Tree Acceptor from positive traces
265
+ pta = self._build_pta(positive_traces)
266
+
267
+ # RPNI merging process
268
+ automaton = pta
269
+ states = sorted([s.id for s in automaton.states])
270
+
271
+ # Try to merge states in order
272
+ i = 1 # Start from second state (never merge initial state)
273
+ while i < len(states):
274
+ merged = False
275
+
276
+ # Try to merge states[i] with any earlier state
277
+ for j in range(i):
278
+ if self._is_compatible(automaton, states[j], states[i], neg_traces):
279
+ # Merge states[i] into states[j]
280
+ automaton = self._merge_states(automaton, states[j], states[i])
281
+ # Update state list
282
+ states = sorted([s.id for s in automaton.states])
283
+ merged = True
284
+ break
285
+
286
+ if not merged:
287
+ i += 1
288
+
289
+ return automaton
290
+
291
+ def _build_pta(self, traces: list[list[str]]) -> FiniteAutomaton:
292
+ """Build Prefix Tree Acceptor from traces.
293
+
294
+ : PTA construction.
295
+
296
+ Args:
297
+ traces: List of sequences
298
+
299
+ Returns:
300
+ Prefix Tree Acceptor as FiniteAutomaton
301
+ """
302
+ # Reset state counter
303
+ self._next_state_id = 0
304
+
305
+ # Create initial state
306
+ initial_state = State(
307
+ id=self._get_next_state_id(), name="q0", is_initial=True, is_accepting=False
308
+ )
309
+
310
+ states: list[State] = [initial_state]
311
+ transitions: list[Transition] = []
312
+ alphabet: set[str] = set()
313
+
314
+ # Build tree from traces
315
+ for trace in traces:
316
+ current_state_id = initial_state.id
317
+
318
+ # Walk/build tree for this trace
319
+ for symbol in trace:
320
+ alphabet.add(symbol)
321
+
322
+ # Check if transition exists
323
+ next_state_id = None
324
+ for trans in transitions:
325
+ if trans.source == current_state_id and trans.symbol == symbol:
326
+ next_state_id = trans.target
327
+ break
328
+
329
+ if next_state_id is None:
330
+ # Create new state and transition
331
+ new_state_id = self._get_next_state_id()
332
+ new_state = State(
333
+ id=new_state_id,
334
+ name=f"q{new_state_id}",
335
+ is_initial=False,
336
+ is_accepting=False,
337
+ )
338
+ states.append(new_state)
339
+
340
+ new_trans = Transition(
341
+ source=current_state_id, target=new_state_id, symbol=symbol
342
+ )
343
+ transitions.append(new_trans)
344
+
345
+ next_state_id = new_state_id
346
+
347
+ current_state_id = next_state_id
348
+
349
+ # Mark final state as accepting
350
+ for state in states:
351
+ if state.id == current_state_id:
352
+ state.is_accepting = True
353
+
354
+ accepting_states = {s.id for s in states if s.is_accepting}
355
+
356
+ return FiniteAutomaton(
357
+ states=states,
358
+ transitions=transitions,
359
+ alphabet=alphabet,
360
+ initial_state=initial_state.id,
361
+ accepting_states=accepting_states,
362
+ )
363
+
364
+ def _merge_states(
365
+ self, automaton: FiniteAutomaton, state_a: int, state_b: int
366
+ ) -> FiniteAutomaton:
367
+ """Merge two states in automaton.
368
+
369
+ : State merging operation.
370
+
371
+ Merges state_b into state_a.
372
+
373
+ Args:
374
+ automaton: Current automaton
375
+ state_a: Target state ID (survives)
376
+ state_b: Source state ID (removed)
377
+
378
+ Returns:
379
+ New automaton with merged states
380
+ """
381
+ # Deep copy to avoid modifying original
382
+ new_automaton = deepcopy(automaton)
383
+
384
+ # Remove state_b
385
+ new_automaton.states = [s for s in new_automaton.states if s.id != state_b]
386
+
387
+ # Update transitions: redirect all transitions to/from state_b to state_a
388
+ for trans in new_automaton.transitions:
389
+ if trans.source == state_b:
390
+ trans.source = state_a
391
+ if trans.target == state_b:
392
+ trans.target = state_a
393
+
394
+ # Merge accepting status
395
+ if state_b in new_automaton.accepting_states:
396
+ new_automaton.accepting_states.add(state_a)
397
+ new_automaton.accepting_states.discard(state_b)
398
+
399
+ # Merge duplicate transitions (same source, target, symbol)
400
+ unique_transitions = []
401
+ seen = set()
402
+
403
+ for trans in new_automaton.transitions:
404
+ key = (trans.source, trans.target, trans.symbol)
405
+ if key not in seen:
406
+ seen.add(key)
407
+ unique_transitions.append(trans)
408
+ else:
409
+ # Increment count on existing transition
410
+ for ut in unique_transitions:
411
+ if (ut.source, ut.target, ut.symbol) == key:
412
+ ut.count += trans.count
413
+ break
414
+
415
+ new_automaton.transitions = unique_transitions
416
+
417
+ return new_automaton
418
+
419
+ def _is_compatible(
420
+ self,
421
+ automaton: FiniteAutomaton,
422
+ state_a: int,
423
+ state_b: int,
424
+ negative_traces: list[list[str]],
425
+ ) -> bool:
426
+ """Check if two states can be merged without accepting negatives.
427
+
428
+ : Compatibility checking for state merging.
429
+
430
+ Args:
431
+ automaton: Current automaton
432
+ state_a: First state ID
433
+ state_b: Second state ID
434
+ negative_traces: Negative example traces
435
+
436
+ Returns:
437
+ True if states are compatible
438
+ """
439
+ # Get accepting status
440
+ _a_accepting = state_a in automaton.accepting_states
441
+ _b_accepting = state_b in automaton.accepting_states
442
+
443
+ # If one is accepting and other is not, they might still be compatible
444
+ # (we'll merge accepting status), but check negative traces
445
+
446
+ # Try merging and test
447
+ test_automaton = self._merge_states(automaton, state_a, state_b)
448
+
449
+ # Check that no negative traces are accepted
450
+ for neg_trace in negative_traces:
451
+ if test_automaton.accepts(neg_trace):
452
+ return False
453
+
454
+ # Recursively check successor compatibility
455
+ _succ_a = test_automaton.get_successors(state_a)
456
+ # state_b has been merged, so its successors are now in state_a
457
+
458
+ return True
459
+
460
+ def _get_next_state_id(self) -> int:
461
+ """Get next available state ID.
462
+
463
+ Returns:
464
+ Next state ID
465
+ """
466
+ state_id = self._next_state_id
467
+ self._next_state_id += 1
468
+ return state_id
469
+
470
+
471
+ def minimize_dfa(automaton: FiniteAutomaton) -> FiniteAutomaton:
472
+ """Minimize DFA using partition refinement.
473
+
474
+ : DFA minimization using Hopcroft's algorithm.
475
+
476
+ Args:
477
+ automaton: DFA to minimize
478
+
479
+ Returns:
480
+ Minimized FiniteAutomaton
481
+ """
482
+ # Use partition refinement (simplified version)
483
+ # Start with two partitions: accepting and non-accepting
484
+ accepting = automaton.accepting_states
485
+ non_accepting = {s.id for s in automaton.states if s.id not in accepting}
486
+
487
+ partitions = []
488
+ if accepting:
489
+ partitions.append(accepting)
490
+ if non_accepting:
491
+ partitions.append(non_accepting)
492
+
493
+ # Refine partitions
494
+ changed = True
495
+ while changed:
496
+ changed = False
497
+ new_partitions = []
498
+
499
+ for partition in partitions:
500
+ # Try to split this partition
501
+ if len(partition) <= 1:
502
+ new_partitions.append(partition)
503
+ continue
504
+
505
+ # Group states by transition signatures
506
+ groups: dict[tuple[tuple[str, int | None], ...], set[int]] = {}
507
+ for state_id in partition:
508
+ successors = automaton.get_successors(state_id)
509
+
510
+ # Create signature based on which partition each successor is in
511
+ signature_list: list[tuple[str, int | None]] = []
512
+ for symbol in sorted(automaton.alphabet):
513
+ if symbol in successors:
514
+ target = successors[symbol]
515
+ # Find which partition target is in
516
+ target_partition: int | None = None
517
+ for i, p in enumerate(partitions):
518
+ if target in p:
519
+ target_partition = i
520
+ break
521
+ signature_list.append((symbol, target_partition))
522
+ else:
523
+ signature_list.append((symbol, None))
524
+
525
+ signature = tuple(signature_list)
526
+ if signature not in groups:
527
+ groups[signature] = set()
528
+ groups[signature].add(state_id)
529
+
530
+ # If we split, mark as changed
531
+ if len(groups) > 1:
532
+ changed = True
533
+
534
+ new_partitions.extend(groups.values())
535
+
536
+ partitions = new_partitions
537
+
538
+ # Build minimized automaton
539
+ # Map old state IDs to partition IDs
540
+ state_to_partition = {}
541
+ for i, partition in enumerate(partitions):
542
+ for state_id in partition:
543
+ state_to_partition[state_id] = i
544
+
545
+ # Create new states
546
+ new_states = []
547
+ for i, partition in enumerate(partitions):
548
+ # Pick representative state
549
+ rep_id = min(partition)
550
+ _rep_state = next(s for s in automaton.states if s.id == rep_id)
551
+
552
+ is_accepting = any(sid in automaton.accepting_states for sid in partition)
553
+ is_initial = automaton.initial_state in partition
554
+
555
+ new_state = State(id=i, name=f"q{i}", is_initial=is_initial, is_accepting=is_accepting)
556
+ new_states.append(new_state)
557
+
558
+ # Create new transitions
559
+ new_transitions = []
560
+ seen_transitions = set()
561
+
562
+ for trans in automaton.transitions:
563
+ src_partition = state_to_partition[trans.source]
564
+ tgt_partition = state_to_partition[trans.target]
565
+
566
+ key = (src_partition, tgt_partition, trans.symbol)
567
+ if key not in seen_transitions:
568
+ seen_transitions.add(key)
569
+ new_transitions.append(
570
+ Transition(
571
+ source=src_partition,
572
+ target=tgt_partition,
573
+ symbol=trans.symbol,
574
+ count=trans.count,
575
+ )
576
+ )
577
+
578
+ # Find new initial state
579
+ new_initial = state_to_partition[automaton.initial_state]
580
+ new_accepting = {s.id for s in new_states if s.is_accepting}
581
+
582
+ return FiniteAutomaton(
583
+ states=new_states,
584
+ transitions=new_transitions,
585
+ alphabet=automaton.alphabet,
586
+ initial_state=new_initial,
587
+ accepting_states=new_accepting,
588
+ )
589
+
590
+
591
+ def to_dot(automaton: FiniteAutomaton) -> str:
592
+ """Export automaton to DOT format.
593
+
594
+ : Convenience function for DOT export.
595
+
596
+ Args:
597
+ automaton: Automaton to export
598
+
599
+ Returns:
600
+ DOT format string
601
+ """
602
+ return automaton.to_dot()
603
+
604
+
605
+ def to_networkx(automaton: FiniteAutomaton) -> Any:
606
+ """Export automaton to NetworkX graph.
607
+
608
+ : Convenience function for NetworkX export.
609
+
610
+ Args:
611
+ automaton: Automaton to export
612
+
613
+ Returns:
614
+ NetworkX DiGraph
615
+ """
616
+ return automaton.to_networkx()
617
+
618
+
619
+ def infer_rpni(
620
+ positive_traces: list[list[str]], negative_traces: list[list[str]] | None = None
621
+ ) -> FiniteAutomaton:
622
+ """Convenience function for RPNI inference.
623
+
624
+ : Top-level API for state machine inference.
625
+
626
+ Args:
627
+ positive_traces: List of accepted sequences
628
+ negative_traces: List of rejected sequences (optional)
629
+
630
+ Returns:
631
+ Inferred FiniteAutomaton
632
+ """
633
+ inferrer = StateMachineInferrer()
634
+ return inferrer.infer_rpni(positive_traces, negative_traces)