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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,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
+ ]
@@ -0,0 +1,185 @@
1
+ """FlexRay CRC calculation algorithms.
2
+
3
+ This module implements FlexRay CRC algorithms:
4
+ - Header CRC-11 (protects frame header)
5
+ - Frame CRC-24 (protects header + payload)
6
+
7
+ References:
8
+ FlexRay Communications System Protocol Specification Version 3.0.1
9
+ Section 4.5: Error Detection Mechanisms
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+
15
+ def calculate_header_crc(
16
+ reserved: int,
17
+ payload_preamble: int,
18
+ null_frame: int,
19
+ sync_frame: int,
20
+ startup_frame: int,
21
+ frame_id: int,
22
+ payload_length: int,
23
+ ) -> int:
24
+ """Calculate 11-bit FlexRay header CRC.
25
+
26
+ The header CRC protects the first 29 bits of the header (excluding the CRC
27
+ itself and the cycle count field).
28
+
29
+ CRC polynomial: x^11 + x^9 + x^8 + x^7 + x^2 + 1 (0x385)
30
+ Initial value: 0x01A
31
+
32
+ Args:
33
+ reserved: Reserved bit (1 bit).
34
+ payload_preamble: Payload preamble indicator (1 bit).
35
+ null_frame: Null frame indicator (1 bit).
36
+ sync_frame: Sync frame indicator (1 bit).
37
+ startup_frame: Startup frame indicator (1 bit).
38
+ frame_id: Frame ID (11 bits, 1-2047).
39
+ payload_length: Payload length in 16-bit words (7 bits, 0-127).
40
+
41
+ Returns:
42
+ 11-bit header CRC value.
43
+
44
+ Example:
45
+ >>> crc = calculate_header_crc(0, 0, 0, 0, 0, 100, 5)
46
+ >>> print(f"Header CRC: 0x{crc:03X}")
47
+ """
48
+ # Combine header fields (29 bits without CRC and cycle count)
49
+ # Bit positions from MSB:
50
+ # [28] reserved
51
+ # [27] payload_preamble
52
+ # [26] null_frame
53
+ # [25] sync_frame
54
+ # [24] startup_frame
55
+ # [23:13] frame_id (11 bits)
56
+ # [12:6] payload_length (7 bits)
57
+ # [5:0] padding (will be shifted out during calculation)
58
+ data = (
59
+ (reserved << 28)
60
+ | (payload_preamble << 27)
61
+ | (null_frame << 26)
62
+ | (sync_frame << 25)
63
+ | (startup_frame << 24)
64
+ | (frame_id << 13)
65
+ | (payload_length << 6)
66
+ )
67
+
68
+ # CRC-11 calculation
69
+ crc = 0x01A # Initial value
70
+ polynomial = 0x385 # x^11 + x^9 + x^8 + x^7 + x^2 + 1
71
+
72
+ # Process 29 data bits
73
+ for i in range(29):
74
+ bit = (data >> (28 - i)) & 1
75
+ msb = (crc >> 10) & 1
76
+
77
+ crc = (crc << 1) & 0x7FF # Shift and keep 11 bits
78
+ if msb ^ bit:
79
+ crc ^= polynomial
80
+
81
+ return crc & 0x7FF
82
+
83
+
84
+ def calculate_frame_crc(header: bytes, payload: bytes) -> int:
85
+ """Calculate 24-bit FlexRay frame CRC.
86
+
87
+ The frame CRC protects the entire frame (header + payload).
88
+
89
+ CRC polynomial: x^24 + x^22 + x^20 + x^19 + x^18 + x^16 + x^14 +
90
+ x^13 + x^11 + x^10 + x^8 + x^7 + x^6 + x^3 + x^1 + 1
91
+ (0x5D6DCB)
92
+ Initial value: 0xFEDCBA
93
+
94
+ Args:
95
+ header: Frame header bytes (5 bytes).
96
+ payload: Frame payload bytes (0-254 bytes).
97
+
98
+ Returns:
99
+ 24-bit frame CRC value.
100
+
101
+ Example:
102
+ >>> header = bytes([0x00, 0x64, 0x12, 0x34, 0x05])
103
+ >>> payload = bytes([0x01, 0x02, 0x03, 0x04, 0x05])
104
+ >>> crc = calculate_frame_crc(header, payload)
105
+ >>> print(f"Frame CRC: 0x{crc:06X}")
106
+ """
107
+ data = header + payload
108
+
109
+ crc = 0xFEDCBA # Initial value
110
+ polynomial = 0x5D6DCB # FlexRay CRC-24 polynomial
111
+
112
+ for byte in data:
113
+ crc ^= byte << 16
114
+
115
+ for _ in range(8):
116
+ if crc & 0x800000:
117
+ crc = ((crc << 1) ^ polynomial) & 0xFFFFFF
118
+ else:
119
+ crc = (crc << 1) & 0xFFFFFF
120
+
121
+ return crc
122
+
123
+
124
+ def verify_header_crc(
125
+ reserved: int,
126
+ payload_preamble: int,
127
+ null_frame: int,
128
+ sync_frame: int,
129
+ startup_frame: int,
130
+ frame_id: int,
131
+ payload_length: int,
132
+ received_crc: int,
133
+ ) -> bool:
134
+ """Verify FlexRay header CRC.
135
+
136
+ Args:
137
+ reserved: Reserved bit.
138
+ payload_preamble: Payload preamble indicator.
139
+ null_frame: Null frame indicator.
140
+ sync_frame: Sync frame indicator.
141
+ startup_frame: Startup frame indicator.
142
+ frame_id: Frame ID (11 bits).
143
+ payload_length: Payload length in words (7 bits).
144
+ received_crc: Received CRC value to verify.
145
+
146
+ Returns:
147
+ True if CRC is valid, False otherwise.
148
+
149
+ Example:
150
+ >>> valid = verify_header_crc(0, 0, 0, 0, 0, 100, 5, 0x3A5)
151
+ >>> print(f"Header CRC valid: {valid}")
152
+ """
153
+ computed_crc = calculate_header_crc(
154
+ reserved, payload_preamble, null_frame, sync_frame, startup_frame, frame_id, payload_length
155
+ )
156
+ return computed_crc == received_crc
157
+
158
+
159
+ def verify_frame_crc(header: bytes, payload: bytes, received_crc: int) -> bool:
160
+ """Verify FlexRay frame CRC.
161
+
162
+ Args:
163
+ header: Frame header bytes (5 bytes).
164
+ payload: Frame payload bytes.
165
+ received_crc: Received CRC value to verify.
166
+
167
+ Returns:
168
+ True if CRC is valid, False otherwise.
169
+
170
+ Example:
171
+ >>> header = bytes([0x00, 0x64, 0x12, 0x34, 0x05])
172
+ >>> payload = bytes([0x01, 0x02, 0x03, 0x04, 0x05])
173
+ >>> valid = verify_frame_crc(header, payload, 0x123456)
174
+ >>> print(f"Frame CRC valid: {valid}")
175
+ """
176
+ computed_crc = calculate_frame_crc(header, payload)
177
+ return computed_crc == received_crc
178
+
179
+
180
+ __all__ = [
181
+ "calculate_frame_crc",
182
+ "calculate_header_crc",
183
+ "verify_frame_crc",
184
+ "verify_header_crc",
185
+ ]