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