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
@@ -166,65 +166,165 @@ def reverse_engineer_signal(
166
166
  >>> for frame in result.frames[:5]:
167
167
  ... print(f" {frame.raw_bytes.hex()}")
168
168
  """
169
- if expected_baud_rates is None:
170
- expected_baud_rates = [9600, 19200, 38400, 57600, 115200, 230400, 460800]
171
-
172
- if checksum_types is None:
173
- checksum_types = ["xor", "sum8", "crc8", "crc16"]
169
+ expected_baud_rates = expected_baud_rates or [
170
+ 9600,
171
+ 19200,
172
+ 38400,
173
+ 57600,
174
+ 115200,
175
+ 230400,
176
+ 460800,
177
+ ]
178
+ checksum_types = checksum_types or ["xor", "sum8", "crc8", "crc16"]
174
179
 
175
180
  warnings: list[str] = []
176
181
  data = trace.data
177
182
  sample_rate = trace.metadata.sample_rate
178
183
 
179
- # ========== Step 1: Signal Characterization ==========
184
+ # ========== Step 1-3: Signal Analysis ==========
180
185
  characterization = _characterize_signal(data, sample_rate)
181
-
182
- # ========== Step 2: Clock Recovery ==========
183
186
  baud_rate, baud_confidence = _detect_baud_rate(
184
187
  data, sample_rate, expected_baud_rates, characterization["threshold"]
185
188
  )
189
+ _validate_baud_confidence(baud_confidence, warnings)
190
+
191
+ bit_stream = _extract_bit_stream(data, sample_rate, baud_rate, characterization["threshold"])
192
+ _validate_bit_stream(bit_stream, warnings)
193
+
194
+ # ========== Step 4-5: Byte and Sync Analysis ==========
195
+ byte_positions, byte_stream = _extract_bytes(bit_stream)
196
+ _validate_byte_stream(byte_stream, warnings)
197
+
198
+ sync_pattern, sync_positions, sync_confidence = _detect_sync_pattern(
199
+ byte_stream, max_frame_length
200
+ )
201
+
202
+ # ========== Step 6-8: Frame and Protocol Analysis ==========
203
+ frames = _extract_frames(bit_stream, byte_stream, byte_positions, sync_positions, sync_pattern)
204
+ _validate_frame_count(frames, min_frames, warnings)
205
+
206
+ field_specs = _infer_fields(frames, sync_pattern)
207
+ checksum_type, checksum_pos, checksum_confidence = _detect_checksum(frames, checksum_types)
208
+ _validate_frame_checksums(frames, checksum_type, checksum_pos)
209
+
210
+ # ========== Build Results ==========
211
+ protocol_spec = _build_protocol_spec(
212
+ baud_rate,
213
+ sync_pattern,
214
+ frames,
215
+ field_specs,
216
+ checksum_type,
217
+ checksum_pos,
218
+ baud_confidence,
219
+ sync_confidence,
220
+ checksum_confidence,
221
+ )
222
+
223
+ return ReverseEngineeringResult(
224
+ protocol_spec=protocol_spec,
225
+ frames=frames,
226
+ baud_rate=baud_rate,
227
+ bit_stream=bit_stream,
228
+ byte_stream=byte_stream,
229
+ sync_positions=sync_positions,
230
+ characterization=characterization,
231
+ confidence=protocol_spec.confidence,
232
+ warnings=warnings,
233
+ )
234
+
235
+
236
+ def _validate_baud_confidence(baud_confidence: float, warnings: list[str]) -> None:
237
+ """Validate baud rate confidence and add warning if low.
238
+
239
+ Args:
240
+ baud_confidence: Confidence score for baud rate detection.
241
+ warnings: List to append warnings to.
242
+ """
186
243
  if baud_confidence < 0.7:
187
244
  warnings.append(f"Low baud rate confidence: {baud_confidence:.2f}")
188
245
 
189
- # ========== Step 3: Bit Stream Extraction ==========
190
- bit_stream = _extract_bit_stream(data, sample_rate, baud_rate, characterization["threshold"])
191
246
 
247
+ def _validate_bit_stream(bit_stream: str, warnings: list[str]) -> None:
248
+ """Validate bit stream length and add warning if short.
249
+
250
+ Args:
251
+ bit_stream: Extracted bit stream.
252
+ warnings: List to append warnings to.
253
+ """
192
254
  if len(bit_stream) < 100:
193
255
  warnings.append("Short bit stream extracted")
194
256
 
195
- # ========== Step 4: Byte Extraction ==========
196
- byte_positions, byte_stream = _extract_bytes(bit_stream)
197
257
 
258
+ def _validate_byte_stream(byte_stream: bytes, warnings: list[str]) -> None:
259
+ """Validate byte stream length and add warning if short.
260
+
261
+ Args:
262
+ byte_stream: Extracted byte stream.
263
+ warnings: List to append warnings to.
264
+ """
198
265
  if len(byte_stream) < 10:
199
266
  warnings.append("Few bytes extracted")
200
267
 
201
- # ========== Step 5: Sync Pattern Detection ==========
202
- sync_pattern, sync_positions, sync_confidence = _detect_sync_pattern(
203
- byte_stream, max_frame_length
204
- )
205
268
 
206
- # ========== Step 6: Frame Extraction ==========
207
- frames = _extract_frames(bit_stream, byte_stream, byte_positions, sync_positions, sync_pattern)
269
+ def _validate_frame_count(
270
+ frames: list[InferredFrame], min_frames: int, warnings: list[str]
271
+ ) -> None:
272
+ """Validate frame count and add warning if insufficient.
208
273
 
274
+ Args:
275
+ frames: List of extracted frames.
276
+ min_frames: Minimum required frames.
277
+ warnings: List to append warnings to.
278
+ """
209
279
  if len(frames) < min_frames:
210
280
  warnings.append(f"Only {len(frames)} frames found (minimum {min_frames})")
211
281
 
212
- # ========== Step 7: Field Analysis ==========
213
- field_specs = _infer_fields(frames, sync_pattern)
214
282
 
215
- # ========== Step 8: Checksum Analysis ==========
216
- checksum_type, checksum_pos, checksum_confidence = _detect_checksum(frames, checksum_types)
283
+ def _validate_frame_checksums(
284
+ frames: list[InferredFrame], checksum_type: str | None, checksum_pos: int | None
285
+ ) -> None:
286
+ """Validate checksums for all frames.
217
287
 
288
+ Args:
289
+ frames: List of frames to validate.
290
+ checksum_type: Detected checksum type.
291
+ checksum_pos: Position of checksum in frame.
292
+ """
218
293
  if checksum_type:
219
- # Validate frames with checksum
220
294
  for frame in frames:
221
295
  frame.checksum_valid = _verify_checksum(frame.raw_bytes, checksum_type, checksum_pos)
222
296
 
223
- # ========== Build Protocol Specification ==========
297
+
298
+ def _build_protocol_spec(
299
+ baud_rate: float,
300
+ sync_pattern: bytes,
301
+ frames: list[InferredFrame],
302
+ field_specs: list[FieldSpec],
303
+ checksum_type: str | None,
304
+ checksum_pos: int | None,
305
+ baud_confidence: float,
306
+ sync_confidence: float,
307
+ checksum_confidence: float,
308
+ ) -> ProtocolSpec:
309
+ """Build protocol specification from analysis results.
310
+
311
+ Args:
312
+ baud_rate: Detected baud rate.
313
+ sync_pattern: Detected sync pattern.
314
+ frames: List of extracted frames.
315
+ field_specs: List of field specifications.
316
+ checksum_type: Detected checksum type.
317
+ checksum_pos: Position of checksum.
318
+ baud_confidence: Baud rate detection confidence.
319
+ sync_confidence: Sync pattern detection confidence.
320
+ checksum_confidence: Checksum detection confidence.
321
+
322
+ Returns:
323
+ Complete protocol specification.
324
+ """
224
325
  frame_lengths = [len(f.raw_bytes) for f in frames]
225
326
  frame_length = int(np.median(frame_lengths)) if len(set(frame_lengths)) == 1 else None
226
327
 
227
- # Calculate overall confidence
228
328
  overall_confidence = (
229
329
  baud_confidence * 0.3
230
330
  + sync_confidence * 0.3
@@ -232,10 +332,10 @@ def reverse_engineer_signal(
232
332
  + min(len(frames) / 10, 1.0) * 0.2
233
333
  )
234
334
 
235
- protocol_spec = ProtocolSpec(
335
+ return ProtocolSpec(
236
336
  name="Unknown Protocol (Inferred)",
237
337
  baud_rate=baud_rate,
238
- frame_format="8N1", # Most common
338
+ frame_format="8N1",
239
339
  sync_pattern=sync_pattern.hex() if sync_pattern else "",
240
340
  frame_length=frame_length,
241
341
  fields=field_specs,
@@ -244,18 +344,6 @@ def reverse_engineer_signal(
244
344
  confidence=overall_confidence,
245
345
  )
246
346
 
247
- return ReverseEngineeringResult(
248
- protocol_spec=protocol_spec,
249
- frames=frames,
250
- baud_rate=baud_rate,
251
- bit_stream=bit_stream,
252
- byte_stream=byte_stream,
253
- sync_positions=sync_positions,
254
- characterization=characterization,
255
- confidence=overall_confidence,
256
- warnings=warnings,
257
- )
258
-
259
347
 
260
348
  def _characterize_signal(
261
349
  data: np.ndarray[Any, np.dtype[np.float64]], sample_rate: float
@@ -371,13 +459,39 @@ def _detect_sync_pattern(
371
459
  byte_stream: bytes,
372
460
  max_frame_length: int,
373
461
  ) -> tuple[bytes, list[int], float]:
374
- """Detect sync pattern by finding repeated byte sequences."""
462
+ """Detect sync pattern by finding repeated byte sequences.
463
+
464
+ Args:
465
+ byte_stream: Stream of bytes to analyze.
466
+ max_frame_length: Maximum expected frame length.
467
+
468
+ Returns:
469
+ Tuple of (pattern, positions, confidence).
470
+ """
375
471
  if len(byte_stream) < 20:
376
472
  return b"", [], 0.0
377
473
 
378
- list(byte_stream)
379
-
380
474
  # Try common sync patterns first
475
+ best_pattern, best_positions, best_confidence = _find_common_sync_patterns(byte_stream)
476
+
477
+ # If no common pattern found, try to find repeating sequences
478
+ if best_confidence < 0.5:
479
+ pattern, positions, confidence = _find_repeating_patterns(byte_stream)
480
+ if confidence > best_confidence:
481
+ best_pattern, best_positions, best_confidence = pattern, positions, confidence
482
+
483
+ return best_pattern, best_positions, best_confidence
484
+
485
+
486
+ def _find_common_sync_patterns(byte_stream: bytes) -> tuple[bytes, list[int], float]:
487
+ """Search for common sync patterns.
488
+
489
+ Args:
490
+ byte_stream: Stream of bytes to analyze.
491
+
492
+ Returns:
493
+ Tuple of (best_pattern, positions, confidence).
494
+ """
381
495
  common_patterns = [
382
496
  bytes([0xAA, 0x55]),
383
497
  bytes([0x55, 0xAA]),
@@ -392,47 +506,85 @@ def _detect_sync_pattern(
392
506
  best_confidence = 0.0
393
507
 
394
508
  for pattern in common_patterns:
395
- positions = []
396
- for i in range(len(byte_stream) - len(pattern)):
397
- if byte_stream[i : i + len(pattern)] == pattern:
398
- positions.append(i)
509
+ positions = _find_pattern_positions(byte_stream, pattern)
399
510
 
400
511
  if len(positions) >= 3:
401
- # Check for regular spacing
402
- spacings = np.diff(positions)
403
- if len(spacings) > 0:
404
- median_spacing = np.median(spacings)
405
- regularity = 1.0 - np.std(spacings) / (median_spacing + 1)
512
+ confidence = _calculate_pattern_confidence(positions)
406
513
 
407
- confidence = min(len(positions) / 10, 1.0) * max(regularity, 0)
514
+ if confidence > best_confidence:
515
+ best_pattern = pattern
516
+ best_positions = positions
517
+ best_confidence = confidence
408
518
 
409
- if confidence > best_confidence:
410
- best_pattern = pattern
411
- best_positions = positions
412
- best_confidence = confidence
519
+ return best_pattern, best_positions, best_confidence
413
520
 
414
- # If no common pattern found, try to find repeating sequences
415
- if best_confidence < 0.5:
416
- for pattern_len in range(1, 4):
417
- for start in range(min(50, len(byte_stream) - pattern_len)):
418
- pattern = byte_stream[start : start + pattern_len]
419
- positions = []
420
- for i in range(len(byte_stream) - pattern_len):
421
- if byte_stream[i : i + pattern_len] == pattern:
422
- positions.append(i)
423
-
424
- if len(positions) >= 5:
425
- spacings = np.diff(positions)
426
- if len(spacings) > 0 and np.std(spacings) / (np.median(spacings) + 1) < 0.2:
427
- confidence = min(len(positions) / 10, 1.0)
428
- if confidence > best_confidence:
429
- best_pattern = pattern
430
- best_positions = positions
431
- best_confidence = confidence
521
+
522
+ def _find_repeating_patterns(byte_stream: bytes) -> tuple[bytes, list[int], float]:
523
+ """Search for repeating byte sequences.
524
+
525
+ Args:
526
+ byte_stream: Stream of bytes to analyze.
527
+
528
+ Returns:
529
+ Tuple of (best_pattern, positions, confidence).
530
+ """
531
+ best_pattern = b""
532
+ best_positions: list[int] = []
533
+ best_confidence = 0.0
534
+
535
+ for pattern_len in range(1, 4):
536
+ for start in range(min(50, len(byte_stream) - pattern_len)):
537
+ pattern = byte_stream[start : start + pattern_len]
538
+ positions = _find_pattern_positions(byte_stream, pattern)
539
+
540
+ if len(positions) >= 5:
541
+ spacings = np.diff(positions)
542
+ if len(spacings) > 0 and np.std(spacings) / (np.median(spacings) + 1) < 0.2:
543
+ confidence = min(len(positions) / 10, 1.0)
544
+ if confidence > best_confidence:
545
+ best_pattern = pattern
546
+ best_positions = positions
547
+ best_confidence = confidence
432
548
 
433
549
  return best_pattern, best_positions, best_confidence
434
550
 
435
551
 
552
+ def _find_pattern_positions(byte_stream: bytes, pattern: bytes) -> list[int]:
553
+ """Find all positions where pattern occurs in byte stream.
554
+
555
+ Args:
556
+ byte_stream: Stream to search.
557
+ pattern: Pattern to find.
558
+
559
+ Returns:
560
+ List of byte positions where pattern occurs.
561
+ """
562
+ positions = []
563
+ for i in range(len(byte_stream) - len(pattern)):
564
+ if byte_stream[i : i + len(pattern)] == pattern:
565
+ positions.append(i)
566
+ return positions
567
+
568
+
569
+ def _calculate_pattern_confidence(positions: list[int]) -> float:
570
+ """Calculate confidence score for pattern regularity.
571
+
572
+ Args:
573
+ positions: List of pattern positions.
574
+
575
+ Returns:
576
+ Confidence score (0.0 to 1.0).
577
+ """
578
+ spacings = np.diff(positions)
579
+ if len(spacings) == 0:
580
+ return 0.0
581
+
582
+ median_spacing = float(np.median(spacings))
583
+ regularity = 1.0 - float(np.std(spacings)) / (median_spacing + 1)
584
+
585
+ return min(len(positions) / 10, 1.0) * max(regularity, 0)
586
+
587
+
436
588
  def _extract_frames(
437
589
  bit_stream: str,
438
590
  byte_stream: bytes,
@@ -481,70 +633,105 @@ def _infer_fields(frames: list[InferredFrame], sync_pattern: bytes) -> list[Fiel
481
633
  fields: list[FieldSpec] = []
482
634
  sync_len = len(sync_pattern)
483
635
 
484
- # Sync field
485
- if sync_len > 0:
636
+ _add_sync_field(fields, sync_pattern)
637
+ length_offset = _try_add_length_field(fields, frames, sync_len)
638
+ _add_data_field(fields, frames, length_offset, sync_len)
639
+ _add_checksum_field(fields)
640
+
641
+ return fields
642
+
643
+
644
+ def _add_sync_field(fields: list[FieldSpec], sync_pattern: bytes) -> None:
645
+ """Add sync field if pattern exists.
646
+
647
+ Args:
648
+ fields: Field list to append to.
649
+ sync_pattern: Sync pattern bytes.
650
+ """
651
+ if len(sync_pattern) > 0:
486
652
  fields.append(
487
653
  FieldSpec(
488
654
  name="sync",
489
655
  offset=0,
490
- size=sync_len,
656
+ size=len(sync_pattern),
491
657
  field_type="constant",
492
658
  value=sync_pattern.hex(),
493
659
  )
494
660
  )
495
661
 
496
- # Analyze remaining bytes
662
+
663
+ def _try_add_length_field(
664
+ fields: list[FieldSpec], frames: list[InferredFrame], sync_len: int
665
+ ) -> int:
666
+ """Try to detect and add length field.
667
+
668
+ Args:
669
+ fields: Field list to append to.
670
+ frames: Frame list to analyze.
671
+ sync_len: Length of sync pattern.
672
+
673
+ Returns:
674
+ Offset after length field (or sync_len if no length detected).
675
+ """
497
676
  frame_lengths = [len(f.raw_bytes) for f in frames]
498
677
  min_len = min(frame_lengths)
499
-
500
- # Check for length field (common at offset 2 or after sync)
501
678
  length_offset = sync_len
502
- if min_len > length_offset:
503
- length_values = [
504
- f.raw_bytes[length_offset] for f in frames if len(f.raw_bytes) > length_offset
505
- ]
506
- frame_lens = [len(f.raw_bytes) for f in frames]
507
-
508
- # Check if value correlates with frame length
509
- if len(length_values) > 2:
510
- correlation = (
511
- np.corrcoef(length_values, frame_lens)[0, 1] if len(set(length_values)) > 1 else 0
512
- )
513
- if correlation > 0.8 or (
514
- len(set(length_values)) == 1 and length_values[0] == frame_lens[0]
515
- ):
516
- fields.append(
517
- FieldSpec(
518
- name="length",
519
- offset=length_offset,
520
- size=1,
521
- field_type="uint8",
522
- )
523
- )
524
- length_offset += 1
525
-
526
- # Assume remaining bytes are data + checksum
679
+
680
+ if min_len <= length_offset:
681
+ return length_offset
682
+
683
+ length_values = [f.raw_bytes[length_offset] for f in frames if len(f.raw_bytes) > length_offset]
684
+
685
+ if len(length_values) <= 2:
686
+ return length_offset
687
+
688
+ # Check correlation
689
+ if len(set(length_values)) > 1:
690
+ correlation = np.corrcoef(length_values, frame_lengths)[0, 1]
691
+ else:
692
+ correlation = 0
693
+
694
+ if correlation > 0.8 or (len(set(length_values)) == 1 and length_values[0] == frame_lengths[0]):
695
+ fields.append(FieldSpec(name="length", offset=length_offset, size=1, field_type="uint8"))
696
+ return length_offset + 1
697
+
698
+ return length_offset
699
+
700
+
701
+ def _add_data_field(
702
+ fields: list[FieldSpec],
703
+ frames: list[InferredFrame],
704
+ length_offset: int,
705
+ sync_len: int,
706
+ ) -> None:
707
+ """Add data field for payload.
708
+
709
+ Args:
710
+ fields: Field list to append to.
711
+ frames: Frame list.
712
+ length_offset: Offset after length field.
713
+ sync_len: Length of sync pattern.
714
+ """
715
+ min_len = min(len(f.raw_bytes) for f in frames)
716
+
527
717
  if min_len > length_offset + 1:
528
718
  fields.append(
529
719
  FieldSpec(
530
720
  name="data",
531
721
  offset=length_offset,
532
- size="length - sync_len - 2", # Variable
722
+ size="length - sync_len - 2",
533
723
  field_type="bytes",
534
724
  )
535
725
  )
536
726
 
537
- # Last byte likely checksum
538
- fields.append(
539
- FieldSpec(
540
- name="checksum",
541
- offset=-1,
542
- size=1,
543
- field_type="checksum",
544
- )
545
- )
546
727
 
547
- return fields
728
+ def _add_checksum_field(fields: list[FieldSpec]) -> None:
729
+ """Add checksum field at end.
730
+
731
+ Args:
732
+ fields: Field list to append to.
733
+ """
734
+ fields.append(FieldSpec(name="checksum", offset=-1, size=1, field_type="checksum"))
548
735
 
549
736
 
550
737
  def _detect_checksum(
@@ -589,40 +776,54 @@ def _detect_checksum(
589
776
 
590
777
  def _calculate_checksum(data: bytes, checksum_type: str) -> int:
591
778
  """Calculate checksum using specified algorithm."""
592
- if checksum_type == "xor":
593
- result = 0
594
- for b in data:
595
- result ^= b
596
- return result
597
-
598
- elif checksum_type == "sum8":
599
- return sum(data) & 0xFF
600
-
601
- elif checksum_type == "crc8":
602
- # Simple CRC-8
603
- crc = 0
604
- for b in data:
605
- crc ^= b
606
- for _ in range(8):
607
- if crc & 0x80:
608
- crc = ((crc << 1) ^ 0x07) & 0xFF
609
- else:
610
- crc = (crc << 1) & 0xFF
611
- return crc
612
-
613
- elif checksum_type == "crc16":
614
- # CRC-16-CCITT (return low byte only for single-byte comparison)
615
- crc = 0xFFFF
616
- for b in data:
617
- crc ^= b << 8
618
- for _ in range(8):
619
- if crc & 0x8000:
620
- crc = ((crc << 1) ^ 0x1021) & 0xFFFF
621
- else:
622
- crc = (crc << 1) & 0xFFFF
623
- return crc & 0xFF # Return low byte
624
-
625
- return 0
779
+ checksum_funcs = {
780
+ "xor": _checksum_xor,
781
+ "sum8": _checksum_sum8,
782
+ "crc8": _checksum_crc8,
783
+ "crc16": _checksum_crc16,
784
+ }
785
+
786
+ func = checksum_funcs.get(checksum_type)
787
+ return func(data) if func else 0
788
+
789
+
790
+ def _checksum_xor(data: bytes) -> int:
791
+ """Calculate XOR checksum."""
792
+ result = 0
793
+ for b in data:
794
+ result ^= b
795
+ return result
796
+
797
+
798
+ def _checksum_sum8(data: bytes) -> int:
799
+ """Calculate 8-bit sum checksum."""
800
+ return sum(data) & 0xFF
801
+
802
+
803
+ def _checksum_crc8(data: bytes) -> int:
804
+ """Calculate simple CRC-8."""
805
+ crc = 0
806
+ for b in data:
807
+ crc ^= b
808
+ for _ in range(8):
809
+ if crc & 0x80:
810
+ crc = ((crc << 1) ^ 0x07) & 0xFF
811
+ else:
812
+ crc = (crc << 1) & 0xFF
813
+ return crc
814
+
815
+
816
+ def _checksum_crc16(data: bytes) -> int:
817
+ """Calculate CRC-16-CCITT (return low byte only)."""
818
+ crc = 0xFFFF
819
+ for b in data:
820
+ crc ^= b << 8
821
+ for _ in range(8):
822
+ if crc & 0x8000:
823
+ crc = ((crc << 1) ^ 0x1021) & 0xFFFF
824
+ else:
825
+ crc = (crc << 1) & 0xFFFF
826
+ return crc & 0xFF
626
827
 
627
828
 
628
829
  def _verify_checksum(data: bytes, checksum_type: str, checksum_pos: int | None) -> bool: