stix-rust 0.1.0__tar.gz

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 (60) hide show
  1. stix_rust-0.1.0/Cargo.toml +19 -0
  2. stix_rust-0.1.0/PKG-INFO +6 -0
  3. stix_rust-0.1.0/bindings/python/.gitignore +5 -0
  4. stix_rust-0.1.0/bindings/python/Cargo.lock +309 -0
  5. stix_rust-0.1.0/bindings/python/Cargo.toml +18 -0
  6. stix_rust-0.1.0/bindings/python/README.md +59 -0
  7. stix_rust-0.1.0/bindings/python/src/engine.rs +67 -0
  8. stix_rust-0.1.0/bindings/python/src/errors.rs +41 -0
  9. stix_rust-0.1.0/bindings/python/src/handles.rs +90 -0
  10. stix_rust-0.1.0/bindings/python/src/lib.rs +16 -0
  11. stix_rust-0.1.0/bindings/python/tests/test_stix.py +96 -0
  12. stix_rust-0.1.0/crates/stix/Cargo.toml +19 -0
  13. stix_rust-0.1.0/crates/stix/examples/custom_model.rs +76 -0
  14. stix_rust-0.1.0/crates/stix/src/lib.rs +30 -0
  15. stix_rust-0.1.0/crates/stix/tests/custom_models.rs +81 -0
  16. stix_rust-0.1.0/crates/stix-ffi/Cargo.toml +12 -0
  17. stix_rust-0.1.0/crates/stix-ffi/src/engine.rs +169 -0
  18. stix_rust-0.1.0/crates/stix-ffi/src/error.rs +80 -0
  19. stix_rust-0.1.0/crates/stix-ffi/src/handles.rs +96 -0
  20. stix_rust-0.1.0/crates/stix-ffi/src/lib.rs +30 -0
  21. stix_rust-0.1.0/crates/stix-ffi/tests/facade.rs +45 -0
  22. stix_rust-0.1.0/crates/stix-matcher/Cargo.toml +16 -0
  23. stix_rust-0.1.0/crates/stix-matcher/src/compare.rs +139 -0
  24. stix_rust-0.1.0/crates/stix-matcher/src/error.rs +23 -0
  25. stix_rust-0.1.0/crates/stix-matcher/src/eval.rs +507 -0
  26. stix_rust-0.1.0/crates/stix-matcher/src/lib.rs +92 -0
  27. stix_rust-0.1.0/crates/stix-matcher/src/observation.rs +49 -0
  28. stix_rust-0.1.0/crates/stix-matcher/src/pattern_ops.rs +71 -0
  29. stix_rust-0.1.0/crates/stix-matcher/src/resolve.rs +201 -0
  30. stix_rust-0.1.0/crates/stix-matcher/src/result.rs +55 -0
  31. stix_rust-0.1.0/crates/stix-matcher/src/subset.rs +121 -0
  32. stix_rust-0.1.0/crates/stix-matcher/tests/fixtures/bundle.json +17 -0
  33. stix_rust-0.1.0/crates/stix-matcher/tests/integration.rs +60 -0
  34. stix_rust-0.1.0/crates/stix-model/Cargo.toml +12 -0
  35. stix_rust-0.1.0/crates/stix-model/src/bundle.rs +77 -0
  36. stix_rust-0.1.0/crates/stix-model/src/error.rs +40 -0
  37. stix_rust-0.1.0/crates/stix-model/src/lib.rs +23 -0
  38. stix_rust-0.1.0/crates/stix-model/src/object.rs +261 -0
  39. stix_rust-0.1.0/crates/stix-model/src/registry.rs +280 -0
  40. stix_rust-0.1.0/crates/stix-model/src/sdo.rs +126 -0
  41. stix_rust-0.1.0/crates/stix-model/src/store.rs +89 -0
  42. stix_rust-0.1.0/crates/stix-model/src/value.rs +169 -0
  43. stix_rust-0.1.0/crates/stix-model/src/version.rs +33 -0
  44. stix_rust-0.1.0/crates/stix-model/src/view.rs +160 -0
  45. stix_rust-0.1.0/crates/stix-model/tests/fixtures/bundle.json +24 -0
  46. stix_rust-0.1.0/crates/stix-model/tests/integration.rs +35 -0
  47. stix_rust-0.1.0/crates/stix-pattern/Cargo.toml +14 -0
  48. stix_rust-0.1.0/crates/stix-pattern/src/ast.rs +190 -0
  49. stix_rust-0.1.0/crates/stix-pattern/src/error.rs +63 -0
  50. stix_rust-0.1.0/crates/stix-pattern/src/lexer.rs +438 -0
  51. stix_rust-0.1.0/crates/stix-pattern/src/lib.rs +22 -0
  52. stix_rust-0.1.0/crates/stix-pattern/src/parser.rs +622 -0
  53. stix_rust-0.1.0/crates/stix-pattern/tests/conformance.rs +42 -0
  54. stix_rust-0.1.0/crates/stix-pattern/tests/fixtures/invalid_patterns.txt +18 -0
  55. stix_rust-0.1.0/crates/stix-pattern/tests/fixtures/valid_patterns.txt +34 -0
  56. stix_rust-0.1.0/crates/stix-pattern/tests/roundtrip.rs +17 -0
  57. stix_rust-0.1.0/pyproject.toml +16 -0
  58. stix_rust-0.1.0/python/stix/__init__.py +24 -0
  59. stix_rust-0.1.0/python/stix/__init__.pyi +34 -0
  60. stix_rust-0.1.0/python/stix/py.typed +0 -0
@@ -0,0 +1,19 @@
1
+ [workspace]
2
+ resolver = "2"
3
+ members = ["crates/stix-pattern", "crates/stix-model", "crates/stix-matcher", "crates/stix", "crates/stix-ffi"]
4
+ exclude = ["bindings/python", "bindings/typescript-node", "bindings/typescript-wasm", "bindings/java/rust"]
5
+
6
+ [workspace.package]
7
+ edition = "2021"
8
+ license = "MIT OR Apache-2.0"
9
+ repository = "https://github.com/benjamin-small/stix-rust"
10
+
11
+ [workspace.dependencies]
12
+ serde = { version = "1", features = ["derive"] }
13
+ serde_json = "1"
14
+ thiserror = "1"
15
+ regex = "1"
16
+ stix-pattern = { path = "crates/stix-pattern", version = "0.1.0" }
17
+ stix-model = { path = "crates/stix-model", version = "0.1.0" }
18
+ stix-matcher = { path = "crates/stix-matcher", version = "0.1.0" }
19
+ stix = { path = "crates/stix", package = "stix-rust", version = "0.1.0" }
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: stix-rust
3
+ Version: 0.1.0
4
+ Summary: Python bindings for the stix-rust toolkit (parse, import, match STIX 2.1).
5
+ License: MIT OR Apache-2.0
6
+ Requires-Python: >=3.8
@@ -0,0 +1,5 @@
1
+ .venv/
2
+ target/
3
+ __pycache__/
4
+ *.so
5
+ .pytest_cache/
@@ -0,0 +1,309 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "aho-corasick"
7
+ version = "1.1.4"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
10
+ dependencies = [
11
+ "memchr",
12
+ ]
13
+
14
+ [[package]]
15
+ name = "heck"
16
+ version = "0.5.0"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
19
+
20
+ [[package]]
21
+ name = "itoa"
22
+ version = "1.0.18"
23
+ source = "registry+https://github.com/rust-lang/crates.io-index"
24
+ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
25
+
26
+ [[package]]
27
+ name = "libc"
28
+ version = "0.2.186"
29
+ source = "registry+https://github.com/rust-lang/crates.io-index"
30
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
31
+
32
+ [[package]]
33
+ name = "memchr"
34
+ version = "2.8.2"
35
+ source = "registry+https://github.com/rust-lang/crates.io-index"
36
+ checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
37
+
38
+ [[package]]
39
+ name = "once_cell"
40
+ version = "1.21.4"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
43
+
44
+ [[package]]
45
+ name = "portable-atomic"
46
+ version = "1.13.1"
47
+ source = "registry+https://github.com/rust-lang/crates.io-index"
48
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
49
+
50
+ [[package]]
51
+ name = "proc-macro2"
52
+ version = "1.0.106"
53
+ source = "registry+https://github.com/rust-lang/crates.io-index"
54
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
55
+ dependencies = [
56
+ "unicode-ident",
57
+ ]
58
+
59
+ [[package]]
60
+ name = "pyo3"
61
+ version = "0.29.0"
62
+ source = "registry+https://github.com/rust-lang/crates.io-index"
63
+ checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c"
64
+ dependencies = [
65
+ "libc",
66
+ "once_cell",
67
+ "portable-atomic",
68
+ "pyo3-build-config",
69
+ "pyo3-ffi",
70
+ "pyo3-macros",
71
+ ]
72
+
73
+ [[package]]
74
+ name = "pyo3-build-config"
75
+ version = "0.29.0"
76
+ source = "registry+https://github.com/rust-lang/crates.io-index"
77
+ checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078"
78
+ dependencies = [
79
+ "target-lexicon",
80
+ ]
81
+
82
+ [[package]]
83
+ name = "pyo3-ffi"
84
+ version = "0.29.0"
85
+ source = "registry+https://github.com/rust-lang/crates.io-index"
86
+ checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b"
87
+ dependencies = [
88
+ "libc",
89
+ "pyo3-build-config",
90
+ ]
91
+
92
+ [[package]]
93
+ name = "pyo3-macros"
94
+ version = "0.29.0"
95
+ source = "registry+https://github.com/rust-lang/crates.io-index"
96
+ checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771"
97
+ dependencies = [
98
+ "proc-macro2",
99
+ "pyo3-macros-backend",
100
+ "quote",
101
+ "syn",
102
+ ]
103
+
104
+ [[package]]
105
+ name = "pyo3-macros-backend"
106
+ version = "0.29.0"
107
+ source = "registry+https://github.com/rust-lang/crates.io-index"
108
+ checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362"
109
+ dependencies = [
110
+ "heck",
111
+ "proc-macro2",
112
+ "quote",
113
+ "syn",
114
+ ]
115
+
116
+ [[package]]
117
+ name = "pythonize"
118
+ version = "0.29.0"
119
+ source = "registry+https://github.com/rust-lang/crates.io-index"
120
+ checksum = "6ec376e1216e0c929a74964ce2020012a1a39f32d80e78aa688721219ea7fb89"
121
+ dependencies = [
122
+ "pyo3",
123
+ "serde",
124
+ ]
125
+
126
+ [[package]]
127
+ name = "quote"
128
+ version = "1.0.46"
129
+ source = "registry+https://github.com/rust-lang/crates.io-index"
130
+ checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
131
+ dependencies = [
132
+ "proc-macro2",
133
+ ]
134
+
135
+ [[package]]
136
+ name = "regex"
137
+ version = "1.12.4"
138
+ source = "registry+https://github.com/rust-lang/crates.io-index"
139
+ checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
140
+ dependencies = [
141
+ "aho-corasick",
142
+ "memchr",
143
+ "regex-automata",
144
+ "regex-syntax",
145
+ ]
146
+
147
+ [[package]]
148
+ name = "regex-automata"
149
+ version = "0.4.14"
150
+ source = "registry+https://github.com/rust-lang/crates.io-index"
151
+ checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
152
+ dependencies = [
153
+ "aho-corasick",
154
+ "memchr",
155
+ "regex-syntax",
156
+ ]
157
+
158
+ [[package]]
159
+ name = "regex-syntax"
160
+ version = "0.8.11"
161
+ source = "registry+https://github.com/rust-lang/crates.io-index"
162
+ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
163
+
164
+ [[package]]
165
+ name = "serde"
166
+ version = "1.0.228"
167
+ source = "registry+https://github.com/rust-lang/crates.io-index"
168
+ checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
169
+ dependencies = [
170
+ "serde_core",
171
+ "serde_derive",
172
+ ]
173
+
174
+ [[package]]
175
+ name = "serde_core"
176
+ version = "1.0.228"
177
+ source = "registry+https://github.com/rust-lang/crates.io-index"
178
+ checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
179
+ dependencies = [
180
+ "serde_derive",
181
+ ]
182
+
183
+ [[package]]
184
+ name = "serde_derive"
185
+ version = "1.0.228"
186
+ source = "registry+https://github.com/rust-lang/crates.io-index"
187
+ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
188
+ dependencies = [
189
+ "proc-macro2",
190
+ "quote",
191
+ "syn",
192
+ ]
193
+
194
+ [[package]]
195
+ name = "serde_json"
196
+ version = "1.0.150"
197
+ source = "registry+https://github.com/rust-lang/crates.io-index"
198
+ checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
199
+ dependencies = [
200
+ "itoa",
201
+ "memchr",
202
+ "serde",
203
+ "serde_core",
204
+ "zmij",
205
+ ]
206
+
207
+ [[package]]
208
+ name = "stix-ffi"
209
+ version = "0.1.0"
210
+ dependencies = [
211
+ "serde_json",
212
+ "stix-rust",
213
+ "thiserror",
214
+ ]
215
+
216
+ [[package]]
217
+ name = "stix-matcher"
218
+ version = "0.1.0"
219
+ dependencies = [
220
+ "regex",
221
+ "stix-model",
222
+ "stix-pattern",
223
+ "thiserror",
224
+ ]
225
+
226
+ [[package]]
227
+ name = "stix-model"
228
+ version = "0.1.0"
229
+ dependencies = [
230
+ "serde",
231
+ "serde_json",
232
+ "thiserror",
233
+ ]
234
+
235
+ [[package]]
236
+ name = "stix-pattern"
237
+ version = "0.1.0"
238
+ dependencies = [
239
+ "serde",
240
+ "thiserror",
241
+ ]
242
+
243
+ [[package]]
244
+ name = "stix-python"
245
+ version = "0.1.0"
246
+ dependencies = [
247
+ "pyo3",
248
+ "pythonize",
249
+ "serde_json",
250
+ "stix-ffi",
251
+ ]
252
+
253
+ [[package]]
254
+ name = "stix-rust"
255
+ version = "0.1.0"
256
+ dependencies = [
257
+ "stix-matcher",
258
+ "stix-model",
259
+ "stix-pattern",
260
+ ]
261
+
262
+ [[package]]
263
+ name = "syn"
264
+ version = "2.0.118"
265
+ source = "registry+https://github.com/rust-lang/crates.io-index"
266
+ checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
267
+ dependencies = [
268
+ "proc-macro2",
269
+ "quote",
270
+ "unicode-ident",
271
+ ]
272
+
273
+ [[package]]
274
+ name = "target-lexicon"
275
+ version = "0.13.5"
276
+ source = "registry+https://github.com/rust-lang/crates.io-index"
277
+ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
278
+
279
+ [[package]]
280
+ name = "thiserror"
281
+ version = "1.0.69"
282
+ source = "registry+https://github.com/rust-lang/crates.io-index"
283
+ checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
284
+ dependencies = [
285
+ "thiserror-impl",
286
+ ]
287
+
288
+ [[package]]
289
+ name = "thiserror-impl"
290
+ version = "1.0.69"
291
+ source = "registry+https://github.com/rust-lang/crates.io-index"
292
+ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
293
+ dependencies = [
294
+ "proc-macro2",
295
+ "quote",
296
+ "syn",
297
+ ]
298
+
299
+ [[package]]
300
+ name = "unicode-ident"
301
+ version = "1.0.24"
302
+ source = "registry+https://github.com/rust-lang/crates.io-index"
303
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
304
+
305
+ [[package]]
306
+ name = "zmij"
307
+ version = "1.0.21"
308
+ source = "registry+https://github.com/rust-lang/crates.io-index"
309
+ checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
@@ -0,0 +1,18 @@
1
+ [package]
2
+ name = "stix-python"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ license = "MIT OR Apache-2.0"
6
+ repository = "https://github.com/benjamin-small/stix-rust"
7
+ description = "Python bindings for the stix-rust toolkit."
8
+ readme = "README.md"
9
+
10
+ [lib]
11
+ name = "_stix"
12
+ crate-type = ["cdylib"]
13
+
14
+ [dependencies]
15
+ stix-ffi = { path = "../../crates/stix-ffi", version = "0.1.0" }
16
+ pyo3 = { version = "0.29", features = ["extension-module", "abi3-py38"] }
17
+ pythonize = "0.29"
18
+ serde_json = "1"
@@ -0,0 +1,59 @@
1
+ # stix-rust — Python binding
2
+
3
+ Python bindings for the [stix-rust](../../README.md) toolkit — parse STIX 2.1
4
+ patterns, import STIX bundles, match patterns against observations, and register
5
+ custom object types, from Python.
6
+
7
+ - **Toolchain:** PyO3 + maturin. Module `stix` (compiled core `stix._stix`).
8
+ - **Surface:** typed handles (`Engine`, `Pattern`, `Bundle`, `MatchResult`); deep
9
+ structure (the pattern AST, bundle objects) as native `dict`/`list`.
10
+
11
+ ## Install (from source)
12
+
13
+ ```bash
14
+ pip install "maturin>=1.5,<2.0"
15
+ cd bindings/python
16
+ maturin develop # builds the extension into the active venv
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```python
22
+ import stix
23
+
24
+ engine = stix.Engine()
25
+ pattern = engine.parse_pattern("[ipv4-addr:value = '198.51.100.5']")
26
+ print(pattern.ast) # AST as a dict
27
+
28
+ bundle = engine.parse_bundle(open("bundle.json").read())
29
+ print(len(bundle), [o["type"] for o in bundle])
30
+
31
+ result = engine.match_bundle(pattern, bundle)
32
+ print(result.matched, result.observations)
33
+ ```
34
+
35
+ ### Custom object types
36
+
37
+ Register a `dict -> dict` hook; it runs at import time and may add computed
38
+ properties or raise to reject (surfaced as `stix.ValidationError`):
39
+
40
+ ```python
41
+ def normalize(obj):
42
+ obj["risk_band"] = "high" if obj.get("risk_score", 0) > 80 else "low"
43
+ return obj
44
+
45
+ engine.register_type("x-acme-widget", normalize)
46
+ ```
47
+
48
+ ### Errors
49
+
50
+ `stix.StixError` is the base; `ParseError`, `ModelError`, `MatchError`, and
51
+ `ValidationError` are subclasses.
52
+
53
+ ## Test
54
+
55
+ ```bash
56
+ cd bindings/python
57
+ maturin develop
58
+ python -m pytest -q
59
+ ```
@@ -0,0 +1,67 @@
1
+ //! The Engine pyclass: parse patterns/bundles and run matches.
2
+ use pyo3::prelude::*;
3
+
4
+ use crate::errors::to_pyerr;
5
+ use crate::handles::{Bundle, MatchResult, Pattern};
6
+
7
+ #[pyclass]
8
+ pub struct Engine {
9
+ inner: stix_ffi::Engine,
10
+ }
11
+
12
+ #[pymethods]
13
+ impl Engine {
14
+ #[new]
15
+ fn new() -> Self {
16
+ Engine {
17
+ inner: stix_ffi::Engine::new(),
18
+ }
19
+ }
20
+
21
+ /// Parse a STIX pattern string into a Pattern handle.
22
+ fn parse_pattern(&self, src: &str) -> PyResult<Pattern> {
23
+ let inner = self.inner.parse_pattern(src).map_err(to_pyerr)?;
24
+ Ok(Pattern { inner })
25
+ }
26
+
27
+ /// Parse a STIX bundle JSON string into a Bundle handle.
28
+ fn parse_bundle(&self, json: &str) -> PyResult<Bundle> {
29
+ let inner = self.inner.parse_bundle(json).map_err(to_pyerr)?;
30
+ Ok(Bundle { inner })
31
+ }
32
+
33
+ /// Match a pattern against a bundle.
34
+ fn match_bundle(&self, pattern: &Pattern, bundle: &Bundle) -> PyResult<MatchResult> {
35
+ let outcome = self
36
+ .inner
37
+ .match_bundle(&pattern.inner, &bundle.inner)
38
+ .map_err(to_pyerr)?;
39
+ Ok(MatchResult {
40
+ matched: outcome.matched,
41
+ observations: outcome.observations,
42
+ })
43
+ }
44
+
45
+ /// Register a custom object type. `hook` is a callable `dict -> dict`: it may
46
+ /// mutate/return the object (adding computed properties) or raise to reject it
47
+ /// (surfaced as ValidationError from parse_bundle). Runs only at parse time.
48
+ fn register_type(&mut self, type_name: &str, hook: Py<PyAny>) {
49
+ self.inner.register_type(
50
+ type_name,
51
+ Box::new(move |value: serde_json::Value| {
52
+ Python::attach(|py| {
53
+ // Value -> Python dict
54
+ let arg = pythonize::pythonize(py, &value).map_err(|e| e.to_string())?;
55
+ // call the Python hook
56
+ let result = hook
57
+ .call1(py, (arg,))
58
+ .map_err(|e| e.value(py).to_string())?;
59
+ // returned dict -> Value
60
+ let out: serde_json::Value =
61
+ pythonize::depythonize(result.bind(py)).map_err(|e| e.to_string())?;
62
+ Ok(out)
63
+ })
64
+ }),
65
+ );
66
+ }
67
+ }
@@ -0,0 +1,41 @@
1
+ //! Python exception types and the FfiError -> PyErr mapping.
2
+ use pyo3::create_exception;
3
+ use pyo3::exceptions::PyException;
4
+ use pyo3::prelude::*;
5
+ use stix_ffi::{ErrorCode, FfiError};
6
+
7
+ create_exception!(
8
+ _stix,
9
+ StixError,
10
+ PyException,
11
+ "Base class for all stix errors."
12
+ );
13
+ create_exception!(_stix, ParseError, StixError, "Pattern failed to parse.");
14
+ create_exception!(_stix, ModelError, StixError, "Object/bundle import failed.");
15
+ create_exception!(_stix, MatchError, StixError, "Matching failed.");
16
+ create_exception!(
17
+ _stix,
18
+ ValidationError,
19
+ StixError,
20
+ "A custom-type hook rejected an object."
21
+ );
22
+
23
+ /// Convert a facade error into the matching Python exception.
24
+ pub fn to_pyerr(err: FfiError) -> PyErr {
25
+ match err.code {
26
+ ErrorCode::Parse => ParseError::new_err(err.message),
27
+ ErrorCode::Model => ModelError::new_err(err.message),
28
+ ErrorCode::Match => MatchError::new_err(err.message),
29
+ ErrorCode::Validation => ValidationError::new_err(err.message),
30
+ }
31
+ }
32
+
33
+ /// Register the exception types on the module.
34
+ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
35
+ m.add("StixError", m.py().get_type::<StixError>())?;
36
+ m.add("ParseError", m.py().get_type::<ParseError>())?;
37
+ m.add("ModelError", m.py().get_type::<ModelError>())?;
38
+ m.add("MatchError", m.py().get_type::<MatchError>())?;
39
+ m.add("ValidationError", m.py().get_type::<ValidationError>())?;
40
+ Ok(())
41
+ }
@@ -0,0 +1,90 @@
1
+ //! Pyclass handles: Pattern, Bundle, MatchResult.
2
+ use pyo3::prelude::*;
3
+ use pyo3::types::PyList;
4
+
5
+ /// Parse a JSON string into a native Python object (dict/list/...).
6
+ fn json_to_py(py: Python<'_>, json: &str) -> PyResult<Py<PyAny>> {
7
+ let value: serde_json::Value = serde_json::from_str(json)
8
+ .map_err(|e| crate::errors::ParseError::new_err(e.to_string()))?;
9
+ let obj = pythonize::pythonize(py, &value)
10
+ .map_err(|e| crate::errors::ModelError::new_err(e.to_string()))?;
11
+ Ok(obj.unbind())
12
+ }
13
+
14
+ #[pyclass]
15
+ pub struct Pattern {
16
+ pub(crate) inner: stix_ffi::Pattern,
17
+ }
18
+
19
+ #[pymethods]
20
+ impl Pattern {
21
+ /// The pattern's AST as a native Python dict.
22
+ #[getter]
23
+ fn ast(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
24
+ json_to_py(py, &self.inner.to_json())
25
+ }
26
+
27
+ fn __repr__(&self) -> String {
28
+ "Pattern(...)".to_string()
29
+ }
30
+ }
31
+
32
+ #[pyclass]
33
+ pub struct Bundle {
34
+ pub(crate) inner: stix_ffi::Bundle,
35
+ }
36
+
37
+ #[pymethods]
38
+ impl Bundle {
39
+ /// Number of objects in the bundle.
40
+ fn object_count(&self) -> usize {
41
+ self.inner.object_count()
42
+ }
43
+
44
+ fn __len__(&self) -> usize {
45
+ self.inner.object_count()
46
+ }
47
+
48
+ /// The object at `index` as a native Python dict, or None if out of range.
49
+ fn object(&self, py: Python<'_>, index: usize) -> PyResult<Option<Py<PyAny>>> {
50
+ match self.inner.object_json(index) {
51
+ Some(json) => Ok(Some(json_to_py(py, &json)?)),
52
+ None => Ok(None),
53
+ }
54
+ }
55
+
56
+ /// Iterate over the objects (each a dict).
57
+ fn __iter__(slf: PyRef<'_, Self>, py: Python<'_>) -> PyResult<Py<PyAny>> {
58
+ let mut items: Vec<Py<PyAny>> = Vec::with_capacity(slf.inner.object_count());
59
+ for i in 0..slf.inner.object_count() {
60
+ if let Some(json) = slf.inner.object_json(i) {
61
+ items.push(json_to_py(py, &json)?);
62
+ }
63
+ }
64
+ let list = PyList::new(py, items)?;
65
+ Ok(list.as_any().try_iter()?.into())
66
+ }
67
+ }
68
+
69
+ #[pyclass]
70
+ pub struct MatchResult {
71
+ pub(crate) matched: bool,
72
+ pub(crate) observations: Vec<u64>,
73
+ }
74
+
75
+ #[pymethods]
76
+ impl MatchResult {
77
+ #[getter]
78
+ fn matched(&self) -> bool {
79
+ self.matched
80
+ }
81
+
82
+ #[getter]
83
+ fn observations(&self) -> Vec<u64> {
84
+ self.observations.clone()
85
+ }
86
+
87
+ fn __bool__(&self) -> bool {
88
+ self.matched
89
+ }
90
+ }
@@ -0,0 +1,16 @@
1
+ //! Python bindings for the stix-rust toolkit (compiled module `stix._stix`).
2
+ use pyo3::prelude::*;
3
+
4
+ mod engine;
5
+ mod errors;
6
+ mod handles;
7
+
8
+ #[pymodule]
9
+ fn _stix(m: &Bound<'_, PyModule>) -> PyResult<()> {
10
+ errors::register(m)?;
11
+ m.add_class::<engine::Engine>()?;
12
+ m.add_class::<handles::Pattern>()?;
13
+ m.add_class::<handles::Bundle>()?;
14
+ m.add_class::<handles::MatchResult>()?;
15
+ Ok(())
16
+ }