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
@@ -70,6 +70,123 @@ class FlexRayFrame:
70
70
  errors: list[str]
71
71
 
72
72
 
73
+ class _FlexRayBitSampler:
74
+ """Helper for sampling bits from FlexRay frames."""
75
+
76
+ def __init__(self, data: NDArray[np.bool_], bit_idx: int, bit_period: float):
77
+ """Initialize bit sampler.
78
+
79
+ Args:
80
+ data: Digital data array.
81
+ bit_idx: Starting bit index.
82
+ bit_period: Bit period in samples.
83
+ """
84
+ self.data = data
85
+ self.bit_idx: float = float(bit_idx)
86
+ self.bit_period = bit_period
87
+
88
+ def sample_bits(self, count: int) -> list[int]:
89
+ """Sample specified number of bits.
90
+
91
+ Args:
92
+ count: Number of bits to sample.
93
+
94
+ Returns:
95
+ List of sampled bit values (0 or 1).
96
+ """
97
+ bits = []
98
+ for _ in range(count):
99
+ sample_idx_raw = self.bit_idx + self.bit_period / 2
100
+ sample_idx = int(sample_idx_raw)
101
+ if sample_idx < len(self.data):
102
+ bits.append(1 if self.data[sample_idx] else 0)
103
+ self.bit_idx += self.bit_period
104
+ else:
105
+ return bits
106
+ return bits
107
+
108
+ def validate_fss(self) -> bool:
109
+ """Validate Frame Start Sequence (1 bit, must be 0).
110
+
111
+ Returns:
112
+ True if FSS is valid.
113
+ """
114
+ fss_bits = self.sample_bits(1)
115
+ return bool(fss_bits and fss_bits[0] == 0)
116
+
117
+ def parse_header(self) -> dict[str, int] | None:
118
+ """Parse 40-bit FlexRay header.
119
+
120
+ Returns:
121
+ Dict with slot_id, header_crc, cycle_count, payload_length or None if incomplete.
122
+ """
123
+ header_bits = self.sample_bits(40)
124
+ if len(header_bits) < 40:
125
+ return None
126
+
127
+ return {
128
+ "slot_id": self._bits_to_int(header_bits[4:15]),
129
+ "header_crc": self._bits_to_int(header_bits[15:26]),
130
+ "cycle_count": self._bits_to_int(header_bits[26:32]),
131
+ "payload_length": self._bits_to_int(header_bits[33:40]),
132
+ }
133
+
134
+ def parse_payload(self, payload_length: int, errors: list[str]) -> list[int]:
135
+ """Parse payload bytes.
136
+
137
+ Args:
138
+ payload_length: Payload length in 16-bit words.
139
+ errors: Error list to append to.
140
+
141
+ Returns:
142
+ List of payload byte values.
143
+ """
144
+ payload_byte_count = payload_length * 2
145
+ payload_bytes = []
146
+
147
+ for _ in range(payload_byte_count):
148
+ byte_bits = self.sample_bits(8)
149
+ if len(byte_bits) == 8:
150
+ payload_bytes.append(self._bits_to_int(byte_bits))
151
+ else:
152
+ errors.append("Incomplete payload")
153
+ break
154
+
155
+ return payload_bytes
156
+
157
+ def parse_crc(self) -> int:
158
+ """Parse 24-bit frame CRC.
159
+
160
+ Returns:
161
+ CRC value.
162
+ """
163
+ crc_bits = self.sample_bits(24)
164
+ return self._bits_to_int(crc_bits)
165
+
166
+ def get_bit_idx(self) -> int:
167
+ """Get current bit index.
168
+
169
+ Returns:
170
+ Current bit index position.
171
+ """
172
+ return int(self.bit_idx)
173
+
174
+ @staticmethod
175
+ def _bits_to_int(bits: list[int]) -> int:
176
+ """Convert bit list to integer.
177
+
178
+ Args:
179
+ bits: List of bit values.
180
+
181
+ Returns:
182
+ Integer value.
183
+ """
184
+ result = 0
185
+ for bit in bits:
186
+ result = (result << 1) | bit
187
+ return result
188
+
189
+
73
190
  class FlexRayDecoder(AsyncDecoder):
74
191
  """FlexRay protocol decoder.
75
192
 
@@ -92,14 +209,14 @@ class FlexRayDecoder(AsyncDecoder):
92
209
  longname = "FlexRay Automotive Network"
93
210
  desc = "FlexRay protocol decoder"
94
211
 
95
- channels = [ # noqa: RUF012
212
+ channels = [
96
213
  ChannelDef("bp", "BP", "FlexRay Bus Plus", required=True),
97
214
  ChannelDef("bm", "BM", "FlexRay Bus Minus", required=True),
98
215
  ]
99
216
 
100
- optional_channels = [] # noqa: RUF012
217
+ optional_channels = []
101
218
 
102
- options = [ # noqa: RUF012
219
+ options = [
103
220
  OptionDef(
104
221
  "bitrate",
105
222
  "Bitrate",
@@ -109,7 +226,7 @@ class FlexRayDecoder(AsyncDecoder):
109
226
  ),
110
227
  ]
111
228
 
112
- annotations = [ # noqa: RUF012
229
+ annotations = [
113
230
  ("tss", "Transmission Start Sequence"),
114
231
  ("fss", "Frame Start Sequence"),
115
232
  ("header", "Frame header"),
@@ -285,95 +402,37 @@ class FlexRayDecoder(AsyncDecoder):
285
402
  errors = []
286
403
  bit_idx = tss_idx + int(3 * bit_period) # Skip TSS
287
404
 
288
- # Sample bits
289
- def sample_bits(count: int) -> list[int]:
290
- nonlocal bit_idx
291
- bits = []
292
- for _ in range(count):
293
- sample_idx = int(bit_idx + bit_period / 2)
294
- if sample_idx < len(data):
295
- bits.append(1 if data[sample_idx] else 0)
296
- bit_idx += bit_period # type: ignore[assignment]
297
- else:
298
- return bits
299
- return bits
300
-
301
- # FSS (Frame Start Sequence) - 1 bit
302
- fss_bits = sample_bits(1)
303
- if not fss_bits or fss_bits[0] != 0:
304
- errors.append("Invalid FSS")
405
+ sampler = _FlexRayBitSampler(data, bit_idx, bit_period)
305
406
 
306
- # Header (5 bytes = 40 bits)
307
- # Byte 1: Reserved (1) + Payload preamble (1) + NULL frame (1) + Sync (1) + Startup (1) + Slot ID[10:8] (3)
308
- # Byte 2: Slot ID[7:0] (8)
309
- # Byte 3: Header CRC[10:3] (8)
310
- # Byte 4: Header CRC[2:0] (3) + Cycle count[5:0] (6) - split to bits 7:5 and 4:0
311
- # Byte 5: Cycle count continued + Payload length[6:0] (7)
407
+ # Validate FSS
408
+ if not sampler.validate_fss():
409
+ errors.append("Invalid FSS")
312
410
 
313
- header_bits = sample_bits(40)
314
- if len(header_bits) < 40:
315
- return None, int(bit_idx)
316
-
317
- # Extract header fields (simplified)
318
- # Slot ID (11 bits): bits 4-14
319
- slot_id_bits = header_bits[4:15]
320
- slot_id = 0
321
- for bit in slot_id_bits:
322
- slot_id = (slot_id << 1) | bit
323
-
324
- # Header CRC (11 bits): bits 15-25
325
- header_crc_bits = header_bits[15:26]
326
- header_crc = 0
327
- for bit in header_crc_bits:
328
- header_crc = (header_crc << 1) | bit
329
-
330
- # Cycle count (6 bits): bits 26-31
331
- cycle_bits = header_bits[26:32]
332
- cycle_count = 0
333
- for bit in cycle_bits:
334
- cycle_count = (cycle_count << 1) | bit
335
-
336
- # Payload length (7 bits): bits 33-39
337
- payload_len_bits = header_bits[33:40]
338
- payload_length = 0
339
- for bit in payload_len_bits:
340
- payload_length = (payload_length << 1) | bit
341
-
342
- # Payload (payload_length * 2 bytes, as length is in 16-bit words)
343
- payload_byte_count = payload_length * 2
344
- payload_bytes = []
411
+ # Parse header
412
+ header_fields = sampler.parse_header()
413
+ if header_fields is None:
414
+ return None, sampler.get_bit_idx()
345
415
 
346
- for _ in range(payload_byte_count):
347
- byte_bits = sample_bits(8)
348
- if len(byte_bits) == 8:
349
- byte_val = 0
350
- for bit in byte_bits:
351
- byte_val = (byte_val << 1) | bit
352
- payload_bytes.append(byte_val)
353
- else:
354
- errors.append("Incomplete payload")
355
- break
416
+ # Parse payload
417
+ payload_bytes = sampler.parse_payload(header_fields["payload_length"], errors)
356
418
 
357
- # Frame CRC (24 bits)
358
- crc_bits = sample_bits(24)
359
- frame_crc = 0
360
- for bit in crc_bits:
361
- frame_crc = (frame_crc << 1) | bit
419
+ # Parse CRC
420
+ frame_crc = sampler.parse_crc()
362
421
 
363
422
  # Create frame
364
423
  frame = FlexRayFrame(
365
- slot_id=slot_id,
366
- cycle_count=cycle_count,
367
- payload_length=payload_length,
368
- header_crc=header_crc,
424
+ slot_id=header_fields["slot_id"],
425
+ cycle_count=header_fields["cycle_count"],
426
+ payload_length=header_fields["payload_length"],
427
+ header_crc=header_fields["header_crc"],
369
428
  payload=bytes(payload_bytes),
370
429
  frame_crc=frame_crc,
371
- segment=FlexRaySegment.STATIC, # Simplified: assume static
430
+ segment=FlexRaySegment.STATIC,
372
431
  timestamp=tss_idx / sample_rate,
373
432
  errors=errors,
374
433
  )
375
434
 
376
- return frame, int(bit_idx)
435
+ return frame, sampler.get_bit_idx()
377
436
 
378
437
 
379
438
  def decode_flexray(
@@ -30,6 +30,7 @@ from oscura.core.types import (
30
30
  TraceMetadata,
31
31
  WaveformTrace,
32
32
  )
33
+ from oscura.utils.bitwise import bits_to_byte
33
34
 
34
35
  if TYPE_CHECKING:
35
36
  from collections.abc import Iterator
@@ -60,13 +61,13 @@ class HDLCDecoder(AsyncDecoder):
60
61
  longname = "High-Level Data Link Control"
61
62
  desc = "HDLC telecom protocol decoder"
62
63
 
63
- channels = [ # noqa: RUF012
64
+ channels = [
64
65
  ChannelDef("data", "DATA", "HDLC data line", required=True),
65
66
  ]
66
67
 
67
- optional_channels = [] # noqa: RUF012
68
+ optional_channels = []
68
69
 
69
- options = [ # noqa: RUF012
70
+ options = [
70
71
  OptionDef("baudrate", "Baud rate", "Bits per second", default=1000000, values=None),
71
72
  OptionDef(
72
73
  "fcs",
@@ -77,7 +78,7 @@ class HDLCDecoder(AsyncDecoder):
77
78
  ),
78
79
  ]
79
80
 
80
- annotations = [ # noqa: RUF012
81
+ annotations = [
81
82
  ("flag", "Flag sequence"),
82
83
  ("address", "Address field"),
83
84
  ("control", "Control field"),
@@ -104,6 +105,72 @@ class HDLCDecoder(AsyncDecoder):
104
105
  self._fcs = fcs
105
106
  self._fcs_bytes = 2 if fcs == "crc16" else 4
106
107
 
108
+ def _process_frame_bytes(
109
+ self,
110
+ unstuffed_bits: list[int],
111
+ ) -> tuple[list[int], int, int, list[int], list[int]]:
112
+ """Convert unstuffed bits to bytes and extract frame fields."""
113
+ field_bytes = []
114
+ for i in range(0, len(unstuffed_bits), 8):
115
+ if i + 8 <= len(unstuffed_bits):
116
+ byte_val = bits_to_byte(unstuffed_bits[i : i + 8], lsb_first=True)
117
+ field_bytes.append(byte_val)
118
+
119
+ address = field_bytes[0]
120
+ control = field_bytes[1]
121
+ info_bytes = field_bytes[2 : -self._fcs_bytes]
122
+ fcs_bytes = field_bytes[-self._fcs_bytes :]
123
+
124
+ return field_bytes, address, control, info_bytes, fcs_bytes
125
+
126
+ def _validate_fcs(
127
+ self,
128
+ frame_data: list[int],
129
+ fcs_bytes: list[int],
130
+ ) -> tuple[bool, list[str]]:
131
+ """Validate Frame Check Sequence and return errors if any."""
132
+ errors = []
133
+
134
+ if self._fcs == "crc16":
135
+ computed_fcs = self._crc16_ccitt(bytes(frame_data))
136
+ received_fcs = (fcs_bytes[1] << 8) | fcs_bytes[0]
137
+ else:
138
+ computed_fcs = self._crc32(bytes(frame_data))
139
+ received_fcs = (
140
+ (fcs_bytes[3] << 24) | (fcs_bytes[2] << 16) | (fcs_bytes[1] << 8) | fcs_bytes[0]
141
+ )
142
+
143
+ if computed_fcs != received_fcs:
144
+ errors.append("FCS mismatch")
145
+
146
+ return computed_fcs == received_fcs, errors
147
+
148
+ def _create_hdlc_packet(
149
+ self,
150
+ frame_num: int,
151
+ address: int,
152
+ control: int,
153
+ info_bytes: list[int],
154
+ errors: list[str],
155
+ start_time: float,
156
+ ) -> ProtocolPacket:
157
+ """Create HDLC protocol packet from decoded frame."""
158
+ annotations = {
159
+ "frame_num": frame_num,
160
+ "address": address,
161
+ "control": control,
162
+ "info_length": len(info_bytes),
163
+ "fcs_type": self._fcs,
164
+ }
165
+
166
+ return ProtocolPacket(
167
+ timestamp=start_time,
168
+ protocol="hdlc",
169
+ data=bytes(info_bytes),
170
+ annotations=annotations,
171
+ errors=errors,
172
+ )
173
+
107
174
  def decode(
108
175
  self,
109
176
  trace: DigitalTrace | WaveformTrace,
@@ -168,38 +235,20 @@ class HDLCDecoder(AsyncDecoder):
168
235
  idx = next_flag_idx + 8
169
236
  continue
170
237
 
171
- # Extract fields
172
- field_bytes = []
173
- for i in range(0, len(unstuffed_bits), 8):
174
- if i + 8 <= len(unstuffed_bits):
175
- byte_val = self._bits_to_byte(unstuffed_bits[i : i + 8])
176
- field_bytes.append(byte_val)
238
+ # Extract and process frame fields
239
+ field_bytes, address, control, info_bytes, fcs_bytes = self._process_frame_bytes(
240
+ unstuffed_bits
241
+ )
177
242
 
178
243
  if len(field_bytes) < 2 + self._fcs_bytes:
179
244
  idx = next_flag_idx + 8
180
245
  continue
181
246
 
182
- # Split into address, control, info, and FCS
183
- address = field_bytes[0]
184
- control = field_bytes[1]
185
- info_bytes = field_bytes[2 : -self._fcs_bytes]
186
- fcs_bytes = field_bytes[-self._fcs_bytes :]
187
-
188
247
  # Validate FCS
189
248
  errors = list(stuff_errors)
190
249
  frame_data = field_bytes[: -self._fcs_bytes]
191
-
192
- if self._fcs == "crc16":
193
- computed_fcs = self._crc16_ccitt(bytes(frame_data))
194
- received_fcs = (fcs_bytes[1] << 8) | fcs_bytes[0]
195
- else:
196
- computed_fcs = self._crc32(bytes(frame_data))
197
- received_fcs = (
198
- (fcs_bytes[3] << 24) | (fcs_bytes[2] << 16) | (fcs_bytes[1] << 8) | fcs_bytes[0]
199
- )
200
-
201
- if computed_fcs != received_fcs:
202
- errors.append("FCS mismatch")
250
+ _, fcs_errors = self._validate_fcs(frame_data, fcs_bytes)
251
+ errors.extend(fcs_errors)
203
252
 
204
253
  # Calculate timing
205
254
  start_time = (flag_idx * bit_period) / sample_rate
@@ -213,23 +262,10 @@ class HDLCDecoder(AsyncDecoder):
213
262
  f"Addr: 0x{address:02X}, Ctrl: 0x{control:02X}",
214
263
  )
215
264
 
216
- # Create packet
217
- annotations = {
218
- "frame_num": frame_num,
219
- "address": address,
220
- "control": control,
221
- "info_length": len(info_bytes),
222
- "fcs_type": self._fcs,
223
- }
224
-
225
- packet = ProtocolPacket(
226
- timestamp=start_time,
227
- protocol="hdlc",
228
- data=bytes(info_bytes),
229
- annotations=annotations,
230
- errors=errors,
265
+ # Create and yield packet
266
+ packet = self._create_hdlc_packet(
267
+ frame_num, address, control, info_bytes, errors, start_time
231
268
  )
232
-
233
269
  yield packet
234
270
 
235
271
  frame_num += 1
@@ -313,20 +349,6 @@ class HDLCDecoder(AsyncDecoder):
313
349
 
314
350
  return unstuffed, errors
315
351
 
316
- def _bits_to_byte(self, bits: list[int]) -> int:
317
- """Convert 8 bits to byte (LSB first).
318
-
319
- Args:
320
- bits: List of 8 bits.
321
-
322
- Returns:
323
- Byte value.
324
- """
325
- value = 0
326
- for i in range(min(8, len(bits))):
327
- value |= bits[i] << i
328
- return value
329
-
330
352
  def _crc16_ccitt(self, data: bytes) -> int:
331
353
  """Compute CRC-16-CCITT.
332
354
 
@@ -362,6 +384,19 @@ class HDLCDecoder(AsyncDecoder):
362
384
  crc >>= 1
363
385
  return crc ^ 0xFFFFFFFF
364
386
 
387
+ def _bits_to_byte(self, bits: list[int]) -> int:
388
+ """Convert bit list to byte value (LSB first).
389
+
390
+ Wrapper for bitwise.bits_to_byte utility.
391
+
392
+ Args:
393
+ bits: List of bits (up to 8).
394
+
395
+ Returns:
396
+ Byte value (0-255).
397
+ """
398
+ return bits_to_byte(bits)
399
+
365
400
 
366
401
  def decode_hdlc(
367
402
  data: NDArray[np.bool_] | WaveformTrace | DigitalTrace,