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
@@ -83,7 +83,23 @@ def detect_protocol(
83
83
  characteristics = _analyze_signal_characteristics(trace)
84
84
 
85
85
  # Define protocol detectors
86
- protocol_detectors: list[tuple[str, Callable[[dict[str, Any]], float], dict[str, Any]]] = [
86
+ protocol_detectors = _build_protocol_detectors(characteristics)
87
+
88
+ # Score protocols
89
+ candidates = _score_protocols(protocol_detectors, characteristics, parallel)
90
+
91
+ # Validate and select primary
92
+ primary = _validate_detection(candidates, min_confidence)
93
+
94
+ # Build result
95
+ return _build_detection_result(primary, characteristics, candidates, return_candidates)
96
+
97
+
98
+ def _build_protocol_detectors(
99
+ characteristics: dict[str, Any],
100
+ ) -> list[tuple[str, Callable[[dict[str, Any]], float], dict[str, Any]]]:
101
+ """Build list of protocol detectors with configurations."""
102
+ return [
87
103
  (
88
104
  "UART",
89
105
  _score_uart,
@@ -121,65 +137,100 @@ def detect_protocol(
121
137
  ),
122
138
  ]
123
139
 
124
- # Score protocols (optionally in parallel)
125
- candidates = []
126
140
 
141
+ def _score_protocols(
142
+ protocol_detectors: list[tuple[str, Callable[[dict[str, Any]], float], dict[str, Any]]],
143
+ characteristics: dict[str, Any],
144
+ parallel: bool,
145
+ ) -> list[dict[str, Any]]:
146
+ """Score all protocol detectors."""
127
147
  if parallel:
128
- # Run scoring in parallel using ThreadPoolExecutor
129
- with ThreadPoolExecutor(max_workers=len(protocol_detectors)) as executor:
130
- # Submit all scoring tasks
131
- future_to_protocol = {
132
- executor.submit(scorer, characteristics): (name, config)
133
- for name, scorer, config in protocol_detectors
134
- }
135
-
136
- # Collect results
137
- for future in as_completed(future_to_protocol):
138
- name, config = future_to_protocol[future]
139
- try:
140
- score = future.result()
141
- if score > 0:
142
- candidates.append(
143
- {
144
- "protocol": name,
145
- "confidence": score,
146
- "config": config,
147
- }
148
- )
149
- except Exception:
150
- # If a scorer fails, skip that protocol
151
- pass
152
- else:
153
- # Sequential scoring (original behavior)
154
- for name, scorer, config in protocol_detectors:
155
- score = scorer(characteristics)
156
- if score > 0:
157
- candidates.append(
158
- {
159
- "protocol": name,
160
- "confidence": score,
161
- "config": config,
162
- }
163
- )
164
-
165
- # Sort by confidence
148
+ return _score_protocols_parallel(protocol_detectors, characteristics)
149
+ return _score_protocols_sequential(protocol_detectors, characteristics)
150
+
151
+
152
+ def _score_protocols_parallel(
153
+ protocol_detectors: list[tuple[str, Callable[[dict[str, Any]], float], dict[str, Any]]],
154
+ characteristics: dict[str, Any],
155
+ ) -> list[dict[str, Any]]:
156
+ """Score protocols in parallel using ThreadPoolExecutor."""
157
+ candidates = []
158
+
159
+ with ThreadPoolExecutor(max_workers=len(protocol_detectors)) as executor:
160
+ future_to_protocol = {
161
+ executor.submit(scorer, characteristics): (name, config)
162
+ for name, scorer, config in protocol_detectors
163
+ }
164
+
165
+ for future in as_completed(future_to_protocol):
166
+ name, config = future_to_protocol[future]
167
+ try:
168
+ score = future.result()
169
+ if score > 0:
170
+ candidates.append(
171
+ {
172
+ "protocol": name,
173
+ "confidence": score,
174
+ "config": config,
175
+ }
176
+ )
177
+ except Exception:
178
+ pass
179
+
180
+ candidates.sort(key=lambda x: x["confidence"], reverse=True) # type: ignore[arg-type, return-value]
181
+ return candidates
182
+
183
+
184
+ def _score_protocols_sequential(
185
+ protocol_detectors: list[tuple[str, Callable[[dict[str, Any]], float], dict[str, Any]]],
186
+ characteristics: dict[str, Any],
187
+ ) -> list[dict[str, Any]]:
188
+ """Score protocols sequentially."""
189
+ candidates = []
190
+
191
+ for name, scorer, config in protocol_detectors:
192
+ score = scorer(characteristics)
193
+ if score > 0:
194
+ candidates.append(
195
+ {
196
+ "protocol": name,
197
+ "confidence": score,
198
+ "config": config,
199
+ }
200
+ )
201
+
166
202
  candidates.sort(key=lambda x: x["confidence"], reverse=True) # type: ignore[arg-type, return-value]
203
+ return candidates
167
204
 
205
+
206
+ def _validate_detection(
207
+ candidates: list[dict[str, Any]],
208
+ min_confidence: float,
209
+ ) -> dict[str, Any]:
210
+ """Validate that detection meets confidence threshold."""
168
211
  if not candidates:
169
212
  raise AnalysisError(
170
213
  "Could not detect protocol type. Signal may be analog or unsupported protocol."
171
214
  )
172
215
 
173
- # Primary detection
174
216
  primary = candidates[0]
175
217
 
176
- if primary["confidence"] < min_confidence: # type: ignore[operator]
218
+ if float(primary["confidence"]) < min_confidence:
177
219
  raise AnalysisError(
178
220
  f"Protocol detection confidence too low: {primary['confidence']:.1%} "
179
221
  f"(minimum: {min_confidence:.1%}). Try specifying protocol manually."
180
222
  )
181
223
 
182
- # Build result
224
+ return primary
225
+
226
+
227
+ def _build_detection_result(
228
+ primary: dict[str, Any],
229
+ characteristics: dict[str, Any],
230
+ candidates: list[dict[str, Any]],
231
+ return_candidates: bool,
232
+ ) -> dict[str, Any]:
233
+ """Build detection result dictionary."""
183
234
  result = {
184
235
  "protocol": primary["protocol"],
185
236
  "confidence": primary["confidence"],
@@ -512,6 +512,79 @@ class ProtocolDecoder:
512
512
  return None
513
513
  return idx
514
514
 
515
+ def _decode_bytes_field(
516
+ self, data: bytes, field: FieldDefinition, context: dict[str, Any]
517
+ ) -> tuple[bytes, int]:
518
+ """Decode bytes field.
519
+
520
+ Args:
521
+ data: Binary data
522
+ field: Field definition
523
+ context: Previously decoded fields
524
+
525
+ Returns:
526
+ Tuple of (bytes value, bytes consumed)
527
+ """
528
+ size = self._resolve_size(field.size, context, data)
529
+ if size > len(data):
530
+ size = len(data) # Use remaining data
531
+ return bytes(data[:size]), size
532
+
533
+ def _decode_string_field(
534
+ self, data: bytes, field: FieldDefinition, context: dict[str, Any]
535
+ ) -> tuple[str, int]:
536
+ """Decode string field.
537
+
538
+ Args:
539
+ data: Binary data
540
+ field: Field definition
541
+ context: Previously decoded fields
542
+
543
+ Returns:
544
+ Tuple of (string value, bytes consumed)
545
+ """
546
+ size = self._resolve_size(field.size, context, data)
547
+ if size > len(data):
548
+ size = len(data) # Use remaining data
549
+ string_bytes = data[:size]
550
+
551
+ # Try to decode as UTF-8, fall back to latin-1
552
+ try:
553
+ value = string_bytes.decode("utf-8").rstrip("\x00")
554
+ except UnicodeDecodeError:
555
+ value = string_bytes.decode("latin-1").rstrip("\x00")
556
+
557
+ return value, size
558
+
559
+ def _decode_bitfield_field(
560
+ self, data: bytes, field: FieldDefinition, endian: str
561
+ ) -> tuple[int, int]:
562
+ """Decode bitfield field.
563
+
564
+ Args:
565
+ data: Binary data
566
+ field: Field definition
567
+ endian: Endianness marker
568
+
569
+ Returns:
570
+ Tuple of (bitfield value, bytes consumed)
571
+
572
+ Raises:
573
+ ValueError: If bitfield size is unsupported
574
+ """
575
+ field_size = field.size if isinstance(field.size, int) else 1
576
+
577
+ if field_size == 1:
578
+ bitfield_value = int(data[0])
579
+ elif field_size == 2:
580
+ bitfield_value = struct.unpack(f"{endian}H", data[:2])[0]
581
+ elif field_size == 4:
582
+ bitfield_value = struct.unpack(f"{endian}I", data[:4])[0]
583
+ else:
584
+ raise ValueError(f"Unsupported bitfield size: {field_size}")
585
+
586
+ return bitfield_value, field_size
587
+
515
588
  def _decode_field(
516
589
  self, data: bytes, field: FieldDefinition, context: dict[str, Any]
517
590
  ) -> tuple[Any, int]:
@@ -538,55 +611,30 @@ class ProtocolDecoder:
538
611
  return self._decode_integer(data, field_type, endian)
539
612
 
540
613
  # Float types
541
- elif field_type in ["float32", "float64"]:
614
+ if field_type in ["float32", "float64"]:
542
615
  return self._decode_float(data, field_type, endian)
543
616
 
544
617
  # Bytes
545
- elif field_type == "bytes":
546
- size = self._resolve_size(field.size, context, data)
547
- if size > len(data):
548
- size = len(data) # Use remaining data
549
- return bytes(data[:size]), size
618
+ if field_type == "bytes":
619
+ return self._decode_bytes_field(data, field, context)
550
620
 
551
621
  # String
552
- elif field_type == "string":
553
- size = self._resolve_size(field.size, context, data)
554
- if size > len(data):
555
- size = len(data) # Use remaining data
556
- string_bytes = data[:size]
557
- # Try to decode as UTF-8, fall back to latin-1
558
- try:
559
- value = string_bytes.decode("utf-8").rstrip("\x00")
560
- except UnicodeDecodeError:
561
- value = string_bytes.decode("latin-1").rstrip("\x00")
562
- return value, size
622
+ if field_type == "string":
623
+ return self._decode_string_field(data, field, context)
563
624
 
564
625
  # Bitfield
565
- elif field_type == "bitfield":
566
- # Decode as uint and extract bits
567
- field_size = field.size if isinstance(field.size, int) else 1
568
- if field_size == 1:
569
- bitfield_value = int(data[0])
570
- elif field_size == 2:
571
- bitfield_value = struct.unpack(f"{endian}H", data[:2])[0]
572
- elif field_size == 4:
573
- bitfield_value = struct.unpack(f"{endian}I", data[:4])[0]
574
- else:
575
- raise ValueError(f"Unsupported bitfield size: {field_size}")
576
-
577
- # Return as-is, caller can extract specific bits
578
- return bitfield_value, field_size
626
+ if field_type == "bitfield":
627
+ return self._decode_bitfield_field(data, field, endian)
579
628
 
580
629
  # Array
581
- elif field_type == "array":
630
+ if field_type == "array":
582
631
  return self._decode_array(data, field, context)
583
632
 
584
633
  # Struct (nested)
585
- elif field_type == "struct":
634
+ if field_type == "struct":
586
635
  return self._decode_struct(data, field, context)
587
636
 
588
- else:
589
- raise ValueError(f"Unknown field type: {field_type}")
637
+ raise ValueError(f"Unknown field type: {field_type}")
590
638
 
591
639
  def _decode_array(
592
640
  self, data: bytes, field: FieldDefinition, context: dict[str, Any]
@@ -910,71 +958,121 @@ class ProtocolEncoder:
910
958
  def _encode_field(self, value: Any, field: FieldDefinition) -> bytes:
911
959
  """Encode single field value.
912
960
 
913
- : Field encoding.
914
-
915
961
  Args:
916
- value: Field value
917
- field: Field definition
962
+ value: Field value.
963
+ field: Field definition.
918
964
 
919
965
  Returns:
920
- Encoded bytes
966
+ Encoded bytes.
921
967
 
922
968
  Raises:
923
- ValueError: If bytes value is invalid or field type is unknown for encoding
969
+ ValueError: If bytes value is invalid or field type is unknown for encoding.
970
+
971
+ Example:
972
+ >>> encoder._encode_field(42, FieldDefinition("counter", "uint16", "big"))
973
+ b'\\x00*'
974
+ """
975
+ field_type = field.field_type
976
+
977
+ # Dispatch to type-specific encoders
978
+ if field_type in {"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64"}:
979
+ return self._encode_integer_field(value, field)
980
+ elif field_type in {"float32", "float64"}:
981
+ return self._encode_float_field(value, field)
982
+ elif field_type == "bytes":
983
+ return self._encode_bytes_field(value)
984
+ elif field_type == "string":
985
+ return self._encode_string_field(value)
986
+ elif field_type == "array":
987
+ return self._encode_array(value, field)
988
+ elif field_type == "struct":
989
+ return self._encode_struct(value, field)
990
+ else:
991
+ raise ValueError(f"Unknown field type for encoding: {field_type}")
992
+
993
+ def _encode_integer_field(self, value: Any, field: FieldDefinition) -> bytes:
994
+ """Encode integer field types.
995
+
996
+ Args:
997
+ value: Integer value.
998
+ field: Field definition with type and endianness.
999
+
1000
+ Returns:
1001
+ Packed integer bytes.
924
1002
  """
925
1003
  endian = self._endian_map.get(field.endian, ">")
926
1004
  field_type = field.field_type
927
1005
 
928
- # Integer types
929
- if field_type == "uint8":
930
- return struct.pack("B", int(value))
931
- elif field_type == "int8":
932
- return struct.pack("b", int(value))
933
- elif field_type == "uint16":
934
- return struct.pack(f"{endian}H", int(value))
935
- elif field_type == "int16":
936
- return struct.pack(f"{endian}h", int(value))
937
- elif field_type == "uint32":
938
- return struct.pack(f"{endian}I", int(value))
939
- elif field_type == "int32":
940
- return struct.pack(f"{endian}i", int(value))
941
- elif field_type == "uint64":
942
- return struct.pack(f"{endian}Q", int(value))
943
- elif field_type == "int64":
944
- return struct.pack(f"{endian}q", int(value))
1006
+ # Map field types to struct format characters
1007
+ _INT_FORMATS = {
1008
+ "uint8": "B",
1009
+ "int8": "b",
1010
+ "uint16": "H",
1011
+ "int16": "h",
1012
+ "uint32": "I",
1013
+ "int32": "i",
1014
+ "uint64": "Q",
1015
+ "int64": "q",
1016
+ }
945
1017
 
946
- # Float types
947
- elif field_type == "float32":
1018
+ fmt_char = _INT_FORMATS[field_type]
1019
+ if fmt_char in {"B", "b"}:
1020
+ # uint8/int8 have no endianness
1021
+ return struct.pack(fmt_char, int(value))
1022
+ else:
1023
+ return struct.pack(f"{endian}{fmt_char}", int(value))
1024
+
1025
+ def _encode_float_field(self, value: Any, field: FieldDefinition) -> bytes:
1026
+ """Encode floating-point field types.
1027
+
1028
+ Args:
1029
+ value: Float value.
1030
+ field: Field definition with type and endianness.
1031
+
1032
+ Returns:
1033
+ Packed float bytes.
1034
+ """
1035
+ endian = self._endian_map.get(field.endian, ">")
1036
+
1037
+ if field.field_type == "float32":
948
1038
  return struct.pack(f"{endian}f", float(value))
949
- elif field_type == "float64":
1039
+ elif field.field_type == "float64":
950
1040
  return struct.pack(f"{endian}d", float(value))
1041
+ else:
1042
+ raise ValueError(f"Unknown float type: {field.field_type}")
951
1043
 
952
- # Bytes
953
- elif field_type == "bytes":
954
- if isinstance(value, bytes):
955
- return value
956
- elif isinstance(value, list | tuple):
957
- return bytes(value)
958
- else:
959
- raise ValueError(f"Invalid bytes value: {value}")
1044
+ def _encode_bytes_field(self, value: Any) -> bytes:
1045
+ """Encode bytes field.
960
1046
 
961
- # String
962
- elif field_type == "string":
963
- if isinstance(value, str):
964
- return value.encode("utf-8")
965
- else:
966
- return bytes(value)
1047
+ Args:
1048
+ value: Bytes, list, or tuple of byte values.
967
1049
 
968
- # Array
969
- elif field_type == "array":
970
- return self._encode_array(value, field)
1050
+ Returns:
1051
+ Byte sequence.
971
1052
 
972
- # Struct
973
- elif field_type == "struct":
974
- return self._encode_struct(value, field)
1053
+ Raises:
1054
+ ValueError: If value cannot be converted to bytes.
1055
+ """
1056
+ if isinstance(value, bytes):
1057
+ return value
1058
+ elif isinstance(value, list | tuple):
1059
+ return bytes(value)
1060
+ else:
1061
+ raise ValueError(f"Invalid bytes value: {value}")
975
1062
 
1063
+ def _encode_string_field(self, value: Any) -> bytes:
1064
+ """Encode string field.
1065
+
1066
+ Args:
1067
+ value: String or bytes.
1068
+
1069
+ Returns:
1070
+ UTF-8 encoded bytes.
1071
+ """
1072
+ if isinstance(value, str):
1073
+ return value.encode("utf-8")
976
1074
  else:
977
- raise ValueError(f"Unknown field type for encoding: {field_type}")
1075
+ return bytes(value)
978
1076
 
979
1077
  def _encode_array(self, value: list[Any], field: FieldDefinition) -> bytes:
980
1078
  """Encode array field.