oscura 0.0.1__py3-none-any.whl → 0.1.1__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.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,620 @@
1
+ """CAN 2.0A/B protocol decoder.
2
+
3
+ This module implements a CAN (Controller Area Network) protocol decoder
4
+ supporting both standard (11-bit ID) and extended (29-bit ID) frames.
5
+
6
+
7
+ Example:
8
+ >>> from oscura.analyzers.protocols.can import CANDecoder
9
+ >>> decoder = CANDecoder(bitrate=500000)
10
+ >>> for packet in decoder.decode(trace):
11
+ ... print(f"ID: {packet.annotations['arbitration_id']:03X}")
12
+ ... print(f"Data: {packet.data.hex()}")
13
+
14
+ References:
15
+ ISO 11898-1:2015 Road vehicles - CAN - Part 1: Data link layer
16
+ CAN Specification Version 2.0 (Bosch, 1991)
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from dataclasses import dataclass
22
+ from enum import IntEnum
23
+ from typing import TYPE_CHECKING
24
+
25
+ import numpy as np
26
+
27
+ from oscura.analyzers.protocols.base import (
28
+ AnnotationLevel,
29
+ AsyncDecoder,
30
+ ChannelDef,
31
+ DecoderState,
32
+ OptionDef,
33
+ )
34
+ from oscura.core.types import DigitalTrace, ProtocolPacket
35
+
36
+ if TYPE_CHECKING:
37
+ from collections.abc import Iterator
38
+
39
+ from numpy.typing import NDArray
40
+
41
+
42
+ class CANFrameType(IntEnum):
43
+ """CAN frame types."""
44
+
45
+ DATA = 0
46
+ REMOTE = 1
47
+ ERROR = 2
48
+ OVERLOAD = 3
49
+
50
+
51
+ @dataclass
52
+ class CANFrame:
53
+ """Decoded CAN frame.
54
+
55
+ Attributes:
56
+ arbitration_id: CAN ID (11-bit or 29-bit).
57
+ is_extended: True for 29-bit extended ID.
58
+ is_remote: True for remote transmission request.
59
+ dlc: Data length code (0-8).
60
+ data: Data bytes.
61
+ crc: Received CRC value.
62
+ crc_computed: Computed CRC value.
63
+ timestamp: Frame start time in seconds.
64
+ end_timestamp: Frame end time in seconds.
65
+ errors: List of detected errors.
66
+ """
67
+
68
+ arbitration_id: int
69
+ is_extended: bool
70
+ is_remote: bool
71
+ dlc: int
72
+ data: bytes
73
+ crc: int
74
+ crc_computed: int
75
+ timestamp: float
76
+ end_timestamp: float
77
+ errors: list[str]
78
+
79
+ @property
80
+ def crc_valid(self) -> bool:
81
+ """Check if CRC matches."""
82
+ return self.crc == self.crc_computed
83
+
84
+
85
+ class CANDecoderState(DecoderState):
86
+ """State machine for CAN decoder."""
87
+
88
+ def reset(self) -> None:
89
+ """Reset state."""
90
+ self.bit_position = 0
91
+ self.stuff_count = 0
92
+ self.last_five_bits = 0
93
+ self.frame_bits: list[int] = []
94
+ self.in_frame = False
95
+ self.frame_start_time = 0.0
96
+
97
+
98
+ # CAN bit timing constants
99
+ CAN_BITRATES = {
100
+ 10000: "10 kbps",
101
+ 20000: "20 kbps",
102
+ 50000: "50 kbps",
103
+ 100000: "100 kbps",
104
+ 125000: "125 kbps",
105
+ 250000: "250 kbps",
106
+ 500000: "500 kbps",
107
+ 800000: "800 kbps",
108
+ 1000000: "1 Mbps",
109
+ }
110
+
111
+ # CRC polynomial for CAN: x^15 + x^14 + x^10 + x^8 + x^7 + x^4 + x^3 + 1
112
+ CAN_CRC_POLY = 0x4599
113
+ CAN_CRC_INIT = 0x0000
114
+
115
+
116
+ class CANDecoder(AsyncDecoder):
117
+ """CAN 2.0A/B protocol decoder.
118
+
119
+ Decodes CAN frames from digital signal captures, supporting:
120
+ - CAN 2.0A: Standard 11-bit identifiers
121
+ - CAN 2.0B: Extended 29-bit identifiers
122
+ - Bit stuffing detection and removal
123
+ - CRC checking
124
+ - Error detection
125
+
126
+ Attributes:
127
+ id: Decoder identifier.
128
+ name: Human-readable name.
129
+ channels: Required input channels.
130
+ options: Configurable decoder options.
131
+
132
+ Example:
133
+ >>> decoder = CANDecoder(bitrate=500000)
134
+ >>> frames = list(decoder.decode(trace))
135
+ >>> for frame in frames:
136
+ ... print(f"CAN ID: 0x{frame.annotations['arbitration_id']:03X}")
137
+ """
138
+
139
+ id = "can"
140
+ name = "CAN"
141
+ longname = "Controller Area Network"
142
+ desc = "CAN 2.0A/B bus decoder"
143
+ license = "MIT"
144
+
145
+ channels = [ # noqa: RUF012
146
+ ChannelDef("can", "CAN", "CAN bus signal (CAN_H - CAN_L or single-ended)"),
147
+ ]
148
+
149
+ options = [ # noqa: RUF012
150
+ OptionDef(
151
+ "bitrate",
152
+ "Bit Rate",
153
+ "CAN bit rate in bps",
154
+ default=500000,
155
+ values=list(CAN_BITRATES.keys()),
156
+ ),
157
+ OptionDef(
158
+ "sample_point",
159
+ "Sample Point",
160
+ "Sample point as fraction of bit time",
161
+ default=0.75,
162
+ ),
163
+ ]
164
+
165
+ def __init__( # type: ignore[no-untyped-def]
166
+ self,
167
+ bitrate: int = 500000,
168
+ sample_point: float = 0.75,
169
+ **options,
170
+ ) -> None:
171
+ """Initialize CAN decoder.
172
+
173
+ Args:
174
+ bitrate: CAN bus bit rate in bps.
175
+ sample_point: Sample point as fraction of bit time (0.5-0.9).
176
+ **options: Additional decoder options.
177
+ """
178
+ super().__init__(baudrate=bitrate, **options)
179
+ self._bitrate = bitrate
180
+ self._sample_point = sample_point
181
+ self._state = CANDecoderState()
182
+
183
+ @property
184
+ def bitrate(self) -> int:
185
+ """Get CAN bit rate."""
186
+ return self._bitrate
187
+
188
+ @bitrate.setter
189
+ def bitrate(self, value: int) -> None:
190
+ """Set CAN bit rate."""
191
+ self._bitrate = value
192
+ self._baudrate = value
193
+
194
+ def decode(
195
+ self,
196
+ trace: DigitalTrace,
197
+ **channels: NDArray[np.bool_],
198
+ ) -> Iterator[ProtocolPacket]:
199
+ """Decode CAN frames from digital trace.
200
+
201
+ Args:
202
+ trace: Digital trace containing CAN signal.
203
+ **channels: Additional channel data (not used for single-wire CAN).
204
+
205
+ Yields:
206
+ ProtocolPacket for each decoded CAN frame.
207
+
208
+ Example:
209
+ >>> decoder = CANDecoder(bitrate=500000)
210
+ >>> for packet in decoder.decode(trace):
211
+ ... can_id = packet.annotations['arbitration_id']
212
+ ... print(f"ID: 0x{can_id:03X}, Data: {packet.data.hex()}")
213
+ """
214
+ self.reset()
215
+
216
+ data = trace.data
217
+ sample_rate = trace.metadata.sample_rate
218
+ 1.0 / sample_rate
219
+
220
+ # Calculate samples per bit
221
+ 1.0 / self._bitrate
222
+ samples_per_bit = round(sample_rate / self._bitrate)
223
+
224
+ if samples_per_bit < 2:
225
+ self.put_annotation(
226
+ 0,
227
+ trace.duration,
228
+ AnnotationLevel.MESSAGES,
229
+ "Error: Sample rate too low for CAN decoding",
230
+ )
231
+ return
232
+
233
+ # Sample offset within bit (where to sample)
234
+ sample_offset = int(samples_per_bit * self._sample_point)
235
+
236
+ # Find start of frames (falling edge from recessive to dominant)
237
+ # In CAN, recessive = 1, dominant = 0
238
+ frame_starts = self._find_frame_starts(data, samples_per_bit)
239
+
240
+ for frame_start_idx in frame_starts:
241
+ # Try to decode frame starting at this position
242
+ frame = self._decode_frame(
243
+ data,
244
+ frame_start_idx,
245
+ sample_rate,
246
+ samples_per_bit,
247
+ sample_offset,
248
+ )
249
+
250
+ if frame is not None:
251
+ # Create packet
252
+ packet = ProtocolPacket(
253
+ timestamp=frame.timestamp,
254
+ protocol="can",
255
+ data=frame.data,
256
+ annotations={
257
+ "arbitration_id": frame.arbitration_id,
258
+ "is_extended": frame.is_extended,
259
+ "is_remote": frame.is_remote,
260
+ "dlc": frame.dlc,
261
+ "crc": frame.crc,
262
+ "crc_valid": frame.crc_valid,
263
+ },
264
+ errors=frame.errors,
265
+ end_timestamp=frame.end_timestamp,
266
+ )
267
+
268
+ self._packets.append(packet)
269
+ yield packet
270
+
271
+ def _find_frame_starts(
272
+ self,
273
+ data: NDArray[np.bool_],
274
+ samples_per_bit: int,
275
+ ) -> list[int]:
276
+ """Find potential frame start positions.
277
+
278
+ CAN frames start with a Start of Frame (SOF) bit, which is a
279
+ dominant (0) bit following bus idle (recessive/1).
280
+
281
+ Args:
282
+ data: Digital signal data.
283
+ samples_per_bit: Samples per CAN bit.
284
+
285
+ Returns:
286
+ List of sample indices for potential frame starts.
287
+ """
288
+ frame_starts = []
289
+
290
+ # Look for falling edges (1 -> 0) after idle period
291
+ min_idle_bits = 3 # Minimum idle time before frame
292
+ min_idle_samples = min_idle_bits * samples_per_bit
293
+
294
+ i = min_idle_samples
295
+ while i < len(data) - samples_per_bit:
296
+ # Check if previous samples are mostly high (idle)
297
+ idle_region = data[max(0, i - min_idle_samples) : i]
298
+ if np.mean(idle_region) > 0.8: # Mostly recessive
299
+ # Check for falling edge (SOF)
300
+ if data[i - 1] and not data[i]:
301
+ frame_starts.append(i)
302
+ # Skip ahead to avoid detecting same frame
303
+ i += samples_per_bit * 20 # Skip at least 20 bits
304
+ continue
305
+ i += 1
306
+
307
+ return frame_starts
308
+
309
+ def _decode_frame(
310
+ self,
311
+ data: NDArray[np.bool_],
312
+ start_idx: int,
313
+ sample_rate: float,
314
+ samples_per_bit: int,
315
+ sample_offset: int,
316
+ ) -> CANFrame | None:
317
+ """Decode a single CAN frame.
318
+
319
+ Args:
320
+ data: Digital signal data.
321
+ start_idx: Sample index of frame start (SOF).
322
+ sample_rate: Sample rate in Hz.
323
+ samples_per_bit: Samples per CAN bit.
324
+ sample_offset: Offset within bit for sampling.
325
+
326
+ Returns:
327
+ Decoded CANFrame or None if decode fails.
328
+ """
329
+ sample_period = 1.0 / sample_rate
330
+ frame_start_time = start_idx * sample_period
331
+
332
+ # Extract bits with bit stuffing removal
333
+ bits = [] # type: ignore[var-annotated]
334
+ stuff_count = 0
335
+ consecutive_same = 0
336
+ last_bit = None
337
+
338
+ bit_idx = 0
339
+ max_frame_bits = 150 # Maximum bits in extended frame with stuffing
340
+
341
+ current_idx = start_idx
342
+
343
+ while len(bits) < 128 and bit_idx < max_frame_bits:
344
+ # Calculate sample position
345
+ sample_pos = current_idx + sample_offset
346
+
347
+ if sample_pos >= len(data):
348
+ break
349
+
350
+ # Sample the bit
351
+ bit = data[sample_pos]
352
+
353
+ # Check for bit stuffing
354
+ if last_bit is not None:
355
+ if bit == last_bit:
356
+ consecutive_same += 1
357
+ else:
358
+ consecutive_same = 1
359
+
360
+ # After 5 consecutive same bits, next bit should be opposite (stuff bit)
361
+ if consecutive_same == 5:
362
+ # Next bit should be stuff bit - skip it
363
+ current_idx += samples_per_bit
364
+ bit_idx += 1
365
+ stuff_count += 1
366
+
367
+ # Sample the stuff bit to verify
368
+ stuff_sample_pos = current_idx + sample_offset
369
+ if stuff_sample_pos < len(data):
370
+ stuff_bit = data[stuff_sample_pos]
371
+ if stuff_bit == bit:
372
+ # Stuff error
373
+ pass
374
+ consecutive_same = 0
375
+ current_idx += samples_per_bit
376
+ bit_idx += 1
377
+ continue
378
+
379
+ bits.append(int(bit))
380
+ last_bit = bit
381
+
382
+ current_idx += samples_per_bit
383
+ bit_idx += 1
384
+
385
+ if len(bits) < 20: # Minimum frame length
386
+ return None
387
+
388
+ # Parse frame fields
389
+ frame = self._parse_frame_bits(bits, frame_start_time, sample_period, current_idx)
390
+ return frame
391
+
392
+ def _parse_frame_bits(
393
+ self,
394
+ bits: list[int],
395
+ start_time: float,
396
+ sample_period: float,
397
+ end_idx: int,
398
+ ) -> CANFrame | None:
399
+ """Parse decoded bits into CAN frame.
400
+
401
+ Args:
402
+ bits: List of bit values (after stuff bit removal).
403
+ start_time: Frame start time.
404
+ sample_period: Sample period.
405
+ end_idx: End sample index.
406
+
407
+ Returns:
408
+ Parsed CANFrame or None if invalid.
409
+ """
410
+ errors = []
411
+
412
+ try:
413
+ pos = 0
414
+
415
+ # SOF (should be 0)
416
+ if pos >= len(bits):
417
+ return None
418
+ sof = bits[pos]
419
+ pos += 1
420
+
421
+ if sof != 0:
422
+ errors.append("Invalid SOF")
423
+
424
+ # Arbitration field
425
+ if pos + 11 > len(bits):
426
+ return None
427
+
428
+ # First 11 bits of ID
429
+ arb_id = 0
430
+ for i in range(11):
431
+ arb_id = (arb_id << 1) | bits[pos + i]
432
+ pos += 11
433
+
434
+ # RTR bit (for standard) or SRR bit (for extended)
435
+ if pos >= len(bits):
436
+ return None
437
+ rtr_or_srr = bits[pos]
438
+ pos += 1
439
+
440
+ # IDE bit
441
+ if pos >= len(bits):
442
+ return None
443
+ ide = bits[pos]
444
+ pos += 1
445
+
446
+ is_extended = bool(ide)
447
+ is_remote = False
448
+
449
+ if is_extended:
450
+ # Extended frame: 18 more ID bits
451
+ if pos + 18 > len(bits):
452
+ return None
453
+
454
+ # ID extension (18 bits)
455
+ for i in range(18):
456
+ arb_id = (arb_id << 1) | bits[pos + i]
457
+ pos += 18
458
+
459
+ # RTR bit
460
+ if pos >= len(bits):
461
+ return None
462
+ is_remote = bool(bits[pos])
463
+ pos += 1
464
+
465
+ # r1, r0 reserved bits
466
+ pos += 2
467
+ else:
468
+ # Standard frame
469
+ is_remote = bool(rtr_or_srr)
470
+ # r0 reserved bit
471
+ pos += 1
472
+
473
+ # DLC (4 bits)
474
+ if pos + 4 > len(bits):
475
+ return None
476
+
477
+ dlc = 0
478
+ for i in range(4):
479
+ dlc = (dlc << 1) | bits[pos + i]
480
+ pos += 4
481
+
482
+ # Limit DLC to 8
483
+ data_len = min(dlc, 8)
484
+
485
+ # Data field (0-8 bytes)
486
+ if not is_remote:
487
+ if pos + data_len * 8 > len(bits):
488
+ return None
489
+
490
+ data_bytes = bytearray()
491
+ for byte_idx in range(data_len):
492
+ byte_val = 0
493
+ for bit_idx in range(8):
494
+ byte_val = (byte_val << 1) | bits[pos + byte_idx * 8 + bit_idx + bit_idx]
495
+ data_bytes.append(byte_val)
496
+ pos += 8
497
+
498
+ data = bytes(data_bytes)
499
+ else:
500
+ data = b""
501
+
502
+ # CRC field (15 bits)
503
+ if pos + 15 > len(bits):
504
+ return None
505
+
506
+ crc_received = 0
507
+ for i in range(15):
508
+ crc_received = (crc_received << 1) | bits[pos + i]
509
+ pos += 15
510
+
511
+ # Compute CRC on frame bits before CRC field
512
+ # CRC covers SOF through data field
513
+ crc_data_end = pos - 15
514
+ crc_computed = self._compute_crc(bits[:crc_data_end])
515
+
516
+ if crc_received != crc_computed:
517
+ errors.append(
518
+ f"CRC error: received 0x{crc_received:04X}, computed 0x{crc_computed:04X}"
519
+ )
520
+
521
+ # CRC delimiter (should be 1)
522
+ if pos < len(bits) and bits[pos] != 1:
523
+ errors.append("CRC delimiter error")
524
+ pos += 1
525
+
526
+ # ACK slot and delimiter
527
+ pos += 2
528
+
529
+ # EOF (7 recessive bits)
530
+ # We don't strictly check this
531
+
532
+ end_time = start_time + pos * (1.0 / self._bitrate)
533
+
534
+ return CANFrame(
535
+ arbitration_id=arb_id,
536
+ is_extended=is_extended,
537
+ is_remote=is_remote,
538
+ dlc=dlc,
539
+ data=data,
540
+ crc=crc_received,
541
+ crc_computed=crc_computed,
542
+ timestamp=start_time,
543
+ end_timestamp=end_time,
544
+ errors=errors,
545
+ )
546
+
547
+ except (IndexError, ValueError):
548
+ return None
549
+
550
+ def _compute_crc(self, bits: list[int]) -> int:
551
+ """Compute CAN CRC-15.
552
+
553
+ Args:
554
+ bits: Input bits for CRC calculation.
555
+
556
+ Returns:
557
+ 15-bit CRC value.
558
+ """
559
+ crc = CAN_CRC_INIT
560
+
561
+ for bit in bits:
562
+ crc_next = (crc >> 14) & 1
563
+ crc = (crc << 1) & 0x7FFF
564
+
565
+ if bit ^ crc_next:
566
+ crc ^= CAN_CRC_POLY
567
+
568
+ return crc
569
+
570
+
571
+ def decode_can(
572
+ trace: DigitalTrace,
573
+ *,
574
+ bitrate: int = 500000,
575
+ sample_point: float = 0.75,
576
+ ) -> list[CANFrame]:
577
+ """Convenience function to decode CAN frames.
578
+
579
+ Args:
580
+ trace: Digital trace containing CAN signal.
581
+ bitrate: CAN bit rate in bps (default 500000).
582
+ sample_point: Sample point as fraction of bit time.
583
+
584
+ Returns:
585
+ List of decoded CANFrame objects.
586
+
587
+ Example:
588
+ >>> frames = decode_can(trace, bitrate=500000)
589
+ >>> for frame in frames:
590
+ ... print(f"ID: 0x{frame.arbitration_id:03X}")
591
+ """
592
+ decoder = CANDecoder(bitrate=bitrate, sample_point=sample_point)
593
+ frames = []
594
+
595
+ for packet in decoder.decode(trace):
596
+ # Reconstruct CANFrame from packet
597
+ frame = CANFrame(
598
+ arbitration_id=packet.annotations["arbitration_id"],
599
+ is_extended=packet.annotations["is_extended"],
600
+ is_remote=packet.annotations["is_remote"],
601
+ dlc=packet.annotations["dlc"],
602
+ data=packet.data,
603
+ crc=packet.annotations["crc"],
604
+ crc_computed=packet.annotations["crc"], # Reconstruct as same
605
+ timestamp=packet.timestamp,
606
+ end_timestamp=packet.end_timestamp or packet.timestamp,
607
+ errors=packet.errors,
608
+ )
609
+ frames.append(frame)
610
+
611
+ return frames
612
+
613
+
614
+ __all__ = [
615
+ "CAN_BITRATES",
616
+ "CANDecoder",
617
+ "CANFrame",
618
+ "CANFrameType",
619
+ "decode_can",
620
+ ]