oscura 0.5.0__py3-none-any.whl → 0.6.0__py3-none-any.whl

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