oscura 0.5.1__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,874 @@
1
+ """Firmware pattern recognition for binary analysis.
2
+
3
+ This module provides comprehensive firmware analysis capabilities including:
4
+ - Function boundary detection using architecture-specific patterns
5
+ - Architecture fingerprinting (ARM Thumb/ARM, x86, MIPS)
6
+ - String and data region identification
7
+ - Interrupt vector table detection
8
+ - Compiler signature detection
9
+
10
+ Example:
11
+ >>> from oscura.hardware.firmware.pattern_recognition import FirmwarePatternRecognizer
12
+ >>> with open("firmware.bin", "rb") as f:
13
+ ... data = f.read()
14
+ >>> recognizer = FirmwarePatternRecognizer()
15
+ >>> result = recognizer.analyze(data, base_address=0x08000000)
16
+ >>> print(f"Architecture: {result.detected_architecture}")
17
+ >>> print(f"Functions: {len(result.functions)}")
18
+ >>> for func in result.functions[:5]:
19
+ ... print(f" {func.address:08X}: {func.name or 'unknown'} ({func.confidence:.2f})")
20
+
21
+ References:
22
+ ARM Architecture Reference Manual (ARMv7-M)
23
+ Intel 64 and IA-32 Architectures Software Developer's Manual
24
+ MIPS Architecture For Programmers
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import json
30
+ import struct
31
+ from dataclasses import dataclass, field
32
+ from enum import Enum
33
+ from typing import TYPE_CHECKING, ClassVar
34
+
35
+ import numpy as np
36
+
37
+ if TYPE_CHECKING:
38
+ from collections.abc import Sequence
39
+
40
+
41
+ class Architecture(Enum):
42
+ """Detected CPU architecture."""
43
+
44
+ UNKNOWN = "unknown"
45
+ ARM_THUMB = "arm_thumb"
46
+ ARM_ARM = "arm_arm"
47
+ X86 = "x86"
48
+ X86_64 = "x86_64"
49
+ MIPS = "mips"
50
+ MIPS64 = "mips64"
51
+
52
+
53
+ class CompilerSignature(Enum):
54
+ """Detected compiler toolchain."""
55
+
56
+ UNKNOWN = "unknown"
57
+ GCC = "gcc"
58
+ IAR = "iar"
59
+ KEIL = "keil"
60
+ LLVM = "llvm"
61
+ MSVC = "msvc"
62
+
63
+
64
+ @dataclass
65
+ class Function:
66
+ """Detected function in firmware.
67
+
68
+ Attributes:
69
+ address: Function start address (absolute or offset from base)
70
+ size: Function size in bytes (0 if unknown)
71
+ name: Function name if identifiable (e.g., "reset_handler")
72
+ confidence: Confidence score 0.0-1.0
73
+ architecture: Detected architecture for this function
74
+ metadata: Additional function information
75
+ """
76
+
77
+ address: int
78
+ size: int = 0
79
+ name: str | None = None
80
+ confidence: float = 0.0
81
+ architecture: Architecture = Architecture.UNKNOWN
82
+ metadata: dict[str, str | int | float] = field(default_factory=dict)
83
+
84
+
85
+ @dataclass
86
+ class StringTable:
87
+ """Detected string table or string region.
88
+
89
+ Attributes:
90
+ address: Start address of string table
91
+ size: Size in bytes
92
+ strings: List of decoded strings
93
+ encoding: String encoding (utf-8, ascii, utf-16le)
94
+ """
95
+
96
+ address: int
97
+ size: int
98
+ strings: list[str]
99
+ encoding: str = "utf-8"
100
+
101
+
102
+ @dataclass
103
+ class InterruptVector:
104
+ """Detected interrupt vector table entry.
105
+
106
+ Attributes:
107
+ index: Vector index (0 = reset/stack pointer)
108
+ address: Handler address (or stack pointer for index 0)
109
+ name: Vector name if known (e.g., "SysTick_Handler")
110
+ """
111
+
112
+ index: int
113
+ address: int
114
+ name: str | None = None
115
+
116
+
117
+ @dataclass
118
+ class FirmwareAnalysisResult:
119
+ """Complete firmware analysis result.
120
+
121
+ Attributes:
122
+ detected_architecture: Most likely CPU architecture
123
+ functions: List of detected functions
124
+ string_tables: List of detected string tables
125
+ interrupt_vectors: List of detected interrupt vectors
126
+ compiler_signature: Detected compiler toolchain
127
+ base_address: Base address used for analysis
128
+ firmware_size: Total firmware size in bytes
129
+ code_regions: List of (start, size) tuples for code regions
130
+ data_regions: List of (start, size) tuples for data regions
131
+ metadata: Additional analysis metadata
132
+ """
133
+
134
+ detected_architecture: Architecture
135
+ functions: list[Function]
136
+ string_tables: list[StringTable]
137
+ interrupt_vectors: list[InterruptVector]
138
+ compiler_signature: CompilerSignature = CompilerSignature.UNKNOWN
139
+ base_address: int = 0
140
+ firmware_size: int = 0
141
+ code_regions: list[tuple[int, int]] = field(default_factory=list)
142
+ data_regions: list[tuple[int, int]] = field(default_factory=list)
143
+ metadata: dict[str, str | int | float] = field(default_factory=dict)
144
+
145
+ def to_dict(self) -> dict[str, object]:
146
+ """Export analysis result to dictionary.
147
+
148
+ Returns:
149
+ Dictionary representation suitable for JSON export
150
+ """
151
+ return {
152
+ "detected_architecture": self.detected_architecture.value,
153
+ "base_address": hex(self.base_address),
154
+ "firmware_size": self.firmware_size,
155
+ "compiler_signature": self.compiler_signature.value,
156
+ "functions": [
157
+ {
158
+ "address": hex(f.address),
159
+ "size": f.size,
160
+ "name": f.name,
161
+ "confidence": f.confidence,
162
+ "architecture": f.architecture.value,
163
+ "metadata": f.metadata,
164
+ }
165
+ for f in self.functions
166
+ ],
167
+ "string_tables": [
168
+ {
169
+ "address": hex(s.address),
170
+ "size": s.size,
171
+ "encoding": s.encoding,
172
+ "strings": s.strings,
173
+ }
174
+ for s in self.string_tables
175
+ ],
176
+ "interrupt_vectors": [
177
+ {"index": v.index, "address": hex(v.address), "name": v.name}
178
+ for v in self.interrupt_vectors
179
+ ],
180
+ "code_regions": [[hex(addr), size] for addr, size in self.code_regions],
181
+ "data_regions": [[hex(addr), size] for addr, size in self.data_regions],
182
+ "metadata": self.metadata,
183
+ }
184
+
185
+ def to_json(self, indent: int = 2) -> str:
186
+ """Export analysis result to JSON.
187
+
188
+ Args:
189
+ indent: JSON indentation level
190
+
191
+ Returns:
192
+ JSON string representation
193
+ """
194
+ return json.dumps(self.to_dict(), indent=indent)
195
+
196
+
197
+ class FirmwarePatternRecognizer:
198
+ """Firmware pattern recognition and analysis.
199
+
200
+ This class provides comprehensive firmware analysis including function
201
+ detection, architecture fingerprinting, and data region identification.
202
+
203
+ Example:
204
+ >>> recognizer = FirmwarePatternRecognizer()
205
+ >>> result = recognizer.analyze(firmware_data, base_address=0x08000000)
206
+ >>> print(result.to_json())
207
+ """
208
+
209
+ # ARM Thumb function prologue patterns (16-bit instructions)
210
+ ARM_THUMB_PROLOGUES: ClassVar[list[bytes]] = [
211
+ b"\xb5\x00", # PUSH {lr} (0xb500)
212
+ b"\xb5\x10", # PUSH {r4, lr}
213
+ b"\xb5\x30", # PUSH {r4, r5, lr}
214
+ b"\xb5\x70", # PUSH {r4, r5, r6, lr}
215
+ b"\xb5\xf0", # PUSH {r4, r5, r6, r7, lr}
216
+ b"\xb5\x80", # PUSH {r7, lr}
217
+ ]
218
+
219
+ # ARM Thumb function epilogue patterns
220
+ ARM_THUMB_EPILOGUES: ClassVar[list[bytes]] = [
221
+ b"\xbd\x00", # POP {pc} (0xbd00)
222
+ b"\xbd\x10", # POP {r4, pc}
223
+ b"\xbd\x30", # POP {r4, r5, pc}
224
+ b"\xbd\x70", # POP {r4, r5, r6, pc}
225
+ b"\xbd\xf0", # POP {r4, r5, r6, r7, pc}
226
+ b"\xbd\x80", # POP {r7, pc}
227
+ b"\x70\x47", # BX lr (0x4770)
228
+ ]
229
+
230
+ # x86 function prologue patterns
231
+ X86_PROLOGUES: ClassVar[list[bytes]] = [
232
+ b"\x55\x89\xe5", # PUSH ebp; MOV ebp, esp
233
+ b"\x55\x8b\xec", # PUSH ebp; MOV ebp, esp (alternate)
234
+ b"\x48\x89\x5c\x24", # x64: MOV [rsp+X], rbx
235
+ b"\x48\x83\xec", # x64: SUB rsp, X
236
+ ]
237
+
238
+ # x86 function epilogue patterns
239
+ X86_EPILOGUES: ClassVar[list[bytes]] = [
240
+ b"\xc9\xc3", # LEAVE; RET
241
+ b"\x5d\xc3", # POP ebp; RET
242
+ b"\xc3", # RET (simple)
243
+ b"\x48\x83\xc4", # x64: ADD rsp, X
244
+ ]
245
+
246
+ # ARM Cortex-M vector table standard handlers
247
+ CORTEX_M_VECTORS: ClassVar[dict[int, str]] = {
248
+ 1: "Reset_Handler",
249
+ 2: "NMI_Handler",
250
+ 3: "HardFault_Handler",
251
+ 4: "MemManage_Handler",
252
+ 5: "BusFault_Handler",
253
+ 6: "UsageFault_Handler",
254
+ 11: "SVC_Handler",
255
+ 12: "DebugMon_Handler",
256
+ 14: "PendSV_Handler",
257
+ 15: "SysTick_Handler",
258
+ }
259
+
260
+ def analyze(
261
+ self,
262
+ firmware_data: bytes | Sequence[int],
263
+ base_address: int = 0,
264
+ architecture_hint: Architecture | None = None,
265
+ ) -> FirmwareAnalysisResult:
266
+ """Analyze firmware binary for patterns and structures.
267
+
268
+ Args:
269
+ firmware_data: Raw firmware binary data
270
+ base_address: Base address for firmware (default 0)
271
+ architecture_hint: Optional architecture hint to guide analysis
272
+
273
+ Returns:
274
+ Complete firmware analysis result
275
+
276
+ Raises:
277
+ ValueError: If firmware_data is empty or invalid
278
+ """
279
+ if not firmware_data:
280
+ raise ValueError("Firmware data cannot be empty")
281
+
282
+ # Convert to bytes if needed
283
+ data: bytes
284
+ if isinstance(firmware_data, bytes):
285
+ data = firmware_data
286
+ else:
287
+ data = bytes(firmware_data)
288
+
289
+ # Detect architecture
290
+ if architecture_hint is None:
291
+ detected_arch = self._detect_architecture(data, base_address)
292
+ else:
293
+ detected_arch = architecture_hint
294
+
295
+ # Detect functions
296
+ functions = self._detect_functions(data, base_address, detected_arch)
297
+
298
+ # Detect string tables
299
+ string_tables = self._detect_string_tables(data, base_address)
300
+
301
+ # Detect interrupt vectors
302
+ interrupt_vectors = self._detect_interrupt_vectors(data, base_address, detected_arch)
303
+
304
+ # Detect compiler signature
305
+ compiler_sig = self._detect_compiler(data)
306
+
307
+ # Classify code vs data regions
308
+ code_regions, data_regions = self._classify_regions(data, base_address, detected_arch)
309
+
310
+ return FirmwareAnalysisResult(
311
+ detected_architecture=detected_arch,
312
+ functions=functions,
313
+ string_tables=string_tables,
314
+ interrupt_vectors=interrupt_vectors,
315
+ compiler_signature=compiler_sig,
316
+ base_address=base_address,
317
+ firmware_size=len(data),
318
+ code_regions=code_regions,
319
+ data_regions=data_regions,
320
+ metadata={
321
+ "function_count": len(functions),
322
+ "string_count": sum(len(st.strings) for st in string_tables),
323
+ "vector_count": len(interrupt_vectors),
324
+ },
325
+ )
326
+
327
+ def _detect_architecture(self, firmware_data: bytes, base_address: int) -> Architecture:
328
+ """Detect CPU architecture from binary patterns.
329
+
330
+ Args:
331
+ firmware_data: Raw firmware binary
332
+ base_address: Base address for firmware
333
+
334
+ Returns:
335
+ Detected architecture
336
+ """
337
+ # Check for ARM Thumb (16-bit instruction alignment)
338
+ thumb_score = self._score_arm_thumb(firmware_data)
339
+
340
+ # Check for ARM (32-bit instruction alignment)
341
+ arm_score = self._score_arm_arm(firmware_data)
342
+
343
+ # Check for x86
344
+ x86_score = self._score_x86(firmware_data)
345
+
346
+ # Check for MIPS
347
+ mips_score = self._score_mips(firmware_data)
348
+
349
+ # Determine architecture based on scores
350
+ scores = {
351
+ Architecture.ARM_THUMB: thumb_score,
352
+ Architecture.ARM_ARM: arm_score,
353
+ Architecture.X86: x86_score,
354
+ Architecture.MIPS: mips_score,
355
+ }
356
+
357
+ max_arch = max(scores, key=lambda a: scores[a])
358
+ if scores[max_arch] > 0.3: # Minimum confidence threshold
359
+ return max_arch
360
+
361
+ return Architecture.UNKNOWN
362
+
363
+ def _score_arm_thumb(self, firmware_data: bytes) -> float:
364
+ """Score likelihood of ARM Thumb architecture.
365
+
366
+ Args:
367
+ firmware_data: Raw firmware binary
368
+
369
+ Returns:
370
+ Confidence score 0.0-1.0
371
+ """
372
+ if len(firmware_data) < 32:
373
+ return 0.0
374
+
375
+ score = 0.0
376
+ score += self._score_thumb_prologues(firmware_data)
377
+ score += self._score_thumb_epilogues(firmware_data)
378
+ score += self._score_thumb_vector_table(firmware_data)
379
+
380
+ return min(score, 1.0)
381
+
382
+ def _score_thumb_prologues(self, firmware_data: bytes) -> float:
383
+ """Score Thumb prologue patterns."""
384
+ prologue_count = sum(firmware_data.count(pattern) for pattern in self.ARM_THUMB_PROLOGUES)
385
+ return min(prologue_count / 10.0, 0.4) if prologue_count > 0 else 0.0
386
+
387
+ def _score_thumb_epilogues(self, firmware_data: bytes) -> float:
388
+ """Score Thumb epilogue patterns."""
389
+ epilogue_count = sum(firmware_data.count(pattern) for pattern in self.ARM_THUMB_EPILOGUES)
390
+ return min(epilogue_count / 10.0, 0.4) if epilogue_count > 0 else 0.0
391
+
392
+ def _score_thumb_vector_table(self, firmware_data: bytes) -> float:
393
+ """Score Thumb LSB bit in vector table."""
394
+ if len(firmware_data) < 64:
395
+ return 0.0
396
+
397
+ try:
398
+ thumb_bit_count = 0
399
+ valid_addresses = 0
400
+ for i in range(1, min(16, len(firmware_data) // 4)):
401
+ word = struct.unpack("<I", firmware_data[i * 4 : i * 4 + 4])[0]
402
+ if word != 0 and word != 0xFFFFFFFF:
403
+ valid_addresses += 1
404
+ if word & 1:
405
+ thumb_bit_count += 1
406
+
407
+ if valid_addresses >= 3 and thumb_bit_count >= valid_addresses // 2:
408
+ return 0.3
409
+ except struct.error:
410
+ pass
411
+
412
+ return 0.0
413
+
414
+ def _score_arm_arm(self, firmware_data: bytes) -> float:
415
+ """Score likelihood of ARM (32-bit) architecture.
416
+
417
+ Args:
418
+ firmware_data: Raw firmware binary
419
+
420
+ Returns:
421
+ Confidence score 0.0-1.0
422
+ """
423
+ if len(firmware_data) < 32:
424
+ return 0.0
425
+
426
+ score = 0.0
427
+
428
+ # ARM instructions are 32-bit aligned and often have condition codes
429
+ # Check for common ARM instruction patterns (simplified)
430
+ arm_patterns = [
431
+ b"\x1e\xff\x2f\xe1", # BX lr (0xe12fff1e)
432
+ b"\x00\x00\xa0\xe3", # MOV r0, #0
433
+ ]
434
+
435
+ for pattern in arm_patterns:
436
+ count = firmware_data.count(pattern)
437
+ if count > 0:
438
+ score += min(count / 50.0, 0.3)
439
+
440
+ return min(score, 1.0)
441
+
442
+ def _score_x86(self, firmware_data: bytes) -> float:
443
+ """Score likelihood of x86 architecture.
444
+
445
+ Args:
446
+ firmware_data: Raw firmware binary
447
+
448
+ Returns:
449
+ Confidence score 0.0-1.0
450
+ """
451
+ if len(firmware_data) < 32:
452
+ return 0.0
453
+
454
+ score = 0.0
455
+
456
+ # Check for x86 prologue patterns
457
+ prologue_count = 0
458
+ for pattern in self.X86_PROLOGUES:
459
+ prologue_count += firmware_data.count(pattern)
460
+ if prologue_count > 0:
461
+ score += min(prologue_count / 5.0, 0.5)
462
+
463
+ # Check for x86 epilogue patterns
464
+ epilogue_count = 0
465
+ for pattern in self.X86_EPILOGUES:
466
+ epilogue_count += firmware_data.count(pattern)
467
+ if epilogue_count > 0:
468
+ score += min(epilogue_count / 5.0, 0.5)
469
+
470
+ return min(score, 1.0)
471
+
472
+ def _score_mips(self, firmware_data: bytes) -> float:
473
+ """Score likelihood of MIPS architecture.
474
+
475
+ Args:
476
+ firmware_data: Raw firmware binary
477
+
478
+ Returns:
479
+ Confidence score 0.0-1.0
480
+ """
481
+ if len(firmware_data) < 32:
482
+ return 0.0
483
+
484
+ # MIPS instructions are 32-bit aligned
485
+ # Check for common MIPS patterns (simplified)
486
+ score = 0.0
487
+
488
+ # JR ra (return) - 0x03e00008
489
+ jr_ra_count = firmware_data.count(b"\x03\xe0\x00\x08")
490
+ if jr_ra_count > 0:
491
+ score += min(jr_ra_count / 50.0, 0.5)
492
+
493
+ return min(score, 1.0)
494
+
495
+ def _detect_functions(
496
+ self, firmware_data: bytes, base_address: int, architecture: Architecture
497
+ ) -> list[Function]:
498
+ """Detect function boundaries using pattern matching.
499
+
500
+ Args:
501
+ firmware_data: Raw firmware binary
502
+ base_address: Base address for firmware
503
+ architecture: Detected architecture
504
+
505
+ Returns:
506
+ List of detected functions
507
+ """
508
+ functions: list[Function] = []
509
+
510
+ if architecture == Architecture.ARM_THUMB:
511
+ functions = self._detect_arm_thumb_functions(firmware_data, base_address)
512
+ elif architecture == Architecture.X86:
513
+ functions = self._detect_x86_functions(firmware_data, base_address)
514
+ elif architecture == Architecture.ARM_ARM:
515
+ functions = self._detect_arm_functions(firmware_data, base_address)
516
+
517
+ return functions
518
+
519
+ def _detect_arm_thumb_functions(
520
+ self, firmware_data: bytes, base_address: int
521
+ ) -> list[Function]:
522
+ """Detect ARM Thumb function boundaries.
523
+
524
+ Args:
525
+ firmware_data: Raw firmware binary
526
+ base_address: Base address for firmware
527
+
528
+ Returns:
529
+ List of detected functions
530
+ """
531
+ functions: list[Function] = []
532
+
533
+ # Find all prologue patterns
534
+ prologues: list[tuple[int, bytes]] = []
535
+ for pattern in self.ARM_THUMB_PROLOGUES:
536
+ offset = 0
537
+ while True:
538
+ idx = firmware_data.find(pattern, offset)
539
+ if idx == -1:
540
+ break
541
+ # ARM Thumb requires 2-byte alignment
542
+ if idx % 2 == 0:
543
+ prologues.append((idx, pattern))
544
+ offset = idx + 1
545
+
546
+ # Find all epilogue patterns
547
+ epilogues: set[int] = set()
548
+ for pattern in self.ARM_THUMB_EPILOGUES:
549
+ offset = 0
550
+ while True:
551
+ idx = firmware_data.find(pattern, offset)
552
+ if idx == -1:
553
+ break
554
+ if idx % 2 == 0:
555
+ epilogues.add(idx)
556
+ offset = idx + 1
557
+
558
+ # Sort prologues by address
559
+ prologues.sort(key=lambda x: x[0])
560
+
561
+ # Match prologues with epilogues
562
+ for i, (prologue_addr, pattern) in enumerate(prologues):
563
+ # Find next epilogue after this prologue
564
+ size = 0
565
+ confidence = 0.5 # Base confidence for pattern match
566
+
567
+ # Find closest epilogue
568
+ matching_epilogues = [e for e in epilogues if e > prologue_addr]
569
+ if matching_epilogues:
570
+ epilogue_addr = min(matching_epilogues)
571
+ size = epilogue_addr - prologue_addr + 2
572
+ confidence = 0.7 # Higher confidence with epilogue match
573
+
574
+ # Check if next prologue is too close (avoid false positives)
575
+ if i + 1 < len(prologues):
576
+ next_prologue = prologues[i + 1][0]
577
+ if size == 0 or next_prologue < prologue_addr + size:
578
+ size = next_prologue - prologue_addr
579
+
580
+ # Create function entry
581
+ func = Function(
582
+ address=base_address + prologue_addr,
583
+ size=size,
584
+ confidence=confidence,
585
+ architecture=Architecture.ARM_THUMB,
586
+ metadata={"prologue_pattern": pattern.hex()},
587
+ )
588
+ functions.append(func)
589
+
590
+ return functions
591
+
592
+ def _detect_x86_functions(self, firmware_data: bytes, base_address: int) -> list[Function]:
593
+ """Detect x86 function boundaries.
594
+
595
+ Args:
596
+ firmware_data: Raw firmware binary
597
+ base_address: Base address for firmware
598
+
599
+ Returns:
600
+ List of detected functions
601
+ """
602
+ functions: list[Function] = []
603
+
604
+ # Find all prologue patterns
605
+ prologues: list[tuple[int, bytes]] = []
606
+ for pattern in self.X86_PROLOGUES:
607
+ offset = 0
608
+ while True:
609
+ idx = firmware_data.find(pattern, offset)
610
+ if idx == -1:
611
+ break
612
+ prologues.append((idx, pattern))
613
+ offset = idx + 1
614
+
615
+ prologues.sort(key=lambda x: x[0])
616
+
617
+ for prologue_addr, pattern in prologues:
618
+ func = Function(
619
+ address=base_address + prologue_addr,
620
+ size=0,
621
+ confidence=0.6,
622
+ architecture=Architecture.X86,
623
+ metadata={"prologue_pattern": pattern.hex()},
624
+ )
625
+ functions.append(func)
626
+
627
+ return functions
628
+
629
+ def _detect_arm_functions(self, firmware_data: bytes, base_address: int) -> list[Function]:
630
+ """Detect ARM (32-bit) function boundaries.
631
+
632
+ Args:
633
+ firmware_data: Raw firmware binary
634
+ base_address: Base address for firmware
635
+
636
+ Returns:
637
+ List of detected functions
638
+ """
639
+ functions: list[Function] = []
640
+
641
+ # ARM BX lr return pattern
642
+ bx_lr = b"\x1e\xff\x2f\xe1"
643
+ offset = 0
644
+ while True:
645
+ idx = firmware_data.find(bx_lr, offset)
646
+ if idx == -1:
647
+ break
648
+ if idx % 4 == 0: # ARM requires 4-byte alignment
649
+ # Assume function starts ~32 bytes before return
650
+ func_start = max(0, idx - 32)
651
+ func = Function(
652
+ address=base_address + func_start,
653
+ size=idx - func_start + 4,
654
+ confidence=0.5,
655
+ architecture=Architecture.ARM_ARM,
656
+ )
657
+ functions.append(func)
658
+ offset = idx + 1
659
+
660
+ return functions
661
+
662
+ def _detect_string_tables(self, firmware_data: bytes, base_address: int) -> list[StringTable]:
663
+ """Detect string tables and string regions.
664
+
665
+ Args:
666
+ firmware_data: Raw firmware binary
667
+ base_address: Base address for firmware
668
+
669
+ Returns:
670
+ List of detected string tables
671
+ """
672
+ string_tables: list[StringTable] = []
673
+ strings: list[str] = []
674
+ current_start = -1
675
+ current_string = bytearray()
676
+
677
+ for i, byte in enumerate(firmware_data):
678
+ if 0x20 <= byte <= 0x7E: # Printable ASCII
679
+ if current_start == -1:
680
+ current_start = i
681
+ current_string.append(byte)
682
+ elif byte == 0: # Null terminator
683
+ if len(current_string) >= 4: # Minimum string length
684
+ try:
685
+ decoded = current_string.decode("utf-8")
686
+ strings.append(decoded)
687
+ except UnicodeDecodeError:
688
+ pass
689
+ current_string.clear()
690
+ else:
691
+ # Non-string data
692
+ if len(strings) >= 3: # Minimum strings per table
693
+ string_tables.append(
694
+ StringTable(
695
+ address=base_address + current_start,
696
+ size=i - current_start,
697
+ strings=strings.copy(),
698
+ encoding="utf-8",
699
+ )
700
+ )
701
+ strings.clear()
702
+ current_start = -1
703
+ current_string.clear()
704
+
705
+ # Final table
706
+ if len(strings) >= 3:
707
+ string_tables.append(
708
+ StringTable(
709
+ address=base_address + current_start,
710
+ size=len(firmware_data) - current_start,
711
+ strings=strings,
712
+ encoding="utf-8",
713
+ )
714
+ )
715
+
716
+ return string_tables
717
+
718
+ def _detect_interrupt_vectors(
719
+ self, firmware_data: bytes, base_address: int, architecture: Architecture
720
+ ) -> list[InterruptVector]:
721
+ """Detect interrupt vector table.
722
+
723
+ Args:
724
+ firmware_data: Raw firmware binary
725
+ base_address: Base address for firmware
726
+ architecture: Detected architecture
727
+
728
+ Returns:
729
+ List of detected interrupt vectors
730
+ """
731
+ vectors: list[InterruptVector] = []
732
+
733
+ # ARM Cortex-M vector table is at offset 0
734
+ if architecture in (Architecture.ARM_THUMB, Architecture.ARM_ARM):
735
+ if len(firmware_data) >= 64: # Minimum vector table size
736
+ try:
737
+ # First word is initial stack pointer
738
+ stack_ptr = struct.unpack("<I", firmware_data[0:4])[0]
739
+ vectors.append(
740
+ InterruptVector(index=0, address=stack_ptr, name="Initial_Stack_Pointer")
741
+ )
742
+
743
+ # Next words are exception/interrupt handlers
744
+ for i in range(1, min(16, len(firmware_data) // 4)):
745
+ handler_addr = struct.unpack("<I", firmware_data[i * 4 : i * 4 + 4])[0]
746
+ # Validate address (should be in firmware range, Thumb bit set)
747
+ if handler_addr != 0 and handler_addr != 0xFFFFFFFF:
748
+ name = self.CORTEX_M_VECTORS.get(i)
749
+ vectors.append(
750
+ InterruptVector(index=i, address=handler_addr & ~1, name=name)
751
+ )
752
+ except struct.error:
753
+ pass
754
+
755
+ return vectors
756
+
757
+ def _detect_compiler(self, firmware_data: bytes) -> CompilerSignature:
758
+ """Detect compiler toolchain from binary signatures.
759
+
760
+ Args:
761
+ firmware_data: Raw firmware binary
762
+
763
+ Returns:
764
+ Detected compiler signature
765
+ """
766
+ # Check for compiler-specific strings
767
+ if b"GCC:" in firmware_data or b"gcc version" in firmware_data:
768
+ return CompilerSignature.GCC
769
+ if b"IAR ANSI C" in firmware_data or b"ICCARM" in firmware_data:
770
+ return CompilerSignature.IAR
771
+ if b"Keil" in firmware_data or b"ARMCC" in firmware_data:
772
+ return CompilerSignature.KEIL
773
+ if b"clang version" in firmware_data or b"LLVM" in firmware_data:
774
+ return CompilerSignature.LLVM
775
+ if b"Microsoft" in firmware_data and b"Visual C++" in firmware_data:
776
+ return CompilerSignature.MSVC
777
+
778
+ return CompilerSignature.UNKNOWN
779
+
780
+ def _classify_regions(
781
+ self, firmware_data: bytes, base_address: int, architecture: Architecture
782
+ ) -> tuple[list[tuple[int, int]], list[tuple[int, int]]]:
783
+ """Classify memory regions as code or data using entropy analysis.
784
+
785
+ Args:
786
+ firmware_data: Raw firmware binary
787
+ base_address: Base address for firmware
788
+ architecture: Detected architecture
789
+
790
+ Returns:
791
+ Tuple of (code_regions, data_regions) as (address, size) pairs
792
+ """
793
+ code_regions: list[tuple[int, int]] = []
794
+ data_regions: list[tuple[int, int]] = []
795
+
796
+ if len(firmware_data) < 64:
797
+ return code_regions, data_regions
798
+
799
+ # Analyze in 64-byte chunks
800
+ chunk_size = 64
801
+ entropies: list[float] = []
802
+
803
+ for i in range(0, len(firmware_data), chunk_size):
804
+ chunk = firmware_data[i : i + chunk_size]
805
+ if len(chunk) < chunk_size:
806
+ break
807
+
808
+ # Calculate Shannon entropy
809
+ entropy = self._calculate_entropy(chunk)
810
+ entropies.append(entropy)
811
+
812
+ if not entropies:
813
+ return code_regions, data_regions
814
+
815
+ # Code typically has medium entropy (4-7 bits/byte)
816
+ # Data/constants have low entropy (<4)
817
+ # Crypto/compressed have high entropy (>7)
818
+
819
+ current_type: str | None = None
820
+ region_start = 0
821
+
822
+ for i, entropy in enumerate(entropies):
823
+ addr = i * chunk_size
824
+
825
+ if 4.0 <= entropy <= 7.0:
826
+ region_type = "code"
827
+ else:
828
+ region_type = "data"
829
+
830
+ if current_type is None:
831
+ current_type = region_type
832
+ region_start = addr
833
+ elif current_type != region_type:
834
+ # Region boundary
835
+ size = addr - region_start
836
+ if current_type == "code":
837
+ code_regions.append((base_address + region_start, size))
838
+ else:
839
+ data_regions.append((base_address + region_start, size))
840
+ current_type = region_type
841
+ region_start = addr
842
+
843
+ # Final region
844
+ if current_type:
845
+ size = len(firmware_data) - region_start
846
+ if current_type == "code":
847
+ code_regions.append((base_address + region_start, size))
848
+ else:
849
+ data_regions.append((base_address + region_start, size))
850
+
851
+ return code_regions, data_regions
852
+
853
+ def _calculate_entropy(self, data: bytes) -> float:
854
+ """Calculate Shannon entropy of byte sequence.
855
+
856
+ Args:
857
+ data: Byte sequence
858
+
859
+ Returns:
860
+ Entropy in bits per byte (0.0-8.0)
861
+ """
862
+ if not data:
863
+ return 0.0
864
+
865
+ # Count byte frequencies
866
+ byte_counts = np.bincount(np.frombuffer(data, dtype=np.uint8), minlength=256)
867
+ probabilities = byte_counts / len(data)
868
+
869
+ # Calculate Shannon entropy
870
+ # Avoid log(0) by filtering zero probabilities
871
+ nonzero_probs = probabilities[probabilities > 0]
872
+ entropy = -np.sum(nonzero_probs * np.log2(nonzero_probs))
873
+
874
+ return float(entropy)