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