oscura 0.5.0__py3-none-any.whl → 0.6.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 (513) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/__init__.py +0 -48
  5. oscura/analyzers/digital/edges.py +325 -65
  6. oscura/analyzers/digital/extraction.py +0 -195
  7. oscura/analyzers/digital/quality.py +293 -166
  8. oscura/analyzers/digital/timing.py +260 -115
  9. oscura/analyzers/digital/timing_numba.py +334 -0
  10. oscura/analyzers/entropy.py +605 -0
  11. oscura/analyzers/eye/diagram.py +176 -109
  12. oscura/analyzers/eye/metrics.py +5 -5
  13. oscura/analyzers/jitter/__init__.py +6 -4
  14. oscura/analyzers/jitter/ber.py +52 -52
  15. oscura/analyzers/jitter/classification.py +156 -0
  16. oscura/analyzers/jitter/decomposition.py +163 -113
  17. oscura/analyzers/jitter/spectrum.py +80 -64
  18. oscura/analyzers/ml/__init__.py +39 -0
  19. oscura/analyzers/ml/features.py +600 -0
  20. oscura/analyzers/ml/signal_classifier.py +604 -0
  21. oscura/analyzers/packet/daq.py +246 -158
  22. oscura/analyzers/packet/parser.py +12 -1
  23. oscura/analyzers/packet/payload.py +50 -2110
  24. oscura/analyzers/packet/payload_analysis.py +361 -181
  25. oscura/analyzers/packet/payload_patterns.py +133 -70
  26. oscura/analyzers/packet/stream.py +84 -23
  27. oscura/analyzers/patterns/__init__.py +26 -5
  28. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  29. oscura/analyzers/patterns/clustering.py +169 -108
  30. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  31. oscura/analyzers/patterns/discovery.py +1 -1
  32. oscura/analyzers/patterns/matching.py +581 -197
  33. oscura/analyzers/patterns/pattern_mining.py +778 -0
  34. oscura/analyzers/patterns/periodic.py +121 -38
  35. oscura/analyzers/patterns/sequences.py +175 -78
  36. oscura/analyzers/power/conduction.py +1 -1
  37. oscura/analyzers/power/soa.py +6 -6
  38. oscura/analyzers/power/switching.py +250 -110
  39. oscura/analyzers/protocol/__init__.py +17 -1
  40. oscura/analyzers/protocols/__init__.py +1 -22
  41. oscura/analyzers/protocols/base.py +6 -6
  42. oscura/analyzers/protocols/ble/__init__.py +38 -0
  43. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  44. oscura/analyzers/protocols/ble/uuids.py +288 -0
  45. oscura/analyzers/protocols/can.py +257 -127
  46. oscura/analyzers/protocols/can_fd.py +107 -80
  47. oscura/analyzers/protocols/flexray.py +139 -80
  48. oscura/analyzers/protocols/hdlc.py +93 -58
  49. oscura/analyzers/protocols/i2c.py +247 -106
  50. oscura/analyzers/protocols/i2s.py +138 -86
  51. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  52. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  53. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  54. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  55. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  56. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  57. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  58. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  59. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  60. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  61. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  62. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  63. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  64. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  65. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  66. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  67. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  68. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  69. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  70. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  71. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  72. oscura/analyzers/protocols/jtag.py +180 -98
  73. oscura/analyzers/protocols/lin.py +219 -114
  74. oscura/analyzers/protocols/manchester.py +4 -4
  75. oscura/analyzers/protocols/onewire.py +253 -149
  76. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  77. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  78. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  79. oscura/analyzers/protocols/spi.py +192 -95
  80. oscura/analyzers/protocols/swd.py +321 -167
  81. oscura/analyzers/protocols/uart.py +267 -125
  82. oscura/analyzers/protocols/usb.py +235 -131
  83. oscura/analyzers/side_channel/power.py +17 -12
  84. oscura/analyzers/signal/__init__.py +15 -0
  85. oscura/analyzers/signal/timing_analysis.py +1086 -0
  86. oscura/analyzers/signal_integrity/__init__.py +4 -1
  87. oscura/analyzers/signal_integrity/sparams.py +2 -19
  88. oscura/analyzers/spectral/chunked.py +129 -60
  89. oscura/analyzers/spectral/chunked_fft.py +300 -94
  90. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  91. oscura/analyzers/statistical/checksum.py +376 -217
  92. oscura/analyzers/statistical/classification.py +229 -107
  93. oscura/analyzers/statistical/entropy.py +78 -53
  94. oscura/analyzers/statistics/correlation.py +407 -211
  95. oscura/analyzers/statistics/outliers.py +2 -2
  96. oscura/analyzers/statistics/streaming.py +30 -5
  97. oscura/analyzers/validation.py +216 -101
  98. oscura/analyzers/waveform/measurements.py +9 -0
  99. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  100. oscura/analyzers/waveform/spectral.py +500 -228
  101. oscura/api/__init__.py +31 -5
  102. oscura/api/dsl/__init__.py +582 -0
  103. oscura/{dsl → api/dsl}/commands.py +43 -76
  104. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  105. oscura/{dsl → api/dsl}/parser.py +107 -77
  106. oscura/{dsl → api/dsl}/repl.py +2 -2
  107. oscura/api/dsl.py +1 -1
  108. oscura/{integrations → api/integrations}/__init__.py +1 -1
  109. oscura/{integrations → api/integrations}/llm.py +201 -102
  110. oscura/api/operators.py +3 -3
  111. oscura/api/optimization.py +144 -30
  112. oscura/api/rest_server.py +921 -0
  113. oscura/api/server/__init__.py +17 -0
  114. oscura/api/server/dashboard.py +850 -0
  115. oscura/api/server/static/README.md +34 -0
  116. oscura/api/server/templates/base.html +181 -0
  117. oscura/api/server/templates/export.html +120 -0
  118. oscura/api/server/templates/home.html +284 -0
  119. oscura/api/server/templates/protocols.html +58 -0
  120. oscura/api/server/templates/reports.html +43 -0
  121. oscura/api/server/templates/session_detail.html +89 -0
  122. oscura/api/server/templates/sessions.html +83 -0
  123. oscura/api/server/templates/waveforms.html +73 -0
  124. oscura/automotive/__init__.py +8 -1
  125. oscura/automotive/can/__init__.py +10 -0
  126. oscura/automotive/can/checksum.py +3 -1
  127. oscura/automotive/can/dbc_generator.py +590 -0
  128. oscura/automotive/can/message_wrapper.py +121 -74
  129. oscura/automotive/can/patterns.py +98 -21
  130. oscura/automotive/can/session.py +292 -56
  131. oscura/automotive/can/state_machine.py +6 -3
  132. oscura/automotive/can/stimulus_response.py +97 -75
  133. oscura/automotive/dbc/__init__.py +10 -2
  134. oscura/automotive/dbc/generator.py +84 -56
  135. oscura/automotive/dbc/parser.py +6 -6
  136. oscura/automotive/dtc/data.json +2763 -0
  137. oscura/automotive/dtc/database.py +2 -2
  138. oscura/automotive/flexray/__init__.py +31 -0
  139. oscura/automotive/flexray/analyzer.py +504 -0
  140. oscura/automotive/flexray/crc.py +185 -0
  141. oscura/automotive/flexray/fibex.py +449 -0
  142. oscura/automotive/j1939/__init__.py +45 -8
  143. oscura/automotive/j1939/analyzer.py +605 -0
  144. oscura/automotive/j1939/spns.py +326 -0
  145. oscura/automotive/j1939/transport.py +306 -0
  146. oscura/automotive/lin/__init__.py +47 -0
  147. oscura/automotive/lin/analyzer.py +612 -0
  148. oscura/automotive/loaders/blf.py +13 -2
  149. oscura/automotive/loaders/csv_can.py +143 -72
  150. oscura/automotive/loaders/dispatcher.py +50 -2
  151. oscura/automotive/loaders/mdf.py +86 -45
  152. oscura/automotive/loaders/pcap.py +111 -61
  153. oscura/automotive/uds/__init__.py +4 -0
  154. oscura/automotive/uds/analyzer.py +725 -0
  155. oscura/automotive/uds/decoder.py +140 -58
  156. oscura/automotive/uds/models.py +7 -1
  157. oscura/automotive/visualization.py +1 -1
  158. oscura/cli/analyze.py +348 -0
  159. oscura/cli/batch.py +142 -122
  160. oscura/cli/benchmark.py +275 -0
  161. oscura/cli/characterize.py +137 -82
  162. oscura/cli/compare.py +224 -131
  163. oscura/cli/completion.py +250 -0
  164. oscura/cli/config_cmd.py +361 -0
  165. oscura/cli/decode.py +164 -87
  166. oscura/cli/export.py +286 -0
  167. oscura/cli/main.py +115 -31
  168. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  169. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  170. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  171. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  172. oscura/cli/progress.py +147 -0
  173. oscura/cli/shell.py +157 -135
  174. oscura/cli/validate_cmd.py +204 -0
  175. oscura/cli/visualize.py +158 -0
  176. oscura/convenience.py +125 -79
  177. oscura/core/__init__.py +4 -2
  178. oscura/core/backend_selector.py +3 -3
  179. oscura/core/cache.py +126 -15
  180. oscura/core/cancellation.py +1 -1
  181. oscura/{config → core/config}/__init__.py +20 -11
  182. oscura/{config → core/config}/defaults.py +1 -1
  183. oscura/{config → core/config}/loader.py +7 -5
  184. oscura/{config → core/config}/memory.py +5 -5
  185. oscura/{config → core/config}/migration.py +1 -1
  186. oscura/{config → core/config}/pipeline.py +99 -23
  187. oscura/{config → core/config}/preferences.py +1 -1
  188. oscura/{config → core/config}/protocol.py +3 -3
  189. oscura/{config → core/config}/schema.py +426 -272
  190. oscura/{config → core/config}/settings.py +1 -1
  191. oscura/{config → core/config}/thresholds.py +195 -153
  192. oscura/core/correlation.py +5 -6
  193. oscura/core/cross_domain.py +0 -2
  194. oscura/core/debug.py +9 -5
  195. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  196. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  197. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  198. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  199. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  200. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  201. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  202. oscura/core/gpu_backend.py +11 -7
  203. oscura/core/log_query.py +101 -11
  204. oscura/core/logging.py +126 -54
  205. oscura/core/logging_advanced.py +5 -5
  206. oscura/core/memory_limits.py +108 -70
  207. oscura/core/memory_monitor.py +2 -2
  208. oscura/core/memory_progress.py +7 -7
  209. oscura/core/memory_warnings.py +1 -1
  210. oscura/core/numba_backend.py +13 -13
  211. oscura/{plugins → core/plugins}/__init__.py +9 -9
  212. oscura/{plugins → core/plugins}/base.py +7 -7
  213. oscura/{plugins → core/plugins}/cli.py +3 -3
  214. oscura/{plugins → core/plugins}/discovery.py +186 -106
  215. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  216. oscura/{plugins → core/plugins}/manager.py +7 -7
  217. oscura/{plugins → core/plugins}/registry.py +3 -3
  218. oscura/{plugins → core/plugins}/versioning.py +1 -1
  219. oscura/core/progress.py +16 -1
  220. oscura/core/provenance.py +8 -2
  221. oscura/{schemas → core/schemas}/__init__.py +2 -2
  222. oscura/core/schemas/bus_configuration.json +322 -0
  223. oscura/core/schemas/device_mapping.json +182 -0
  224. oscura/core/schemas/packet_format.json +418 -0
  225. oscura/core/schemas/protocol_definition.json +363 -0
  226. oscura/core/types.py +4 -0
  227. oscura/core/uncertainty.py +3 -3
  228. oscura/correlation/__init__.py +52 -0
  229. oscura/correlation/multi_protocol.py +811 -0
  230. oscura/discovery/auto_decoder.py +117 -35
  231. oscura/discovery/comparison.py +191 -86
  232. oscura/discovery/quality_validator.py +155 -68
  233. oscura/discovery/signal_detector.py +196 -79
  234. oscura/export/__init__.py +18 -20
  235. oscura/export/kaitai_struct.py +513 -0
  236. oscura/export/scapy_layer.py +801 -0
  237. oscura/export/wireshark/README.md +15 -15
  238. oscura/export/wireshark/generator.py +1 -1
  239. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  240. oscura/export/wireshark_dissector.py +746 -0
  241. oscura/guidance/wizard.py +207 -111
  242. oscura/hardware/__init__.py +19 -0
  243. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  244. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  245. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  246. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  247. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  248. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  249. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  250. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  251. oscura/hardware/firmware/__init__.py +29 -0
  252. oscura/hardware/firmware/pattern_recognition.py +874 -0
  253. oscura/hardware/hal_detector.py +736 -0
  254. oscura/hardware/security/__init__.py +37 -0
  255. oscura/hardware/security/side_channel_detector.py +1126 -0
  256. oscura/inference/__init__.py +4 -0
  257. oscura/inference/active_learning/README.md +7 -7
  258. oscura/inference/active_learning/observation_table.py +4 -1
  259. oscura/inference/alignment.py +216 -123
  260. oscura/inference/bayesian.py +113 -33
  261. oscura/inference/crc_reverse.py +101 -55
  262. oscura/inference/logic.py +6 -2
  263. oscura/inference/message_format.py +342 -183
  264. oscura/inference/protocol.py +95 -44
  265. oscura/inference/protocol_dsl.py +180 -82
  266. oscura/inference/signal_intelligence.py +1439 -706
  267. oscura/inference/spectral.py +99 -57
  268. oscura/inference/state_machine.py +810 -158
  269. oscura/inference/stream.py +270 -110
  270. oscura/iot/__init__.py +34 -0
  271. oscura/iot/coap/__init__.py +32 -0
  272. oscura/iot/coap/analyzer.py +668 -0
  273. oscura/iot/coap/options.py +212 -0
  274. oscura/iot/lorawan/__init__.py +21 -0
  275. oscura/iot/lorawan/crypto.py +206 -0
  276. oscura/iot/lorawan/decoder.py +801 -0
  277. oscura/iot/lorawan/mac_commands.py +341 -0
  278. oscura/iot/mqtt/__init__.py +27 -0
  279. oscura/iot/mqtt/analyzer.py +999 -0
  280. oscura/iot/mqtt/properties.py +315 -0
  281. oscura/iot/zigbee/__init__.py +31 -0
  282. oscura/iot/zigbee/analyzer.py +615 -0
  283. oscura/iot/zigbee/security.py +153 -0
  284. oscura/iot/zigbee/zcl.py +349 -0
  285. oscura/jupyter/display.py +125 -45
  286. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  287. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  288. oscura/jupyter/exploratory/fuzzy.py +746 -0
  289. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  290. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  291. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  292. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  293. oscura/jupyter/exploratory/sync.py +612 -0
  294. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  295. oscura/jupyter/magic.py +4 -4
  296. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  297. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  298. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  299. oscura/loaders/__init__.py +171 -63
  300. oscura/loaders/binary.py +88 -1
  301. oscura/loaders/chipwhisperer.py +153 -137
  302. oscura/loaders/configurable.py +208 -86
  303. oscura/loaders/csv_loader.py +458 -215
  304. oscura/loaders/hdf5_loader.py +278 -119
  305. oscura/loaders/lazy.py +87 -54
  306. oscura/loaders/mmap_loader.py +1 -1
  307. oscura/loaders/numpy_loader.py +253 -116
  308. oscura/loaders/pcap.py +226 -151
  309. oscura/loaders/rigol.py +110 -49
  310. oscura/loaders/sigrok.py +201 -78
  311. oscura/loaders/tdms.py +81 -58
  312. oscura/loaders/tektronix.py +291 -174
  313. oscura/loaders/touchstone.py +182 -87
  314. oscura/loaders/vcd.py +215 -117
  315. oscura/loaders/wav.py +155 -68
  316. oscura/reporting/__init__.py +9 -7
  317. oscura/reporting/analyze.py +352 -146
  318. oscura/reporting/argument_preparer.py +69 -14
  319. oscura/reporting/auto_report.py +97 -61
  320. oscura/reporting/batch.py +131 -58
  321. oscura/reporting/chart_selection.py +57 -45
  322. oscura/reporting/comparison.py +63 -17
  323. oscura/reporting/content/executive.py +76 -24
  324. oscura/reporting/core_formats/multi_format.py +11 -8
  325. oscura/reporting/engine.py +312 -158
  326. oscura/reporting/enhanced_reports.py +949 -0
  327. oscura/reporting/export.py +86 -43
  328. oscura/reporting/formatting/numbers.py +69 -42
  329. oscura/reporting/html.py +139 -58
  330. oscura/reporting/index.py +137 -65
  331. oscura/reporting/output.py +158 -67
  332. oscura/reporting/pdf.py +67 -102
  333. oscura/reporting/plots.py +191 -112
  334. oscura/reporting/sections.py +88 -47
  335. oscura/reporting/standards.py +104 -61
  336. oscura/reporting/summary_generator.py +75 -55
  337. oscura/reporting/tables.py +138 -54
  338. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  339. oscura/reporting/templates/index.md +13 -13
  340. oscura/sessions/__init__.py +14 -23
  341. oscura/sessions/base.py +3 -3
  342. oscura/sessions/blackbox.py +106 -10
  343. oscura/sessions/generic.py +2 -2
  344. oscura/sessions/legacy.py +783 -0
  345. oscura/side_channel/__init__.py +63 -0
  346. oscura/side_channel/dpa.py +1025 -0
  347. oscura/utils/__init__.py +15 -1
  348. oscura/utils/autodetect.py +1 -5
  349. oscura/utils/bitwise.py +118 -0
  350. oscura/{builders → utils/builders}/__init__.py +1 -1
  351. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  352. oscura/{comparison → utils/comparison}/compare.py +202 -101
  353. oscura/{comparison → utils/comparison}/golden.py +83 -63
  354. oscura/{comparison → utils/comparison}/limits.py +313 -89
  355. oscura/{comparison → utils/comparison}/mask.py +151 -45
  356. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  357. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  358. oscura/{component → utils/component}/__init__.py +3 -3
  359. oscura/{component → utils/component}/impedance.py +122 -58
  360. oscura/{component → utils/component}/reactive.py +165 -168
  361. oscura/{component → utils/component}/transmission_line.py +3 -3
  362. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  363. oscura/{filtering → utils/filtering}/base.py +1 -1
  364. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  365. oscura/{filtering → utils/filtering}/design.py +169 -93
  366. oscura/{filtering → utils/filtering}/filters.py +2 -2
  367. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  368. oscura/utils/geometry.py +31 -0
  369. oscura/utils/imports.py +184 -0
  370. oscura/utils/lazy.py +1 -1
  371. oscura/{math → utils/math}/__init__.py +2 -2
  372. oscura/{math → utils/math}/arithmetic.py +114 -48
  373. oscura/{math → utils/math}/interpolation.py +139 -106
  374. oscura/utils/memory.py +129 -66
  375. oscura/utils/memory_advanced.py +92 -9
  376. oscura/utils/memory_extensions.py +10 -8
  377. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  378. oscura/{optimization → utils/optimization}/search.py +2 -2
  379. oscura/utils/performance/__init__.py +58 -0
  380. oscura/utils/performance/caching.py +889 -0
  381. oscura/utils/performance/lsh_clustering.py +333 -0
  382. oscura/utils/performance/memory_optimizer.py +699 -0
  383. oscura/utils/performance/optimizations.py +675 -0
  384. oscura/utils/performance/parallel.py +654 -0
  385. oscura/utils/performance/profiling.py +661 -0
  386. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  387. oscura/{pipeline → utils/pipeline}/composition.py +11 -3
  388. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  389. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  390. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  391. oscura/{search → utils/search}/__init__.py +3 -3
  392. oscura/{search → utils/search}/anomaly.py +188 -58
  393. oscura/utils/search/context.py +294 -0
  394. oscura/{search → utils/search}/pattern.py +138 -10
  395. oscura/utils/serial.py +51 -0
  396. oscura/utils/storage/__init__.py +61 -0
  397. oscura/utils/storage/database.py +1166 -0
  398. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  399. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  400. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  401. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  402. oscura/{triggering → utils/triggering}/base.py +6 -6
  403. oscura/{triggering → utils/triggering}/edge.py +2 -2
  404. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  405. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  406. oscura/{triggering → utils/triggering}/window.py +2 -2
  407. oscura/utils/validation.py +32 -0
  408. oscura/validation/__init__.py +121 -0
  409. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  410. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  411. oscura/{compliance → validation/compliance}/masks.py +1 -1
  412. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  413. oscura/{compliance → validation/compliance}/testing.py +114 -52
  414. oscura/validation/compliance_tests.py +915 -0
  415. oscura/validation/fuzzer.py +990 -0
  416. oscura/validation/grammar_tests.py +596 -0
  417. oscura/validation/grammar_validator.py +904 -0
  418. oscura/validation/hil_testing.py +977 -0
  419. oscura/{quality → validation/quality}/__init__.py +4 -4
  420. oscura/{quality → validation/quality}/ensemble.py +251 -171
  421. oscura/{quality → validation/quality}/explainer.py +3 -3
  422. oscura/{quality → validation/quality}/scoring.py +1 -1
  423. oscura/{quality → validation/quality}/warnings.py +4 -4
  424. oscura/validation/regression_suite.py +808 -0
  425. oscura/validation/replay.py +788 -0
  426. oscura/{testing → validation/testing}/__init__.py +2 -2
  427. oscura/{testing → validation/testing}/synthetic.py +5 -5
  428. oscura/visualization/__init__.py +9 -0
  429. oscura/visualization/accessibility.py +1 -1
  430. oscura/visualization/annotations.py +64 -67
  431. oscura/visualization/colors.py +7 -7
  432. oscura/visualization/digital.py +180 -81
  433. oscura/visualization/eye.py +236 -85
  434. oscura/visualization/interactive.py +320 -143
  435. oscura/visualization/jitter.py +587 -247
  436. oscura/visualization/layout.py +169 -134
  437. oscura/visualization/optimization.py +103 -52
  438. oscura/visualization/palettes.py +1 -1
  439. oscura/visualization/power.py +427 -211
  440. oscura/visualization/power_extended.py +626 -297
  441. oscura/visualization/presets.py +2 -0
  442. oscura/visualization/protocols.py +495 -181
  443. oscura/visualization/render.py +79 -63
  444. oscura/visualization/reverse_engineering.py +171 -124
  445. oscura/visualization/signal_integrity.py +460 -279
  446. oscura/visualization/specialized.py +190 -100
  447. oscura/visualization/spectral.py +670 -255
  448. oscura/visualization/thumbnails.py +166 -137
  449. oscura/visualization/waveform.py +150 -63
  450. oscura/workflows/__init__.py +3 -0
  451. oscura/{batch → workflows/batch}/__init__.py +5 -5
  452. oscura/{batch → workflows/batch}/advanced.py +150 -75
  453. oscura/workflows/batch/aggregate.py +531 -0
  454. oscura/workflows/batch/analyze.py +236 -0
  455. oscura/{batch → workflows/batch}/logging.py +2 -2
  456. oscura/{batch → workflows/batch}/metrics.py +1 -1
  457. oscura/workflows/complete_re.py +1144 -0
  458. oscura/workflows/compliance.py +44 -54
  459. oscura/workflows/digital.py +197 -51
  460. oscura/workflows/legacy/__init__.py +12 -0
  461. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  462. oscura/workflows/multi_trace.py +9 -9
  463. oscura/workflows/power.py +42 -62
  464. oscura/workflows/protocol.py +82 -49
  465. oscura/workflows/reverse_engineering.py +351 -150
  466. oscura/workflows/signal_integrity.py +157 -82
  467. oscura-0.6.0.dist-info/METADATA +643 -0
  468. oscura-0.6.0.dist-info/RECORD +590 -0
  469. oscura/analyzers/digital/ic_database.py +0 -498
  470. oscura/analyzers/digital/timing_paths.py +0 -339
  471. oscura/analyzers/digital/vintage.py +0 -377
  472. oscura/analyzers/digital/vintage_result.py +0 -148
  473. oscura/analyzers/protocols/parallel_bus.py +0 -449
  474. oscura/batch/aggregate.py +0 -300
  475. oscura/batch/analyze.py +0 -139
  476. oscura/dsl/__init__.py +0 -73
  477. oscura/exceptions.py +0 -59
  478. oscura/exploratory/fuzzy.py +0 -513
  479. oscura/exploratory/sync.py +0 -384
  480. oscura/export/wavedrom.py +0 -430
  481. oscura/exporters/__init__.py +0 -94
  482. oscura/exporters/csv.py +0 -303
  483. oscura/exporters/exporters.py +0 -44
  484. oscura/exporters/hdf5.py +0 -217
  485. oscura/exporters/html_export.py +0 -701
  486. oscura/exporters/json_export.py +0 -338
  487. oscura/exporters/markdown_export.py +0 -367
  488. oscura/exporters/matlab_export.py +0 -354
  489. oscura/exporters/npz_export.py +0 -219
  490. oscura/exporters/spice_export.py +0 -210
  491. oscura/exporters/vintage_logic_csv.py +0 -247
  492. oscura/reporting/vintage_logic_report.py +0 -523
  493. oscura/search/context.py +0 -149
  494. oscura/session/__init__.py +0 -34
  495. oscura/session/annotations.py +0 -289
  496. oscura/session/history.py +0 -313
  497. oscura/session/session.py +0 -520
  498. oscura/visualization/digital_advanced.py +0 -718
  499. oscura/visualization/figure_manager.py +0 -156
  500. oscura/workflow/__init__.py +0 -13
  501. oscura-0.5.0.dist-info/METADATA +0 -407
  502. oscura-0.5.0.dist-info/RECORD +0 -486
  503. /oscura/core/{config.py → config/legacy.py} +0 -0
  504. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  505. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  506. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  507. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  508. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  509. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  510. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  511. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/WHEEL +0 -0
  512. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/entry_points.txt +0 -0
  513. {oscura-0.5.0.dist-info → oscura-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -64,16 +64,16 @@ class SWDDecoder(SyncDecoder):
64
64
  longname = "Serial Wire Debug"
65
65
  desc = "ARM Serial Wire Debug protocol decoder"
66
66
 
67
- channels = [ # noqa: RUF012
67
+ channels = [
68
68
  ChannelDef("swclk", "SWCLK", "Serial Wire Clock", required=True),
69
69
  ChannelDef("swdio", "SWDIO", "Serial Wire Data I/O", required=True),
70
70
  ]
71
71
 
72
- optional_channels = [] # noqa: RUF012
72
+ optional_channels = []
73
73
 
74
- options = [] # noqa: RUF012
74
+ options = []
75
75
 
76
- annotations = [ # noqa: RUF012
76
+ annotations = [
77
77
  ("request", "Request packet"),
78
78
  ("ack", "ACK response"),
79
79
  ("data", "Data phase"),
@@ -85,6 +85,164 @@ class SWDDecoder(SyncDecoder):
85
85
  """Initialize SWD decoder."""
86
86
  super().__init__()
87
87
 
88
+ def _is_line_reset_sequence(
89
+ self,
90
+ swdio: NDArray[np.bool_],
91
+ start_idx: int,
92
+ edge_idx: int,
93
+ rising_edges: NDArray[np.intp],
94
+ ) -> bool:
95
+ """Check if current start bit is part of a line reset sequence.
96
+
97
+ Args:
98
+ swdio: SWDIO signal data.
99
+ start_idx: Index of potential start bit.
100
+ edge_idx: Current edge index in rising_edges array.
101
+ rising_edges: Array of rising edge indices.
102
+
103
+ Returns:
104
+ True if this appears to be a line reset sequence.
105
+ """
106
+ if start_idx == 0 or edge_idx == 0:
107
+ return False
108
+
109
+ prev_edge_idx = rising_edges[edge_idx - 1]
110
+ swdio_between = swdio[prev_edge_idx:start_idx]
111
+
112
+ # If SWDIO stayed high between edges, likely line reset
113
+ return bool(len(swdio_between) > 0 and np.all(swdio_between))
114
+
115
+ def _parse_swd_request(
116
+ self, swdio: NDArray[np.bool_], edge_idx: int, rising_edges: NDArray[np.intp]
117
+ ) -> tuple[dict[str, int], list[str], int] | None:
118
+ """Parse SWD request packet (8 bits).
119
+
120
+ Args:
121
+ swdio: SWDIO signal data.
122
+ edge_idx: Starting edge index.
123
+ rising_edges: Array of rising edge indices.
124
+
125
+ Returns:
126
+ Tuple of (fields dict, errors list, register_addr) or None if insufficient edges.
127
+ """
128
+ if edge_idx + 8 > len(rising_edges):
129
+ return None
130
+
131
+ request_bits = []
132
+ for i in range(8):
133
+ bit_idx = rising_edges[edge_idx + i]
134
+ request_bits.append(1 if swdio[bit_idx] else 0)
135
+
136
+ # Extract fields
137
+ fields = {
138
+ "start_bit": request_bits[0],
139
+ "apndp": request_bits[1],
140
+ "rnw": request_bits[2],
141
+ "addr_2": request_bits[3],
142
+ "addr_3": request_bits[4],
143
+ "parity": request_bits[5],
144
+ "stop_bit": request_bits[6],
145
+ "park_bit": request_bits[7],
146
+ }
147
+
148
+ # Validate request format
149
+ errors = []
150
+ if fields["start_bit"] != 1:
151
+ errors.append("Invalid start bit")
152
+ if fields["stop_bit"] != 0:
153
+ errors.append("Invalid stop bit")
154
+ if fields["park_bit"] != 1:
155
+ errors.append("Invalid park bit")
156
+
157
+ # Check parity (odd parity of APnDP, RnW, A[2:3])
158
+ expected_parity = (
159
+ fields["apndp"] + fields["rnw"] + fields["addr_2"] + fields["addr_3"]
160
+ ) % 2
161
+ if fields["parity"] != expected_parity:
162
+ errors.append("Request parity error")
163
+
164
+ # Construct register address
165
+ register_addr = (fields["addr_3"] << 3) | (fields["addr_2"] << 2)
166
+
167
+ return (fields, errors, register_addr)
168
+
169
+ def _parse_swd_ack(
170
+ self, swdio: NDArray[np.bool_], edge_idx: int, rising_edges: NDArray[np.intp]
171
+ ) -> tuple[int, str, list[str]] | None:
172
+ """Parse SWD ACK response (3 bits).
173
+
174
+ Args:
175
+ swdio: SWDIO signal data.
176
+ edge_idx: Starting edge index.
177
+ rising_edges: Array of rising edge indices.
178
+
179
+ Returns:
180
+ Tuple of (ack_value, ack_str, errors) or None if insufficient edges.
181
+ """
182
+ if edge_idx + 3 > len(rising_edges):
183
+ return None
184
+
185
+ ack_bits = []
186
+ for i in range(3):
187
+ bit_idx = rising_edges[edge_idx + i]
188
+ ack_bits.append(1 if swdio[bit_idx] else 0)
189
+
190
+ ack_value = (ack_bits[2] << 2) | (ack_bits[1] << 1) | ack_bits[0]
191
+
192
+ # Decode ACK
193
+ errors = []
194
+ if ack_value == SWDResponse.OK.value:
195
+ ack_str = "OK"
196
+ elif ack_value == SWDResponse.WAIT.value:
197
+ ack_str = "WAIT"
198
+ errors.append("Target responded with WAIT")
199
+ elif ack_value == SWDResponse.FAULT.value:
200
+ ack_str = "FAULT"
201
+ errors.append("Target responded with FAULT")
202
+ else:
203
+ ack_str = "INVALID"
204
+ errors.append(f"Invalid ACK: 0b{ack_value:03b}")
205
+
206
+ return (ack_value, ack_str, errors)
207
+
208
+ def _parse_swd_data(
209
+ self, swdio: NDArray[np.bool_], edge_idx: int, rising_edges: NDArray[np.intp]
210
+ ) -> tuple[int, list[str]] | None:
211
+ """Parse SWD data phase (32 bits + parity).
212
+
213
+ Args:
214
+ swdio: SWDIO signal data.
215
+ edge_idx: Starting edge index.
216
+ rising_edges: Array of rising edge indices.
217
+
218
+ Returns:
219
+ Tuple of (data_value, errors) or None if insufficient edges.
220
+ """
221
+ if edge_idx + 33 > len(rising_edges):
222
+ return None
223
+
224
+ data_bits = []
225
+ for i in range(32):
226
+ bit_idx = rising_edges[edge_idx + i]
227
+ data_bits.append(1 if swdio[bit_idx] else 0)
228
+
229
+ # Convert to value (LSB first)
230
+ data_value = 0
231
+ for i, bit in enumerate(data_bits):
232
+ data_value |= bit << i
233
+
234
+ # Parity bit
235
+ parity_idx = rising_edges[edge_idx + 32]
236
+ data_parity = 1 if swdio[parity_idx] else 0
237
+
238
+ # Check data parity (odd parity)
239
+ errors = []
240
+ expected_data_parity = sum(data_bits) % 2
241
+ if data_parity != expected_data_parity:
242
+ errors.append("Data parity error")
243
+
244
+ return (data_value, errors)
245
+
88
246
  def decode( # type: ignore[override]
89
247
  self,
90
248
  trace: DigitalTrace | None = None,
@@ -112,190 +270,186 @@ class SWDDecoder(SyncDecoder):
112
270
  if swclk is None or swdio is None:
113
271
  return
114
272
 
115
- n_samples = min(len(swclk), len(swdio))
116
- swclk = swclk[:n_samples]
117
- swdio = swdio[:n_samples]
118
-
119
- # Find rising edges of SWCLK (data sampled on rising edge)
120
- rising_edges = np.where(~swclk[:-1] & swclk[1:])[0] + 1
273
+ # Prepare signals
274
+ swclk_trimmed, swdio_trimmed = self._prepare_signals(swclk, swdio)
275
+ rising_edges = self._find_rising_edges(swclk_trimmed)
121
276
 
122
277
  if len(rising_edges) == 0:
123
278
  return
124
279
 
280
+ # Decode transactions
281
+ yield from self._decode_transactions(swdio_trimmed, rising_edges, sample_rate)
282
+
283
+ def _prepare_signals(
284
+ self,
285
+ swclk: NDArray[np.bool_],
286
+ swdio: NDArray[np.bool_],
287
+ ) -> tuple[NDArray[np.bool_], NDArray[np.bool_]]:
288
+ """Trim signals to same length."""
289
+ n_samples = min(len(swclk), len(swdio))
290
+ return swclk[:n_samples], swdio[:n_samples]
291
+
292
+ def _find_rising_edges(self, swclk: NDArray[np.bool_]) -> NDArray[np.intp]:
293
+ """Find rising edges of SWCLK."""
294
+ return np.where(~swclk[:-1] & swclk[1:])[0] + 1
295
+
296
+ def _decode_transactions(
297
+ self,
298
+ swdio: NDArray[np.bool_],
299
+ rising_edges: NDArray[np.intp],
300
+ sample_rate: float,
301
+ ) -> Iterator[ProtocolPacket]:
302
+ """Decode all SWD transactions from edge stream."""
125
303
  trans_num = 0
126
304
  edge_idx = 0
127
305
 
128
306
  while edge_idx < len(rising_edges):
129
- # Look for start bit (should be 1)
307
+ # Check for valid start bit
130
308
  start_idx = rising_edges[edge_idx]
131
309
  if not swdio[start_idx]:
132
310
  edge_idx += 1
133
311
  continue
134
312
 
135
- # Check if we have a low period before this start bit
136
- # (to avoid decoding line reset sequences as transactions)
137
- if start_idx > 0 and edge_idx > 0:
138
- prev_edge_idx = rising_edges[edge_idx - 1]
139
- # Check if there was a low period between previous and current edge
140
- # If SWDIO stayed high between edges, this might be part of line reset
141
- swdio_between = swdio[prev_edge_idx:start_idx]
142
- if len(swdio_between) > 0 and np.all(swdio_between):
143
- # SWDIO was high the entire time - likely line reset, skip
144
- edge_idx += 1
145
- continue
146
-
147
- # Parse request packet (8 bits total)
148
- # Bit 0: Start (1)
149
- # Bit 1: APnDP (0=DP, 1=AP)
150
- # Bit 2: RnW (0=Write, 1=Read)
151
- # Bits 3-4: A[2:3] (register address bits)
152
- # Bit 5: Parity (odd parity of bits 1-4)
153
- # Bit 6: Stop (0)
154
- # Bit 7: Park (1)
155
-
156
- if edge_idx + 8 > len(rising_edges):
157
- break
158
-
159
- request_bits = []
160
- for i in range(8):
161
- bit_idx = rising_edges[edge_idx + i]
162
- request_bits.append(1 if swdio[bit_idx] else 0)
163
-
164
- # Extract fields
165
- start_bit = request_bits[0]
166
- apndp = request_bits[1]
167
- rnw = request_bits[2]
168
- addr_2 = request_bits[3]
169
- addr_3 = request_bits[4]
170
- parity = request_bits[5]
171
- stop_bit = request_bits[6]
172
- park_bit = request_bits[7]
173
-
174
- # Validate request format
175
- errors = []
176
- if start_bit != 1:
177
- errors.append("Invalid start bit")
178
- if stop_bit != 0:
179
- errors.append("Invalid stop bit")
180
- if park_bit != 1:
181
- errors.append("Invalid park bit")
182
-
183
- # Check parity (odd parity of APnDP, RnW, A[2:3])
184
- expected_parity = (apndp + rnw + addr_2 + addr_3) % 2
185
- if parity != expected_parity:
186
- errors.append("Request parity error")
187
-
188
- # Construct register address
189
- register_addr = (addr_3 << 3) | (addr_2 << 2)
190
-
191
- edge_idx += 8
192
-
193
- # Turnaround period (1 clock, host releases SWDIO)
194
- edge_idx += 1
195
-
196
- # ACK response (3 bits from target)
197
- if edge_idx + 3 > len(rising_edges):
198
- break
199
-
200
- ack_bits = []
201
- for i in range(3):
202
- bit_idx = rising_edges[edge_idx + i]
203
- ack_bits.append(1 if swdio[bit_idx] else 0)
204
-
205
- ack_value = (ack_bits[2] << 2) | (ack_bits[1] << 1) | ack_bits[0]
206
-
207
- # Decode ACK
208
- if ack_value == SWDResponse.OK.value:
209
- ack_str = "OK"
210
- elif ack_value == SWDResponse.WAIT.value:
211
- ack_str = "WAIT"
212
- errors.append("Target responded with WAIT")
213
- elif ack_value == SWDResponse.FAULT.value:
214
- ack_str = "FAULT"
215
- errors.append("Target responded with FAULT")
216
- else:
217
- ack_str = "INVALID"
218
- errors.append(f"Invalid ACK: 0b{ack_value:03b}")
219
-
220
- edge_idx += 3
221
-
222
- # If ACK is OK, there's a data phase
223
- data_value = 0
224
- if ack_value == SWDResponse.OK.value:
225
- # Turnaround (1 clock)
313
+ # Skip line reset sequences
314
+ if self._is_line_reset_sequence(swdio, start_idx, edge_idx, rising_edges):
226
315
  edge_idx += 1
316
+ continue
227
317
 
228
- # Data phase (32 bits + 1 parity)
229
- if edge_idx + 33 > len(rising_edges):
230
- break
231
-
232
- data_bits = []
233
- for i in range(32):
234
- bit_idx = rising_edges[edge_idx + i]
235
- data_bits.append(1 if swdio[bit_idx] else 0)
236
-
237
- # Convert to value (LSB first)
238
- for i, bit in enumerate(data_bits):
239
- data_value |= bit << i
240
-
241
- # Parity bit
242
- parity_idx = rising_edges[edge_idx + 32]
243
- data_parity = 1 if swdio[parity_idx] else 0
244
-
245
- # Check data parity (odd parity)
246
- expected_data_parity = sum(data_bits) % 2
247
- if data_parity != expected_data_parity:
248
- errors.append("Data parity error")
249
-
250
- edge_idx += 33
251
-
252
- # Calculate timing
253
- start_time = start_idx / sample_rate
254
- end_time = rising_edges[min(edge_idx - 1, len(rising_edges) - 1)] / sample_rate
255
-
256
- # Add annotations
257
- port_type = "AP" if apndp else "DP"
258
- access_type = "Read" if rnw else "Write"
259
-
260
- self.put_annotation(
261
- start_time,
262
- end_time,
263
- AnnotationLevel.PACKETS,
264
- f"{port_type} {access_type} @ 0x{register_addr:02X}: {ack_str}",
265
- )
266
-
267
- # Create packet
268
- annotations = {
269
- "transaction_num": trans_num,
270
- "apndp": "AP" if apndp else "DP",
271
- "read": bool(rnw),
272
- "register_addr": register_addr,
273
- "ack": ack_str,
274
- "ack_value": ack_value,
275
- }
276
-
277
- if ack_value == SWDResponse.OK.value:
278
- annotations["data"] = data_value
279
-
280
- # Encode data as bytes (little-endian)
281
- data_bytes = (
282
- data_value.to_bytes(4, "little") if ack_value == SWDResponse.OK.value else b""
283
- )
284
-
285
- packet = ProtocolPacket(
286
- timestamp=start_time,
287
- protocol="swd",
288
- data=data_bytes,
289
- annotations=annotations,
290
- errors=errors,
318
+ # Parse complete transaction
319
+ packet_result = self._parse_transaction(
320
+ swdio, rising_edges, edge_idx, trans_num, sample_rate
291
321
  )
322
+ if packet_result is None:
323
+ break
292
324
 
325
+ packet, edge_advance = packet_result
293
326
  yield packet
294
327
 
295
328
  trans_num += 1
329
+ edge_idx += edge_advance
296
330
 
297
- # Turnaround and idle
298
- edge_idx += 1
331
+ def _parse_transaction(
332
+ self,
333
+ swdio: NDArray[np.bool_],
334
+ rising_edges: NDArray[np.intp],
335
+ edge_idx: int,
336
+ trans_num: int,
337
+ sample_rate: float,
338
+ ) -> tuple[ProtocolPacket, int] | None:
339
+ """Parse complete SWD transaction."""
340
+ # Parse request
341
+ request_result = self._parse_swd_request(swdio, edge_idx, rising_edges)
342
+ if request_result is None:
343
+ return None
344
+ fields, errors, register_addr = request_result
345
+
346
+ current_idx = edge_idx + 8 + 1 # Request + turnaround
347
+
348
+ # Parse ACK
349
+ ack_result = self._parse_swd_ack(swdio, current_idx, rising_edges)
350
+ if ack_result is None:
351
+ return None
352
+ ack_value, ack_str, ack_errors = ack_result
353
+ errors.extend(ack_errors)
354
+
355
+ current_idx += 3 # ACK bits
356
+
357
+ # Parse data if ACK is OK
358
+ data_value = self._parse_data_phase(swdio, rising_edges, current_idx, ack_value, errors)
359
+
360
+ if ack_value == SWDResponse.OK.value:
361
+ current_idx += 1 + 33 # Turnaround + data + parity
362
+
363
+ # Build packet
364
+ start_idx = rising_edges[edge_idx]
365
+ packet = self._build_packet(
366
+ fields,
367
+ register_addr,
368
+ ack_value,
369
+ ack_str,
370
+ data_value,
371
+ errors,
372
+ trans_num,
373
+ start_idx,
374
+ current_idx,
375
+ rising_edges,
376
+ sample_rate,
377
+ )
378
+
379
+ edge_advance = current_idx - edge_idx + 1
380
+ return packet, edge_advance
381
+
382
+ def _parse_data_phase(
383
+ self,
384
+ swdio: NDArray[np.bool_],
385
+ rising_edges: NDArray[np.intp],
386
+ current_idx: int,
387
+ ack_value: int,
388
+ errors: list[str],
389
+ ) -> int:
390
+ """Parse data phase if ACK is OK."""
391
+ if ack_value != SWDResponse.OK.value:
392
+ return 0
393
+
394
+ data_result = self._parse_swd_data(swdio, current_idx + 1, rising_edges)
395
+ if data_result is None:
396
+ return 0
397
+
398
+ data_value, data_errors = data_result
399
+ errors.extend(data_errors)
400
+ return data_value
401
+
402
+ def _build_packet(
403
+ self,
404
+ fields: dict[str, int],
405
+ register_addr: int,
406
+ ack_value: int,
407
+ ack_str: str,
408
+ data_value: int,
409
+ errors: list[str],
410
+ trans_num: int,
411
+ start_idx: int,
412
+ end_idx: int,
413
+ rising_edges: NDArray[np.intp],
414
+ sample_rate: float,
415
+ ) -> ProtocolPacket:
416
+ """Build protocol packet from parsed data."""
417
+ start_time = start_idx / sample_rate
418
+ end_time = rising_edges[min(end_idx - 1, len(rising_edges) - 1)] / sample_rate
419
+
420
+ # Add annotation
421
+ port_type = "AP" if fields["apndp"] else "DP"
422
+ access_type = "Read" if fields["rnw"] else "Write"
423
+ self.put_annotation(
424
+ start_time,
425
+ end_time,
426
+ AnnotationLevel.PACKETS,
427
+ f"{port_type} {access_type} @ 0x{register_addr:02X}: {ack_str}",
428
+ )
429
+
430
+ # Create annotations dict
431
+ annotations = {
432
+ "transaction_num": trans_num,
433
+ "apndp": "AP" if fields["apndp"] else "DP",
434
+ "read": bool(fields["rnw"]),
435
+ "register_addr": register_addr,
436
+ "ack": ack_str,
437
+ "ack_value": ack_value,
438
+ }
439
+
440
+ if ack_value == SWDResponse.OK.value:
441
+ annotations["data"] = data_value
442
+
443
+ # Encode data bytes
444
+ data_bytes = data_value.to_bytes(4, "little") if ack_value == SWDResponse.OK.value else b""
445
+
446
+ return ProtocolPacket(
447
+ timestamp=start_time,
448
+ protocol="swd",
449
+ data=data_bytes,
450
+ annotations=annotations,
451
+ errors=errors,
452
+ )
299
453
 
300
454
 
301
455
  def decode_swd(