├── rust-toolchain ├── .dockerignore ├── samples └── Security_short_selected.evtx ├── .gitignore ├── Cargo.toml ├── pyproject.toml ├── scripts └── evtx_dump.py ├── CHANGELOG.md ├── README.md ├── tests └── test_pyevtx.py ├── .github └── workflows │ └── ci.yml ├── src └── lib.rs └── Cargo.lock /rust-toolchain: -------------------------------------------------------------------------------- 1 | stable 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .venv 2 | target 3 | -------------------------------------------------------------------------------- /samples/Security_short_selected.evtx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omerbenamram/pyevtx-rs/HEAD/samples/Security_short_selected.evtx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | 4 | .venv 5 | *.pyc 6 | *.egg-info/ 7 | *.so 8 | .idea 9 | *.pyd 10 | .history 11 | dist 12 | .vscode 13 | .mypy_cache 14 | py3.10 15 | 16 | .DS_Store 17 | .pytest_cache 18 | 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "evtx_py" 3 | description = "Python bindings for https://github.com/omerbenamram/evtx" 4 | version = "0.8.9" 5 | authors = ["Omer Ben-Amram "] 6 | edition = "2018" 7 | license = "MIT/Apache-2.0" 8 | readme = "README.md" 9 | 10 | [lib] 11 | name = "evtx" 12 | crate-type = ["cdylib"] 13 | 14 | [dependencies] 15 | evtx_rs = { version = "0.8.5", default-features = false, features = [ 16 | "multithreading", 17 | ], package = "evtx" } 18 | encoding = "0.2" 19 | pyo3 = { version = "0.23.4", features = ["extension-module", "abi3-py37"] } 20 | pyo3-file = "0.10.0" 21 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=1.0,<2.0"] 3 | build-backend = "maturin" 4 | 5 | [project] 6 | name = "evtx" 7 | requires-python = ">=3.7" 8 | classifiers = [ 9 | "Development Status :: 3 - Alpha", 10 | "Intended Audience :: Developers", 11 | "License :: OSI Approved :: MIT License", 12 | "Operating System :: MacOS :: MacOS X", 13 | "Operating System :: POSIX", 14 | "Programming Language :: Python :: Implementation :: CPython", 15 | "Programming Language :: Python :: Implementation :: PyPy", 16 | "Programming Language :: Python", 17 | "Programming Language :: Rust", 18 | ] 19 | 20 | [project.optional-dependencies] 21 | test = ["pytest"] 22 | -------------------------------------------------------------------------------- /scripts/evtx_dump.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | from evtx import PyEvtxParser 5 | 6 | def main(): 7 | # first parameter is the path to the evtx file 8 | evtx_file = os.path.abspath(os.path.expanduser(sys.argv[1])) 9 | parser = PyEvtxParser(evtx_file, number_of_threads=0) 10 | try: 11 | for record in parser.records(): 12 | print(f'Event Record ID: {record["event_record_id"]}') 13 | print(f'Event Timestamp: {record["timestamp"]}') 14 | print(record['data']) 15 | print(f'------------------------------------------') 16 | except RuntimeError as e: 17 | print(f'Error: {e}') 18 | exit(0) 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to 7 | [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 8 | 9 | ## [0.8.6] 10 | 11 | - Update PyO3 Version to 0.22.2. 12 | - Update evtx library to 0.8.3. 13 | 14 | ## [0.8.5] 15 | 16 | - Update evtx library to 0.8.2. 17 | 18 | 19 | ## [0.8.4] 20 | 21 | - Update PyO3 Version to 0.21. 22 | - Update evtx library to 0.8.2. 23 | 24 | ## [0.8.3] 25 | 26 | - Preserve record order during chunk iteration. 27 | 28 | ## [0.8.2] 29 | 30 | - Update PyO3 version 31 | 32 | ## [0.8.1] 33 | 34 | - Wheels are now published using abi3 tag, which means a single wheel can be used for all interpreters. 35 | - Added profile guided optimization for the wheels - should yield a 10% improvement in performance. 36 | 37 | ## [0.8.0] 38 | 39 | - Updated release to match `evtx 0.8.0` 40 | - Added m1 builds 41 | 42 | ## [0.7.4] 43 | 44 | - The parser's settings are now passed correctly to the iterator. 45 | 46 | ## [0.7.3] - 2022-02-13 47 | 48 | - Publish python 3.10 wheels. 49 | 50 | ## [0.7.2] - 2020-07-13 51 | 52 | - Bump to evtx library 0.7.2, and update PyO3 53 | - Publish python 3.9 wheels. 54 | 55 | ## [0.6.11] - 2020-07-13 56 | 57 | Bump to evtx library 0.6.8, and update PyO3. 58 | 59 | ## [0.6.10] - 2020-07-13 60 | 61 | Updated wheels to rust stable, support python 3.8. 62 | 63 | ## [0.6.5] - 2020-01-14 64 | 65 | Updated release to match `evtx 0.6.5` - also opts out of `evtx_dump` 66 | dependencies. 67 | 68 | ## [0.6.3] - 2019-12-17 69 | 70 | Updated release to match `evtx 0.6.3` 71 | 72 | ## [0.6.2] - 2019-12-17 73 | 74 | Updated release to match `evtx 0.6.2` 75 | 76 | ## [0.4.0] - 2019-06-02 77 | 78 | ### Added 79 | 80 | - PyEvtxParser now supports settings number of worker threads, and underlying 81 | ansi strings codec. 82 | 83 | ## [0.3.2] - 2019-05-20 84 | 85 | ### Added 86 | 87 | - PyEvtxParser now supports file-like objects. 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | Download 6 | 7 |
8 | 9 | 10 | # pyevtx-rs 11 | 12 | Python bindings for `https://github.com/omerbenamram/evtx/`. 13 | 14 | ## Installation 15 | 16 | Available on PyPi - https://pypi.org/project/evtx/. 17 | 18 | To install from PyPi - `pip install evtx` 19 | 20 | ### Wheels 21 | 22 | Wheels are currently automatically built for Python 3.7+ using abi3 tag (which means they are compatible with all version from 3.7 onwards). 23 | 24 | Supported platforms are: 25 | - Linux x86_64 26 | - macOS x86_64 27 | - macOS arm64 (m1) 28 | - Windows x86_64 29 | 30 | ### Installation from sources 31 | 32 | Installation is possible for other platforms by installing from sources. 33 | 34 | This requires a Rust compiler and a recent enough Setuptools and Pip. 35 | 36 | Run `pip install -e .` 37 | 38 | ## Usage 39 | 40 | The API surface is currently fairly limited (only yields events as XML/JSON documents), but is planned to be expanded in the future. 41 | 42 | 43 | This will print each record as an XML string. 44 | 45 | ```python 46 | from evtx import PyEvtxParser 47 | 48 | 49 | def main(): 50 | parser = PyEvtxParser("./samples/Security_short_selected.evtx") 51 | for record in parser.records(): 52 | print(f'Event Record ID: {record["event_record_id"]}') 53 | print(f'Event Timestamp: {record["timestamp"]}') 54 | print(record['data']) 55 | print(f'------------------------------------------') 56 | ``` 57 | 58 | 59 | And this will print each record as a JSON string. 60 | 61 | ```python 62 | from evtx.parser import PyEvtxParser 63 | 64 | 65 | def main(): 66 | parser = PyEvtxParser("./samples/Security_short_selected.evtx") 67 | for record in parser.records_json(): 68 | print(f'Event Record ID: {record["event_record_id"]}') 69 | print(f'Event Timestamp: {record["timestamp"]}') 70 | print(record['data']) 71 | print(f'------------------------------------------') 72 | ``` 73 | 74 | File-like objects are also supported. 75 | 76 | ```python 77 | from evtx.parser import PyEvtxParser 78 | 79 | 80 | def main(): 81 | a = open("./samples/Security_short_selected.evtx", 'rb') 82 | 83 | # io.BytesIO is also supported. 84 | parser = PyEvtxParser(a) 85 | for record in parser.records_json(): 86 | print(f'Event Record ID: {record["event_record_id"]}') 87 | print(f'Event Timestamp: {record["timestamp"]}') 88 | print(record['data']) 89 | print(f'------------------------------------------') 90 | ``` 91 | -------------------------------------------------------------------------------- /tests/test_pyevtx.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import io 3 | 4 | from pathlib import Path 5 | from evtx import PyEvtxParser 6 | import json 7 | 8 | SAMPLES = Path(__file__).parent.parent / 'samples' 9 | 10 | 11 | @pytest.fixture 12 | def small_sample() -> str: 13 | return str(SAMPLES / 'Security_short_selected.evtx') 14 | 15 | 16 | def test_it_works(small_sample): 17 | parser = PyEvtxParser(small_sample) 18 | records = list(parser) 19 | 20 | assert len(records) == 7 21 | 22 | assert records[0]['event_record_id'] 23 | assert records[0]['timestamp'].endswith('UTC') 24 | assert '' in records[0]['data'] 25 | 26 | 27 | def test_it_works_with_records(small_sample): 28 | parser = PyEvtxParser(small_sample) 29 | records = list(parser.records()) 30 | assert len(records) == 7 31 | 32 | assert records[0]['event_record_id'] 33 | assert records[0]['timestamp'].endswith('UTC') 34 | assert '' in records[0]['data'] 35 | 36 | 37 | def test_it_works_with_json(small_sample): 38 | parser = PyEvtxParser(small_sample) 39 | records = list(parser.records_json()) 40 | assert len(records) == 7 41 | 42 | assert records[0]['event_record_id'] 43 | assert records[0]['timestamp'].endswith('UTC') 44 | assert json.loads(records[0]['data'])['Event']['System']['EventID'] 45 | 46 | 47 | def test_it_returns_error_when_iterating_twice(small_sample): 48 | parser = PyEvtxParser(small_sample) 49 | _ = list(parser.records()) 50 | 51 | with pytest.raises(RuntimeError): 52 | parser.records() 53 | 54 | 55 | def test_it_returns_error_on_non_existing_path(): 56 | with pytest.raises(FileNotFoundError): 57 | parser = PyEvtxParser("non_existing") 58 | 59 | 60 | def test_it_returns_error_when_using_next_on_parser(small_sample): 61 | parser = PyEvtxParser(small_sample) 62 | 63 | with pytest.raises(NotImplementedError): 64 | next(parser) 65 | 66 | 67 | def test_it_works_on_io_object(small_sample): 68 | with open(small_sample, "rb") as o: 69 | r = o.read() 70 | 71 | parser = PyEvtxParser(io.BytesIO(r)) 72 | records = list(parser.records()) 73 | assert len(records) == 7 74 | 75 | assert records[0]['event_record_id'] 76 | assert records[0]['timestamp'].endswith('UTC') 77 | assert '' in records[0]['data'] 78 | 79 | 80 | def test_it_works_on_file_backed_object(small_sample): 81 | with open(small_sample, "rb") as o: 82 | parser = PyEvtxParser(o) 83 | 84 | records = list(parser.records()) 85 | 86 | assert len(records) == 7 87 | 88 | assert records[0]['event_record_id'] 89 | assert records[0]['timestamp'].endswith('UTC') 90 | assert '' in records[0]['data'] 91 | 92 | 93 | def test_it_fails_on_file_opened_as_text(small_sample): 94 | with pytest.raises(OSError) as e: 95 | with open(small_sample, "rt") as o: 96 | parser = PyEvtxParser(o) 97 | 98 | assert "decode byte" in e.value.args[0] 99 | 100 | 101 | def test_it_fails_nicely_on_close_files(small_sample): 102 | with open(small_sample, "rb") as o: 103 | parser = PyEvtxParser(o) 104 | 105 | with pytest.raises(OSError) as e: 106 | records = list(parser.records()) 107 | 108 | assert "closed file" in e.value.args[0] 109 | 110 | 111 | def test_it_fails_on_non_file_object(): 112 | with pytest.raises(TypeError): 113 | parser = PyEvtxParser(3) 114 | 115 | 116 | def test_it_supports_various_ascii_codecs(small_sample): 117 | with open(small_sample, "rb") as o: 118 | parser = PyEvtxParser(o, ansi_codec="ascii") 119 | 120 | records = list(parser.records()) 121 | 122 | assert len(records) == 7 123 | 124 | assert records[0]['event_record_id'] 125 | assert records[0]['timestamp'].endswith('UTC') 126 | assert '' in records[0]['data'] 127 | 128 | 129 | def test_it_supports_various_num_threads(small_sample): 130 | with open(small_sample, "rb") as o: 131 | parser = PyEvtxParser(o, number_of_threads=1) 132 | 133 | records = list(parser.records()) 134 | 135 | assert len(records) == 7 136 | 137 | assert records[0]['event_record_id'] == 1, "Expect records to be in order when using a single thread" 138 | assert records[0]['timestamp'].endswith('UTC') 139 | assert '5152' in records[0]['data'] 140 | 141 | 142 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | tags: 6 | - "[0-9]+.[0-9]+.[0-9]+" 7 | pull_request: 8 | 9 | jobs: 10 | linux: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Checkout Rust EVTX (for samples) 15 | uses: actions/checkout@v4 16 | with: 17 | repository: omerbenamram/evtx 18 | path: evtx-rs 19 | - name: Install Python 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: '3.10' 23 | - name: Build instrumentation wheels 24 | uses: messense/maturin-action@v1 25 | with: 26 | manylinux: auto 27 | command: build 28 | args: --release -o dist 29 | env: 30 | RUSTFLAGS: "-Cprofile-generate=${{ github.workspace }}/pgo-data" 31 | - name: Install Rust 32 | uses: dtolnay/rust-toolchain@stable 33 | with: 34 | target: x86_64-unknown-linux-gnu 35 | components: llvm-tools 36 | 37 | - name: PGO optimize 38 | run: | 39 | PATH=$HOME/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/bin/:$PATH 40 | pip install --no-index --find-links ./dist evtx 41 | echo "Running instrumented binary" 42 | sudo mkdir -p $PWD/pgo-data 43 | sudo chmod -R 777 $PWD/pgo-data 44 | for i in $(find $PWD/evtx-rs/samples -name "*.evtx"); do 45 | echo "Processing $i" 46 | python scripts/evtx_dump.py $i 1>/dev/null 47 | done 48 | echo "Merging profile data" 49 | llvm-profdata merge -o $PWD/pgo-data/merged.profdata $PWD/pgo-data 50 | - uses: messense/maturin-action@v1 51 | with: 52 | manylinux: auto 53 | command: build 54 | args: --release -o dist 55 | env: 56 | RUSTFLAGS: "-Cprofile-use=${{ github.workspace }}/pgo-data/merged.profdata" 57 | - name: Upload wheels 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: wheels-linux 61 | path: dist 62 | - run: pip install -U pytest 63 | - run: pip install --no-index --find-links ./dist evtx 64 | - run: pytest 65 | 66 | linux-arm64: 67 | runs-on: ubuntu-latest 68 | steps: 69 | - uses: actions/checkout@v4 70 | - name: Install Rust 71 | uses: actions-rs/toolchain@v1 72 | with: 73 | toolchain: stable 74 | target: aarch64-unknown-linux-gnu 75 | - uses: messense/maturin-action@v1 76 | with: 77 | command: build 78 | args: --release -o dist --zig --target aarch64-unknown-linux-gnu 79 | - name: Upload wheels 80 | uses: actions/upload-artifact@v4 81 | with: 82 | name: wheels-linux-arm64 83 | path: dist 84 | 85 | linux-aarch64-musl: 86 | runs-on: ubuntu-latest 87 | steps: 88 | - uses: actions/checkout@v4 89 | - name: Install Rust 90 | uses: actions-rs/toolchain@v1 91 | with: 92 | toolchain: stable 93 | target: aarch64-unknown-linux-musl 94 | - uses: messense/maturin-action@v1 95 | with: 96 | command: build 97 | args: --release -o dist --zig --target aarch64-unknown-linux-musl 98 | - name: Upload wheels 99 | uses: actions/upload-artifact@v4 100 | with: 101 | name: wheels-linux-aarch64-musl 102 | path: dist 103 | 104 | linux-x86_64-musl: 105 | runs-on: ubuntu-latest 106 | steps: 107 | - uses: actions/checkout@v4 108 | - name: Install Rust 109 | uses: actions-rs/toolchain@v1 110 | with: 111 | toolchain: stable 112 | target: x86_64-unknown-linux-musl 113 | - uses: messense/maturin-action@v1 114 | with: 115 | command: build 116 | args: --release -o dist --zig --target x86_64-unknown-linux-musl 117 | - name: Upload wheels 118 | uses: actions/upload-artifact@v4 119 | with: 120 | name: wheels-linux-x86_64-musl 121 | path: dist 122 | 123 | windows: 124 | runs-on: windows-latest 125 | steps: 126 | - uses: actions/checkout@v4 127 | - uses: messense/maturin-action@v1 128 | with: 129 | command: build 130 | args: --release -o dist 131 | - name: Upload wheels 132 | uses: actions/upload-artifact@v4 133 | with: 134 | name: wheels-windows 135 | path: dist 136 | - run: pip install -U pytest 137 | - run: pip install --no-index --find-links ./dist evtx 138 | - run: pytest 139 | 140 | macos: 141 | runs-on: macos-13 142 | steps: 143 | - uses: actions/checkout@v4 144 | - name: Set up Python 145 | uses: actions/setup-python@v4 146 | with: 147 | python-version: '3.10' # Specify the Python version explicitly 148 | - uses: messense/maturin-action@v1 149 | with: 150 | command: build 151 | args: --release -o dist 152 | - name: List dist directory 153 | run: ls -l dist 154 | - name: Upload wheels 155 | uses: actions/upload-artifact@v4 156 | with: 157 | name: wheels-macos 158 | path: dist 159 | - run: pip install -U pytest 160 | - name: Install evtx 161 | run: | 162 | pip install --no-index --find-links ./dist evtx 163 | pip list # List installed packages 164 | - run: pytest 165 | 166 | macos-aarch64: 167 | runs-on: macos-14 168 | steps: 169 | - uses: actions/checkout@v4 170 | - name: Checkout Rust EVTX (for samples) 171 | uses: actions/checkout@v4 172 | with: 173 | repository: omerbenamram/evtx 174 | path: evtx-rs 175 | 176 | - name: Install Python 177 | uses: actions/setup-python@v4 178 | with: 179 | python-version: '3.10' 180 | 181 | - name: Build instrumentation wheels 182 | uses: messense/maturin-action@v1 183 | with: 184 | manylinux: auto 185 | command: build 186 | args: --release -o dist 187 | env: 188 | RUSTFLAGS: "-Cprofile-generate=${{ github.workspace }}/pgo-data" 189 | 190 | - name: Install Rust 191 | uses: dtolnay/rust-toolchain@stable 192 | with: 193 | target: aarch64-apple-darwin 194 | components: llvm-tools 195 | 196 | - name: Install Xcode 197 | uses: maxim-lobanov/setup-xcode@v1 198 | with: 199 | xcode-version: latest-stable 200 | - name: PGO optimize 201 | run: | 202 | PATH=$HOME/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/bin/:$PATH 203 | pip install --no-index --find-links ./dist evtx 204 | echo "Running instrumented binary" 205 | sudo mkdir -p $PWD/pgo-data 206 | sudo chmod -R 777 $PWD/pgo-data 207 | for i in $(find $PWD/evtx-rs/samples -name "*.evtx"); do 208 | echo "Processing $i" 209 | python scripts/evtx_dump.py $i 1>/dev/null 210 | done 211 | echo "Merging profile data" 212 | llvm-profdata merge -o $PWD/pgo-data/merged.profdata $PWD/pgo-data 213 | 214 | - name: Build Optimized Wheels 215 | uses: messense/maturin-action@v1 216 | with: 217 | command: build 218 | args: --release -o dist 219 | env: 220 | RUSTFLAGS: "-Cprofile-use=${{ github.workspace }}/pgo-data/merged.profdata" 221 | 222 | - name: Upload wheels 223 | uses: actions/upload-artifact@v4 224 | with: 225 | name: wheels-macos-aarch64 226 | path: dist 227 | - run: pip install -U pytest 228 | - run: pip install --no-index --find-links ./dist evtx 229 | - run: pytest 230 | 231 | release: 232 | name: Release 233 | runs-on: ubuntu-latest 234 | if: "startsWith(github.ref, 'refs/tags/')" 235 | needs: [ macos, windows, linux, macos-aarch64, linux-arm64, linux-aarch64-musl, linux-x86_64-musl ] 236 | steps: 237 | - uses: actions/download-artifact@v4 238 | with: 239 | path: wheels 240 | pattern: wheels-* 241 | merge-multiple: true 242 | - name: Publish to PyPI 243 | uses: messense/maturin-action@v1 244 | env: 245 | MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} 246 | with: 247 | command: upload 248 | args: --skip-existing ./wheels/*.whl 249 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::new_ret_no_self)] 2 | #![deny(unused_must_use)] 3 | #![cfg_attr(not(debug_assertions), deny(clippy::dbg_macro))] 4 | 5 | use evtx_rs::{ 6 | err, 7 | err::{ChunkError, DeserializationError, EvtxError, InputError, SerializationError}, 8 | EvtxParser, IntoIterChunks, ParserSettings, SerializedEvtxRecord, 9 | }; 10 | 11 | use pyo3::types::PyDict; 12 | use pyo3::types::PyString; 13 | 14 | use pyo3::{ 15 | exceptions::PyFileNotFoundError, exceptions::PyNotImplementedError, exceptions::PyOSError, 16 | exceptions::PyRuntimeError, exceptions::PyValueError, prelude::*, 17 | }; 18 | 19 | use encoding::all::encodings; 20 | use pyo3_file::PyFileLikeObject; 21 | 22 | use std::error::Error; 23 | use std::fs::File; 24 | use std::io; 25 | use std::io::{Read, Seek}; 26 | use std::sync::Arc; 27 | use std::vec::IntoIter; 28 | 29 | pub trait ReadSeek: Read + Seek + Send + Sync + 'static { 30 | fn tell(&mut self) -> io::Result { 31 | self.stream_position() 32 | } 33 | } 34 | 35 | impl ReadSeek for T {} 36 | 37 | struct PyEvtxError(EvtxError); 38 | 39 | fn py_err_from_io_err(e: &io::Error) -> PyErr { 40 | match e.kind() { 41 | io::ErrorKind::NotFound => PyErr::new::(format!("{}", e)), 42 | _ => PyErr::new::(format!("{}", e)), 43 | } 44 | } 45 | 46 | impl From for PyErr { 47 | fn from(err: PyEvtxError) -> Self { 48 | match err.0 { 49 | err::EvtxError::FailedToParseChunk { 50 | chunk_id: _, 51 | source, 52 | } => match source { 53 | ChunkError::FailedToSeekToChunk(io) => py_err_from_io_err(&io), 54 | _ => PyErr::new::(format!("{}", source)), 55 | }, 56 | EvtxError::InputError(e) => match e { 57 | InputError::FailedToOpenFile { 58 | source: inner, 59 | path: _, 60 | } => py_err_from_io_err(&inner), 61 | }, 62 | EvtxError::SerializationError(e) => match e { 63 | SerializationError::Unimplemented { .. } => { 64 | PyErr::new::(format!("{}", e)) 65 | } 66 | _ => PyErr::new::(format!("{}", e)), 67 | }, 68 | EvtxError::DeserializationError(e) => match e { 69 | DeserializationError::UnexpectedIoError(ref io) => match io.source() { 70 | Some(inner_io_err) => match inner_io_err.downcast_ref::() { 71 | Some(actual_inner_io_err) => py_err_from_io_err(actual_inner_io_err), 72 | None => PyErr::new::(format!("{}", e)), 73 | }, 74 | None => PyErr::new::(format!("{}", e)), 75 | }, 76 | _ => PyErr::new::(format!("{}", e)), 77 | }, 78 | EvtxError::Unimplemented { .. } => { 79 | PyErr::new::(format!("{}", err.0)) 80 | } 81 | _ => PyErr::new::(format!("{}", err.0)), 82 | } 83 | } 84 | } 85 | 86 | #[derive(Copy, Clone, PartialOrd, PartialEq, Eq)] 87 | pub enum OutputFormat { 88 | JSON, 89 | XML, 90 | } 91 | 92 | #[derive(Debug)] 93 | enum FileOrFileLike { 94 | File(String), 95 | FileLike(PyFileLikeObject), 96 | } 97 | 98 | impl FileOrFileLike { 99 | pub fn from_pyobject(path_or_file_like: PyObject) -> PyResult { 100 | Python::with_gil(|py| { 101 | if let Ok(string_ref) = path_or_file_like.downcast_bound::(py) { 102 | return Ok(FileOrFileLike::File( 103 | string_ref.to_string_lossy().to_string(), 104 | )); 105 | } 106 | 107 | // We only need read + seek 108 | match PyFileLikeObject::with_requirements(path_or_file_like, true, false, true, true) { 109 | Ok(f) => Ok(FileOrFileLike::FileLike(f)), 110 | Err(e) => Err(e), 111 | } 112 | }) 113 | } 114 | } 115 | 116 | #[pyclass] 117 | /// PyEvtxParser(self, path_or_file_like, number_of_threads=0, ansi_codec='windows-1252', /) 118 | /// -- 119 | /// 120 | /// Returns an instance of the parser. 121 | /// 122 | /// Args: 123 | /// `path_or_file_like`: a path (string), or a file-like object. 124 | /// 125 | /// `number_of_threads` (int, optional): 126 | /// limit the number of worker threads used by rust. 127 | /// `0` (the default) will let the library decide how many threads to use 128 | /// based on the number of cores available. 129 | /// 130 | /// `ansi_codec`(str, optional) to control encoding of ansi strings inside the evtx file. 131 | /// 132 | /// Possible values: 133 | /// ascii, ibm866, iso-8859-1, iso-8859-2, iso-8859-3, iso-8859-4, 134 | /// iso-8859-5, iso-8859-6, iso-8859-7, iso-8859-8, iso-8859-10, 135 | /// iso-8859-13, iso-8859-14, iso-8859-15, iso-8859-16, 136 | /// koi8-r, koi8-u, mac-roman, windows-874, windows-1250, windows-1251, 137 | /// windows-1252, windows-1253, windows-1254, windows-1255, 138 | /// windows-1256, windows-1257, windows-1258, mac-cyrillic, utf-8, 139 | /// windows-949, euc-jp, windows-31j, gbk, gb18030, hz, big5-2003, 140 | /// pua-mapped-binary, iso-8859-8-i 141 | /// 142 | pub struct PyEvtxParser { 143 | inner: Option>>, 144 | configuration: ParserSettings, 145 | } 146 | 147 | #[pymethods] 148 | impl PyEvtxParser { 149 | #[new] 150 | #[pyo3(signature = (path_or_file_like, number_of_threads=None, ansi_codec=None))] 151 | fn new( 152 | path_or_file_like: PyObject, 153 | number_of_threads: Option, 154 | ansi_codec: Option, 155 | ) -> PyResult { 156 | let file_or_file_like = FileOrFileLike::from_pyobject(path_or_file_like)?; 157 | 158 | // Setup `ansi_codec` 159 | let codec = if let Some(codec) = ansi_codec { 160 | match encodings().iter().find(|c| c.name() == codec) { 161 | Some(encoding) => *encoding, 162 | None => { 163 | return Err(PyErr::new::(format!( 164 | "Unknown encoding `[{}]`, see help for possible values", 165 | codec 166 | ))); 167 | } 168 | } 169 | } else { 170 | ParserSettings::default().get_ansi_codec() 171 | }; 172 | 173 | // Setup `number_of_threads` 174 | let number_of_threads = match number_of_threads { 175 | Some(number) => number, 176 | None => *ParserSettings::default().get_num_threads(), 177 | }; 178 | 179 | let configuration = ParserSettings::new() 180 | .ansi_codec(codec) 181 | .num_threads(number_of_threads); 182 | 183 | let boxed_read_seek = match file_or_file_like { 184 | FileOrFileLike::File(s) => { 185 | let file = File::open(s)?; 186 | Box::new(file) as Box 187 | } 188 | FileOrFileLike::FileLike(f) => Box::new(f) as Box, 189 | }; 190 | 191 | let parser = EvtxParser::from_read_seek(boxed_read_seek) 192 | .map_err(PyEvtxError)? 193 | .with_configuration(configuration.clone()); 194 | 195 | Ok(PyEvtxParser { 196 | inner: Some(parser), 197 | configuration, 198 | }) 199 | } 200 | 201 | /// records(self, /) 202 | /// -- 203 | /// 204 | /// Returns an iterator that yields either an XML record, or a `RuntimeError` object. 205 | /// 206 | /// Note - Iterating over records can raise a `RuntimeError` if the parser encounters an invalid record. 207 | /// If using a regular for-loop, this could abruptly terminate the iteration. 208 | /// 209 | /// It is recommended to wrap this iterator with a logic that will continue iteration 210 | /// in case an exception object is returned. 211 | fn records(&mut self) -> PyResult { 212 | self.records_iterator(OutputFormat::XML) 213 | } 214 | 215 | /// records_json(self, /) 216 | /// -- 217 | /// 218 | /// Returns an iterator that yields either a JSON record, or a `RuntimeError` object. 219 | /// 220 | /// Note - Iterating over records can raise a `RuntimeError` if the parser encounters an invalid record. 221 | /// If using a regular for-loop, this could abruptly terminate the iteration. 222 | /// 223 | /// It is recommended to wrap this iterator with a logic that will continue iteration 224 | /// in case an exception object is returned. 225 | fn records_json(&mut self) -> PyResult { 226 | self.records_iterator(OutputFormat::JSON) 227 | } 228 | 229 | fn __iter__(mut slf: PyRefMut) -> PyResult { 230 | slf.records() 231 | } 232 | fn __next__(_slf: PyRefMut) -> PyResult> { 233 | Err(PyErr::new::("Using `next()` over `PyEvtxParser` is not supported. Try iterating over `PyEvtxParser(...).records()`")) 234 | } 235 | } 236 | 237 | impl PyEvtxParser { 238 | fn records_iterator(&mut self, output_format: OutputFormat) -> PyResult { 239 | let inner = match self.inner.take() { 240 | Some(inner) => inner, 241 | None => { 242 | return Err(PyErr::new::( 243 | "PyEvtxParser can only be used once", 244 | )); 245 | } 246 | }; 247 | 248 | Ok(PyRecordsIterator { 249 | inner: inner.into_chunks(), 250 | records_iter: Vec::new().into_iter(), 251 | settings: Arc::new(self.configuration.clone()), 252 | output_format, 253 | }) 254 | } 255 | } 256 | 257 | fn record_to_pydict(record: SerializedEvtxRecord, py: Python) -> PyResult> { 258 | let pyrecord = PyDict::new(py); 259 | 260 | pyrecord.set_item("event_record_id", record.event_record_id)?; 261 | pyrecord.set_item("timestamp", format!("{}", record.timestamp))?; 262 | pyrecord.set_item("data", record.data)?; 263 | Ok(pyrecord) 264 | } 265 | 266 | fn record_to_pyobject( 267 | r: Result, EvtxError>, 268 | py: Python, 269 | ) -> PyResult { 270 | match r { 271 | Ok(r) => match record_to_pydict(r, py) { 272 | Ok(dict) => Ok(dict.into_pyobject(py)?.into()), 273 | Err(e) => Ok(e.into_pyobject(py)?.into()), 274 | }, 275 | Err(e) => Err(PyEvtxError(e).into()), 276 | } 277 | } 278 | 279 | #[pyclass] 280 | pub struct PyRecordsIterator { 281 | inner: IntoIterChunks>, 282 | records_iter: IntoIter, EvtxError>>, 283 | settings: Arc, 284 | output_format: OutputFormat, 285 | } 286 | 287 | impl PyRecordsIterator { 288 | fn next(&mut self) -> PyResult> { 289 | let mut chunk_id = 0; 290 | 291 | loop { 292 | if let Some(record) = self.records_iter.next() { 293 | let record = Python::with_gil(|py| record_to_pyobject(record, py).map(Some)); 294 | 295 | return record; 296 | } 297 | 298 | let chunk = self.inner.next(); 299 | chunk_id += 1; 300 | 301 | match chunk { 302 | None => return Ok(None), 303 | Some(chunk_result) => match chunk_result { 304 | Err(e) => { 305 | return Err(PyEvtxError(e).into()); 306 | } 307 | Ok(mut chunk) => { 308 | let parsed_chunk = chunk.parse(self.settings.clone()); 309 | 310 | match parsed_chunk { 311 | Err(e) => { 312 | return Err(PyEvtxError(EvtxError::FailedToParseChunk { 313 | chunk_id, 314 | source: e, 315 | }) 316 | .into()); 317 | } 318 | Ok(mut chunk) => { 319 | let records: Vec<_> = match self.output_format { 320 | OutputFormat::XML => chunk 321 | .iter() 322 | .filter_map(|r| r.ok()) 323 | .map(|r| r.into_xml()) 324 | .collect(), 325 | OutputFormat::JSON => chunk 326 | .iter() 327 | .filter_map(|r| r.ok()) 328 | .map(|r| r.into_json()) 329 | .collect(), 330 | }; 331 | 332 | self.records_iter = records.into_iter(); 333 | } 334 | } 335 | } 336 | }, 337 | } 338 | } 339 | } 340 | } 341 | 342 | #[pymethods] 343 | impl PyRecordsIterator { 344 | fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { 345 | slf 346 | } 347 | 348 | fn __next__(mut slf: PyRefMut<'_, Self>) -> PyResult> { 349 | slf.next() 350 | } 351 | } 352 | 353 | // Don't use double quotes ("") inside this docstring, this will crash pyo3. 354 | /// Parses an evtx file. 355 | /// 356 | /// This will print each record as an XML string. 357 | /// 358 | ///```python 359 | /// from evtx import PyEvtxParser 360 | /// 361 | /// def main(): 362 | /// parser = PyEvtxParser('./samples/Security_short_selected.evtx') 363 | /// for record in parser.records(): 364 | /// print(f'Event Record ID: {record['event_record_id']}') 365 | /// print(f'Event Timestamp: {record['timestamp']}') 366 | /// print(record['data']) 367 | /// print('------------------------------------------') 368 | ///``` 369 | /// 370 | /// And this will print each record as a JSON string. 371 | /// 372 | /// ```python 373 | /// from evtx import PyEvtxParser 374 | /// 375 | /// def main(): 376 | /// parser = PyEvtxParser('./samples/Security_short_selected.evtx') 377 | /// for record in parser.records_json(): 378 | /// print(f'Event Record ID: {record['event_record_id']}') 379 | /// print(f'Event Timestamp: {record['timestamp']}') 380 | /// print(record['data']) 381 | /// print(f'------------------------------------------') 382 | ///``` 383 | #[pymodule] 384 | fn evtx(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { 385 | m.add_class::()?; 386 | m.add_class::()?; 387 | 388 | Ok(()) 389 | } 390 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.8.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 10 | dependencies = [ 11 | "cfg-if", 12 | "once_cell", 13 | "version_check", 14 | "zerocopy", 15 | ] 16 | 17 | [[package]] 18 | name = "allocator-api2" 19 | version = "0.2.20" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" 22 | 23 | [[package]] 24 | name = "android-tzdata" 25 | version = "0.1.1" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 28 | 29 | [[package]] 30 | name = "android_system_properties" 31 | version = "0.1.5" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 34 | dependencies = [ 35 | "libc", 36 | ] 37 | 38 | [[package]] 39 | name = "autocfg" 40 | version = "1.4.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 43 | 44 | [[package]] 45 | name = "bitflags" 46 | version = "1.3.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 49 | 50 | [[package]] 51 | name = "bitflags" 52 | version = "2.6.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 55 | 56 | [[package]] 57 | name = "bumpalo" 58 | version = "3.16.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 61 | 62 | [[package]] 63 | name = "bytecount" 64 | version = "0.6.8" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" 67 | 68 | [[package]] 69 | name = "byteorder" 70 | version = "1.5.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 73 | 74 | [[package]] 75 | name = "camino" 76 | version = "1.1.9" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" 79 | dependencies = [ 80 | "serde", 81 | ] 82 | 83 | [[package]] 84 | name = "cargo-platform" 85 | version = "0.1.8" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" 88 | dependencies = [ 89 | "serde", 90 | ] 91 | 92 | [[package]] 93 | name = "cargo_metadata" 94 | version = "0.14.2" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" 97 | dependencies = [ 98 | "camino", 99 | "cargo-platform", 100 | "semver", 101 | "serde", 102 | "serde_json", 103 | ] 104 | 105 | [[package]] 106 | name = "cc" 107 | version = "1.2.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" 110 | dependencies = [ 111 | "shlex", 112 | ] 113 | 114 | [[package]] 115 | name = "cfg-if" 116 | version = "1.0.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 119 | 120 | [[package]] 121 | name = "chrono" 122 | version = "0.4.38" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 125 | dependencies = [ 126 | "android-tzdata", 127 | "iana-time-zone", 128 | "js-sys", 129 | "num-traits", 130 | "serde", 131 | "wasm-bindgen", 132 | "windows-targets", 133 | ] 134 | 135 | [[package]] 136 | name = "core-foundation-sys" 137 | version = "0.8.7" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 140 | 141 | [[package]] 142 | name = "crc32fast" 143 | version = "1.4.2" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 146 | dependencies = [ 147 | "cfg-if", 148 | ] 149 | 150 | [[package]] 151 | name = "crossbeam-deque" 152 | version = "0.8.5" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 155 | dependencies = [ 156 | "crossbeam-epoch", 157 | "crossbeam-utils", 158 | ] 159 | 160 | [[package]] 161 | name = "crossbeam-epoch" 162 | version = "0.9.18" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 165 | dependencies = [ 166 | "crossbeam-utils", 167 | ] 168 | 169 | [[package]] 170 | name = "crossbeam-utils" 171 | version = "0.8.20" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 174 | 175 | [[package]] 176 | name = "either" 177 | version = "1.13.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 180 | 181 | [[package]] 182 | name = "encoding" 183 | version = "0.2.33" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" 186 | dependencies = [ 187 | "encoding-index-japanese", 188 | "encoding-index-korean", 189 | "encoding-index-simpchinese", 190 | "encoding-index-singlebyte", 191 | "encoding-index-tradchinese", 192 | ] 193 | 194 | [[package]] 195 | name = "encoding-index-japanese" 196 | version = "1.20141219.5" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" 199 | dependencies = [ 200 | "encoding_index_tests", 201 | ] 202 | 203 | [[package]] 204 | name = "encoding-index-korean" 205 | version = "1.20141219.5" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" 208 | dependencies = [ 209 | "encoding_index_tests", 210 | ] 211 | 212 | [[package]] 213 | name = "encoding-index-simpchinese" 214 | version = "1.20141219.5" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" 217 | dependencies = [ 218 | "encoding_index_tests", 219 | ] 220 | 221 | [[package]] 222 | name = "encoding-index-singlebyte" 223 | version = "1.20141219.5" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" 226 | dependencies = [ 227 | "encoding_index_tests", 228 | ] 229 | 230 | [[package]] 231 | name = "encoding-index-tradchinese" 232 | version = "1.20141219.5" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" 235 | dependencies = [ 236 | "encoding_index_tests", 237 | ] 238 | 239 | [[package]] 240 | name = "encoding_index_tests" 241 | version = "0.1.4" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" 244 | 245 | [[package]] 246 | name = "equivalent" 247 | version = "1.0.1" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 250 | 251 | [[package]] 252 | name = "errno" 253 | version = "0.3.9" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 256 | dependencies = [ 257 | "libc", 258 | "windows-sys 0.52.0", 259 | ] 260 | 261 | [[package]] 262 | name = "error-chain" 263 | version = "0.12.4" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 266 | dependencies = [ 267 | "version_check", 268 | ] 269 | 270 | [[package]] 271 | name = "evtx" 272 | version = "0.8.5" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "cb8273ed69ad5086ec987e16e883ac35589a1cf0f2b000d965a693fc2a937a24" 275 | dependencies = [ 276 | "bitflags 2.6.0", 277 | "byteorder", 278 | "chrono", 279 | "crc32fast", 280 | "encoding", 281 | "hashbrown 0.14.5", 282 | "log", 283 | "quick-xml", 284 | "rayon", 285 | "serde", 286 | "serde_json", 287 | "skeptic", 288 | "thiserror", 289 | "winstructs", 290 | ] 291 | 292 | [[package]] 293 | name = "evtx_py" 294 | version = "0.8.9" 295 | dependencies = [ 296 | "encoding", 297 | "evtx", 298 | "pyo3", 299 | "pyo3-file", 300 | ] 301 | 302 | [[package]] 303 | name = "fastrand" 304 | version = "2.2.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" 307 | 308 | [[package]] 309 | name = "glob" 310 | version = "0.3.1" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 313 | 314 | [[package]] 315 | name = "hashbrown" 316 | version = "0.14.5" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 319 | dependencies = [ 320 | "ahash", 321 | "allocator-api2", 322 | ] 323 | 324 | [[package]] 325 | name = "hashbrown" 326 | version = "0.15.1" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" 329 | 330 | [[package]] 331 | name = "heck" 332 | version = "0.5.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 335 | 336 | [[package]] 337 | name = "iana-time-zone" 338 | version = "0.1.61" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 341 | dependencies = [ 342 | "android_system_properties", 343 | "core-foundation-sys", 344 | "iana-time-zone-haiku", 345 | "js-sys", 346 | "wasm-bindgen", 347 | "windows-core", 348 | ] 349 | 350 | [[package]] 351 | name = "iana-time-zone-haiku" 352 | version = "0.1.2" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 355 | dependencies = [ 356 | "cc", 357 | ] 358 | 359 | [[package]] 360 | name = "indexmap" 361 | version = "2.6.0" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 364 | dependencies = [ 365 | "equivalent", 366 | "hashbrown 0.15.1", 367 | ] 368 | 369 | [[package]] 370 | name = "indoc" 371 | version = "2.0.5" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" 374 | 375 | [[package]] 376 | name = "itoa" 377 | version = "1.0.13" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" 380 | 381 | [[package]] 382 | name = "js-sys" 383 | version = "0.3.72" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 386 | dependencies = [ 387 | "wasm-bindgen", 388 | ] 389 | 390 | [[package]] 391 | name = "libc" 392 | version = "0.2.164" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" 395 | 396 | [[package]] 397 | name = "linux-raw-sys" 398 | version = "0.4.14" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 401 | 402 | [[package]] 403 | name = "log" 404 | version = "0.4.22" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 407 | 408 | [[package]] 409 | name = "memchr" 410 | version = "2.7.4" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 413 | 414 | [[package]] 415 | name = "memoffset" 416 | version = "0.9.1" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" 419 | dependencies = [ 420 | "autocfg", 421 | ] 422 | 423 | [[package]] 424 | name = "num-derive" 425 | version = "0.3.3" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 428 | dependencies = [ 429 | "proc-macro2", 430 | "quote", 431 | "syn 1.0.109", 432 | ] 433 | 434 | [[package]] 435 | name = "num-traits" 436 | version = "0.2.19" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 439 | dependencies = [ 440 | "autocfg", 441 | ] 442 | 443 | [[package]] 444 | name = "once_cell" 445 | version = "1.20.2" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 448 | 449 | [[package]] 450 | name = "portable-atomic" 451 | version = "1.9.0" 452 | source = "registry+https://github.com/rust-lang/crates.io-index" 453 | checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" 454 | 455 | [[package]] 456 | name = "proc-macro2" 457 | version = "1.0.92" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 460 | dependencies = [ 461 | "unicode-ident", 462 | ] 463 | 464 | [[package]] 465 | name = "pulldown-cmark" 466 | version = "0.9.6" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" 469 | dependencies = [ 470 | "bitflags 2.6.0", 471 | "memchr", 472 | "unicase", 473 | ] 474 | 475 | [[package]] 476 | name = "pyo3" 477 | version = "0.23.4" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc" 480 | dependencies = [ 481 | "cfg-if", 482 | "indoc", 483 | "libc", 484 | "memoffset", 485 | "once_cell", 486 | "portable-atomic", 487 | "pyo3-build-config", 488 | "pyo3-ffi", 489 | "pyo3-macros", 490 | "unindent", 491 | ] 492 | 493 | [[package]] 494 | name = "pyo3-build-config" 495 | version = "0.23.4" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7" 498 | dependencies = [ 499 | "once_cell", 500 | "target-lexicon", 501 | ] 502 | 503 | [[package]] 504 | name = "pyo3-ffi" 505 | version = "0.23.4" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d" 508 | dependencies = [ 509 | "libc", 510 | "pyo3-build-config", 511 | ] 512 | 513 | [[package]] 514 | name = "pyo3-file" 515 | version = "0.10.0" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "3134dfe0d6458ea56d6c1cd4047c8894f331297aaf53eb9bf046ac7485dd469b" 518 | dependencies = [ 519 | "pyo3", 520 | "skeptic", 521 | ] 522 | 523 | [[package]] 524 | name = "pyo3-macros" 525 | version = "0.23.4" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7" 528 | dependencies = [ 529 | "proc-macro2", 530 | "pyo3-macros-backend", 531 | "quote", 532 | "syn 2.0.89", 533 | ] 534 | 535 | [[package]] 536 | name = "pyo3-macros-backend" 537 | version = "0.23.4" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4" 540 | dependencies = [ 541 | "heck", 542 | "proc-macro2", 543 | "pyo3-build-config", 544 | "quote", 545 | "syn 2.0.89", 546 | ] 547 | 548 | [[package]] 549 | name = "quick-xml" 550 | version = "0.36.2" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" 553 | dependencies = [ 554 | "memchr", 555 | ] 556 | 557 | [[package]] 558 | name = "quote" 559 | version = "1.0.37" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 562 | dependencies = [ 563 | "proc-macro2", 564 | ] 565 | 566 | [[package]] 567 | name = "rayon" 568 | version = "1.10.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 571 | dependencies = [ 572 | "either", 573 | "rayon-core", 574 | ] 575 | 576 | [[package]] 577 | name = "rayon-core" 578 | version = "1.12.1" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 581 | dependencies = [ 582 | "crossbeam-deque", 583 | "crossbeam-utils", 584 | ] 585 | 586 | [[package]] 587 | name = "rustix" 588 | version = "0.38.41" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" 591 | dependencies = [ 592 | "bitflags 2.6.0", 593 | "errno", 594 | "libc", 595 | "linux-raw-sys", 596 | "windows-sys 0.52.0", 597 | ] 598 | 599 | [[package]] 600 | name = "ryu" 601 | version = "1.0.18" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 604 | 605 | [[package]] 606 | name = "same-file" 607 | version = "1.0.6" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 610 | dependencies = [ 611 | "winapi-util", 612 | ] 613 | 614 | [[package]] 615 | name = "semver" 616 | version = "1.0.23" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 619 | dependencies = [ 620 | "serde", 621 | ] 622 | 623 | [[package]] 624 | name = "serde" 625 | version = "1.0.215" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 628 | dependencies = [ 629 | "serde_derive", 630 | ] 631 | 632 | [[package]] 633 | name = "serde_derive" 634 | version = "1.0.215" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 637 | dependencies = [ 638 | "proc-macro2", 639 | "quote", 640 | "syn 2.0.89", 641 | ] 642 | 643 | [[package]] 644 | name = "serde_json" 645 | version = "1.0.133" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" 648 | dependencies = [ 649 | "indexmap", 650 | "itoa", 651 | "memchr", 652 | "ryu", 653 | "serde", 654 | ] 655 | 656 | [[package]] 657 | name = "shlex" 658 | version = "1.3.0" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 661 | 662 | [[package]] 663 | name = "skeptic" 664 | version = "0.13.7" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" 667 | dependencies = [ 668 | "bytecount", 669 | "cargo_metadata", 670 | "error-chain", 671 | "glob", 672 | "pulldown-cmark", 673 | "tempfile", 674 | "walkdir", 675 | ] 676 | 677 | [[package]] 678 | name = "syn" 679 | version = "1.0.109" 680 | source = "registry+https://github.com/rust-lang/crates.io-index" 681 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 682 | dependencies = [ 683 | "proc-macro2", 684 | "quote", 685 | "unicode-ident", 686 | ] 687 | 688 | [[package]] 689 | name = "syn" 690 | version = "2.0.89" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" 693 | dependencies = [ 694 | "proc-macro2", 695 | "quote", 696 | "unicode-ident", 697 | ] 698 | 699 | [[package]] 700 | name = "target-lexicon" 701 | version = "0.12.16" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" 704 | 705 | [[package]] 706 | name = "tempfile" 707 | version = "3.14.0" 708 | source = "registry+https://github.com/rust-lang/crates.io-index" 709 | checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" 710 | dependencies = [ 711 | "cfg-if", 712 | "fastrand", 713 | "once_cell", 714 | "rustix", 715 | "windows-sys 0.59.0", 716 | ] 717 | 718 | [[package]] 719 | name = "thiserror" 720 | version = "1.0.69" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 723 | dependencies = [ 724 | "thiserror-impl", 725 | ] 726 | 727 | [[package]] 728 | name = "thiserror-impl" 729 | version = "1.0.69" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 732 | dependencies = [ 733 | "proc-macro2", 734 | "quote", 735 | "syn 2.0.89", 736 | ] 737 | 738 | [[package]] 739 | name = "unicase" 740 | version = "2.8.0" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" 743 | 744 | [[package]] 745 | name = "unicode-ident" 746 | version = "1.0.14" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 749 | 750 | [[package]] 751 | name = "unindent" 752 | version = "0.2.3" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" 755 | 756 | [[package]] 757 | name = "version_check" 758 | version = "0.9.5" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 761 | 762 | [[package]] 763 | name = "walkdir" 764 | version = "2.5.0" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 767 | dependencies = [ 768 | "same-file", 769 | "winapi-util", 770 | ] 771 | 772 | [[package]] 773 | name = "wasm-bindgen" 774 | version = "0.2.95" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 777 | dependencies = [ 778 | "cfg-if", 779 | "once_cell", 780 | "wasm-bindgen-macro", 781 | ] 782 | 783 | [[package]] 784 | name = "wasm-bindgen-backend" 785 | version = "0.2.95" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 788 | dependencies = [ 789 | "bumpalo", 790 | "log", 791 | "once_cell", 792 | "proc-macro2", 793 | "quote", 794 | "syn 2.0.89", 795 | "wasm-bindgen-shared", 796 | ] 797 | 798 | [[package]] 799 | name = "wasm-bindgen-macro" 800 | version = "0.2.95" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 803 | dependencies = [ 804 | "quote", 805 | "wasm-bindgen-macro-support", 806 | ] 807 | 808 | [[package]] 809 | name = "wasm-bindgen-macro-support" 810 | version = "0.2.95" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 813 | dependencies = [ 814 | "proc-macro2", 815 | "quote", 816 | "syn 2.0.89", 817 | "wasm-bindgen-backend", 818 | "wasm-bindgen-shared", 819 | ] 820 | 821 | [[package]] 822 | name = "wasm-bindgen-shared" 823 | version = "0.2.95" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 826 | 827 | [[package]] 828 | name = "winapi-util" 829 | version = "0.1.9" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 832 | dependencies = [ 833 | "windows-sys 0.59.0", 834 | ] 835 | 836 | [[package]] 837 | name = "windows-core" 838 | version = "0.52.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 841 | dependencies = [ 842 | "windows-targets", 843 | ] 844 | 845 | [[package]] 846 | name = "windows-sys" 847 | version = "0.52.0" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 850 | dependencies = [ 851 | "windows-targets", 852 | ] 853 | 854 | [[package]] 855 | name = "windows-sys" 856 | version = "0.59.0" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 859 | dependencies = [ 860 | "windows-targets", 861 | ] 862 | 863 | [[package]] 864 | name = "windows-targets" 865 | version = "0.52.6" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 868 | dependencies = [ 869 | "windows_aarch64_gnullvm", 870 | "windows_aarch64_msvc", 871 | "windows_i686_gnu", 872 | "windows_i686_gnullvm", 873 | "windows_i686_msvc", 874 | "windows_x86_64_gnu", 875 | "windows_x86_64_gnullvm", 876 | "windows_x86_64_msvc", 877 | ] 878 | 879 | [[package]] 880 | name = "windows_aarch64_gnullvm" 881 | version = "0.52.6" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 884 | 885 | [[package]] 886 | name = "windows_aarch64_msvc" 887 | version = "0.52.6" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 890 | 891 | [[package]] 892 | name = "windows_i686_gnu" 893 | version = "0.52.6" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 896 | 897 | [[package]] 898 | name = "windows_i686_gnullvm" 899 | version = "0.52.6" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 902 | 903 | [[package]] 904 | name = "windows_i686_msvc" 905 | version = "0.52.6" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 908 | 909 | [[package]] 910 | name = "windows_x86_64_gnu" 911 | version = "0.52.6" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 914 | 915 | [[package]] 916 | name = "windows_x86_64_gnullvm" 917 | version = "0.52.6" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 920 | 921 | [[package]] 922 | name = "windows_x86_64_msvc" 923 | version = "0.52.6" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 926 | 927 | [[package]] 928 | name = "winstructs" 929 | version = "0.3.2" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "6dc7406cd936173d9cc3a4fd5dc5b295bc59612439d72038e3d7ac4e5dd42de9" 932 | dependencies = [ 933 | "bitflags 1.3.2", 934 | "byteorder", 935 | "chrono", 936 | "log", 937 | "num-derive", 938 | "num-traits", 939 | "serde", 940 | "serde_json", 941 | "thiserror", 942 | ] 943 | 944 | [[package]] 945 | name = "zerocopy" 946 | version = "0.7.35" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 949 | dependencies = [ 950 | "zerocopy-derive", 951 | ] 952 | 953 | [[package]] 954 | name = "zerocopy-derive" 955 | version = "0.7.35" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 958 | dependencies = [ 959 | "proc-macro2", 960 | "quote", 961 | "syn 2.0.89", 962 | ] 963 | --------------------------------------------------------------------------------