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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,263 @@
1
+ """DCP (Discovery and Configuration Protocol) implementation for PROFINET.
2
+
3
+ DCP is used for device identification, configuration, and network assignment
4
+ in PROFINET networks. It runs directly on Ethernet Layer 2.
5
+
6
+ References:
7
+ PROFINET Specification V2.4 - Section 4.3 DCP
8
+ IEC 61158-6-10:2014
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, ClassVar
14
+
15
+
16
+ class DCPParser:
17
+ """DCP (Discovery and Configuration Protocol) frame parser.
18
+
19
+ DCP is used for device discovery, identification, and configuration
20
+ in PROFINET networks. It operates at Layer 2 (Ethernet).
21
+
22
+ Attributes:
23
+ SERVICE_IDS: Mapping of DCP service IDs to names.
24
+ SERVICE_TYPES: Mapping of service types to names.
25
+ OPTIONS: Mapping of DCP option codes to names.
26
+ """
27
+
28
+ # DCP Service IDs (Request/Response)
29
+ SERVICE_IDS: ClassVar[dict[int, str]] = {
30
+ 0x00: "Reserved",
31
+ 0x01: "Get",
32
+ 0x02: "Set",
33
+ 0x03: "Identify",
34
+ 0x04: "Hello",
35
+ }
36
+
37
+ # DCP Service Types
38
+ SERVICE_TYPES: ClassVar[dict[int, str]] = {
39
+ 0x00: "Request",
40
+ 0x01: "Response Success",
41
+ 0x05: "Response Not Supported",
42
+ }
43
+
44
+ # DCP Options
45
+ OPTIONS: ClassVar[dict[int, str]] = {
46
+ 0x01: "IP",
47
+ 0x02: "Device Properties",
48
+ 0x03: "DHCP",
49
+ 0x04: "Reserved",
50
+ 0x05: "Control",
51
+ 0x06: "Device Initiative",
52
+ 0xFF: "All Selector",
53
+ }
54
+
55
+ # DCP Suboptions for Device Properties (Option 0x02)
56
+ DEVICE_PROPS_SUBOPTIONS: ClassVar[dict[int, str]] = {
57
+ 0x01: "Manufacturer specific",
58
+ 0x02: "Name of Station",
59
+ 0x03: "Device ID",
60
+ 0x04: "Device Role",
61
+ 0x05: "Device Options",
62
+ 0x06: "Alias Name",
63
+ 0x07: "Device Instance",
64
+ 0x08: "OEM Device ID",
65
+ }
66
+
67
+ # DCP Suboptions for IP (Option 0x01)
68
+ IP_SUBOPTIONS: ClassVar[dict[int, str]] = {
69
+ 0x01: "MAC address",
70
+ 0x02: "IP parameter",
71
+ 0x03: "Full IP Suite",
72
+ }
73
+
74
+ @staticmethod
75
+ def parse_frame(data: bytes) -> dict[str, Any]:
76
+ """Parse complete DCP frame.
77
+
78
+ DCP Frame Format:
79
+ - Service ID (1 byte)
80
+ - Service Type (1 byte)
81
+ - XID (4 bytes) - Transaction ID
82
+ - Response Delay (2 bytes)
83
+ - DCPDataLength (2 bytes)
84
+ - Blocks (variable)
85
+
86
+ Args:
87
+ data: Raw DCP frame data (without Ethernet header).
88
+
89
+ Returns:
90
+ Parsed DCP frame data.
91
+
92
+ Raises:
93
+ ValueError: If frame is too short or invalid.
94
+
95
+ Example:
96
+ >>> parser = DCPParser()
97
+ >>> result = parser.parse_frame(dcp_data)
98
+ >>> print(f"Service: {result['service']}, Blocks: {len(result['blocks'])}")
99
+ """
100
+ if len(data) < 10:
101
+ raise ValueError(f"DCP frame too short: {len(data)} bytes (minimum 10)")
102
+
103
+ service_id = data[0]
104
+ service_type = data[1]
105
+ xid = int.from_bytes(data[2:6], "big")
106
+ response_delay = int.from_bytes(data[6:8], "big")
107
+ dcp_data_length = int.from_bytes(data[8:10], "big")
108
+
109
+ blocks = []
110
+ offset = 10
111
+
112
+ # Parse DCP blocks
113
+ while offset < len(data) and offset < 10 + dcp_data_length:
114
+ if offset + 4 > len(data):
115
+ break
116
+
117
+ option = data[offset]
118
+ suboption = data[offset + 1]
119
+ block_length = int.from_bytes(data[offset + 2 : offset + 4], "big")
120
+
121
+ if offset + 4 + block_length > len(data):
122
+ break
123
+
124
+ block_data = data[offset + 4 : offset + 4 + block_length]
125
+
126
+ # Parse block data based on option/suboption
127
+ parsed_block = DCPParser._parse_block(option, suboption, block_data)
128
+ blocks.append(parsed_block)
129
+
130
+ offset += 4 + block_length
131
+ # Align to 2-byte boundary
132
+ if block_length % 2:
133
+ offset += 1
134
+
135
+ return {
136
+ "service": DCPParser.SERVICE_IDS.get(service_id, f"Unknown (0x{service_id:02X})"),
137
+ "service_id": service_id,
138
+ "service_type": DCPParser.SERVICE_TYPES.get(
139
+ service_type, f"Unknown (0x{service_type:02X})"
140
+ ),
141
+ "service_type_raw": service_type,
142
+ "transaction_id": xid,
143
+ "response_delay": response_delay,
144
+ "data_length": dcp_data_length,
145
+ "blocks": blocks,
146
+ }
147
+
148
+ @staticmethod
149
+ def _parse_block(option: int, suboption: int, data: bytes) -> dict[str, Any]:
150
+ """Parse individual DCP block based on option/suboption.
151
+
152
+ Args:
153
+ option: DCP option code.
154
+ suboption: DCP suboption code.
155
+ data: Block data bytes.
156
+
157
+ Returns:
158
+ Parsed block data.
159
+ """
160
+ block: dict[str, Any] = {
161
+ "option": DCPParser.OPTIONS.get(option, f"Unknown (0x{option:02X})"),
162
+ "option_raw": option,
163
+ "suboption": suboption,
164
+ "length": len(data),
165
+ }
166
+
167
+ # Parse based on option type
168
+ if option == 0x01: # IP
169
+ DCPParser._parse_ip_option(block, suboption, data)
170
+ elif option == 0x02: # Device Properties
171
+ DCPParser._parse_device_properties(block, suboption, data)
172
+ else:
173
+ block["data_hex"] = data.hex()
174
+
175
+ return block
176
+
177
+ @staticmethod
178
+ def _parse_ip_option(block: dict[str, Any], suboption: int, data: bytes) -> None:
179
+ """Parse IP option suboptions.
180
+
181
+ Args:
182
+ block: Block dictionary to update.
183
+ suboption: IP suboption code.
184
+ data: Block data bytes.
185
+ """
186
+ block["suboption_name"] = DCPParser.IP_SUBOPTIONS.get(
187
+ suboption, f"Unknown (0x{suboption:02X})"
188
+ )
189
+
190
+ if suboption == 0x01 and len(data) >= 6: # MAC address
191
+ block["mac_address"] = ":".join(f"{b:02x}" for b in data[:6])
192
+ elif suboption == 0x02 and len(data) >= 12: # IP parameter
193
+ DCPParser._parse_ip_parameter(block, data)
194
+ else:
195
+ block["data_hex"] = data.hex()
196
+
197
+ @staticmethod
198
+ def _parse_ip_parameter(block: dict[str, Any], data: bytes) -> None:
199
+ """Parse IP parameter suboption (IP, subnet, gateway).
200
+
201
+ Args:
202
+ block: Block dictionary to update.
203
+ data: Block data bytes (must be >= 12 bytes).
204
+ """
205
+ block["ip_address"] = ".".join(str(b) for b in data[0:4])
206
+ block["subnet_mask"] = ".".join(str(b) for b in data[4:8])
207
+ block["gateway"] = ".".join(str(b) for b in data[8:12])
208
+
209
+ @staticmethod
210
+ def _parse_device_properties(block: dict[str, Any], suboption: int, data: bytes) -> None:
211
+ """Parse Device Properties option.
212
+
213
+ Args:
214
+ block: Block dictionary to update.
215
+ suboption: Device property suboption code.
216
+ data: Block data bytes.
217
+ """
218
+ block["suboption_name"] = DCPParser.DEVICE_PROPS_SUBOPTIONS.get(
219
+ suboption, f"Unknown (0x{suboption:02X})"
220
+ )
221
+
222
+ if suboption == 0x02: # Name of Station
223
+ DCPParser._parse_device_name(block, data)
224
+ elif suboption == 0x03 and len(data) >= 4: # Device ID
225
+ DCPParser._parse_device_id(block, data)
226
+ elif suboption == 0x04 and len(data) >= 2: # Device Role
227
+ DCPParser._parse_device_role(block, data)
228
+ else:
229
+ block["data_hex"] = data.hex()
230
+
231
+ @staticmethod
232
+ def _parse_device_name(block: dict[str, Any], data: bytes) -> None:
233
+ """Parse device name."""
234
+ try:
235
+ block["device_name"] = data.decode("ascii").rstrip("\x00")
236
+ except UnicodeDecodeError:
237
+ block["device_name_hex"] = data.hex()
238
+
239
+ @staticmethod
240
+ def _parse_device_id(block: dict[str, Any], data: bytes) -> None:
241
+ """Parse device ID (vendor + device)."""
242
+ block["vendor_id"] = int.from_bytes(data[0:2], "big")
243
+ block["device_id"] = int.from_bytes(data[2:4], "big")
244
+
245
+ @staticmethod
246
+ def _parse_device_role(block: dict[str, Any], data: bytes) -> None:
247
+ """Parse device role bitmask."""
248
+ device_role = int.from_bytes(data[0:2], "big")
249
+ block["device_role"] = device_role
250
+
251
+ roles = []
252
+ if device_role & 0x01:
253
+ roles.append("IO-Device")
254
+ if device_role & 0x02:
255
+ roles.append("IO-Controller")
256
+ if device_role & 0x04:
257
+ roles.append("IO-Multidevice")
258
+ if device_role & 0x08:
259
+ roles.append("IO-Supervisor")
260
+ block["role_names"] = roles
261
+
262
+
263
+ __all__ = ["DCPParser"]
@@ -0,0 +1,200 @@
1
+ """PTCP (Precision Transparent Clock Protocol) implementation for PROFINET.
2
+
3
+ PTCP provides time synchronization for PROFINET IRT (Isochronous Real-Time)
4
+ communication with sub-microsecond accuracy.
5
+
6
+ References:
7
+ PROFINET Specification V2.4 - Section 4.7 PTCP
8
+ IEC 61158-6-10:2014
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, ClassVar
14
+
15
+
16
+ class PTCPParser:
17
+ """PTCP (Precision Transparent Clock Protocol) frame parser.
18
+
19
+ PTCP provides precise time synchronization for PROFINET networks,
20
+ supporting IRT communication with sub-microsecond accuracy.
21
+
22
+ Attributes:
23
+ TLV_TYPES: Mapping of PTCP TLV (Type-Length-Value) types.
24
+ """
25
+
26
+ # PTCP Frame Types (based on Frame ID range 0xFF20-0xFF8F)
27
+ FRAME_TYPES: ClassVar[dict[int, str]] = {
28
+ 0xFF40: "RTSync PDU with Follow-Up",
29
+ 0xFF41: "RTSync PDU",
30
+ 0xFF42: "Follow-Up",
31
+ 0xFF43: "Delay Request",
32
+ 0xFF44: "Delay Response with Follow-Up",
33
+ 0xFF45: "Delay Response",
34
+ }
35
+
36
+ # PTCP TLV Types
37
+ TLV_TYPES: ClassVar[dict[int, str]] = {
38
+ 0x00: "End",
39
+ 0x01: "Subdomain UUID",
40
+ 0x02: "Time",
41
+ 0x03: "Time Extension",
42
+ 0x04: "Master Source Address",
43
+ 0x05: "Port Parameter",
44
+ 0x06: "Delay Parameter",
45
+ 0x07: "Port Time",
46
+ 0x08: "Optional",
47
+ }
48
+
49
+ @staticmethod
50
+ def parse_frame(frame_id: int, data: bytes) -> dict[str, Any]:
51
+ """Parse PTCP frame.
52
+
53
+ PTCP Frame Format:
54
+ - Sequence ID (2 bytes)
55
+ - Reserved (2 bytes)
56
+ - TLV blocks (variable)
57
+
58
+ Args:
59
+ frame_id: PROFINET frame ID (0xFF20-0xFF8F range).
60
+ data: Raw PTCP frame data.
61
+
62
+ Returns:
63
+ Parsed PTCP frame data.
64
+
65
+ Raises:
66
+ ValueError: If frame is too short or invalid.
67
+
68
+ Example:
69
+ >>> parser = PTCPParser()
70
+ >>> result = parser.parse_frame(0xFF41, ptcp_data)
71
+ >>> print(f"Type: {result['frame_type']}, Sequence: {result['sequence_id']}")
72
+ """
73
+ if len(data) < 4:
74
+ raise ValueError(f"PTCP frame too short: {len(data)} bytes (minimum 4)")
75
+
76
+ sequence_id = int.from_bytes(data[0:2], "big")
77
+ reserved = int.from_bytes(data[2:4], "big")
78
+
79
+ result: dict[str, Any] = {
80
+ "frame_id": frame_id,
81
+ "frame_type": PTCPParser.FRAME_TYPES.get(frame_id, f"Unknown (0x{frame_id:04X})"),
82
+ "sequence_id": sequence_id,
83
+ "reserved": reserved,
84
+ "tlv_blocks": [],
85
+ }
86
+
87
+ # Parse TLV blocks
88
+ offset = 4
89
+ while offset < len(data):
90
+ if offset + 2 > len(data):
91
+ break
92
+
93
+ tlv_type = data[offset]
94
+ tlv_length = data[offset + 1]
95
+
96
+ if tlv_type == 0x00: # End marker
97
+ break
98
+
99
+ if offset + 2 + tlv_length > len(data):
100
+ break
101
+
102
+ tlv_data = data[offset + 2 : offset + 2 + tlv_length]
103
+ tlv_block = PTCPParser._parse_tlv(tlv_type, tlv_data)
104
+ result["tlv_blocks"].append(tlv_block)
105
+
106
+ offset += 2 + tlv_length
107
+
108
+ return result
109
+
110
+ @staticmethod
111
+ def _parse_tlv(tlv_type: int, data: bytes) -> dict[str, Any]:
112
+ """Parse PTCP TLV (Type-Length-Value) block.
113
+
114
+ Args:
115
+ tlv_type: TLV type code.
116
+ data: TLV data bytes.
117
+
118
+ Returns:
119
+ Parsed TLV block data.
120
+ """
121
+ block: dict[str, Any] = {
122
+ "type": PTCPParser.TLV_TYPES.get(tlv_type, f"Unknown (0x{tlv_type:02X})"),
123
+ "type_raw": tlv_type,
124
+ "length": len(data),
125
+ }
126
+
127
+ # Parse based on TLV type
128
+ if tlv_type == 0x01 and len(data) >= 16:
129
+ PTCPParser._parse_subdomain_uuid(block, data)
130
+ elif tlv_type == 0x02 and len(data) >= 10:
131
+ PTCPParser._parse_time(block, data)
132
+ elif tlv_type == 0x03 and len(data) >= 6:
133
+ PTCPParser._parse_time_extension(block, data)
134
+ elif tlv_type == 0x04 and len(data) >= 6:
135
+ PTCPParser._parse_master_source_address(block, data)
136
+ elif tlv_type == 0x05 and len(data) >= 14:
137
+ PTCPParser._parse_port_parameter(block, data)
138
+ elif tlv_type == 0x06 and len(data) >= 20:
139
+ PTCPParser._parse_delay_parameter(block, data)
140
+ elif tlv_type == 0x07 and len(data) >= 10:
141
+ PTCPParser._parse_port_time(block, data)
142
+ else:
143
+ block["data_hex"] = data.hex()
144
+
145
+ return block
146
+
147
+ @staticmethod
148
+ def _parse_subdomain_uuid(block: dict[str, Any], data: bytes) -> None:
149
+ """Parse Subdomain UUID TLV."""
150
+ block["subdomain_uuid"] = data[:16].hex()
151
+
152
+ @staticmethod
153
+ def _parse_time(block: dict[str, Any], data: bytes) -> None:
154
+ """Parse Time TLV."""
155
+ seconds = int.from_bytes(data[0:6], "big")
156
+ nanoseconds = int.from_bytes(data[6:10], "big")
157
+ block["seconds"] = seconds
158
+ block["nanoseconds"] = nanoseconds
159
+ block["timestamp"] = seconds + nanoseconds / 1e9
160
+
161
+ @staticmethod
162
+ def _parse_time_extension(block: dict[str, Any], data: bytes) -> None:
163
+ """Parse Time Extension TLV."""
164
+ epoch = int.from_bytes(data[0:2], "big")
165
+ seconds_high = int.from_bytes(data[2:6], "big")
166
+ block["epoch"] = epoch
167
+ block["seconds_high"] = seconds_high
168
+
169
+ @staticmethod
170
+ def _parse_master_source_address(block: dict[str, Any], data: bytes) -> None:
171
+ """Parse Master Source Address TLV."""
172
+ block["mac_address"] = ":".join(f"{b:02x}" for b in data[:6])
173
+
174
+ @staticmethod
175
+ def _parse_port_parameter(block: dict[str, Any], data: bytes) -> None:
176
+ """Parse Port Parameter TLV."""
177
+ block["t2_port_rx_delay"] = int.from_bytes(data[0:4], "big")
178
+ block["t3_port_tx_delay"] = int.from_bytes(data[4:8], "big")
179
+ block["port_mac_address"] = ":".join(f"{b:02x}" for b in data[8:14])
180
+
181
+ @staticmethod
182
+ def _parse_delay_parameter(block: dict[str, Any], data: bytes) -> None:
183
+ """Parse Delay Parameter TLV."""
184
+ block["request_port_rx_delay"] = int.from_bytes(data[0:4], "big")
185
+ block["request_port_tx_delay"] = int.from_bytes(data[4:8], "big")
186
+ block["response_port_rx_delay"] = int.from_bytes(data[8:12], "big")
187
+ block["response_port_tx_delay"] = int.from_bytes(data[12:16], "big")
188
+ block["cable_delay"] = int.from_bytes(data[16:20], "big")
189
+
190
+ @staticmethod
191
+ def _parse_port_time(block: dict[str, Any], data: bytes) -> None:
192
+ """Parse Port Time TLV."""
193
+ seconds = int.from_bytes(data[0:6], "big")
194
+ nanoseconds = int.from_bytes(data[6:10], "big")
195
+ block["seconds"] = seconds
196
+ block["nanoseconds"] = nanoseconds
197
+ block["timestamp"] = seconds + nanoseconds / 1e9
198
+
199
+
200
+ __all__ = ["PTCPParser"]