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
@@ -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: