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,590 @@
1
+ """DBC (CAN Database) file generator from reverse-engineered CAN protocols.
2
+
3
+ This module provides functionality to generate DBC files from CAN message
4
+ specifications, supporting all DBC elements including messages, signals,
5
+ enums, comments, and attributes.
6
+
7
+ DBC Format Reference:
8
+ Vector DBC File Format Specification
9
+ https://www.csselectronics.com/pages/can-dbc-file-format-explained
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+ from typing import Any, Literal
17
+
18
+ __all__ = [
19
+ "DBCGenerator",
20
+ "DBCMessage",
21
+ "DBCNode",
22
+ "DBCSignal",
23
+ ]
24
+
25
+
26
+ @dataclass
27
+ class DBCSignal:
28
+ """DBC signal definition.
29
+
30
+ Attributes:
31
+ name: Signal name (must be valid C identifier).
32
+ start_bit: Starting bit position (0-63).
33
+ bit_length: Signal length in bits (1-64).
34
+ byte_order: Byte order - "little_endian" (Intel) or "big_endian" (Motorola).
35
+ value_type: Value type - "unsigned" or "signed".
36
+ factor: Scaling factor (physical = raw * factor + offset).
37
+ offset: Value offset.
38
+ min_value: Minimum physical value.
39
+ max_value: Maximum physical value.
40
+ unit: Physical unit (e.g., "rpm", "km/h", "°C").
41
+ receivers: List of receiving nodes (ECUs).
42
+ value_table: Optional mapping of raw values to descriptions.
43
+ comment: Signal description/documentation.
44
+ multiplexer_indicator: Multiplexer indicator ("M" for multiplexer, "mX" for
45
+ multiplexed where X is the multiplexer value).
46
+
47
+ Example:
48
+ >>> signal = DBCSignal(
49
+ ... name="EngineSpeed",
50
+ ... start_bit=0,
51
+ ... bit_length=16,
52
+ ... byte_order="little_endian",
53
+ ... value_type="unsigned",
54
+ ... factor=0.25,
55
+ ... offset=0.0,
56
+ ... min_value=0.0,
57
+ ... max_value=16383.75,
58
+ ... unit="rpm",
59
+ ... receivers=["Gateway", "Dashboard"],
60
+ ... )
61
+ """
62
+
63
+ name: str
64
+ start_bit: int
65
+ bit_length: int
66
+ byte_order: Literal["little_endian", "big_endian"] = "little_endian"
67
+ value_type: Literal["unsigned", "signed"] = "unsigned"
68
+ factor: float = 1.0
69
+ offset: float = 0.0
70
+ min_value: float = 0.0
71
+ max_value: float = 0.0
72
+ unit: str = ""
73
+ receivers: list[str] = field(default_factory=lambda: ["Vector__XXX"])
74
+ value_table: dict[int, str] | None = None
75
+ comment: str = ""
76
+ multiplexer_indicator: str | None = None
77
+
78
+ def __post_init__(self) -> None:
79
+ """Validate signal definition."""
80
+ if self.bit_length < 1 or self.bit_length > 64:
81
+ raise ValueError(f"bit_length must be 1-64, got {self.bit_length}")
82
+ if self.start_bit < 0:
83
+ raise ValueError(f"start_bit must be >= 0, got {self.start_bit}")
84
+
85
+
86
+ @dataclass
87
+ class DBCMessage:
88
+ """DBC message definition.
89
+
90
+ Attributes:
91
+ message_id: CAN message ID (11-bit: 0-0x7FF, 29-bit: 0-0x1FFFFFFF).
92
+ name: Message name (must be valid C identifier).
93
+ dlc: Data Length Code (0-8 for CAN 2.0, up to 64 for CAN-FD).
94
+ sender: Transmitting node (ECU) name.
95
+ signals: List of signals in this message.
96
+ comment: Message description/documentation.
97
+ cycle_time: Message transmission cycle time in milliseconds.
98
+ send_type: Transmission type ("Cyclic", "Event", "IfActive", etc.).
99
+
100
+ Example:
101
+ >>> msg = DBCMessage(
102
+ ... message_id=0x200,
103
+ ... name="EngineData",
104
+ ... dlc=8,
105
+ ... sender="ECU_Engine",
106
+ ... signals=[...],
107
+ ... comment="Engine status and RPM",
108
+ ... cycle_time=10,
109
+ ... send_type="Cyclic",
110
+ ... )
111
+ """
112
+
113
+ message_id: int
114
+ name: str
115
+ dlc: int
116
+ sender: str = "Vector__XXX"
117
+ signals: list[DBCSignal] = field(default_factory=list)
118
+ comment: str = ""
119
+ cycle_time: int | None = None
120
+ send_type: str = "Cyclic"
121
+
122
+ def __post_init__(self) -> None:
123
+ """Validate message definition."""
124
+ if self.message_id < 0:
125
+ raise ValueError(f"message_id must be >= 0, got {self.message_id}")
126
+ if self.dlc < 0 or self.dlc > 64:
127
+ raise ValueError(f"dlc must be 0-64, got {self.dlc}")
128
+
129
+
130
+ @dataclass
131
+ class DBCNode:
132
+ """DBC network node (ECU) definition.
133
+
134
+ Attributes:
135
+ name: Node name (must be valid C identifier).
136
+ comment: Node description/documentation.
137
+
138
+ Example:
139
+ >>> node = DBCNode(name="ECU_Engine", comment="Engine Control Unit")
140
+ """
141
+
142
+ name: str
143
+ comment: str = ""
144
+
145
+
146
+ class DBCGenerator:
147
+ """DBC file generator from CAN specifications.
148
+
149
+ This class generates complete DBC files from message and signal definitions,
150
+ including all standard DBC sections (nodes, messages, signals, value tables,
151
+ comments, attributes).
152
+
153
+ Example:
154
+ >>> gen = DBCGenerator()
155
+ >>> gen.add_node(DBCNode("ECU_Engine", "Engine Control Unit"))
156
+ >>> gen.add_node(DBCNode("Gateway"))
157
+ >>> signal = DBCSignal(
158
+ ... name="EngineSpeed",
159
+ ... start_bit=0,
160
+ ... bit_length=16,
161
+ ... factor=0.25,
162
+ ... unit="rpm",
163
+ ... receivers=["Gateway"],
164
+ ... )
165
+ >>> msg = DBCMessage(
166
+ ... message_id=0x200,
167
+ ... name="EngineData",
168
+ ... dlc=8,
169
+ ... sender="ECU_Engine",
170
+ ... signals=[signal],
171
+ ... )
172
+ >>> gen.add_message(msg)
173
+ >>> gen.generate(Path("output.dbc"))
174
+ """
175
+
176
+ VERSION = "1.0"
177
+
178
+ def __init__(self) -> None:
179
+ """Initialize DBC generator."""
180
+ self.nodes: list[DBCNode] = []
181
+ self.messages: list[DBCMessage] = []
182
+ self.value_tables: dict[str, dict[int, str]] = {}
183
+ self.environment_variables: dict[str, Any] = {}
184
+
185
+ def add_node(self, node: DBCNode) -> None:
186
+ """Add network node (ECU).
187
+
188
+ Args:
189
+ node: Node definition to add.
190
+
191
+ Example:
192
+ >>> gen = DBCGenerator()
193
+ >>> gen.add_node(DBCNode("ECU_Engine", "Engine Control Unit"))
194
+ """
195
+ self.nodes.append(node)
196
+
197
+ def add_message(self, message: DBCMessage) -> None:
198
+ """Add CAN message definition.
199
+
200
+ Args:
201
+ message: Message definition to add.
202
+
203
+ Example:
204
+ >>> gen = DBCGenerator()
205
+ >>> msg = DBCMessage(0x200, "EngineData", 8, "ECU_Engine")
206
+ >>> gen.add_message(msg)
207
+ """
208
+ self.messages.append(message)
209
+
210
+ def add_value_table(self, name: str, values: dict[int, str]) -> None:
211
+ """Add value table (enum) for signals.
212
+
213
+ Args:
214
+ name: Value table name.
215
+ values: Mapping of raw values to descriptions.
216
+
217
+ Example:
218
+ >>> gen = DBCGenerator()
219
+ >>> gen.add_value_table("GearPosition", {
220
+ ... 0: "Park",
221
+ ... 1: "Reverse",
222
+ ... 2: "Neutral",
223
+ ... 3: "Drive",
224
+ ... })
225
+ """
226
+ self.value_tables[name] = values
227
+
228
+ def generate(self, output_path: Path) -> None:
229
+ """Generate complete DBC file.
230
+
231
+ DBC file structure (in order):
232
+ 1. VERSION
233
+ 2. NS_ (New symbols)
234
+ 3. BS_ (Bit timing)
235
+ 4. BU_ (Network nodes)
236
+ 5. VAL_TABLE_ (Value tables)
237
+ 6. BO_ (Messages with signals)
238
+ 7. CM_ (Comments)
239
+ 8. BA_DEF_ (Attribute definitions)
240
+ 9. BA_ (Attribute values)
241
+ 10. VAL_ (Signal value descriptions)
242
+
243
+ Args:
244
+ output_path: Path to output DBC file.
245
+
246
+ Example:
247
+ >>> gen = DBCGenerator()
248
+ >>> # ... add nodes, messages, signals ...
249
+ >>> gen.generate(Path("network.dbc"))
250
+ """
251
+ dbc_lines = []
252
+
253
+ # Header
254
+ dbc_lines.append(self._generate_header())
255
+
256
+ # Nodes
257
+ dbc_lines.append(self._generate_nodes())
258
+
259
+ # Value tables
260
+ value_tables = self._generate_value_tables()
261
+ if value_tables:
262
+ dbc_lines.append(value_tables)
263
+
264
+ # Messages and signals
265
+ dbc_lines.append(self._generate_messages())
266
+
267
+ # Comments
268
+ comments = self._generate_comments()
269
+ if comments:
270
+ dbc_lines.append(comments)
271
+
272
+ # Attributes
273
+ attributes = self._generate_attributes()
274
+ if attributes:
275
+ dbc_lines.append(attributes)
276
+
277
+ # Value descriptions
278
+ value_desc = self._generate_value_descriptions()
279
+ if value_desc:
280
+ dbc_lines.append(value_desc)
281
+
282
+ dbc_content = "\n".join(dbc_lines)
283
+
284
+ # Write to file
285
+ output_path.write_text(dbc_content, encoding="utf-8")
286
+
287
+ def _generate_header(self) -> str:
288
+ """Generate DBC file header.
289
+
290
+ Returns:
291
+ Header section with VERSION, NS_, and BS_.
292
+ """
293
+ return f"""VERSION "{self.VERSION}"
294
+
295
+
296
+ NS_ :
297
+ \tNS_DESC_
298
+ \tCM_
299
+ \tBA_DEF_
300
+ \tBA_
301
+ \tVAL_
302
+ \tCAT_DEF_
303
+ \tCAT_
304
+ \tFILTER
305
+ \tBA_DEF_DEF_
306
+ \tEV_DATA_
307
+ \tENVVAR_DATA_
308
+ \tSGTYPE_
309
+ \tSGTYPE_VAL_
310
+ \tBA_DEF_SGTYPE_
311
+ \tBA_SGTYPE_
312
+ \tSIG_TYPE_REF_
313
+ \tVAL_TABLE_
314
+ \tSIG_GROUP_
315
+ \tSIG_VALTYPE_
316
+ \tSIGTYPE_VALTYPE_
317
+ \tBO_TX_BU_
318
+ \tBA_DEF_REL_
319
+ \tBA_REL_
320
+ \tBA_SGTYPE_REL_
321
+ \tSG_MUL_VAL_
322
+
323
+ BS_:"""
324
+
325
+ def _generate_nodes(self) -> str:
326
+ """Generate BU_ (nodes/ECUs) section.
327
+
328
+ Returns:
329
+ BU_ section with all network nodes.
330
+ """
331
+ if not self.nodes:
332
+ return "BU_:"
333
+
334
+ node_names = " ".join(node.name for node in self.nodes)
335
+ return f"BU_: {node_names}"
336
+
337
+ def _generate_value_tables(self) -> str:
338
+ """Generate VAL_TABLE_ section.
339
+
340
+ Returns:
341
+ VAL_TABLE_ section with all value tables, or empty string if none.
342
+ """
343
+ if not self.value_tables:
344
+ return ""
345
+
346
+ lines = []
347
+ for name, values in sorted(self.value_tables.items()):
348
+ value_pairs = " ".join(f'{val} "{desc}"' for val, desc in sorted(values.items()))
349
+ lines.append(f"VAL_TABLE_ {name} {value_pairs} ;")
350
+
351
+ return "\n".join(lines)
352
+
353
+ def _generate_messages(self) -> str:
354
+ """Generate BO_ (messages) section.
355
+
356
+ Returns:
357
+ BO_ section with all messages and their signals.
358
+ """
359
+ if not self.messages:
360
+ return ""
361
+
362
+ lines = []
363
+
364
+ for message in sorted(self.messages, key=lambda m: m.message_id):
365
+ # Message line: BO_ MessageID MessageName: DLC Sender
366
+ lines.append(f"BO_ {message.message_id} {message.name}: {message.dlc} {message.sender}")
367
+
368
+ # Signal lines
369
+ for signal in message.signals:
370
+ lines.append(self._generate_signal(signal, message.message_id))
371
+
372
+ # Blank line between messages
373
+ lines.append("")
374
+
375
+ return "\n".join(lines)
376
+
377
+ def _generate_signal(self, signal: DBCSignal, message_id: int) -> str:
378
+ """Generate SG_ (signal) line.
379
+
380
+ Format:
381
+ SG_ SignalName [M|mX] : StartBit|BitLength@ByteOrder ValueType
382
+ (Factor,Offset) [Min|Max] "Unit" Receivers
383
+
384
+ Where:
385
+ ByteOrder: 1 = little endian (Intel), 0 = big endian (Motorola)
386
+ ValueType: + = unsigned, - = signed
387
+
388
+ Args:
389
+ signal: Signal definition.
390
+ message_id: Parent message ID (used for Motorola bit calculation).
391
+
392
+ Returns:
393
+ Formatted signal line.
394
+ """
395
+ # Determine byte order indicator (1 = Intel/little, 0 = Motorola/big)
396
+ byte_order_indicator = "1" if signal.byte_order == "little_endian" else "0"
397
+
398
+ # Convert start_bit to DBC format
399
+ # For Intel (little-endian): start_bit is LSB position (use as-is)
400
+ # For Motorola (big-endian): start_bit must be MSB position
401
+ if signal.byte_order == "big_endian":
402
+ # Convert from Intel LSB position to Motorola MSB position
403
+ # Motorola start_bit = Intel start_bit + bit_length - 1
404
+ start_bit = signal.start_bit + signal.bit_length - 1
405
+ else:
406
+ start_bit = signal.start_bit
407
+
408
+ # Determine value type (+ = unsigned, - = signed)
409
+ value_type_indicator = "+" if signal.value_type == "unsigned" else "-"
410
+
411
+ # Multiplexer indicator
412
+ multiplex = ""
413
+ if signal.multiplexer_indicator:
414
+ multiplex = f" {signal.multiplexer_indicator}"
415
+
416
+ # Receivers
417
+ receivers = ",".join(signal.receivers)
418
+
419
+ return (
420
+ f" SG_ {signal.name}{multiplex} : {start_bit}|{signal.bit_length}@"
421
+ f"{byte_order_indicator}{value_type_indicator} "
422
+ f"({signal.factor},{signal.offset}) "
423
+ f"[{signal.min_value}|{signal.max_value}] "
424
+ f'"{signal.unit}" {receivers}'
425
+ )
426
+
427
+ def _generate_comments(self) -> str:
428
+ """Generate CM_ (comments) section.
429
+
430
+ Returns:
431
+ CM_ section with all comments, or empty string if none.
432
+ """
433
+ lines = []
434
+
435
+ # Node comments
436
+ for node in self.nodes:
437
+ if node.comment:
438
+ lines.append(f'CM_ BU_ {node.name} "{node.comment}";')
439
+
440
+ # Message comments
441
+ for message in self.messages:
442
+ if message.comment:
443
+ lines.append(f'CM_ BO_ {message.message_id} "{message.comment}";')
444
+
445
+ # Signal comments
446
+ for message in self.messages:
447
+ for signal in message.signals:
448
+ if signal.comment:
449
+ lines.append(f'CM_ SG_ {message.message_id} {signal.name} "{signal.comment}";')
450
+
451
+ return "\n".join(lines) if lines else ""
452
+
453
+ def _generate_attributes(self) -> str:
454
+ """Generate BA_ (attributes) section.
455
+
456
+ Returns:
457
+ BA_ section with attribute definitions and values.
458
+ """
459
+ lines = []
460
+
461
+ # Define standard attributes
462
+ lines.append('BA_DEF_ "BusType" STRING ;')
463
+ lines.append('BA_DEF_ BO_ "GenMsgCycleTime" INT 0 10000;')
464
+ lines.append('BA_DEF_ BO_ "GenMsgSendType" STRING ;')
465
+
466
+ # Default attribute values
467
+ lines.append('BA_DEF_DEF_ "BusType" "CAN";')
468
+ lines.append('BA_DEF_DEF_ "GenMsgCycleTime" 0;')
469
+ lines.append('BA_DEF_DEF_ "GenMsgSendType" "Cyclic";')
470
+
471
+ # Message-specific attribute values
472
+ for message in self.messages:
473
+ if message.cycle_time is not None:
474
+ lines.append(
475
+ f'BA_ "GenMsgCycleTime" BO_ {message.message_id} {message.cycle_time};'
476
+ )
477
+ if message.send_type:
478
+ lines.append(
479
+ f'BA_ "GenMsgSendType" BO_ {message.message_id} "{message.send_type}";'
480
+ )
481
+
482
+ return "\n".join(lines)
483
+
484
+ def _generate_value_descriptions(self) -> str:
485
+ """Generate VAL_ (signal value descriptions) section.
486
+
487
+ Returns:
488
+ VAL_ section with signal value descriptions, or empty string if none.
489
+ """
490
+ lines = []
491
+
492
+ for message in self.messages:
493
+ for signal in message.signals:
494
+ if signal.value_table:
495
+ value_pairs = " ".join(
496
+ f'{val} "{desc}"' for val, desc in sorted(signal.value_table.items())
497
+ )
498
+ lines.append(f"VAL_ {message.message_id} {signal.name} {value_pairs} ;")
499
+
500
+ return "\n".join(lines) if lines else ""
501
+
502
+ def _calculate_motorola_start_bit(self, start_bit: int, bit_length: int) -> int:
503
+ """Calculate start bit for Motorola (big-endian) byte order.
504
+
505
+ In DBC files, Motorola (big-endian) signals use the MSB position as the start bit.
506
+ Intel (little-endian) signals use the LSB position as the start bit.
507
+
508
+ For Motorola byte order, the start bit is simply the MSB position,
509
+ which is start_bit + bit_length - 1.
510
+
511
+ Args:
512
+ start_bit: Intel (little-endian) start bit position (LSB).
513
+ bit_length: Signal length in bits.
514
+
515
+ Returns:
516
+ Motorola (big-endian) start bit position (MSB).
517
+
518
+ Example:
519
+ >>> gen = DBCGenerator()
520
+ >>> # 8-bit signal starting at bit 0 (Intel LSB)
521
+ >>> # Bits 0-7 -> MSB is at bit 7
522
+ >>> gen._calculate_motorola_start_bit(0, 8)
523
+ 7
524
+ >>> # 16-bit signal starting at bit 0 (Intel LSB)
525
+ >>> # Bits 0-15 -> MSB is at bit 15
526
+ >>> gen._calculate_motorola_start_bit(0, 16)
527
+ 15
528
+ """
529
+ # Motorola start bit is simply the MSB position
530
+ return start_bit + bit_length - 1
531
+
532
+ def validate_dbc(self, dbc_content: str) -> bool:
533
+ """Validate generated DBC syntax.
534
+
535
+ Basic syntax validation checking for:
536
+ - Required sections (VERSION, NS_, BS_, BU_)
537
+ - Well-formed message definitions
538
+ - Well-formed signal definitions
539
+
540
+ Args:
541
+ dbc_content: DBC file content to validate.
542
+
543
+ Returns:
544
+ True if basic syntax is valid, False otherwise.
545
+
546
+ Example:
547
+ >>> gen = DBCGenerator()
548
+ >>> gen.add_node(DBCNode("Test"))
549
+ >>> msg = DBCMessage(0x100, "TestMsg", 8, "Test")
550
+ >>> gen.add_message(msg)
551
+ >>> from pathlib import Path
552
+ >>> import tempfile
553
+ >>> with tempfile.NamedTemporaryFile(mode='w', suffix='.dbc', delete=False) as f:
554
+ ... gen.generate(Path(f.name))
555
+ ... content = Path(f.name).read_text()
556
+ ... gen.validate_dbc(content)
557
+ True
558
+ """
559
+ lines = dbc_content.strip().split("\n")
560
+
561
+ # Check for required sections
562
+ has_version = any(line.startswith("VERSION") for line in lines)
563
+ has_ns = any(line.startswith("NS_") for line in lines)
564
+ has_bs = any(line.startswith("BS_") for line in lines)
565
+ has_bu = any(line.startswith("BU_") for line in lines)
566
+
567
+ if not (has_version and has_ns and has_bs and has_bu):
568
+ return False
569
+
570
+ # Check message definitions (BO_)
571
+ for line in lines:
572
+ if line.startswith("BO_ "):
573
+ # Format: BO_ <ID> <Name>: <DLC> <Sender>
574
+ parts = line.split()
575
+ if len(parts) < 5:
576
+ return False
577
+ try:
578
+ int(parts[1]) # Message ID
579
+ int(parts[3]) # DLC (after colon)
580
+ except (ValueError, IndexError):
581
+ return False
582
+
583
+ # Check signal definitions (SG_)
584
+ for line in lines:
585
+ if line.strip().startswith("SG_ "):
586
+ # Format: SG_ <Name> : <StartBit>|<Length>@<ByteOrder><ValueType> ...
587
+ if "|" not in line or "@" not in line:
588
+ return False
589
+
590
+ return True