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
@@ -106,7 +106,7 @@ class DTCDatabase:
106
106
 
107
107
  Args:
108
108
  code: DTC code to look up (e.g., "P0420", "p0420")
109
- Case-insensitive
109
+ Case-insensitive, whitespace is stripped
110
110
 
111
111
  Returns:
112
112
  DTCInfo object if found, None if code not in database
@@ -117,7 +117,7 @@ class DTCDatabase:
117
117
  ... print(f"{info.code}: {info.description}")
118
118
  P0420: Catalyst System Efficiency Below Threshold (Bank 1)
119
119
  """
120
- return DTCS.get(code.upper())
120
+ return DTCS.get(code.strip().upper())
121
121
 
122
122
  @staticmethod
123
123
  def search(keyword: str) -> list[DTCInfo]:
@@ -0,0 +1,31 @@
1
+ """FlexRay automotive protocol analysis.
2
+
3
+ This module provides FlexRay-specific analysis tools including frame parsing,
4
+ CRC validation, signal decoding, and FIBEX format support.
5
+
6
+ Example:
7
+ >>> from oscura.automotive.flexray import FlexRayAnalyzer, FlexRaySignal
8
+ >>> analyzer = FlexRayAnalyzer()
9
+ >>> frame = analyzer.parse_frame(data, timestamp=0.0, channel="A")
10
+ >>> print(f"Slot {frame.header.frame_id}, CRC valid: {frame.crc_valid}")
11
+ """
12
+
13
+ from oscura.automotive.flexray.analyzer import (
14
+ FlexRayAnalyzer,
15
+ FlexRayFrame,
16
+ FlexRayHeader,
17
+ FlexRaySignal,
18
+ )
19
+ from oscura.automotive.flexray.crc import calculate_frame_crc, calculate_header_crc
20
+ from oscura.automotive.flexray.fibex import FIBEXExporter, FIBEXImporter
21
+
22
+ __all__ = [
23
+ "FIBEXExporter",
24
+ "FIBEXImporter",
25
+ "FlexRayAnalyzer",
26
+ "FlexRayFrame",
27
+ "FlexRayHeader",
28
+ "FlexRaySignal",
29
+ "calculate_frame_crc",
30
+ "calculate_header_crc",
31
+ ]
@@ -0,0 +1,504 @@
1
+ """FlexRay protocol analyzer.
2
+
3
+ This module provides comprehensive FlexRay frame analysis including header parsing,
4
+ CRC validation, signal decoding, and multi-channel support.
5
+
6
+ Example:
7
+ >>> from oscura.automotive.flexray import FlexRayAnalyzer, FlexRaySignal
8
+ >>> analyzer = FlexRayAnalyzer()
9
+ >>> frame = analyzer.parse_frame(data, timestamp=0.0, channel="A")
10
+ >>> print(f"Slot {frame.header.frame_id}, valid: {frame.crc_valid}")
11
+ >>>
12
+ >>> # Add signal definitions
13
+ >>> signal = FlexRaySignal(
14
+ ... name="EngineSpeed",
15
+ ... frame_id=100,
16
+ ... start_bit=0,
17
+ ... bit_length=16,
18
+ ... factor=0.25,
19
+ ... offset=0,
20
+ ... unit="rpm"
21
+ ... )
22
+ >>> analyzer.add_signal(signal)
23
+ >>> signals = analyzer.decode_signals(100, frame.payload)
24
+ >>> print(f"Engine Speed: {signals['EngineSpeed']} rpm")
25
+
26
+ References:
27
+ FlexRay Communications System Protocol Specification Version 3.0.1
28
+ ISO 17458 (FlexRay standard)
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from dataclasses import dataclass, field
34
+ from typing import Any
35
+
36
+ from oscura.automotive.flexray.crc import (
37
+ verify_frame_crc,
38
+ verify_header_crc,
39
+ )
40
+
41
+
42
+ @dataclass
43
+ class FlexRayHeader:
44
+ """FlexRay frame header (40 bits / 5 bytes).
45
+
46
+ The header contains frame identification, control flags, and CRC.
47
+
48
+ Attributes:
49
+ reserved: Reserved bit (always 0).
50
+ payload_preamble: Payload preamble indicator.
51
+ null_frame: Null frame indicator (no payload).
52
+ sync_frame: Synchronization frame indicator.
53
+ startup_frame: Startup frame indicator.
54
+ frame_id: Frame identifier (1-2047).
55
+ payload_length: Payload length in bytes (0-254).
56
+ header_crc: 11-bit header CRC.
57
+ cycle_count: Cycle counter (0-63).
58
+
59
+ Example:
60
+ >>> header = FlexRayHeader(
61
+ ... reserved=0, payload_preamble=0, null_frame=False,
62
+ ... sync_frame=True, startup_frame=False, frame_id=100,
63
+ ... payload_length=10, header_crc=0x3A5, cycle_count=5
64
+ ... )
65
+ >>> print(f"Frame ID: {header.frame_id}, Cycle: {header.cycle_count}")
66
+ """
67
+
68
+ reserved: int # 1 bit
69
+ payload_preamble: int # 1 bit
70
+ null_frame: bool # 1 bit
71
+ sync_frame: bool # 1 bit
72
+ startup_frame: bool # 1 bit
73
+ frame_id: int # 11 bits (1-2047)
74
+ payload_length: int # In bytes (0-254, stored as words internally)
75
+ header_crc: int # 11 bits
76
+ cycle_count: int # 6 bits (0-63)
77
+
78
+
79
+ @dataclass
80
+ class FlexRayFrame:
81
+ """FlexRay frame representation.
82
+
83
+ Attributes:
84
+ timestamp: Frame timestamp in seconds.
85
+ channel: Channel identifier ("A" or "B").
86
+ header: Parsed frame header.
87
+ payload: Frame payload bytes (0-254 bytes).
88
+ frame_crc: Received 24-bit frame CRC.
89
+ crc_valid: True if frame CRC is valid.
90
+ segment_type: Segment type ("static" or "dynamic").
91
+ decoded_signals: Decoded signal values (populated after decoding).
92
+
93
+ Example:
94
+ >>> frame = FlexRayFrame(
95
+ ... timestamp=1.234, channel="A", header=header,
96
+ ... payload=b"\\x01\\x02\\x03", frame_crc=0x123456,
97
+ ... crc_valid=True, segment_type="static"
98
+ ... )
99
+ >>> print(f"Channel {frame.channel}, Slot {frame.header.frame_id}")
100
+ """
101
+
102
+ timestamp: float
103
+ channel: str # "A" or "B"
104
+ header: FlexRayHeader
105
+ payload: bytes # 0-254 bytes
106
+ frame_crc: int # 24 bits
107
+ crc_valid: bool
108
+ segment_type: str = "static" # "static" or "dynamic"
109
+ decoded_signals: dict[str, Any] = field(default_factory=dict)
110
+
111
+
112
+ @dataclass
113
+ class FlexRaySignal:
114
+ """FlexRay signal definition.
115
+
116
+ Defines how to extract and decode a signal from a frame payload.
117
+
118
+ Attributes:
119
+ name: Signal name.
120
+ frame_id: Frame ID containing this signal.
121
+ start_bit: Start bit position in payload.
122
+ bit_length: Signal length in bits.
123
+ byte_order: Byte order ("big_endian" or "little_endian").
124
+ factor: Scaling factor (physical = raw * factor + offset).
125
+ offset: Offset for physical value.
126
+ unit: Physical unit (e.g., "rpm", "km/h").
127
+
128
+ Example:
129
+ >>> signal = FlexRaySignal(
130
+ ... name="VehicleSpeed",
131
+ ... frame_id=200,
132
+ ... start_bit=16,
133
+ ... bit_length=16,
134
+ ... byte_order="big_endian",
135
+ ... factor=0.01,
136
+ ... offset=0,
137
+ ... unit="km/h"
138
+ ... )
139
+ """
140
+
141
+ name: str
142
+ frame_id: int
143
+ start_bit: int
144
+ bit_length: int
145
+ byte_order: str = "big_endian" # FlexRay is typically big-endian
146
+ factor: float = 1.0
147
+ offset: float = 0.0
148
+ unit: str = ""
149
+
150
+
151
+ class FlexRayAnalyzer:
152
+ """FlexRay protocol analyzer.
153
+
154
+ Provides comprehensive FlexRay frame analysis including parsing, CRC validation,
155
+ signal decoding, and FIBEX support.
156
+
157
+ Attributes:
158
+ MAX_FRAME_ID: Maximum frame ID (2047).
159
+ MAX_PAYLOAD_LENGTH: Maximum payload length in bytes (254).
160
+ HEADER_LENGTH: Header length in bytes (5).
161
+ CRC_LENGTH: CRC length in bytes (3).
162
+ STATIC_SEGMENT: Static segment identifier.
163
+ DYNAMIC_SEGMENT: Dynamic segment identifier.
164
+
165
+ Example:
166
+ >>> analyzer = FlexRayAnalyzer()
167
+ >>> frame = analyzer.parse_frame(raw_data, timestamp=1.0, channel="A")
168
+ >>> if frame.crc_valid:
169
+ ... print(f"Valid frame on slot {frame.header.frame_id}")
170
+ """
171
+
172
+ # FlexRay constants
173
+ MAX_FRAME_ID = 2047
174
+ MAX_PAYLOAD_LENGTH = 254 # bytes (127 16-bit words)
175
+ HEADER_LENGTH = 5 # bytes (40 bits)
176
+ CRC_LENGTH = 3 # bytes (24 bits)
177
+
178
+ # Segment types
179
+ STATIC_SEGMENT = "static"
180
+ DYNAMIC_SEGMENT = "dynamic"
181
+
182
+ def __init__(self, cluster_config: dict[str, Any] | None = None) -> None:
183
+ """Initialize FlexRay analyzer.
184
+
185
+ Args:
186
+ cluster_config: Optional cluster configuration containing:
187
+ - static_slot_count: Number of static slots
188
+ - dynamic_slot_count: Number of dynamic slots
189
+ - sample_rate: Sample rate in Hz
190
+
191
+ Example:
192
+ >>> config = {"static_slot_count": 100, "dynamic_slot_count": 50}
193
+ >>> analyzer = FlexRayAnalyzer(cluster_config=config)
194
+ """
195
+ self.frames: list[FlexRayFrame] = []
196
+ self.signals: list[FlexRaySignal] = []
197
+ self.cluster_config = cluster_config or {}
198
+
199
+ def parse_frame(self, data: bytes, timestamp: float = 0.0, channel: str = "A") -> FlexRayFrame:
200
+ """Parse FlexRay frame from raw bytes.
201
+
202
+ The frame structure is:
203
+ - Header (5 bytes)
204
+ - Payload (0-254 bytes)
205
+ - CRC (3 bytes)
206
+
207
+ Args:
208
+ data: Raw frame data (minimum 8 bytes: 5 header + 0 payload + 3 CRC).
209
+ timestamp: Frame timestamp in seconds.
210
+ channel: Channel identifier ("A" or "B").
211
+
212
+ Returns:
213
+ Parsed FlexRay frame.
214
+
215
+ Raises:
216
+ ValueError: If data is too short or invalid.
217
+
218
+ Example:
219
+ >>> raw_data = bytes([0x00, 0x64, 0x12, 0x34, 0x05] + [0] * 10 + [0, 0, 0])
220
+ >>> frame = analyzer.parse_frame(raw_data, timestamp=1.0, channel="A")
221
+ >>> print(f"Frame ID: {frame.header.frame_id}")
222
+ """
223
+ if len(data) < self.HEADER_LENGTH + self.CRC_LENGTH:
224
+ raise ValueError(
225
+ f"Frame data too short: {len(data)} bytes "
226
+ f"(minimum {self.HEADER_LENGTH + self.CRC_LENGTH})"
227
+ )
228
+
229
+ # Extract header, payload, and CRC
230
+ header_bytes = data[: self.HEADER_LENGTH]
231
+ crc_bytes = data[-self.CRC_LENGTH :]
232
+ payload_bytes = data[self.HEADER_LENGTH : -self.CRC_LENGTH]
233
+
234
+ # Parse header
235
+ header = self._parse_header(header_bytes)
236
+
237
+ # Verify payload length matches header
238
+ expected_payload_length = header.payload_length
239
+ if len(payload_bytes) != expected_payload_length:
240
+ raise ValueError(
241
+ f"Payload length mismatch: expected {expected_payload_length}, "
242
+ f"got {len(payload_bytes)}"
243
+ )
244
+
245
+ # Parse CRC
246
+ frame_crc = int.from_bytes(crc_bytes, "big")
247
+
248
+ # Verify CRC
249
+ crc_valid = verify_frame_crc(header_bytes, payload_bytes, frame_crc)
250
+
251
+ # Determine segment type
252
+ segment_type = self._determine_segment_type(header.frame_id)
253
+
254
+ # Create frame
255
+ frame = FlexRayFrame(
256
+ timestamp=timestamp,
257
+ channel=channel,
258
+ header=header,
259
+ payload=payload_bytes,
260
+ frame_crc=frame_crc,
261
+ crc_valid=crc_valid,
262
+ segment_type=segment_type,
263
+ )
264
+
265
+ self.frames.append(frame)
266
+ return frame
267
+
268
+ def _parse_header(self, header_bytes: bytes) -> FlexRayHeader:
269
+ """Parse 40-bit FlexRay header.
270
+
271
+ Header Format (5 bytes, 40 bits, MSB first):
272
+ Byte 0, bit 7 (MSB): Reserved (1 bit)
273
+ Byte 0, bit 6: Payload Preamble Indicator (1 bit)
274
+ Byte 0, bit 5: Null Frame Indicator (1 bit)
275
+ Byte 0, bit 4: Sync Frame Indicator (1 bit)
276
+ Byte 0, bit 3: Startup Frame Indicator (1 bit)
277
+ Byte 0, bits 2-0 + Byte 1, bits 7-0: Frame ID (11 bits, 1-2047)
278
+ Byte 2, bits 7-1: Payload Length (7 bits, in 16-bit words)
279
+ Byte 2, bit 0 + Byte 3, bits 7-6: Header CRC (11 bits)
280
+ Byte 3, bits 5-0: Cycle Count (6 bits, 0-63)
281
+
282
+ Args:
283
+ header_bytes: 5-byte header data.
284
+
285
+ Returns:
286
+ Parsed FlexRay header.
287
+
288
+ Raises:
289
+ ValueError: If header is invalid.
290
+ """
291
+ if len(header_bytes) < 5:
292
+ raise ValueError(f"FlexRay header must be 5 bytes, got {len(header_bytes)}")
293
+
294
+ # Convert to 40-bit integer (big-endian)
295
+ header_int = int.from_bytes(header_bytes[:5], "big")
296
+
297
+ # Extract fields (MSB first, bit 39 is leftmost)
298
+ reserved = (header_int >> 39) & 0x01
299
+ payload_preamble = (header_int >> 38) & 0x01
300
+ null_frame = bool((header_int >> 37) & 0x01)
301
+ sync_frame = bool((header_int >> 36) & 0x01)
302
+ startup_frame = bool((header_int >> 35) & 0x01)
303
+ frame_id = (header_int >> 24) & 0x7FF # 11 bits
304
+ payload_length_words = (header_int >> 17) & 0x7F # 7 bits (in words)
305
+ header_crc = (header_int >> 6) & 0x7FF # 11 bits
306
+ cycle_count = header_int & 0x3F # 6 bits
307
+
308
+ # Convert payload length from words to bytes
309
+ payload_length = payload_length_words * 2
310
+
311
+ # Validate frame ID
312
+ if frame_id < 1 or frame_id > self.MAX_FRAME_ID:
313
+ raise ValueError(f"Invalid frame ID: {frame_id} (must be 1-{self.MAX_FRAME_ID})")
314
+
315
+ # Verify header CRC
316
+ header_crc_valid = verify_header_crc(
317
+ reserved,
318
+ payload_preamble,
319
+ int(null_frame),
320
+ int(sync_frame),
321
+ int(startup_frame),
322
+ frame_id,
323
+ payload_length_words,
324
+ header_crc,
325
+ )
326
+
327
+ if not header_crc_valid:
328
+ # Note: We don't raise an error, but we could log a warning
329
+ pass
330
+
331
+ return FlexRayHeader(
332
+ reserved=reserved,
333
+ payload_preamble=payload_preamble,
334
+ null_frame=null_frame,
335
+ sync_frame=sync_frame,
336
+ startup_frame=startup_frame,
337
+ frame_id=frame_id,
338
+ payload_length=payload_length,
339
+ header_crc=header_crc,
340
+ cycle_count=cycle_count,
341
+ )
342
+
343
+ def _determine_segment_type(self, frame_id: int) -> str:
344
+ """Determine if frame is in static or dynamic segment.
345
+
346
+ Static segment frames have IDs from 1 to static_slot_count.
347
+ Dynamic segment frames have IDs from static_slot_count+1 to 2047.
348
+
349
+ Args:
350
+ frame_id: Frame ID to classify.
351
+
352
+ Returns:
353
+ Segment type ("static" or "dynamic").
354
+
355
+ Example:
356
+ >>> analyzer.cluster_config = {"static_slot_count": 100}
357
+ >>> segment = analyzer._determine_segment_type(50)
358
+ >>> print(segment) # "static"
359
+ >>> segment = analyzer._determine_segment_type(150)
360
+ >>> print(segment) # "dynamic"
361
+ """
362
+ static_slot_count = self.cluster_config.get("static_slot_count", 100)
363
+
364
+ if frame_id <= static_slot_count:
365
+ return self.STATIC_SEGMENT
366
+ else:
367
+ return self.DYNAMIC_SEGMENT
368
+
369
+ def add_signal(self, signal: FlexRaySignal) -> None:
370
+ """Add signal definition for decoding.
371
+
372
+ Args:
373
+ signal: Signal definition to add.
374
+
375
+ Example:
376
+ >>> signal = FlexRaySignal(
377
+ ... name="EngineSpeed", frame_id=100, start_bit=0,
378
+ ... bit_length=16, factor=0.25, unit="rpm"
379
+ ... )
380
+ >>> analyzer.add_signal(signal)
381
+ """
382
+ self.signals.append(signal)
383
+
384
+ def decode_signals(self, frame_id: int, payload: bytes) -> dict[str, Any]:
385
+ """Decode signals from frame payload.
386
+
387
+ Extracts and scales signal values according to signal definitions.
388
+
389
+ Args:
390
+ frame_id: Frame ID to decode.
391
+ payload: Frame payload bytes.
392
+
393
+ Returns:
394
+ Dictionary mapping signal names to physical values.
395
+
396
+ Example:
397
+ >>> signals = analyzer.decode_signals(100, b"\\x00\\x64\\x01\\x00")
398
+ >>> print(f"EngineSpeed: {signals['EngineSpeed']} rpm")
399
+ """
400
+ decoded: dict[str, Any] = {}
401
+
402
+ # Find signals for this frame
403
+ frame_signals = [s for s in self.signals if s.frame_id == frame_id]
404
+
405
+ for signal in frame_signals:
406
+ # Extract raw value
407
+ raw_value = self._extract_signal_value(payload, signal)
408
+
409
+ # Apply scaling
410
+ physical_value = raw_value * signal.factor + signal.offset
411
+
412
+ decoded[signal.name] = physical_value
413
+
414
+ return decoded
415
+
416
+ def _extract_signal_value(self, payload: bytes, signal: FlexRaySignal) -> int:
417
+ """Extract raw signal value from payload.
418
+
419
+ Args:
420
+ payload: Frame payload bytes.
421
+ signal: Signal definition.
422
+
423
+ Returns:
424
+ Raw signal value (unscaled).
425
+
426
+ Raises:
427
+ ValueError: If signal extends beyond payload.
428
+ """
429
+ # Calculate byte range
430
+ start_byte = signal.start_bit // 8
431
+ end_byte = (signal.start_bit + signal.bit_length - 1) // 8 + 1
432
+
433
+ if end_byte > len(payload):
434
+ raise ValueError(
435
+ f"Signal {signal.name} extends beyond payload: "
436
+ f"needs bytes {start_byte}-{end_byte}, payload has {len(payload)}"
437
+ )
438
+
439
+ # Extract bytes
440
+ signal_bytes = payload[start_byte:end_byte]
441
+
442
+ # Convert to integer based on byte order
443
+ if signal.byte_order == "big_endian":
444
+ value = int.from_bytes(signal_bytes, "big")
445
+ else:
446
+ value = int.from_bytes(signal_bytes, "little")
447
+
448
+ # Extract specific bits
449
+ bit_offset = signal.start_bit % 8
450
+ mask = (1 << signal.bit_length) - 1
451
+ value = (value >> bit_offset) & mask
452
+
453
+ return value
454
+
455
+ def get_frame_statistics(self) -> dict[str, Any]:
456
+ """Get statistics about parsed frames.
457
+
458
+ Returns:
459
+ Dictionary with frame statistics including:
460
+ - total_frames: Total number of frames
461
+ - frames_by_channel: Count per channel
462
+ - frames_by_id: Count per frame ID
463
+ - crc_errors: Number of CRC errors
464
+ - segment_distribution: Static vs dynamic frame counts
465
+
466
+ Example:
467
+ >>> stats = analyzer.get_frame_statistics()
468
+ >>> print(f"Total frames: {stats['total_frames']}")
469
+ >>> print(f"CRC errors: {stats['crc_errors']}")
470
+ """
471
+ stats: dict[str, Any] = {
472
+ "total_frames": len(self.frames),
473
+ "frames_by_channel": {},
474
+ "frames_by_id": {},
475
+ "crc_errors": 0,
476
+ "segment_distribution": {"static": 0, "dynamic": 0},
477
+ }
478
+
479
+ for frame in self.frames:
480
+ # Count by channel
481
+ stats["frames_by_channel"][frame.channel] = (
482
+ stats["frames_by_channel"].get(frame.channel, 0) + 1
483
+ )
484
+
485
+ # Count by frame ID
486
+ frame_id = frame.header.frame_id
487
+ stats["frames_by_id"][frame_id] = stats["frames_by_id"].get(frame_id, 0) + 1
488
+
489
+ # Count CRC errors
490
+ if not frame.crc_valid:
491
+ stats["crc_errors"] += 1
492
+
493
+ # Count segment types
494
+ stats["segment_distribution"][frame.segment_type] += 1
495
+
496
+ return stats
497
+
498
+
499
+ __all__ = [
500
+ "FlexRayAnalyzer",
501
+ "FlexRayFrame",
502
+ "FlexRayHeader",
503
+ "FlexRaySignal",
504
+ ]