oscura 0.5.1__py3-none-any.whl → 0.7.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 (497) 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/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,612 @@
1
+ """Fuzzy synchronization pattern search for corrupted data.
2
+
3
+
4
+ This module provides fuzzy pattern matching for finding sync words and markers
5
+ in noisy or corrupted logic analyzer captures, with configurable bit error
6
+ tolerance using Hamming distance.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from enum import Enum
13
+ from typing import Literal
14
+
15
+ import numpy as np
16
+ from numpy.typing import NDArray
17
+
18
+
19
+ class RecoveryStrategy(Enum):
20
+ """Error recovery strategies for corrupted packets.
21
+
22
+ Attributes:
23
+ NEXT_SYNC: Skip to next sync word when corruption detected
24
+ SKIP_BYTES: Skip fixed byte count and retry parsing
25
+ HEURISTIC: Use statistical packet length model for recovery
26
+ """
27
+
28
+ NEXT_SYNC = "next_sync"
29
+ SKIP_BYTES = "skip_bytes"
30
+ HEURISTIC = "heuristic"
31
+
32
+
33
+ @dataclass
34
+ class SyncMatch:
35
+ """Result from fuzzy sync pattern search.
36
+
37
+ Attributes:
38
+ index: Starting position of match in bits or bytes
39
+ matched_value: The actual value that matched (may differ from pattern)
40
+ hamming_distance: Number of bit errors in the match
41
+ confidence: Match confidence (1.0 - bit_errors/pattern_length)
42
+ pattern_length: Length of pattern in bits
43
+ """
44
+
45
+ index: int
46
+ matched_value: int
47
+ hamming_distance: int
48
+ confidence: float
49
+ pattern_length: int
50
+
51
+
52
+ @dataclass
53
+ class PacketParseResult:
54
+ """Result from robust packet parsing.
55
+
56
+ Attributes:
57
+ packets: List of successfully parsed packet data
58
+ valid: List of validity flags for each packet
59
+ errors: List of error types ('length_corruption', 'sync_lost', None)
60
+ error_positions: Byte positions where errors occurred
61
+ recovery_count: Number of times recovery was triggered
62
+ """
63
+
64
+ packets: list[bytes]
65
+ valid: list[bool]
66
+ errors: list[str | None]
67
+ error_positions: list[int]
68
+ recovery_count: int
69
+
70
+
71
+ def hamming_distance(a: int, b: int, pattern_bits: int) -> int:
72
+ """Calculate Hamming distance between two integers.
73
+
74
+ Args:
75
+ a: First integer
76
+ b: Second integer
77
+ pattern_bits: Number of bits to compare (8, 16, 32, or 64)
78
+
79
+ Returns:
80
+ Number of differing bits
81
+
82
+ Examples:
83
+ >>> hamming_distance(0b10101010, 0b10101011, 8)
84
+ 1
85
+ >>> hamming_distance(0xAA55, 0xAA54, 16)
86
+ 1
87
+ """
88
+ # XOR gives 1s where bits differ
89
+ diff = a ^ b
90
+ # Mask to pattern length
91
+ mask = (1 << pattern_bits) - 1
92
+ diff &= mask
93
+ # Count set bits (population count)
94
+ return (diff).bit_count()
95
+
96
+
97
+ def fuzzy_sync_search(
98
+ data: NDArray[np.uint8],
99
+ pattern: int,
100
+ *,
101
+ pattern_bits: Literal[8, 16, 32, 64] = 8,
102
+ max_errors: int = 2,
103
+ min_confidence: float = 0.85,
104
+ ) -> list[SyncMatch]:
105
+ """Find sync patterns with bit error tolerance using Hamming distance.
106
+
107
+ : Searches for sync words even with bit errors,
108
+ essential for recovering corrupted logic analyzer captures.
109
+
110
+ Performance targets (DAQ-001):
111
+ - ≥10 MB/s for max_errors=2
112
+ - ≥5 MB/s for max_errors=4
113
+ - ≥1 MB/s for max_errors=8
114
+
115
+ Confidence scoring (DAQ-001):
116
+ - ≥0.95 (0-1 bit errors): Highly reliable
117
+ - 0.85-0.95 (2-4 bit errors): Reliable
118
+ - <0.85 (>4 bit errors): Verify manually
119
+
120
+ Args:
121
+ data: Input byte array to search
122
+ pattern: Sync pattern to find (e.g., 0xAA55F0F0 for 32-bit)
123
+ pattern_bits: Pattern length in bits (8, 16, 32, or 64)
124
+ max_errors: Maximum tolerable bit errors (0-8)
125
+ min_confidence: Minimum confidence threshold (0.0-1.0)
126
+
127
+ Returns:
128
+ List of SyncMatch objects with position, matched value, distance,
129
+ and confidence score
130
+
131
+ Raises:
132
+ ValueError: If pattern_bits not in [8, 16, 32, 64]
133
+ ValueError: If max_errors < 0 or > 8
134
+ ValueError: If min_confidence not in [0.0, 1.0]
135
+
136
+ Examples:
137
+ >>> data = np.array([0xAA, 0x55, 0xF0, 0xF0, 0xFF], dtype=np.uint8)
138
+ >>> # Find exact match
139
+ >>> matches = fuzzy_sync_search(data, 0xAA55, pattern_bits=16)
140
+ >>> print(matches[0].confidence)
141
+ 1.0
142
+
143
+ >>> # Find with 1 bit error (0xAA54 instead of 0xAA55)
144
+ >>> data = np.array([0xAA, 0x54, 0x00], dtype=np.uint8)
145
+ >>> matches = fuzzy_sync_search(data, 0xAA55, pattern_bits=16, max_errors=2)
146
+ >>> print(matches[0].hamming_distance)
147
+ 1
148
+
149
+ References:
150
+ DAQ-001: Fuzzy Bit Pattern Search with Hamming Distance Tolerance
151
+ """
152
+ _validate_fuzzy_search_params(pattern_bits, max_errors, min_confidence)
153
+
154
+ pattern_bytes = pattern_bits // 8
155
+ if len(data) < pattern_bytes:
156
+ return []
157
+
158
+ matches: list[SyncMatch] = []
159
+
160
+ for i in range(len(data) - pattern_bytes + 1):
161
+ window = data[i : i + pattern_bytes]
162
+ value = _bytes_to_int(window, pattern_bytes)
163
+ dist = hamming_distance(value, pattern, pattern_bits)
164
+
165
+ if dist <= max_errors:
166
+ confidence = 1.0 - (dist / pattern_bits)
167
+ if confidence >= min_confidence:
168
+ matches.append(
169
+ SyncMatch(
170
+ index=i,
171
+ matched_value=value,
172
+ hamming_distance=dist,
173
+ confidence=confidence,
174
+ pattern_length=pattern_bits,
175
+ )
176
+ )
177
+
178
+ return matches
179
+
180
+
181
+ def _validate_fuzzy_search_params(
182
+ pattern_bits: int, max_errors: int, min_confidence: float
183
+ ) -> None:
184
+ """Validate fuzzy search parameters.
185
+
186
+ Args:
187
+ pattern_bits: Pattern length in bits.
188
+ max_errors: Maximum bit errors.
189
+ min_confidence: Minimum confidence threshold.
190
+
191
+ Raises:
192
+ ValueError: If parameters invalid.
193
+ """
194
+ if pattern_bits not in (8, 16, 32, 64):
195
+ raise ValueError("pattern_bits must be 8, 16, 32, or 64")
196
+ if max_errors < 0 or max_errors > 8:
197
+ raise ValueError("max_errors must be in range [0, 8]")
198
+ if not 0.0 <= min_confidence <= 1.0:
199
+ raise ValueError("min_confidence must be in range [0.0, 1.0]")
200
+
201
+
202
+ def _bytes_to_int(window: NDArray[np.uint8], pattern_bytes: int) -> int:
203
+ """Convert byte window to integer (big-endian).
204
+
205
+ Args:
206
+ window: Byte array window.
207
+ pattern_bytes: Number of bytes (1, 2, 4, or 8).
208
+
209
+ Returns:
210
+ Integer representation.
211
+ """
212
+ if pattern_bytes == 1:
213
+ return int(window[0])
214
+ elif pattern_bytes == 2:
215
+ return (int(window[0]) << 8) | int(window[1])
216
+ elif pattern_bytes == 4:
217
+ return (
218
+ (int(window[0]) << 24) | (int(window[1]) << 16) | (int(window[2]) << 8) | int(window[3])
219
+ )
220
+ else: # 8 bytes
221
+ value = 0
222
+ for j in range(8):
223
+ value = (value << 8) | int(window[j])
224
+ return value
225
+
226
+
227
+ def _validate_packet_parse_inputs(
228
+ length_size: int,
229
+ recovery_strategy: RecoveryStrategy,
230
+ sync_pattern: int | None,
231
+ ) -> None:
232
+ """Validate packet parsing input parameters.
233
+
234
+ Args:
235
+ length_size: Length field size in bytes
236
+ recovery_strategy: Recovery strategy to use
237
+ sync_pattern: Optional sync pattern
238
+
239
+ Raises:
240
+ ValueError: If length_size not 1 or 2
241
+ ValueError: If NEXT_SYNC strategy without sync_pattern
242
+ """
243
+ if length_size not in (1, 2):
244
+ raise ValueError("length_size must be 1 or 2")
245
+
246
+ if recovery_strategy == RecoveryStrategy.NEXT_SYNC and sync_pattern is None:
247
+ raise ValueError("NEXT_SYNC strategy requires sync_pattern")
248
+
249
+
250
+ def _has_sufficient_data_for_header(
251
+ pos: int,
252
+ data_length: int,
253
+ length_offset: int,
254
+ length_size: int,
255
+ ) -> bool:
256
+ """Check if sufficient data remains for packet header.
257
+
258
+ Args:
259
+ pos: Current position in data
260
+ data_length: Total data length
261
+ length_offset: Offset to length field
262
+ length_size: Size of length field
263
+
264
+ Returns:
265
+ True if enough data for header, False otherwise
266
+ """
267
+ return pos + length_offset + length_size <= data_length
268
+
269
+
270
+ def _extract_length_field(
271
+ data: NDArray[np.uint8],
272
+ pos: int,
273
+ length_offset: int,
274
+ length_size: int,
275
+ ) -> int:
276
+ """Extract packet length field from data.
277
+
278
+ Args:
279
+ data: Input byte array
280
+ pos: Current position
281
+ length_offset: Offset to length field
282
+ length_size: Size of length field (1 or 2 bytes)
283
+
284
+ Returns:
285
+ Extracted packet length value
286
+ """
287
+ length_pos = pos + length_offset
288
+ if length_size == 1:
289
+ return int(data[length_pos])
290
+ else: # 2 bytes
291
+ return (int(data[length_pos]) << 8) | int(data[length_pos + 1])
292
+
293
+
294
+ def _validate_length_field(
295
+ pkt_length: int,
296
+ length_size: int,
297
+ min_packet_size: int,
298
+ max_packet_size: int,
299
+ packet_lengths: list[int],
300
+ ) -> tuple[bool, str | None]:
301
+ """Validate packet length field for corruption.
302
+
303
+ Args:
304
+ pkt_length: Extracted packet length
305
+ length_size: Size of length field (1 or 2)
306
+ min_packet_size: Minimum valid packet size
307
+ max_packet_size: Maximum valid packet size
308
+ packet_lengths: History of valid packet lengths
309
+
310
+ Returns:
311
+ Tuple of (is_valid, error_type)
312
+ """
313
+ # Check for obviously corrupted lengths
314
+ if (
315
+ pkt_length == 0
316
+ or pkt_length > max_packet_size
317
+ or (length_size == 2 and (pkt_length & 0xFF00) == 0xFF00)
318
+ or pkt_length < min_packet_size
319
+ ):
320
+ return False, "length_corruption"
321
+
322
+ # Check suspiciously large length (heuristic)
323
+ if len(packet_lengths) >= 10:
324
+ p90 = np.percentile(packet_lengths, 90)
325
+ if pkt_length > p90 * 2:
326
+ return False, "length_corruption"
327
+
328
+ return True, None
329
+
330
+
331
+ def _try_extract_packet(
332
+ data: NDArray[np.uint8],
333
+ pos: int,
334
+ pkt_length: int,
335
+ packets: list[bytes],
336
+ valid: list[bool],
337
+ errors: list[str | None],
338
+ error_positions: list[int],
339
+ packet_lengths: list[int],
340
+ ) -> tuple[int | None, bool]:
341
+ """Try to extract complete packet from current position.
342
+
343
+ Args:
344
+ data: Input byte array
345
+ pos: Current position
346
+ pkt_length: Expected packet length
347
+ packets: List to append packet to
348
+ valid: List to append validity flag
349
+ errors: List to append error info
350
+ error_positions: List to append error positions
351
+ packet_lengths: List to append packet length
352
+
353
+ Returns:
354
+ Tuple of (new_position, continue_parsing)
355
+ - new_position: Next position to parse (None if truncated)
356
+ - continue_parsing: Whether to continue parsing
357
+ """
358
+ packet_end = pos + pkt_length
359
+
360
+ if packet_end <= len(data):
361
+ # Successfully extract packet
362
+ packet_data = bytes(data[pos:packet_end])
363
+ packets.append(packet_data)
364
+ valid.append(True)
365
+ errors.append(None)
366
+ packet_lengths.append(pkt_length)
367
+ return packet_end, True
368
+ else:
369
+ # Packet extends beyond data
370
+ errors.append("truncated")
371
+ error_positions.append(pos)
372
+ return None, False
373
+
374
+
375
+ def _apply_recovery_strategy(
376
+ recovery_strategy: RecoveryStrategy,
377
+ pos: int,
378
+ data: NDArray[np.uint8],
379
+ sync_pattern: int | None,
380
+ sync_bits: Literal[8, 16, 32, 64],
381
+ skip_bytes: int,
382
+ packet_lengths: list[int],
383
+ min_packet_size: int,
384
+ errors: list[str | None],
385
+ error_type: str | None,
386
+ ) -> tuple[int | None, bool]:
387
+ """Apply error recovery strategy.
388
+
389
+ Args:
390
+ recovery_strategy: Strategy to apply
391
+ pos: Current position
392
+ data: Input byte array
393
+ sync_pattern: Sync pattern for NEXT_SYNC strategy
394
+ sync_bits: Sync pattern bit length
395
+ skip_bytes: Bytes to skip for SKIP_BYTES strategy
396
+ packet_lengths: History for HEURISTIC strategy
397
+ min_packet_size: Minimum packet size
398
+ errors: List to append error info
399
+ error_type: Error type to append
400
+
401
+ Returns:
402
+ Tuple of (new_position, continue_parsing)
403
+ """
404
+ if recovery_strategy == RecoveryStrategy.NEXT_SYNC:
405
+ # Search for next sync word
406
+ assert sync_pattern is not None
407
+ search_start = pos + 1
408
+ search_data = data[search_start:]
409
+
410
+ if len(search_data) >= sync_bits // 8:
411
+ matches = fuzzy_sync_search(
412
+ search_data,
413
+ sync_pattern,
414
+ pattern_bits=sync_bits,
415
+ max_errors=0,
416
+ )
417
+
418
+ if matches:
419
+ # Found sync, jump to it
420
+ errors.append("sync_lost")
421
+ return search_start + matches[0].index, True
422
+ else:
423
+ # No more syncs found
424
+ return None, False
425
+ else:
426
+ return None, False
427
+
428
+ elif recovery_strategy == RecoveryStrategy.SKIP_BYTES:
429
+ # Skip fixed bytes and retry
430
+ errors.append(error_type)
431
+ return pos + skip_bytes, True
432
+
433
+ elif recovery_strategy == RecoveryStrategy.HEURISTIC:
434
+ # Use median packet length as guess
435
+ if packet_lengths:
436
+ guess_length = int(np.median(packet_lengths))
437
+ new_pos = pos + guess_length
438
+ else:
439
+ # No history, skip minimal amount
440
+ new_pos = pos + min_packet_size
441
+ errors.append(error_type)
442
+ return new_pos, True
443
+
444
+ raise RuntimeError("Unreachable: must either handle error or return None")
445
+
446
+
447
+ def _initialize_parse_state() -> tuple[
448
+ list[bytes], list[bool], list[str | None], list[int], list[int]
449
+ ]:
450
+ """Initialize packet parsing state containers.
451
+
452
+ Returns:
453
+ Tuple of (packets, valid, errors, error_positions, packet_lengths).
454
+ """
455
+ return [], [], [], [], []
456
+
457
+
458
+ def _handle_packet_extraction(
459
+ data: NDArray[np.uint8],
460
+ pos: int,
461
+ pkt_length: int,
462
+ is_valid_length: bool,
463
+ error_type: str | None,
464
+ packets: list[bytes],
465
+ valid: list[bool],
466
+ errors: list[str | None],
467
+ error_positions: list[int],
468
+ packet_lengths: list[int],
469
+ recovery_strategy: RecoveryStrategy,
470
+ sync_pattern: int | None,
471
+ sync_bits: Literal[8, 16, 32, 64],
472
+ skip_bytes: int,
473
+ min_packet_size: int,
474
+ ) -> tuple[int | None, bool, int]:
475
+ """Handle packet extraction or error recovery.
476
+
477
+ Args:
478
+ data: Input byte array.
479
+ pos: Current position.
480
+ pkt_length: Packet length.
481
+ is_valid_length: Whether length is valid.
482
+ error_type: Error type if invalid.
483
+ packets: Packets list.
484
+ valid: Valid flags list.
485
+ errors: Errors list.
486
+ error_positions: Error positions list.
487
+ packet_lengths: Packet lengths list.
488
+ recovery_strategy: Recovery strategy.
489
+ sync_pattern: Sync pattern.
490
+ sync_bits: Sync pattern bits.
491
+ skip_bytes: Bytes to skip.
492
+ min_packet_size: Minimum packet size.
493
+
494
+ Returns:
495
+ Tuple of (new_position, should_continue, recovery_increment).
496
+ """
497
+ if is_valid_length:
498
+ new_pos, should_continue = _try_extract_packet(
499
+ data, pos, pkt_length, packets, valid, errors, error_positions, packet_lengths
500
+ )
501
+ return new_pos, should_continue, 0
502
+ else:
503
+ error_positions.append(pos)
504
+ new_pos, should_continue = _apply_recovery_strategy(
505
+ recovery_strategy,
506
+ pos,
507
+ data,
508
+ sync_pattern,
509
+ sync_bits,
510
+ skip_bytes,
511
+ packet_lengths,
512
+ min_packet_size,
513
+ errors,
514
+ error_type,
515
+ )
516
+ return new_pos, should_continue, 1
517
+
518
+
519
+ def parse_variable_length_packets(
520
+ data: NDArray[np.uint8],
521
+ *,
522
+ sync_pattern: int | None = None,
523
+ sync_bits: Literal[8, 16, 32, 64] = 16,
524
+ length_offset: int = 2,
525
+ length_size: int = 2,
526
+ min_packet_size: int = 4,
527
+ max_packet_size: int = 1024,
528
+ recovery_strategy: RecoveryStrategy = RecoveryStrategy.NEXT_SYNC,
529
+ skip_bytes: int = 1,
530
+ ) -> PacketParseResult:
531
+ """Parse variable-length packets with error recovery.
532
+
533
+ Robust parsing that continues after corruption, falling back to sync word
534
+ search when length fields are corrupted. Error detection: length=0,
535
+ length>max_packet_size, or length&0xFF00=0xFF00. Recovery strategies:
536
+ next_sync (requires sync_pattern), skip_bytes, or heuristic.
537
+
538
+ Args:
539
+ data: Input byte array containing packets
540
+ sync_pattern: Optional sync word to search for on errors
541
+ sync_bits: Sync pattern length in bits
542
+ length_offset: Byte offset to length field from start
543
+ length_size: Length field size in bytes (1 or 2)
544
+ min_packet_size: Minimum valid packet size in bytes
545
+ max_packet_size: Maximum valid packet size in bytes
546
+ recovery_strategy: Strategy to use when corruption detected
547
+ skip_bytes: Number of bytes to skip for SKIP_BYTES strategy
548
+
549
+ Returns:
550
+ PacketParseResult with parsed packets and error information
551
+
552
+ Raises:
553
+ ValueError: If length_size not 1 or 2
554
+ ValueError: If recovery_strategy is NEXT_SYNC without sync_pattern
555
+
556
+ Examples:
557
+ >>> # Simple TLV parsing with sync word
558
+ >>> data = np.array([0xAA, 0x55, 0x00, 0x04, 0x01, 0x02], dtype=np.uint8)
559
+ >>> result = parse_variable_length_packets(
560
+ ... data, sync_pattern=0xAA55, length_offset=2
561
+ ... )
562
+ >>> len(result.packets)
563
+ 1
564
+
565
+ References:
566
+ DAQ-002: Robust Variable-Length Packet Parsing with Error Recovery
567
+ """
568
+ _validate_packet_parse_inputs(length_size, recovery_strategy, sync_pattern)
569
+ packets, valid, errors, error_positions, packet_lengths = _initialize_parse_state()
570
+ recovery_count = 0
571
+ pos = 0
572
+
573
+ while pos < len(data):
574
+ if not _has_sufficient_data_for_header(pos, len(data), length_offset, length_size):
575
+ break
576
+
577
+ pkt_length = _extract_length_field(data, pos, length_offset, length_size)
578
+ is_valid_length, error_type = _validate_length_field(
579
+ pkt_length, length_size, min_packet_size, max_packet_size, packet_lengths
580
+ )
581
+
582
+ new_pos, should_continue, recovery_inc = _handle_packet_extraction(
583
+ data,
584
+ pos,
585
+ pkt_length,
586
+ is_valid_length,
587
+ error_type,
588
+ packets,
589
+ valid,
590
+ errors,
591
+ error_positions,
592
+ packet_lengths,
593
+ recovery_strategy,
594
+ sync_pattern,
595
+ sync_bits,
596
+ skip_bytes,
597
+ min_packet_size,
598
+ )
599
+
600
+ recovery_count += recovery_inc
601
+ if not should_continue:
602
+ break
603
+ assert new_pos is not None
604
+ pos = new_pos
605
+
606
+ return PacketParseResult(
607
+ packets=packets,
608
+ valid=valid,
609
+ errors=errors,
610
+ error_positions=error_positions,
611
+ recovery_count=recovery_count,
612
+ )