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
@@ -172,6 +172,84 @@ class MessageFormatInferrer:
172
172
 
173
173
  return schema
174
174
 
175
+ def _calculate_entropy_boundaries(
176
+ self, messages: list[NDArray[np.uint8]], msg_len: int
177
+ ) -> list[int]:
178
+ """Detect boundaries using entropy transitions.
179
+
180
+ Args:
181
+ messages: List of message arrays
182
+ msg_len: Length of messages
183
+
184
+ Returns:
185
+ List of boundary positions
186
+ """
187
+ boundaries = []
188
+ entropies = []
189
+
190
+ # Calculate entropy at each byte position
191
+ for offset in range(msg_len):
192
+ entropy = self._calculate_byte_entropy(messages, offset)
193
+ entropies.append(entropy)
194
+
195
+ # Find transitions (entropy changes > threshold)
196
+ entropy_threshold = 1.5 # bits
197
+ for i in range(1, len(entropies)):
198
+ delta = abs(entropies[i] - entropies[i - 1])
199
+ if delta > entropy_threshold:
200
+ boundaries.append(i)
201
+
202
+ return boundaries
203
+
204
+ def _calculate_variance_boundaries(
205
+ self, messages: list[NDArray[np.uint8]], msg_len: int
206
+ ) -> list[int]:
207
+ """Detect boundaries using variance transitions.
208
+
209
+ Args:
210
+ messages: List of message arrays
211
+ msg_len: Length of messages
212
+
213
+ Returns:
214
+ List of boundary positions
215
+ """
216
+ boundaries = []
217
+ variances = []
218
+
219
+ # Calculate variance at each byte position
220
+ for offset in range(msg_len):
221
+ values = [msg[offset] for msg in messages]
222
+ variance = np.var(values)
223
+ variances.append(variance)
224
+
225
+ # Find variance transitions
226
+ var_threshold = 1000.0
227
+ for i in range(1, len(variances)):
228
+ delta = abs(variances[i] - variances[i - 1])
229
+ if delta > var_threshold:
230
+ boundaries.append(i)
231
+
232
+ return boundaries
233
+
234
+ def _merge_close_boundaries(self, boundaries: list[int]) -> list[int]:
235
+ """Merge boundaries that are too close together.
236
+
237
+ Args:
238
+ boundaries: Sorted list of boundaries
239
+
240
+ Returns:
241
+ Merged boundary list
242
+ """
243
+ if not boundaries:
244
+ return []
245
+
246
+ merged = [boundaries[0]]
247
+ for b in boundaries[1:]:
248
+ if b - merged[-1] >= 2:
249
+ merged.append(b)
250
+
251
+ return merged
252
+
175
253
  def detect_field_boundaries(
176
254
  self,
177
255
  messages: list[NDArray[np.uint8]],
@@ -194,45 +272,18 @@ class MessageFormatInferrer:
194
272
  msg_len = len(messages[0])
195
273
  boundaries = [0] # Always start at offset 0
196
274
 
275
+ # Detect boundaries using selected method
197
276
  if method in ["entropy", "combined"]:
198
- # Calculate entropy at each byte position
199
- entropies = []
200
- for offset in range(msg_len):
201
- entropy = self._calculate_byte_entropy(messages, offset)
202
- entropies.append(entropy)
203
-
204
- # Find transitions (entropy changes > threshold)
205
- entropy_threshold = 1.5 # bits
206
- for i in range(1, len(entropies)):
207
- delta = abs(entropies[i] - entropies[i - 1])
208
- if delta > entropy_threshold and i not in boundaries:
209
- boundaries.append(i)
277
+ entropy_boundaries = self._calculate_entropy_boundaries(messages, msg_len)
278
+ boundaries.extend(entropy_boundaries)
210
279
 
211
280
  if method in ["variance", "combined"]:
212
- # Calculate variance at each byte position
213
- variances = []
214
- for offset in range(msg_len):
215
- values = [msg[offset] for msg in messages]
216
- variance = np.var(values)
217
- variances.append(variance)
218
-
219
- # Find variance transitions
220
- var_threshold = 1000.0
221
- for i in range(1, len(variances)):
222
- delta = abs(variances[i] - variances[i - 1])
223
- if delta > var_threshold and i not in boundaries:
224
- boundaries.append(i)
225
-
226
- # Sort and ensure we don't have too many tiny fields
227
- boundaries = sorted(set(boundaries))
281
+ variance_boundaries = self._calculate_variance_boundaries(messages, msg_len)
282
+ boundaries.extend(variance_boundaries)
228
283
 
229
- # Merge boundaries that are too close (< 2 bytes apart)
230
- merged = [boundaries[0]]
231
- for b in boundaries[1:]:
232
- if b - merged[-1] >= 2:
233
- merged.append(b)
234
-
235
- return merged
284
+ # Merge and sort boundaries
285
+ boundaries = sorted(set(boundaries))
286
+ return self._merge_close_boundaries(boundaries)
236
287
 
237
288
  def detect_boundaries_voting(
238
289
  self,
@@ -532,7 +583,30 @@ class MessageFormatInferrer:
532
583
  if len(messages) < self.min_samples:
533
584
  raise ValueError(f"Need at least {self.min_samples} messages, got {len(messages)}")
534
585
 
535
- # Convert all to bytes for voting
586
+ bytes_messages = self._convert_messages_to_bytes(messages)
587
+ msg_len = self._validate_message_lengths(bytes_messages)
588
+ boundaries = self.detect_boundaries_voting(
589
+ bytes_messages, min_confidence=min_boundary_confidence
590
+ )
591
+ msg_arrays = [np.frombuffer(msg, dtype=np.uint8) for msg in bytes_messages]
592
+ fields = self._infer_fields_from_boundaries(
593
+ boundaries, msg_arrays, msg_len, min_field_confidence
594
+ )
595
+ checksum_field, length_field = self._find_special_fields(fields)
596
+ header_size = self._estimate_header_size(fields)
597
+
598
+ return MessageSchema(
599
+ total_size=msg_len,
600
+ fields=fields,
601
+ field_boundaries=boundaries,
602
+ header_size=header_size,
603
+ payload_offset=header_size,
604
+ checksum_field=checksum_field,
605
+ length_field=length_field,
606
+ )
607
+
608
+ def _convert_messages_to_bytes(self, messages: list[bytes | NDArray[np.uint8]]) -> list[bytes]:
609
+ """Convert messages to bytes format."""
536
610
  bytes_messages = []
537
611
  for msg in messages:
538
612
  if isinstance(msg, bytes):
@@ -541,99 +615,81 @@ class MessageFormatInferrer:
541
615
  bytes_messages.append(msg.tobytes())
542
616
  else:
543
617
  raise ValueError(f"Invalid message type: {type(msg)}")
618
+ return bytes_messages
544
619
 
545
- # Check all messages are same length
546
- lengths = [len(m) for m in bytes_messages]
620
+ def _validate_message_lengths(self, messages: list[bytes]) -> int:
621
+ """Validate all messages have same length and return that length."""
622
+ lengths = [len(m) for m in messages]
547
623
  if len(set(lengths)) > 1:
548
624
  raise ValueError(f"Messages have varying lengths: {set(lengths)}")
625
+ return lengths[0]
549
626
 
550
- msg_len = lengths[0]
551
-
552
- # Detect boundaries with voting
553
- boundaries = self.detect_boundaries_voting(
554
- bytes_messages, min_confidence=min_boundary_confidence
555
- )
556
-
557
- # Convert to numpy arrays for field type detection
558
- msg_arrays = []
559
- for msg in bytes_messages:
560
- msg_arrays.append(np.frombuffer(msg, dtype=np.uint8))
561
-
562
- # Extract field candidates
627
+ def _infer_fields_from_boundaries(
628
+ self,
629
+ boundaries: list[int],
630
+ msg_arrays: list[NDArray[np.uint8]],
631
+ msg_len: int,
632
+ min_confidence: float,
633
+ ) -> list[InferredField]:
634
+ """Infer fields from detected boundaries."""
563
635
  fields: list[InferredField] = []
564
636
  for i in range(len(boundaries)):
565
637
  offset = boundaries[i]
566
-
567
- # Determine field size
568
- if i < len(boundaries) - 1:
569
- size = boundaries[i + 1] - offset
570
- else:
571
- size = msg_len - offset
572
-
573
- # Extract field values
638
+ size = boundaries[i + 1] - offset if i < len(boundaries) - 1 else msg_len - offset
574
639
  field_data = self._extract_field_data(msg_arrays, offset, size)
575
-
576
- # Run multiple field type detectors
577
- entropy_type, entropy_conf = self._detect_type_entropy(field_data)
578
- pattern_type, pattern_conf = self._detect_type_patterns(
579
- field_data, offset, size, msg_len
580
- )
581
- stats_type, stats_conf = self._detect_type_statistics(field_data)
582
-
583
- # Vote on field type
584
- field_type, confidence, evidence = self._vote_field_type(
585
- [
586
- (entropy_type, entropy_conf),
587
- (pattern_type, pattern_conf),
588
- (stats_type, stats_conf),
589
- ]
640
+ field_obj = self._classify_field_ensemble(
641
+ field_data, offset, size, msg_len, min_confidence
590
642
  )
591
-
592
- if confidence >= min_field_confidence:
593
- # Sample values (first 5)
594
- sample_values = field_data["values"][:5]
595
-
596
- field_obj = InferredField(
597
- name=f"field_{len(fields)}",
598
- offset=offset,
599
- size=size,
600
- field_type=field_type, # type: ignore[arg-type]
601
- entropy=float(field_data["entropy"]),
602
- variance=float(field_data["variance"]),
603
- confidence=confidence,
604
- values_seen=sample_values,
605
- evidence=evidence,
606
- )
607
-
643
+ if field_obj is not None:
644
+ field_obj.name = f"field_{len(fields)}"
608
645
  fields.append(field_obj)
646
+ return fields
609
647
 
610
- # Determine header size
611
- header_size = self._estimate_header_size(fields)
648
+ def _classify_field_ensemble(
649
+ self,
650
+ field_data: dict[str, Any],
651
+ offset: int,
652
+ size: int,
653
+ msg_len: int,
654
+ min_confidence: float,
655
+ ) -> InferredField | None:
656
+ """Classify field using ensemble of detectors."""
657
+ entropy_type, entropy_conf = self._detect_type_entropy(field_data)
658
+ pattern_type, pattern_conf = self._detect_type_patterns(field_data, offset, size, msg_len)
659
+ stats_type, stats_conf = self._detect_type_statistics(field_data)
660
+ field_type, confidence, evidence = self._vote_field_type(
661
+ [
662
+ (entropy_type, entropy_conf),
663
+ (pattern_type, pattern_conf),
664
+ (stats_type, stats_conf),
665
+ ]
666
+ )
667
+ if confidence < min_confidence:
668
+ return None
669
+ return InferredField(
670
+ name="", # Will be set by caller
671
+ offset=offset,
672
+ size=size,
673
+ field_type=field_type, # type: ignore[arg-type]
674
+ entropy=float(field_data["entropy"]),
675
+ variance=float(field_data["variance"]),
676
+ confidence=confidence,
677
+ values_seen=field_data["values"][:5],
678
+ evidence=evidence,
679
+ )
612
680
 
613
- # Find checksum and length fields
681
+ def _find_special_fields(
682
+ self, fields: list[InferredField]
683
+ ) -> tuple[InferredField | None, InferredField | None]:
684
+ """Find checksum and length fields."""
614
685
  checksum_field = None
615
686
  length_field = None
616
-
617
687
  for f in fields:
618
688
  if f.field_type == "checksum":
619
689
  checksum_field = f
620
690
  elif f.field_type == "length":
621
691
  length_field = f
622
-
623
- # Payload starts after header
624
- payload_offset = header_size
625
-
626
- schema = MessageSchema(
627
- total_size=msg_len,
628
- fields=fields,
629
- field_boundaries=boundaries,
630
- header_size=header_size,
631
- payload_offset=payload_offset,
632
- checksum_field=checksum_field,
633
- length_field=length_field,
634
- )
635
-
636
- return schema
692
+ return checksum_field, length_field
637
693
 
638
694
  def _extract_field_data(
639
695
  self, messages: list[NDArray[np.uint8]], offset: int, size: int
@@ -669,14 +725,14 @@ class MessageFormatInferrer:
669
725
  int(msg[offset]) << 16 | int(msg[offset + 1]) << 8 | int(msg[offset + 2])
670
726
  )
671
727
  int_values.append(val_int)
672
- values = list(int_values) # type: ignore[assignment]
728
+ values = list(int_values)
673
729
  else:
674
730
  # For larger fields, use bytes
675
731
  tuple_values: list[tuple[int, ...]] = []
676
732
  for msg in messages:
677
733
  val_tuple = tuple(int(b) for b in msg[offset : offset + size])
678
734
  tuple_values.append(val_tuple)
679
- values = list(tuple_values) # type: ignore[assignment]
735
+ values = list(tuple_values)
680
736
 
681
737
  # Calculate statistics
682
738
  if size > 4:
@@ -733,57 +789,115 @@ class MessageFormatInferrer:
733
789
  ) -> tuple[str, float]:
734
790
  """Detect field type using pattern matching.
735
791
 
736
- : Pattern-based field type detection.
737
-
738
792
  Detects:
739
793
  - Counters (incrementing values)
794
+ - Timestamps (steady large increments)
740
795
  - Lengths (correlates with message size)
741
- - Checksums (high entropy, end of message)
796
+ - Checksums (end of message)
742
797
  - Constants (no variation)
743
- - Timestamps (steady increase)
744
798
 
745
799
  Args:
746
- field_data: Field data dictionary
747
- offset: Field offset
748
- size: Field size
749
- msg_len: Total message length
800
+ field_data: Field data dictionary with 'values' key.
801
+ offset: Field offset in bytes.
802
+ size: Field size in bytes.
803
+ msg_len: Total message length in bytes.
750
804
 
751
805
  Returns:
752
- Tuple of (field_type, confidence)
806
+ Tuple of (field_type, confidence_score).
753
807
  """
754
808
  values = field_data["values"]
755
809
 
756
- # Check for counter (if integer values)
757
- if not isinstance(values[0], tuple):
758
- int_values = [v for v in values if isinstance(v, int)]
759
- if self._detect_counter_field(int_values):
760
- return ("counter", 0.9)
761
-
762
- # Check for timestamp (similar to counter but larger values)
763
- if len(int_values) >= 3:
764
- diffs = [int_values[i + 1] - int_values[i] for i in range(len(int_values) - 1)]
765
- positive_diffs = [d for d in diffs if d > 0]
766
- if len(positive_diffs) >= len(diffs) * 0.7:
767
- # Check if increments are relatively steady
768
- if len(positive_diffs) > 0:
769
- avg_diff = sum(positive_diffs) / len(positive_diffs)
770
- if avg_diff > 100: # Large increments suggest timestamp
771
- return ("timestamp", 0.7)
772
-
773
- # Check for length field (small values, near start)
774
- if offset < 8 and size <= 2 and not isinstance(values[0], tuple):
775
- int_values = [v for v in values if isinstance(v, int)]
776
- if int_values:
777
- max_val = max(int_values)
778
- if max_val < msg_len * 2:
779
- return ("length", 0.6)
780
-
781
- # Check for checksum (near end of message, but not the entire message)
782
- if offset + size >= msg_len - 4 and offset > 0:
810
+ # Skip tuple values (byte arrays)
811
+ if isinstance(values[0], tuple):
812
+ return ("unknown", 0.3)
813
+
814
+ # Extract integer values
815
+ int_values = [v for v in values if isinstance(v, int)]
816
+ if not int_values:
817
+ return ("unknown", 0.3)
818
+
819
+ # Check for counter pattern
820
+ if self._detect_counter_field(int_values):
821
+ return ("counter", 0.9)
822
+
823
+ # Check for timestamp pattern
824
+ timestamp_result = self._check_timestamp_pattern(int_values)
825
+ if timestamp_result is not None:
826
+ return timestamp_result
827
+
828
+ # Check for length field
829
+ if self._is_likely_length_field(offset, size, msg_len, int_values):
830
+ return ("length", 0.6)
831
+
832
+ # Check for checksum field
833
+ if self._is_likely_checksum_field(offset, size, msg_len):
783
834
  return ("checksum", 0.5)
784
835
 
785
836
  return ("unknown", 0.3)
786
837
 
838
+ def _check_timestamp_pattern(self, values: list[int]) -> tuple[str, float] | None:
839
+ """Check if values follow timestamp pattern.
840
+
841
+ Args:
842
+ values: Integer values.
843
+
844
+ Returns:
845
+ ('timestamp', confidence) or None if not a timestamp.
846
+ """
847
+ if len(values) < 3:
848
+ return None
849
+
850
+ # Calculate differences between consecutive values
851
+ diffs = [values[i + 1] - values[i] for i in range(len(values) - 1)]
852
+ positive_diffs = [d for d in diffs if d > 0]
853
+
854
+ # Need mostly increasing values
855
+ if len(positive_diffs) < len(diffs) * 0.7:
856
+ return None
857
+
858
+ # Check for large increments (typical of timestamps)
859
+ avg_diff = sum(positive_diffs) / len(positive_diffs)
860
+ if avg_diff > 100:
861
+ return ("timestamp", 0.7)
862
+
863
+ return None
864
+
865
+ def _is_likely_length_field(
866
+ self, offset: int, size: int, msg_len: int, values: list[int]
867
+ ) -> bool:
868
+ """Check if field is likely a length field.
869
+
870
+ Args:
871
+ offset: Field offset.
872
+ size: Field size.
873
+ msg_len: Message length.
874
+ values: Integer values.
875
+
876
+ Returns:
877
+ True if likely a length field.
878
+ """
879
+ # Length fields are typically near message start and small
880
+ if offset >= 8 or size > 2:
881
+ return False
882
+
883
+ # Values should be reasonable for message lengths
884
+ max_val = max(values)
885
+ return max_val < msg_len * 2
886
+
887
+ def _is_likely_checksum_field(self, offset: int, size: int, msg_len: int) -> bool:
888
+ """Check if field is likely a checksum.
889
+
890
+ Args:
891
+ offset: Field offset.
892
+ size: Field size.
893
+ msg_len: Message length.
894
+
895
+ Returns:
896
+ True if likely a checksum field.
897
+ """
898
+ # Checksums are typically near the end but not the whole message
899
+ return offset + size >= msg_len - 4 and offset > 0
900
+
787
901
  def _detect_type_statistics(self, field_data: dict[str, Any]) -> tuple[str, float]:
788
902
  """Detect field type using statistical properties.
789
903
 
@@ -909,14 +1023,14 @@ class MessageFormatInferrer:
909
1023
  | int(msg[offset + 2])
910
1024
  )
911
1025
  int_values.append(val_int)
912
- values = list(int_values) # type: ignore[assignment]
1026
+ values = list(int_values)
913
1027
  else:
914
1028
  # For larger fields, use bytes
915
1029
  tuple_values: list[tuple[int, ...]] = []
916
1030
  for msg in messages:
917
1031
  val_tuple = tuple(int(b) for b in msg[offset : offset + size])
918
1032
  tuple_values.append(val_tuple)
919
- values = list(tuple_values) # type: ignore[assignment]
1033
+ values = list(tuple_values)
920
1034
 
921
1035
  # Calculate statistics
922
1036
  if size > 4:
@@ -1025,55 +1139,100 @@ class MessageFormatInferrer:
1025
1139
  ) -> tuple[str, float]:
1026
1140
  """Classify field type based on patterns.
1027
1141
 
1028
- : Field type classification logic.
1029
-
1030
1142
  Args:
1031
- values: Field values across all messages
1032
- offset: Field offset
1033
- size: Field size
1034
- messages: Original messages
1143
+ values: Field values across all messages.
1144
+ offset: Field offset in bytes.
1145
+ size: Field size in bytes.
1146
+ messages: Original message byte arrays.
1035
1147
 
1036
1148
  Returns:
1037
- Tuple of (field_type, confidence)
1149
+ Tuple of (field_type, confidence_score).
1150
+
1151
+ Example:
1152
+ >>> field_type, confidence = analyzer._classify_field(values, 0, 1, messages)
1038
1153
  """
1039
- # Handle byte fields (larger than 4 bytes)
1154
+ # Handle byte array fields (tuples)
1040
1155
  if isinstance(values[0], tuple):
1041
- # Check if all tuples are identical (truly constant)
1042
- if len(set(values)) == 1:
1043
- return ("constant", 1.0)
1044
-
1045
- entropy = self._calculate_entropy(np.concatenate([np.array(v) for v in values]))
1046
- if entropy < 1.0:
1047
- return ("constant", 0.9)
1048
- elif entropy > 7.0:
1049
- return ("data", 0.6)
1050
- else:
1051
- return ("data", 0.5)
1156
+ return self._classify_byte_array_field(values)
1157
+
1158
+ # Handle scalar fields
1159
+ return self._classify_scalar_field(values, offset, size, messages)
1160
+
1161
+ def _classify_byte_array_field(self, values: list[int | tuple[int, ...]]) -> tuple[str, float]:
1162
+ """Classify byte array field (multi-byte fields).
1163
+
1164
+ Args:
1165
+ values: List of byte tuples.
1166
+
1167
+ Returns:
1168
+ Tuple of (field_type, confidence).
1169
+ """
1170
+ # Check for constant byte arrays
1171
+ if len(set(values)) == 1:
1172
+ return ("constant", 1.0)
1173
+
1174
+ # Calculate entropy across all bytes
1175
+ all_bytes = np.concatenate([np.array(v) for v in values])
1176
+ entropy = self._calculate_entropy(all_bytes)
1177
+
1178
+ if entropy < 1.0:
1179
+ return ("constant", 0.9)
1180
+ elif entropy > 7.0:
1181
+ return ("data", 0.6)
1182
+ else:
1183
+ return ("data", 0.5)
1184
+
1185
+ def _classify_scalar_field(
1186
+ self,
1187
+ values: list[int | tuple[int, ...]],
1188
+ offset: int,
1189
+ size: int,
1190
+ messages: list[NDArray[np.uint8]],
1191
+ ) -> tuple[str, float]:
1192
+ """Classify scalar (integer) field.
1052
1193
 
1053
- # Check for constant field
1194
+ Args:
1195
+ values: List of integer values.
1196
+ offset: Field offset.
1197
+ size: Field size.
1198
+ messages: Original messages.
1199
+
1200
+ Returns:
1201
+ Tuple of (field_type, confidence).
1202
+ """
1203
+ # Check for constant values
1054
1204
  if len(set(values)) == 1:
1055
1205
  return ("constant", 1.0)
1056
1206
 
1057
- # Check for counter field
1058
- if not isinstance(values[0], tuple) and self._detect_counter_field( # type: ignore[misc, unreachable]
1059
- [v for v in values if isinstance(v, int)]
1060
- ):
1207
+ # Check for counter pattern
1208
+ int_values = [v for v in values if isinstance(v, int)]
1209
+ if self._detect_counter_field(int_values):
1061
1210
  return ("counter", 0.9)
1062
1211
 
1063
- # Check for checksum (if near end of message)
1212
+ # Check for checksum (near end of message)
1064
1213
  msg_len = len(messages[0])
1065
- if offset + size >= msg_len - 4: # Within last 4 bytes
1214
+ if offset + size >= msg_len - 4:
1066
1215
  if self._detect_checksum_field(messages, offset, size):
1067
1216
  return ("checksum", 0.8)
1068
1217
 
1069
- # Check for length field (small values, near start)
1218
+ # Check for length field (early, small values)
1070
1219
  if offset < 8 and size <= 2:
1071
- if not isinstance(values[0], tuple): # type: ignore[unreachable]
1072
- max_val = max(v for v in values if isinstance(v, int))
1073
- if max_val < msg_len * 2: # Reasonable length value
1074
- return ("length", 0.6)
1220
+ max_val = max(int_values)
1221
+ if max_val < msg_len * 2:
1222
+ return ("length", 0.6)
1075
1223
 
1076
- # Check variance for classification
1224
+ # Classify by variance and entropy
1225
+ return self._classify_by_statistics(values)
1226
+
1227
+ def _classify_by_statistics(self, values: list[int | tuple[int, ...]]) -> tuple[str, float]:
1228
+ """Classify field by statistical properties.
1229
+
1230
+ Args:
1231
+ values: Field values.
1232
+
1233
+ Returns:
1234
+ Tuple of (field_type, confidence).
1235
+ """
1077
1236
  variance = np.var(values)
1078
1237
  entropy = self._calculate_entropy(np.array(values))
1079
1238
 
@@ -1304,4 +1463,4 @@ def find_dependencies(
1304
1463
  # Cast to expected type (msg_arrays contains only NDArray after conversion)
1305
1464
  schema = inferrer.infer_format(msg_arrays) # type: ignore[arg-type]
1306
1465
 
1307
- return inferrer.find_dependencies(msg_arrays, schema) # type: ignore[arg-type]
1466
+ return inferrer.find_dependencies(msg_arrays, schema)