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
@@ -91,97 +91,292 @@ def detect_checksum_fields(
91
91
  if not messages:
92
92
  return []
93
93
 
94
- # Convert all messages to bytes
95
- byte_messages = []
96
- for msg in messages:
97
- if isinstance(msg, np.ndarray):
98
- byte_messages.append(msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten()))
99
- else:
100
- byte_messages.append(bytes(msg))
101
-
102
- # Find minimum message length
94
+ byte_messages = _convert_messages_to_bytes(messages)
103
95
  min_len = min(len(msg) for msg in byte_messages)
104
96
 
105
97
  if min_len < 2:
106
98
  return []
107
99
 
108
- # Determine candidate positions
109
100
  if candidate_offsets is None:
110
- # Check header (first 16 bytes) and trailer (last 16 bytes)
111
- header_positions = list(range(min(16, min_len - 1)))
112
- trailer_start = max(0, min_len - 16)
113
- trailer_positions = list(range(trailer_start, min_len - 1))
114
- candidate_offsets = list(set(header_positions + trailer_positions))
101
+ candidate_offsets = _generate_default_candidate_offsets(min_len)
102
+
103
+ candidates = _test_candidate_offsets(byte_messages, candidate_offsets, min_len)
104
+ candidates.sort(key=lambda c: c.correlation, reverse=True)
105
+
106
+ return candidates
107
+
115
108
 
109
+ def _generate_default_candidate_offsets(min_len: int) -> list[int]:
110
+ """Generate default candidate positions in header and trailer."""
111
+ header_positions = list(range(min(16, min_len - 1)))
112
+ trailer_start = max(0, min_len - 16)
113
+ trailer_positions = list(range(trailer_start, min_len - 1))
114
+ return list(set(header_positions + trailer_positions))
115
+
116
+
117
+ def _test_candidate_offsets(
118
+ byte_messages: list[bytes], candidate_offsets: list[int], min_len: int
119
+ ) -> list[ChecksumCandidate]:
120
+ """Test each candidate offset for checksum correlation."""
116
121
  candidates = []
117
122
 
118
- # Test each candidate offset for different field sizes
119
123
  for offset in candidate_offsets:
120
124
  for size in [1, 2, 4]:
121
125
  if offset + size > min_len:
122
126
  continue
123
127
 
124
- # Extract field values and content
125
- field_values = []
126
- content_hashes = []
128
+ candidate = _analyze_field_correlation(byte_messages, offset, size, min_len)
129
+ if candidate is not None:
130
+ candidates.append(candidate)
127
131
 
128
- for msg in byte_messages:
129
- if len(msg) < offset + size:
130
- continue
132
+ return candidates
131
133
 
132
- # Extract field value
133
- field_bytes = msg[offset : offset + size]
134
- field_value = int.from_bytes(field_bytes, byteorder="big")
135
- field_values.append(field_value)
136
134
 
137
- # Hash content (excluding the field itself)
138
- content = msg[:offset] + msg[offset + size :]
139
- content_hash = hash(content)
140
- content_hashes.append(content_hash)
135
+ def _analyze_field_correlation(
136
+ byte_messages: list[bytes], offset: int, size: int, min_len: int
137
+ ) -> ChecksumCandidate | None:
138
+ """Analyze correlation between field and message content."""
139
+ field_values = []
140
+ content_hashes = []
141
141
 
142
- if len(field_values) < 2:
143
- continue
142
+ for msg in byte_messages:
143
+ if len(msg) < offset + size:
144
+ continue
144
145
 
145
- # Calculate correlation between field and content
146
- # If field varies with content, it's a good candidate
147
- unique_content = len(set(content_hashes))
148
- unique_fields = len(set(field_values))
146
+ field_bytes = msg[offset : offset + size]
147
+ field_value = int.from_bytes(field_bytes, byteorder="big")
148
+ field_values.append(field_value)
149
149
 
150
- if unique_content > 1:
151
- # Correlation estimate: how much field varies with content
152
- correlation = min(1.0, unique_fields / unique_content)
153
- else:
154
- correlation = 0.0
150
+ content = msg[:offset] + msg[offset + size :]
151
+ content_hash = hash(content)
152
+ content_hashes.append(content_hash)
153
+
154
+ if len(field_values) < 2:
155
+ return None
156
+
157
+ correlation = _calculate_field_correlation(content_hashes, field_values)
158
+
159
+ if correlation < 0.3:
160
+ return None
161
+
162
+ position: Literal["header", "trailer"] = "header" if offset < min_len // 2 else "trailer"
163
+ likely_scope = (offset + size, min_len) if position == "header" else (0, offset)
164
+
165
+ return ChecksumCandidate(
166
+ offset=offset,
167
+ size=size,
168
+ position=position,
169
+ correlation=correlation,
170
+ likely_scope=likely_scope,
171
+ )
172
+
173
+
174
+ def _calculate_field_correlation(content_hashes: list[int], field_values: list[int]) -> float:
175
+ """Calculate correlation between content and field variability."""
176
+ unique_content = len(set(content_hashes))
177
+ unique_fields = len(set(field_values))
178
+
179
+ if unique_content > 1:
180
+ return min(1.0, unique_fields / unique_content)
181
+
182
+ return 0.0
183
+
184
+
185
+ def _convert_messages_to_bytes(messages: list[DataType]) -> list[bytes]:
186
+ """Convert messages to bytes format.
187
+
188
+ Args:
189
+ messages: List of messages in various formats.
190
+
191
+ Returns:
192
+ List of messages as bytes objects.
193
+ """
194
+ byte_messages = []
195
+ for msg in messages:
196
+ if isinstance(msg, np.ndarray):
197
+ byte_messages.append(msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten()))
198
+ else:
199
+ byte_messages.append(bytes(msg))
200
+ return byte_messages
201
+
202
+
203
+ def _get_algorithms_for_size(size: int) -> list[str]:
204
+ """Get list of checksum algorithms to test for given field size.
205
+
206
+ Args:
207
+ size: Field size in bytes (1, 2, or 4).
208
+
209
+ Returns:
210
+ List of algorithm names to test.
211
+ """
212
+ if size == 1:
213
+ return ["xor", "sum8"]
214
+ elif size == 2:
215
+ return ["sum16_big", "sum16_little", "crc16_ccitt", "crc16_ibm", "crc16", "checksum"]
216
+ elif size == 4:
217
+ return ["crc32"]
218
+ return []
219
+
220
+
221
+ def _normalize_algorithm_name(algo: str) -> str:
222
+ """Normalize algorithm name to actual function name.
223
+
224
+ Args:
225
+ algo: Original algorithm name.
226
+
227
+ Returns:
228
+ Normalized algorithm name.
229
+ """
230
+ if algo == "crc16":
231
+ return "crc16_ccitt"
232
+ elif algo == "checksum":
233
+ return "sum16_big"
234
+ return algo
235
+
236
+
237
+ def _get_init_values_for_algorithm(algo: str) -> list[int | None]:
238
+ """Get list of initialization values to test for algorithm.
239
+
240
+ Args:
241
+ algo: Algorithm name.
242
+
243
+ Returns:
244
+ List of init values to test (None means use default).
245
+ """
246
+ if algo in ["crc16_ccitt", "crc16_ibm"]:
247
+ return [0x0000, 0xFFFF]
248
+ return [None]
249
+
250
+
251
+ def _extract_checksummed_data(
252
+ msg: bytes, scope_start: int, scope_end: int, field_offset: int, field_size: int
253
+ ) -> bytes:
254
+ """Extract data region for checksum calculation.
255
+
256
+ Args:
257
+ msg: Full message bytes.
258
+ scope_start: Start offset of checksummed region.
259
+ scope_end: End offset of checksummed region.
260
+ field_offset: Offset of checksum field.
261
+ field_size: Size of checksum field.
262
+
263
+ Returns:
264
+ Data bytes to be checksummed (excluding checksum field if in range).
265
+ """
266
+ if scope_start < field_offset < scope_end:
267
+ # Exclude checksum field from data
268
+ return msg[scope_start:field_offset] + msg[field_offset + field_size : scope_end]
269
+ else:
270
+ return msg[scope_start:scope_end]
271
+
272
+
273
+ def _test_checksum_match(
274
+ byte_messages: list[bytes],
275
+ field_offset: int,
276
+ field_size: int,
277
+ algo: str,
278
+ init_val: int | None,
279
+ scope_start: int,
280
+ scope_end: int,
281
+ ) -> int:
282
+ """Test if checksum algorithm matches messages in given scope.
155
283
 
156
- # Skip if correlation is too low
157
- if correlation < 0.3:
284
+ Args:
285
+ byte_messages: List of message bytes.
286
+ field_offset: Offset of checksum field.
287
+ field_size: Size of checksum field in bytes.
288
+ algo: Algorithm name.
289
+ init_val: Initialization value for CRC algorithms.
290
+ scope_start: Start of checksummed region.
291
+ scope_end: End of checksummed region.
292
+
293
+ Returns:
294
+ Number of messages that match the algorithm.
295
+ """
296
+ matches = 0
297
+
298
+ for msg in byte_messages:
299
+ if len(msg) < scope_end:
300
+ continue
301
+
302
+ # Try both big and little endian for field extraction
303
+ endian_val: Literal["big", "little"]
304
+ for endian_val in ("big", "little"):
305
+ expected = int.from_bytes(
306
+ msg[field_offset : field_offset + field_size], byteorder=endian_val
307
+ )
308
+
309
+ data = _extract_checksummed_data(msg, scope_start, scope_end, field_offset, field_size)
310
+
311
+ # Compute checksum
312
+ try:
313
+ if init_val is not None:
314
+ computed = compute_checksum(data, algo, init=init_val)
315
+ else:
316
+ computed = compute_checksum(data, algo)
317
+
318
+ if computed == expected:
319
+ matches += 1
320
+ break # Found match with this endian
321
+ except Exception:
322
+ pass
323
+
324
+ return matches
325
+
326
+
327
+ def _test_algorithm_variant(
328
+ byte_messages: list[bytes],
329
+ field_offset: int,
330
+ size: int,
331
+ algo: str,
332
+ init_val: int | None,
333
+ ) -> ChecksumMatch | None:
334
+ """Test an algorithm variant across different scopes.
335
+
336
+ Args:
337
+ byte_messages: List of message bytes.
338
+ field_offset: Offset of checksum field.
339
+ size: Field size in bytes.
340
+ algo: Original algorithm name.
341
+ init_val: Initialization value for CRC.
342
+
343
+ Returns:
344
+ ChecksumMatch if match rate >= 80%, None otherwise.
345
+ """
346
+ actual_algo = _normalize_algorithm_name(algo)
347
+ best_match = None
348
+ best_rate = 0.0
349
+
350
+ # Try different scopes
351
+ for scope_start in [0, field_offset + size]:
352
+ for scope_end in [field_offset, len(byte_messages[0])]:
353
+ if scope_end <= scope_start:
158
354
  continue
159
355
 
160
- # Determine position (header vs trailer)
161
- position: Literal["header", "trailer"] = (
162
- "header" if offset < min_len // 2 else "trailer"
356
+ matches = _test_checksum_match(
357
+ byte_messages, field_offset, size, actual_algo, init_val, scope_start, scope_end
163
358
  )
164
359
 
165
- # Estimate likely scope
166
- if position == "header":
167
- likely_scope = (offset + size, min_len)
168
- else:
169
- likely_scope = (0, offset)
360
+ total = sum(1 for msg in byte_messages if len(msg) >= scope_end)
361
+ if total == 0:
362
+ continue
363
+
364
+ match_rate = matches / total
170
365
 
171
- candidates.append(
172
- ChecksumCandidate(
173
- offset=offset,
366
+ # Consider it a match if >= 80% of messages match
367
+ if match_rate >= 0.8 and match_rate > best_rate:
368
+ best_rate = match_rate
369
+ best_match = ChecksumMatch(
370
+ algorithm=algo,
371
+ offset=field_offset,
174
372
  size=size,
175
- position=position,
176
- correlation=correlation,
177
- likely_scope=likely_scope,
373
+ scope_start=scope_start,
374
+ scope_end=scope_end,
375
+ match_rate=match_rate,
376
+ init_value=init_val,
178
377
  )
179
- )
180
378
 
181
- # Sort by correlation descending
182
- candidates.sort(key=lambda c: c.correlation, reverse=True)
183
-
184
- return candidates
379
+ return best_match
185
380
 
186
381
 
187
382
  def identify_checksum_algorithm(
@@ -211,19 +406,10 @@ def identify_checksum_algorithm(
211
406
  if not messages:
212
407
  return None
213
408
 
214
- # Convert messages to bytes
215
- byte_messages = []
216
- for msg in messages:
217
- if isinstance(msg, np.ndarray):
218
- byte_messages.append(msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten()))
219
- else:
220
- byte_messages.append(bytes(msg))
409
+ byte_messages = _convert_messages_to_bytes(messages)
221
410
 
222
- # Determine field size if not specified
223
- if field_size is None:
224
- field_sizes = [1, 2, 4]
225
- else:
226
- field_sizes = [field_size]
411
+ # Determine field sizes to test
412
+ field_sizes = [1, 2, 4] if field_size is None else [field_size]
227
413
 
228
414
  best_match = None
229
415
  best_rate = 0.0
@@ -233,103 +419,21 @@ def identify_checksum_algorithm(
233
419
  if any(len(msg) < field_offset + size for msg in byte_messages):
234
420
  continue
235
421
 
236
- # Define algorithm tests based on field size
237
- if size == 1:
238
- algorithms = ["xor", "sum8"]
239
- elif size == 2:
240
- # Include both big and little endian CRC variants
241
- algorithms = [
242
- "sum16_big",
243
- "sum16_little",
244
- "crc16_ccitt",
245
- "crc16_ibm",
246
- "crc16",
247
- "checksum",
248
- ]
249
- elif size == 4:
250
- algorithms = ["crc32"]
251
- else:
422
+ algorithms = _get_algorithms_for_size(size)
423
+ if not algorithms:
252
424
  continue
253
425
 
254
426
  # Test each algorithm
255
427
  for algo in algorithms:
256
- # Map algorithm names to computation functions
257
- actual_algo = algo
258
- if algo == "crc16":
259
- actual_algo = "crc16_ccitt"
260
- elif algo == "checksum":
261
- actual_algo = "sum16_big"
262
-
263
- # For CRC algorithms, try different init values
264
- init_values: list[int | None] = [None]
265
- if actual_algo in ["crc16_ccitt", "crc16_ibm"]:
266
- init_values = [0x0000, 0xFFFF]
428
+ actual_algo = _normalize_algorithm_name(algo)
429
+ init_values = _get_init_values_for_algorithm(actual_algo)
267
430
 
268
431
  for init_val in init_values:
269
- # Try different scopes
270
- for scope_start in [0, field_offset + size]:
271
- for scope_end in [field_offset, len(byte_messages[0])]:
272
- if scope_end <= scope_start:
273
- continue
274
-
275
- # Test algorithm on all messages
276
- matches = 0
277
- total = 0
278
-
279
- for msg in byte_messages:
280
- if len(msg) < scope_end:
281
- continue
282
-
283
- # Try both big and little endian for field extraction
284
- endian_val: Literal["big", "little"]
285
- for endian_val in ("big", "little"): # type: ignore[assignment]
286
- expected = int.from_bytes(
287
- msg[field_offset : field_offset + size], byteorder=endian_val
288
- )
289
-
290
- # Extract data to checksum
291
- if scope_start < field_offset < scope_end:
292
- # Exclude checksum field from data
293
- data = (
294
- msg[scope_start:field_offset]
295
- + msg[field_offset + size : scope_end]
296
- )
297
- else:
298
- data = msg[scope_start:scope_end]
299
-
300
- # Compute checksum
301
- try:
302
- if init_val is not None:
303
- computed = compute_checksum(
304
- data, actual_algo, init=init_val
305
- )
306
- else:
307
- computed = compute_checksum(data, actual_algo)
308
- if computed == expected:
309
- matches += 1
310
- break # Found match with this endian
311
- except Exception:
312
- pass
313
-
314
- total += 1
315
-
316
- if total == 0:
317
- continue
318
-
319
- match_rate = matches / total
320
-
321
- # Consider it a match if >= 80% of messages match
322
- if match_rate >= 0.8 and match_rate > best_rate:
323
- best_rate = match_rate
324
- best_match = ChecksumMatch(
325
- algorithm=algo,
326
- offset=field_offset,
327
- size=size,
328
- scope_start=scope_start,
329
- scope_end=scope_end,
330
- match_rate=match_rate,
331
- init_value=init_val,
332
- )
432
+ match = _test_algorithm_variant(byte_messages, field_offset, size, algo, init_val)
433
+
434
+ if match and match.match_rate > best_rate:
435
+ best_rate = match.match_rate
436
+ best_match = match
333
437
 
334
438
  return best_match
335
439
 
@@ -344,14 +448,13 @@ def verify_checksums(
344
448
  ) -> tuple[int, int]:
345
449
  """Verify checksums using identified algorithm.
346
450
 
347
- : Checksum and CRC Field Detection
348
-
349
451
  Validates checksums across multiple messages using the specified algorithm.
452
+ Tries both big-endian and little-endian byte orders.
350
453
 
351
454
  Args:
352
455
  messages: List of messages to verify
353
- algorithm: Checksum algorithm name
354
- field_offset: Offset of checksum field
456
+ algorithm: Checksum algorithm name (e.g., 'xor', 'sum8', 'crc16', 'crc32')
457
+ field_offset: Offset of checksum field in message
355
458
  scope_start: Start of checksummed data (default: 0)
356
459
  scope_end: End of checksummed data (None = message end)
357
460
  init_value: Initial value for CRC algorithms (None = use default)
@@ -368,66 +471,122 @@ def verify_checksums(
368
471
  if not messages:
369
472
  return (0, 0)
370
473
 
474
+ field_size = _get_checksum_field_size(algorithm)
371
475
  passed = 0
372
476
  failed = 0
373
477
 
374
- # Determine field size from algorithm
375
- if algorithm in ["xor", "sum8"]:
376
- field_size = 1
377
- elif algorithm.startswith("sum16") or algorithm.startswith("crc16"):
378
- field_size = 2
379
- elif algorithm == "crc32":
380
- field_size = 4
381
- else:
382
- # Try to infer from first message
383
- field_size = 1
384
-
385
478
  for msg in messages:
386
- if isinstance(msg, np.ndarray):
387
- msg = msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten())
479
+ msg_bytes = _normalize_message_to_bytes(msg)
480
+ if _verify_single_message(
481
+ msg_bytes, algorithm, field_offset, field_size, scope_start, scope_end, init_value
482
+ ):
483
+ passed += 1
388
484
  else:
389
- msg = bytes(msg)
485
+ failed += 1
390
486
 
391
- msg_scope_end = scope_end if scope_end is not None else len(msg)
487
+ return (passed, failed)
392
488
 
393
- if len(msg) < field_offset + field_size or len(msg) < msg_scope_end:
394
- failed += 1
395
- continue
396
489
 
397
- # Try both endiannesses
398
- matched = False
399
- endian_val2: Literal["big", "little"]
400
- for endian_val2 in ("big", "little"): # type: ignore[assignment]
401
- expected = int.from_bytes(
402
- msg[field_offset : field_offset + field_size], byteorder=endian_val2
403
- )
490
+ def _get_checksum_field_size(algorithm: str) -> int:
491
+ """Determine field size in bytes from algorithm name.
404
492
 
405
- # Extract data to checksum
406
- if scope_start < field_offset < msg_scope_end:
407
- data = (
408
- msg[scope_start:field_offset] + msg[field_offset + field_size : msg_scope_end]
409
- )
410
- else:
411
- data = msg[scope_start:msg_scope_end]
493
+ Args:
494
+ algorithm: Checksum algorithm name.
412
495
 
413
- # Compute checksum
414
- try:
415
- if init_value is not None:
416
- computed = compute_checksum(data, algorithm, init=init_value)
417
- else:
418
- computed = compute_checksum(data, algorithm)
419
- if computed == expected:
420
- matched = True
421
- break
422
- except Exception:
423
- pass
496
+ Returns:
497
+ Field size in bytes (1, 2, or 4).
498
+ """
499
+ if algorithm in ["xor", "sum8"]:
500
+ return 1
501
+ if algorithm.startswith("sum16") or algorithm.startswith("crc16"):
502
+ return 2
503
+ if algorithm == "crc32":
504
+ return 4
505
+ return 1 # Default to 1 byte
424
506
 
425
- if matched:
426
- passed += 1
427
- else:
428
- failed += 1
429
507
 
430
- return (passed, failed)
508
+ def _normalize_message_to_bytes(msg: DataType) -> bytes:
509
+ """Normalize message to bytes.
510
+
511
+ Args:
512
+ msg: Message as bytes, numpy array, or byte-like object.
513
+
514
+ Returns:
515
+ Message as bytes.
516
+ """
517
+ if isinstance(msg, np.ndarray):
518
+ return msg.tobytes() if msg.dtype == np.uint8 else bytes(msg.flatten())
519
+ return bytes(msg)
520
+
521
+
522
+ def _verify_single_message(
523
+ msg: bytes,
524
+ algorithm: str,
525
+ field_offset: int,
526
+ field_size: int,
527
+ scope_start: int,
528
+ scope_end: int | None,
529
+ init_value: int | None,
530
+ ) -> bool:
531
+ """Verify checksum for a single message.
532
+
533
+ Args:
534
+ msg: Message bytes.
535
+ algorithm: Checksum algorithm name.
536
+ field_offset: Offset of checksum field.
537
+ field_size: Size of checksum field in bytes.
538
+ scope_start: Start of checksummed data.
539
+ scope_end: End of checksummed data (None = message end).
540
+ init_value: Initial value for CRC algorithms.
541
+
542
+ Returns:
543
+ True if checksum matches, False otherwise.
544
+ """
545
+ msg_scope_end = scope_end if scope_end is not None else len(msg)
546
+
547
+ # Validate message length
548
+ if len(msg) < field_offset + field_size or len(msg) < msg_scope_end:
549
+ return False
550
+
551
+ # Try both endiannesses
552
+ for endian in ("big", "little"):
553
+ expected = int.from_bytes(msg[field_offset : field_offset + field_size], byteorder=endian)
554
+
555
+ # Extract data to checksum (excluding checksum field)
556
+ data = _extract_checksummed_data(msg, scope_start, msg_scope_end, field_offset, field_size)
557
+
558
+ # Compute and compare checksum
559
+ if _compute_and_compare(data, algorithm, expected, init_value):
560
+ return True
561
+
562
+ return False
563
+
564
+
565
+ def _compute_and_compare(
566
+ data: bytes,
567
+ algorithm: str,
568
+ expected: int,
569
+ init_value: int | None,
570
+ ) -> bool:
571
+ """Compute checksum and compare with expected value.
572
+
573
+ Args:
574
+ data: Data to checksum.
575
+ algorithm: Checksum algorithm name.
576
+ expected: Expected checksum value.
577
+ init_value: Initial value for CRC algorithms.
578
+
579
+ Returns:
580
+ True if computed checksum matches expected.
581
+ """
582
+ try:
583
+ if init_value is not None:
584
+ computed = compute_checksum(data, algorithm, init=init_value)
585
+ else:
586
+ computed = compute_checksum(data, algorithm)
587
+ return computed == expected
588
+ except Exception:
589
+ return False
431
590
 
432
591
 
433
592
  def compute_checksum(data: bytes, algorithm: str, **kwargs: Any) -> int: