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
@@ -17,7 +17,7 @@ from collections.abc import Callable, Sequence
17
17
  from dataclasses import dataclass, field
18
18
  from datetime import datetime
19
19
  from pathlib import Path
20
- from typing import Any, ClassVar, Literal
20
+ from typing import Any, ClassVar, Literal, cast
21
21
 
22
22
  logger = logging.getLogger(__name__)
23
23
 
@@ -206,6 +206,111 @@ class REPipeline:
206
206
  self._checkpoint_path: str | None = None
207
207
  self._checkpoint_data: dict[str, Any] = {}
208
208
 
209
+ def _initialize_analysis_context(
210
+ self, data: bytes | Sequence[dict[str, Any]] | Sequence[bytes]
211
+ ) -> dict[str, Any]:
212
+ """Initialize analysis context with empty containers.
213
+
214
+ Args:
215
+ data: Input data to analyze.
216
+
217
+ Returns:
218
+ Initialized context dictionary.
219
+ """
220
+ return {
221
+ "raw_data": data,
222
+ "flows": [],
223
+ "payloads": [],
224
+ "messages": [],
225
+ "patterns": [],
226
+ "clusters": [],
227
+ "schemas": {},
228
+ "protocol_candidates": [],
229
+ "state_machine": None,
230
+ "warnings": [],
231
+ "statistics": {},
232
+ }
233
+
234
+ def _execute_stage(
235
+ self,
236
+ stage_name: str,
237
+ context: dict[str, Any],
238
+ checkpoint: str | None,
239
+ ) -> StageResult:
240
+ """Execute single pipeline stage.
241
+
242
+ Args:
243
+ stage_name: Name of stage to execute.
244
+ context: Analysis context.
245
+ checkpoint: Checkpoint path.
246
+
247
+ Returns:
248
+ StageResult with execution outcome.
249
+ """
250
+ handler = self._stage_handlers.get(stage_name)
251
+ if not handler:
252
+ return StageResult(
253
+ stage_name=stage_name, success=False, duration=0, output=None, error="No handler"
254
+ )
255
+
256
+ try:
257
+ stage_start = time.time()
258
+ output = handler(context)
259
+ stage_duration = time.time() - stage_start
260
+
261
+ if output:
262
+ context.update(output)
263
+
264
+ if checkpoint:
265
+ self._save_checkpoint(checkpoint, stage_name, context)
266
+
267
+ return StageResult(
268
+ stage_name=stage_name,
269
+ success=True,
270
+ duration=stage_duration,
271
+ output=output,
272
+ )
273
+
274
+ except Exception as e:
275
+ warnings_list: list[str] = context.get("warnings", [])
276
+ warnings_list.append(f"Stage {stage_name} failed: {e}")
277
+ context["warnings"] = warnings_list
278
+
279
+ return StageResult(
280
+ stage_name=stage_name,
281
+ success=False,
282
+ duration=0,
283
+ output=None,
284
+ error=str(e),
285
+ )
286
+
287
+ def _execute_all_stages(
288
+ self, context: dict[str, Any], checkpoint: str | None
289
+ ) -> list[StageResult]:
290
+ """Execute all pipeline stages.
291
+
292
+ Args:
293
+ context: Analysis context.
294
+ checkpoint: Checkpoint path.
295
+
296
+ Returns:
297
+ List of stage results.
298
+ """
299
+ stage_results = []
300
+ total_stages = len(self.stages)
301
+
302
+ for i, stage_name in enumerate(self.stages):
303
+ if stage_name in self._checkpoint_data:
304
+ context.update(self._checkpoint_data[stage_name])
305
+ continue
306
+
307
+ self._report_progress(stage_name, (i / total_stages) * 100)
308
+ stage_result = self._execute_stage(stage_name, context, checkpoint)
309
+ stage_results.append(stage_result)
310
+
311
+ self._report_progress("complete", 100)
312
+ return stage_results
313
+
209
314
  def analyze(
210
315
  self,
211
316
  data: bytes | Sequence[dict[str, Any]] | Sequence[bytes],
@@ -229,85 +334,22 @@ class REPipeline:
229
334
  >>> for msg_type in results.message_types:
230
335
  ... print(f"{msg_type.name}: {msg_type.sample_count} samples")
231
336
  """
337
+ # Setup: initialize state and load checkpoint
232
338
  start_time = time.time()
233
339
  self._progress_callback = progress_callback
234
340
  self._checkpoint_path = checkpoint
235
341
  self._checkpoint_data = {}
236
342
 
237
- # Load checkpoint if available
238
343
  if checkpoint and os.path.exists(checkpoint):
239
344
  self._load_checkpoint(checkpoint)
240
345
 
241
- # Initialize context
242
- context: dict[str, Any] = {
243
- "raw_data": data,
244
- "flows": [],
245
- "payloads": [],
246
- "messages": [],
247
- "patterns": [],
248
- "clusters": [],
249
- "schemas": {},
250
- "protocol_candidates": [],
251
- "state_machine": None,
252
- "warnings": [],
253
- "statistics": {},
254
- }
255
-
256
- # Execute stages
257
- stage_results = []
258
- total_stages = len(self.stages)
259
-
260
- for i, stage_name in enumerate(self.stages):
261
- if stage_name in self._checkpoint_data:
262
- # Skip completed stages
263
- context.update(self._checkpoint_data[stage_name])
264
- continue
265
-
266
- self._report_progress(stage_name, (i / total_stages) * 100)
346
+ context = self._initialize_analysis_context(data)
267
347
 
268
- handler = self._stage_handlers.get(stage_name)
269
- if handler:
270
- try:
271
- stage_start = time.time()
272
- output = handler(context)
273
- stage_duration = time.time() - stage_start
274
-
275
- stage_results.append(
276
- StageResult(
277
- stage_name=stage_name,
278
- success=True,
279
- duration=stage_duration,
280
- output=output,
281
- )
282
- )
348
+ # Processing: execute pipeline stages
349
+ stage_results = self._execute_all_stages(context, checkpoint)
283
350
 
284
- # Update context with stage output
285
- if output:
286
- context.update(output)
287
-
288
- # Checkpoint after each stage
289
- if checkpoint:
290
- self._save_checkpoint(checkpoint, stage_name, context)
291
-
292
- except Exception as e:
293
- stage_results.append(
294
- StageResult(
295
- stage_name=stage_name,
296
- success=False,
297
- duration=0,
298
- output=None,
299
- error=str(e),
300
- )
301
- )
302
- warnings_list: list[str] = context.get("warnings", [])
303
- warnings_list.append(f"Stage {stage_name} failed: {e}")
304
- context["warnings"] = warnings_list
305
-
306
- self._report_progress("complete", 100)
307
-
308
- # Build result
351
+ # Result building: construct final result
309
352
  duration = time.time() - start_time
310
-
311
353
  flows_list: list[Any] = context.get("flows", [])
312
354
  messages_list: list[Any] = context.get("messages", [])
313
355
  protocol_candidates_list: list[ProtocolCandidate] = context.get("protocol_candidates", [])
@@ -416,115 +458,205 @@ class REPipeline:
416
458
  context: Pipeline context.
417
459
 
418
460
  Returns:
419
- Updated context with flows.
461
+ Updated context with flows and payloads.
420
462
  """
421
463
  data = context["raw_data"]
422
- flows = []
423
- payloads = []
424
464
 
425
465
  if isinstance(data, bytes):
426
- # Raw binary - treat as single payload
427
- payloads.append(data)
466
+ flows, payloads = self._extract_from_raw_bytes(data)
467
+ elif isinstance(data, list | tuple):
468
+ flows, payloads = self._extract_from_packet_list(data)
469
+ else:
470
+ flows, payloads = [], []
471
+
472
+ self._update_flow_statistics(context, flows, payloads)
473
+ return {"flows": flows, "payloads": payloads}
474
+
475
+ def _extract_from_raw_bytes(self, data: bytes) -> tuple[list[FlowInfo], list[bytes]]:
476
+ """Extract flow from raw binary data.
477
+
478
+ Args:
479
+ data: Raw binary data.
480
+
481
+ Returns:
482
+ Tuple of (flows, payloads).
483
+ """
484
+ flow = FlowInfo(
485
+ flow_id="flow_0",
486
+ src_ip="unknown",
487
+ dst_ip="unknown",
488
+ src_port=0,
489
+ dst_port=0,
490
+ protocol="unknown",
491
+ packet_count=1,
492
+ byte_count=len(data),
493
+ start_time=0,
494
+ end_time=0,
495
+ )
496
+ return [flow], [data]
497
+
498
+ def _extract_from_packet_list(
499
+ self, packets: Sequence[dict[str, Any] | bytes]
500
+ ) -> tuple[list[FlowInfo], list[bytes]]:
501
+ """Extract flows from list of packets.
502
+
503
+ Args:
504
+ packets: List of packet dicts or raw bytes.
505
+
506
+ Returns:
507
+ Tuple of (flows, payloads).
508
+ """
509
+ flow_map: dict[str, dict[str, Any]] = {}
510
+ payloads: list[bytes] = []
511
+ raw_bytes_payloads: list[bytes] = []
512
+
513
+ for pkt in packets:
514
+ if isinstance(pkt, dict):
515
+ self._process_packet_dict(pkt, flow_map, payloads)
516
+ else:
517
+ payload = bytes(pkt) if not isinstance(pkt, bytes) else pkt
518
+ payloads.append(payload)
519
+ raw_bytes_payloads.append(payload)
520
+
521
+ flows = self._build_flows_from_map(flow_map)
522
+
523
+ # Create default flow for raw bytes if needed
524
+ if raw_bytes_payloads and not flows:
525
+ flows.append(self._create_default_flow(raw_bytes_payloads))
526
+
527
+ return flows, payloads
528
+
529
+ def _process_packet_dict(
530
+ self,
531
+ pkt: dict[str, Any],
532
+ flow_map: dict[str, dict[str, Any]],
533
+ payloads: list[bytes],
534
+ ) -> None:
535
+ """Process a packet dictionary and update flow map.
536
+
537
+ Args:
538
+ pkt: Packet dictionary with metadata.
539
+ flow_map: Flow mapping to update.
540
+ payloads: Payloads list to append to.
541
+ """
542
+ # Extract payload
543
+ payload_raw = pkt.get("data", pkt.get("payload", b""))
544
+ if isinstance(payload_raw, list | tuple):
545
+ payload = bytes(payload_raw)
546
+ else:
547
+ payload = payload_raw if isinstance(payload_raw, bytes) else b""
548
+
549
+ # Create flow key
550
+ flow_key = self._create_flow_key(pkt)
551
+
552
+ # Initialize flow if new
553
+ if flow_key not in flow_map:
554
+ flow_map[flow_key] = self._create_flow_entry(pkt)
555
+
556
+ # Update flow data
557
+ flow_map[flow_key]["packets"].append(pkt)
558
+ flow_map[flow_key]["payloads"].append(payload)
559
+ if "timestamp" in pkt:
560
+ flow_map[flow_key]["timestamps"].append(pkt["timestamp"])
561
+
562
+ payloads.append(payload)
563
+
564
+ def _create_flow_key(self, pkt: dict[str, Any]) -> str:
565
+ """Create flow identifier key from packet.
566
+
567
+ Args:
568
+ pkt: Packet dictionary.
569
+
570
+ Returns:
571
+ Flow key string.
572
+ """
573
+ src_ip = pkt.get("src_ip", "0.0.0.0")
574
+ dst_ip = pkt.get("dst_ip", "0.0.0.0")
575
+ src_port = pkt.get("src_port", 0)
576
+ dst_port = pkt.get("dst_port", 0)
577
+ protocol = pkt.get("protocol", "unknown")
578
+ return f"{src_ip}:{src_port}-{dst_ip}:{dst_port}-{protocol}"
579
+
580
+ def _create_flow_entry(self, pkt: dict[str, Any]) -> dict[str, Any]:
581
+ """Create new flow entry from packet.
582
+
583
+ Args:
584
+ pkt: Packet dictionary.
585
+
586
+ Returns:
587
+ Flow entry dictionary.
588
+ """
589
+ return {
590
+ "src_ip": pkt.get("src_ip", "0.0.0.0"),
591
+ "dst_ip": pkt.get("dst_ip", "0.0.0.0"),
592
+ "src_port": pkt.get("src_port", 0),
593
+ "dst_port": pkt.get("dst_port", 0),
594
+ "protocol": pkt.get("protocol", "unknown"),
595
+ "packets": [],
596
+ "payloads": [],
597
+ "timestamps": [],
598
+ }
599
+
600
+ def _build_flows_from_map(self, flow_map: dict[str, dict[str, Any]]) -> list[FlowInfo]:
601
+ """Build FlowInfo objects from flow map.
602
+
603
+ Args:
604
+ flow_map: Mapping of flow keys to flow data.
605
+
606
+ Returns:
607
+ List of FlowInfo objects.
608
+ """
609
+ flows = []
610
+ for flow_id, flow_data in flow_map.items():
611
+ timestamps = flow_data.get("timestamps", [0])
428
612
  flows.append(
429
613
  FlowInfo(
430
- flow_id="flow_0",
431
- src_ip="unknown",
432
- dst_ip="unknown",
433
- src_port=0,
434
- dst_port=0,
435
- protocol="unknown",
436
- packet_count=1,
437
- byte_count=len(data),
438
- start_time=0,
439
- end_time=0,
614
+ flow_id=flow_id,
615
+ src_ip=flow_data["src_ip"],
616
+ dst_ip=flow_data["dst_ip"],
617
+ src_port=flow_data["src_port"],
618
+ dst_port=flow_data["dst_port"],
619
+ protocol=flow_data["protocol"],
620
+ packet_count=len(flow_data["packets"]),
621
+ byte_count=sum(len(p) for p in flow_data["payloads"]),
622
+ start_time=min(timestamps) if timestamps else 0,
623
+ end_time=max(timestamps) if timestamps else 0,
440
624
  )
441
625
  )
626
+ return flows
442
627
 
443
- elif isinstance(data, list | tuple):
444
- # List of packets
445
- flow_map: dict[str, dict[str, Any]] = {}
446
- raw_bytes_payloads: list[bytes] = []
447
-
448
- for _i, pkt in enumerate(data):
449
- if isinstance(pkt, dict):
450
- # Packet with metadata
451
- payload_raw = pkt.get("data", pkt.get("payload", b""))
452
- if isinstance(payload_raw, list | tuple):
453
- payload = bytes(payload_raw)
454
- else:
455
- payload = payload_raw if isinstance(payload_raw, bytes) else b""
456
-
457
- # Create flow key
458
- src_ip = pkt.get("src_ip", "0.0.0.0")
459
- dst_ip = pkt.get("dst_ip", "0.0.0.0")
460
- src_port = pkt.get("src_port", 0)
461
- dst_port = pkt.get("dst_port", 0)
462
- protocol = pkt.get("protocol", "unknown")
463
-
464
- flow_key = f"{src_ip}:{src_port}-{dst_ip}:{dst_port}-{protocol}"
465
-
466
- if flow_key not in flow_map:
467
- flow_map[flow_key] = {
468
- "src_ip": src_ip,
469
- "dst_ip": dst_ip,
470
- "src_port": src_port,
471
- "dst_port": dst_port,
472
- "protocol": protocol,
473
- "packets": [],
474
- "payloads": [],
475
- "timestamps": [],
476
- }
477
-
478
- flow_map[flow_key]["packets"].append(pkt)
479
- flow_map[flow_key]["payloads"].append(payload)
480
- if "timestamp" in pkt:
481
- flow_map[flow_key]["timestamps"].append(pkt["timestamp"])
482
-
483
- payloads.append(payload)
484
-
485
- else:
486
- # Raw bytes - collect for default flow
487
- raw_payload = bytes(pkt) if not isinstance(pkt, bytes) else pkt
488
- payloads.append(raw_payload)
489
- raw_bytes_payloads.append(raw_payload)
490
-
491
- # Build flow objects from flow_map
492
- for flow_id, flow_data in flow_map.items():
493
- timestamps = flow_data.get("timestamps", [0])
494
- flows.append(
495
- FlowInfo(
496
- flow_id=flow_id,
497
- src_ip=flow_data["src_ip"],
498
- dst_ip=flow_data["dst_ip"],
499
- src_port=flow_data["src_port"],
500
- dst_port=flow_data["dst_port"],
501
- protocol=flow_data["protocol"],
502
- packet_count=len(flow_data["packets"]),
503
- byte_count=sum(len(p) for p in flow_data["payloads"]),
504
- start_time=min(timestamps) if timestamps else 0,
505
- end_time=max(timestamps) if timestamps else 0,
506
- )
507
- )
628
+ def _create_default_flow(self, payloads: list[bytes]) -> FlowInfo:
629
+ """Create default flow for raw bytes.
508
630
 
509
- # Create a default flow for raw bytes if we have any
510
- # This ensures flow_count >= 1 when analyzing raw byte sequences
511
- if raw_bytes_payloads and not flows:
512
- flows.append(
513
- FlowInfo(
514
- flow_id="flow_default",
515
- src_ip="unknown",
516
- dst_ip="unknown",
517
- src_port=0,
518
- dst_port=0,
519
- protocol="unknown",
520
- packet_count=len(raw_bytes_payloads),
521
- byte_count=sum(len(p) for p in raw_bytes_payloads),
522
- start_time=0,
523
- end_time=0,
524
- )
525
- )
631
+ Args:
632
+ payloads: List of raw byte payloads.
526
633
 
527
- # Initialize statistics dict if it doesn't exist
634
+ Returns:
635
+ Default FlowInfo object.
636
+ """
637
+ return FlowInfo(
638
+ flow_id="flow_default",
639
+ src_ip="unknown",
640
+ dst_ip="unknown",
641
+ src_port=0,
642
+ dst_port=0,
643
+ protocol="unknown",
644
+ packet_count=len(payloads),
645
+ byte_count=sum(len(p) for p in payloads),
646
+ start_time=0,
647
+ end_time=0,
648
+ )
649
+
650
+ def _update_flow_statistics(
651
+ self, context: dict[str, Any], flows: list[FlowInfo], payloads: list[bytes]
652
+ ) -> None:
653
+ """Update context statistics with flow extraction results.
654
+
655
+ Args:
656
+ context: Pipeline context to update.
657
+ flows: Extracted flows.
658
+ payloads: Extracted payloads.
659
+ """
528
660
  if "statistics" not in context:
529
661
  context["statistics"] = {}
530
662
 
@@ -534,8 +666,6 @@ class REPipeline:
534
666
  "total_bytes": sum(len(p) for p in payloads),
535
667
  }
536
668
 
537
- return {"flows": flows, "payloads": payloads}
538
-
539
669
  def _stage_payload_analysis(self, context: dict[str, Any]) -> dict[str, Any]:
540
670
  """Analyze payloads for structure.
541
671
 
@@ -713,9 +843,27 @@ class REPipeline:
713
843
  """
714
844
  messages = context.get("messages", [])
715
845
  flows = context.get("flows", [])
716
- candidates = []
846
+ candidates: list[ProtocolCandidate] = []
847
+
848
+ # Detect protocols from multiple sources
849
+ candidates.extend(self._detect_by_port(flows))
850
+ candidates.extend(self._detect_by_magic_bytes(messages))
851
+ candidates.extend(self._detect_from_library(messages))
717
852
 
718
- # Check well-known port mappings
853
+ # Deduplicate candidates
854
+ unique_candidates = self._deduplicate_candidates(candidates)
855
+
856
+ return {"protocol_candidates": unique_candidates}
857
+
858
+ def _detect_by_port(self, flows: list[FlowInfo]) -> list[ProtocolCandidate]:
859
+ """Detect protocols based on well-known port numbers.
860
+
861
+ Args:
862
+ flows: List of network flows.
863
+
864
+ Returns:
865
+ List of protocol candidates.
866
+ """
719
867
  port_protocols = {
720
868
  53: "dns",
721
869
  80: "http",
@@ -726,6 +874,7 @@ class REPipeline:
726
874
  47808: "bacnet",
727
875
  }
728
876
 
877
+ candidates = []
729
878
  for flow in flows:
730
879
  port = flow.dst_port or flow.src_port
731
880
  if port in port_protocols:
@@ -737,69 +886,110 @@ class REPipeline:
737
886
  )
738
887
  )
739
888
 
740
- # Check magic byte signatures
741
- if messages:
742
- try:
743
- from oscura.inference.binary import MagicByteDetector
744
-
745
- detector = MagicByteDetector()
746
- sample = messages[0] if messages else b""
747
-
748
- if len(sample) >= 2:
749
- result = detector.detect(sample)
750
- if result and result.known_format:
751
- candidates.append(
752
- ProtocolCandidate(
753
- name=result.known_format,
754
- confidence=result.confidence,
755
- header_match=True,
756
- )
889
+ return candidates
890
+
891
+ def _detect_by_magic_bytes(self, messages: list[bytes]) -> list[ProtocolCandidate]:
892
+ """Detect protocols by magic byte signatures.
893
+
894
+ Args:
895
+ messages: List of message bytes.
896
+
897
+ Returns:
898
+ List of protocol candidates.
899
+ """
900
+ if not messages:
901
+ return []
902
+
903
+ try:
904
+ from oscura.inference.binary import MagicByteDetector
905
+
906
+ detector = MagicByteDetector()
907
+ sample = messages[0]
908
+
909
+ if len(sample) >= 2:
910
+ result = detector.detect(sample)
911
+ if result and result.known_format:
912
+ return [
913
+ ProtocolCandidate(
914
+ name=result.known_format,
915
+ confidence=result.confidence,
916
+ header_match=True,
757
917
  )
918
+ ]
758
919
 
759
- except Exception as e:
760
- logger.debug("Magic byte detection failed (non-critical): %s", e)
920
+ except Exception as e:
921
+ logger.debug("Magic byte detection failed (non-critical): %s", e)
922
+
923
+ return []
924
+
925
+ def _detect_from_library(self, messages: list[bytes]) -> list[ProtocolCandidate]:
926
+ """Detect protocols from protocol library.
761
927
 
762
- # Check protocol library
928
+ Args:
929
+ messages: List of message bytes.
930
+
931
+ Returns:
932
+ List of protocol candidates.
933
+ """
763
934
  try:
764
935
  from oscura.inference.protocol_library import get_library
765
936
 
766
937
  library = get_library()
938
+ candidates = []
767
939
 
768
940
  for protocol in library.list_protocols():
769
- if protocol.definition:
770
- # Check if first bytes match protocol header
771
- for msg in messages[:10]:
772
- if len(msg) >= 4:
773
- # Simple header matching
774
- first_field = (
775
- protocol.definition.fields[0]
776
- if protocol.definition.fields
777
- else None
778
- )
779
- if first_field and hasattr(first_field, "value"):
780
- # Has expected value
781
- candidates.append(
782
- ProtocolCandidate(
783
- name=protocol.name,
784
- confidence=0.4,
785
- matched_patterns=["header_value"],
786
- )
787
- )
788
- break
941
+ if self._matches_protocol_header(protocol, messages):
942
+ candidates.append(
943
+ ProtocolCandidate(
944
+ name=protocol.name,
945
+ confidence=0.4,
946
+ matched_patterns=["header_value"],
947
+ )
948
+ )
949
+
950
+ return candidates
789
951
 
790
952
  except Exception as e:
791
953
  logger.debug("Protocol library matching failed (non-critical): %s", e)
954
+ return []
955
+
956
+ def _matches_protocol_header(self, protocol: Any, messages: list[bytes]) -> bool:
957
+ """Check if messages match protocol header.
792
958
 
793
- # Deduplicate by name, keeping highest confidence
794
- unique_candidates: dict[str, ProtocolCandidate] = {}
959
+ Args:
960
+ protocol: Protocol definition.
961
+ messages: List of message bytes.
962
+
963
+ Returns:
964
+ True if matches.
965
+ """
966
+ if not protocol.definition or not protocol.definition.fields:
967
+ return False
968
+
969
+ first_field = protocol.definition.fields[0]
970
+ if not hasattr(first_field, "value"):
971
+ return False
972
+
973
+ # Check first 10 messages
974
+ return any(len(msg) >= 4 for msg in messages[:10])
975
+
976
+ def _deduplicate_candidates(
977
+ self, candidates: list[ProtocolCandidate]
978
+ ) -> list[ProtocolCandidate]:
979
+ """Deduplicate candidates, keeping highest confidence.
980
+
981
+ Args:
982
+ candidates: List of candidates.
983
+
984
+ Returns:
985
+ Deduplicated list.
986
+ """
987
+ unique: dict[str, ProtocolCandidate] = {}
795
988
  for c in candidates:
796
- if (
797
- c.name not in unique_candidates
798
- or c.confidence > unique_candidates[c.name].confidence
799
- ):
800
- unique_candidates[c.name] = c
989
+ if c.name not in unique or c.confidence > unique[c.name].confidence:
990
+ unique[c.name] = c
801
991
 
802
- return {"protocol_candidates": list(unique_candidates.values())}
992
+ return list(unique.values())
803
993
 
804
994
  def _stage_state_machine(self, context: dict[str, Any]) -> dict[str, Any]:
805
995
  """Infer protocol state machine.
@@ -825,7 +1015,7 @@ class REPipeline:
825
1015
  message_to_cluster[idx] = getattr(cluster, "cluster_id", 0)
826
1016
 
827
1017
  # Build observation sequence
828
- sequence = [
1018
+ sequence: list[str] = [
829
1019
  f"type_{message_to_cluster.get(i, 0)}"
830
1020
  for i in range(len(messages))
831
1021
  if i in message_to_cluster
@@ -835,7 +1025,8 @@ class REPipeline:
835
1025
  from oscura.inference.state_machine import StateMachineInferrer
836
1026
 
837
1027
  inferrer = StateMachineInferrer()
838
- automaton = inferrer.infer_rpni([sequence])
1028
+ # Cast list[str] to list[str | int] for API compatibility
1029
+ automaton = inferrer.infer_rpni([cast("list[str | int]", sequence)])
839
1030
 
840
1031
  return {
841
1032
  "state_machine": {