gameplay: port filter expiration command effects
Browse files- crates/urt-gameplay/src/bin/urt-filter-loading.rs +69 -0
- crates/urt-gameplay/src/filter_loading.rs +113 -0
- docs/FINDINGS.md +2 -0
- docs/PROVISIONAL_TODO.md +2 -0
- docs/RESEARCH_LEDGER.md +4 -0
- docs/REVERSE_ENGINEERING_TODO.md +2 -0
- docs/research/README.md +2 -0
- docs/research/RUST_FILTER_DURATION.md +5 -0
- docs/research/rust-filter-duration.json +0 -0
- docs/research/rust-health-impulse.json +1 -1
- scripts/analyze_rust_filter_duration.py +17 -0
- scripts/test_rust_filter_duration.py +9 -0
- xtask/checks.tsv +16 -0
crates/urt-gameplay/src/bin/urt-filter-loading.rs
CHANGED
|
@@ -62,6 +62,7 @@ fn main() {
|
|
| 62 |
);
|
| 63 |
}
|
| 64 |
"load" => println!("{}", loading(&w)),
|
|
|
|
| 65 |
_ => panic!("unknown policy"),
|
| 66 |
}
|
| 67 |
}
|
|
@@ -171,3 +172,71 @@ fn loading(w: &[u32]) -> String {
|
|
| 171 |
.unwrap();
|
| 172 |
load_json(out, &rows, count)
|
| 173 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
);
|
| 63 |
}
|
| 64 |
"load" => println!("{}", loading(&w)),
|
| 65 |
+
"duration" => println!("{}", duration(&w)),
|
| 66 |
_ => panic!("unknown policy"),
|
| 67 |
}
|
| 68 |
}
|
|
|
|
| 172 |
.unwrap();
|
| 173 |
load_json(out, &rows, count)
|
| 174 |
}
|
| 175 |
+
|
| 176 |
+
fn duration(w: &[u32]) -> String {
|
| 177 |
+
let mut pos = 3;
|
| 178 |
+
let address = blob(w, &mut pos);
|
| 179 |
+
let duration = blob(w, &mut pos);
|
| 180 |
+
assert_eq!(pos, w.len());
|
| 181 |
+
let fields = [13, w[0], 0, 4, 5, 126, 6, 0, 0];
|
| 182 |
+
let client = if w[1] == u32::MAX {
|
| 183 |
+
None
|
| 184 |
+
} else {
|
| 185 |
+
Some(w[1] as i32)
|
| 186 |
+
};
|
| 187 |
+
let mut ring = w[2];
|
| 188 |
+
let mut rows = [
|
| 189 |
+
urt_gameplay::native_policy::FilterRecord {
|
| 190 |
+
mask: u32::MAX,
|
| 191 |
+
comparison: 0x04030201,
|
| 192 |
+
metadata: 77,
|
| 193 |
+
},
|
| 194 |
+
urt_gameplay::native_policy::FilterRecord {
|
| 195 |
+
mask: u32::MAX,
|
| 196 |
+
comparison: 0x04030201,
|
| 197 |
+
metadata: 88,
|
| 198 |
+
},
|
| 199 |
+
];
|
| 200 |
+
let p = filter_duration(&mut rows, &address, &duration, &fields, client, &mut ring).unwrap();
|
| 201 |
+
let effects = p
|
| 202 |
+
.effects
|
| 203 |
+
.iter()
|
| 204 |
+
.map(|e| match e {
|
| 205 |
+
FilterDurationEffect::Console(t) => {
|
| 206 |
+
format!(r#"{{"op":"console","text":"{}"}}"#, hex(t))
|
| 207 |
+
}
|
| 208 |
+
FilterDurationEffect::Send { recipient, text } => format!(
|
| 209 |
+
r#"{{"op":"send","recipient":{},"text":"{}"}}"#,
|
| 210 |
+
recipient,
|
| 211 |
+
hex(text)
|
| 212 |
+
),
|
| 213 |
+
FilterDurationEffect::Time => r#"{"op":"time"}"#.into(),
|
| 214 |
+
FilterDurationEffect::Metadata { text, value } => format!(
|
| 215 |
+
r#"{{"op":"metadata","text":"{}","metadata":{},"save":1}}"#,
|
| 216 |
+
hex(text),
|
| 217 |
+
value
|
| 218 |
+
),
|
| 219 |
+
})
|
| 220 |
+
.collect::<Vec<String>>()
|
| 221 |
+
.join(",");
|
| 222 |
+
let updates = p
|
| 223 |
+
.update
|
| 224 |
+
.map(|p| {
|
| 225 |
+
format!(
|
| 226 |
+
r#"{{"result":{},"diagnostics":[{}],"exports":[{}]}}"#,
|
| 227 |
+
u8::from(p.result),
|
| 228 |
+
notice_json(&p),
|
| 229 |
+
if p.export { "252500" } else { "" }
|
| 230 |
+
)
|
| 231 |
+
})
|
| 232 |
+
.unwrap_or_default();
|
| 233 |
+
let rows = rows
|
| 234 |
+
.iter()
|
| 235 |
+
.map(|r| list(&[r.mask, r.comparison, r.metadata]))
|
| 236 |
+
.collect::<Vec<_>>()
|
| 237 |
+
.join(",");
|
| 238 |
+
format!(
|
| 239 |
+
r#"{{"events":[{}],"updates":[{}],"ring":{},"rows":[{}]}}"#,
|
| 240 |
+
effects, updates, ring, rows
|
| 241 |
+
)
|
| 242 |
+
}
|
crates/urt-gameplay/src/filter_loading.rs
CHANGED
|
@@ -295,3 +295,116 @@ mod load_tests {
|
|
| 295 |
assert_eq!(count, old);
|
| 296 |
}
|
| 297 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
assert_eq!(count, old);
|
| 296 |
}
|
| 297 |
}
|
| 298 |
+
|
| 299 |
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
| 300 |
+
pub enum FilterDurationEffect {
|
| 301 |
+
Console(Vec<u8>),
|
| 302 |
+
Send { recipient: i32, text: Vec<u8> },
|
| 303 |
+
Time,
|
| 304 |
+
Metadata { text: Vec<u8>, value: u32 },
|
| 305 |
+
}
|
| 306 |
+
#[derive(Clone, Debug, PartialEq, Eq)]
|
| 307 |
+
pub struct FilterDuration {
|
| 308 |
+
pub effects: Vec<FilterDurationEffect>,
|
| 309 |
+
pub update: Option<FilterMutation>,
|
| 310 |
+
}
|
| 311 |
+
/// Original252213: empty arguments select usage; otherwise add parsed duration
|
| 312 |
+
/// to supplied current minutes and request first-match metadata update with save.
|
| 313 |
+
pub fn filter_duration(
|
| 314 |
+
records: &mut [FilterRecord],
|
| 315 |
+
address: &[u8],
|
| 316 |
+
duration: &[u8],
|
| 317 |
+
fields: &[u32; 9],
|
| 318 |
+
client: Option<i32>,
|
| 319 |
+
ring: &mut u32,
|
| 320 |
+
) -> Result<FilterDuration, Error> {
|
| 321 |
+
if records.len() > 4096 {
|
| 322 |
+
return Err(Error::InvalidCount);
|
| 323 |
+
}
|
| 324 |
+
if client.is_some_and(|s| !(0..64).contains(&s)) {
|
| 325 |
+
return Err(Error::InvalidIndex);
|
| 326 |
+
}
|
| 327 |
+
let address = address.split(|b| *b == 0).next().unwrap_or_default();
|
| 328 |
+
let duration = duration.split(|b| *b == 0).next().unwrap_or_default();
|
| 329 |
+
if address.len() > 1024 || duration.len() > 1024 {
|
| 330 |
+
return Err(Error::InvalidInput);
|
| 331 |
+
}
|
| 332 |
+
if address.is_empty() || duration.is_empty() {
|
| 333 |
+
let effect = if let Some(recipient) = client {
|
| 334 |
+
let mut text = crate::qvm_format::render(
|
| 335 |
+
b"chat \"^5[rcon] ^7Usage: /addipexpire <ip-mask> <minutes>\"",
|
| 336 |
+
&[],
|
| 337 |
+
)
|
| 338 |
+
.map_err(|_| Error::InvalidInput)?;
|
| 339 |
+
text.pop();
|
| 340 |
+
*ring = ring.wrapping_add(1);
|
| 341 |
+
FilterDurationEffect::Send { recipient, text }
|
| 342 |
+
} else {
|
| 343 |
+
FilterDurationEffect::Console(b"Usage: addipexpire <ip-mask> <minutes>\n".to_vec())
|
| 344 |
+
};
|
| 345 |
+
return Ok(FilterDuration {
|
| 346 |
+
effects: vec![effect],
|
| 347 |
+
update: None,
|
| 348 |
+
});
|
| 349 |
+
}
|
| 350 |
+
let value =
|
| 351 |
+
filter_minutes(fields).wrapping_add(crate::native_policy::signed_prefix(duration) as u32);
|
| 352 |
+
let mut staged = records.to_vec();
|
| 353 |
+
let update = update_filter_metadata(&mut staged, address, value, 1)?;
|
| 354 |
+
records.copy_from_slice(&staged);
|
| 355 |
+
Ok(FilterDuration {
|
| 356 |
+
effects: vec![
|
| 357 |
+
FilterDurationEffect::Time,
|
| 358 |
+
FilterDurationEffect::Metadata {
|
| 359 |
+
text: address.to_vec(),
|
| 360 |
+
value,
|
| 361 |
+
},
|
| 362 |
+
],
|
| 363 |
+
update: Some(update),
|
| 364 |
+
})
|
| 365 |
+
}
|
| 366 |
+
#[cfg(test)]
|
| 367 |
+
mod duration_tests {
|
| 368 |
+
use super::*;
|
| 369 |
+
#[test]
|
| 370 |
+
fn usage_wraps_only_client_ring_and_skips_time() {
|
| 371 |
+
let mut ring = u32::MAX;
|
| 372 |
+
let p = filter_duration(&mut [], b"", b"5", &[0; 9], Some(3), &mut ring).unwrap();
|
| 373 |
+
assert_eq!(ring, 0);
|
| 374 |
+
assert!(p.update.is_none());
|
| 375 |
+
assert!(matches!(
|
| 376 |
+
&p.effects[..],
|
| 377 |
+
[FilterDurationEffect::Send { recipient: 3, .. }]
|
| 378 |
+
));
|
| 379 |
+
filter_duration(&mut [], b"1.2.3.4", b"", &[0; 9], None, &mut ring).unwrap();
|
| 380 |
+
assert_eq!(ring, 0);
|
| 381 |
+
}
|
| 382 |
+
#[test]
|
| 383 |
+
fn negative_duration_can_store_permanent_marker_and_preserves_duplicate() {
|
| 384 |
+
let row = FilterRecord {
|
| 385 |
+
mask: u32::MAX,
|
| 386 |
+
comparison: 0x04030201,
|
| 387 |
+
metadata: 77,
|
| 388 |
+
};
|
| 389 |
+
let mut rows = [row, row];
|
| 390 |
+
let mut ring = 7;
|
| 391 |
+
let p = filter_duration(&mut rows, b"1.2.3.4", b"-1", &[0; 9], None, &mut ring).unwrap();
|
| 392 |
+
assert!(p.update.unwrap().export);
|
| 393 |
+
assert_eq!(rows[0].metadata, u32::MAX);
|
| 394 |
+
assert_eq!(rows[1], row);
|
| 395 |
+
assert_eq!(ring, 7);
|
| 396 |
+
}
|
| 397 |
+
#[test]
|
| 398 |
+
fn unsupported_address_rejects_without_row_or_ring_changes() {
|
| 399 |
+
let row = FilterRecord {
|
| 400 |
+
mask: 7,
|
| 401 |
+
comparison: 8,
|
| 402 |
+
metadata: 9,
|
| 403 |
+
};
|
| 404 |
+
let mut rows = [row];
|
| 405 |
+
let mut ring = 7;
|
| 406 |
+
assert!(filter_duration(&mut rows, &[b'1'; 128], b"5", &[0; 9], None, &mut ring).is_err());
|
| 407 |
+
assert_eq!(rows, [row]);
|
| 408 |
+
assert_eq!(ring, 7);
|
| 409 |
+
}
|
| 410 |
+
}
|
docs/FINDINGS.md
CHANGED
|
@@ -1833,3 +1833,5 @@ RE-948: Thirty-two original252933 fixtures compose actual insertion/metadata/par
|
|
| 1833 |
RE-949: Rust matches32 original loader compositions, preserving active count, first-prior-match metadata routing, short-read buffer provenance, NUL/line handling and unconditional read/close effects. The staged API accepts explicit declared length, delivered bytes, initial buffer and raw handle. It rejects unsafe negative lengths, overlong lines/tokens and insufficient table storage atomically. Unit tests cover duplicates, missing-file replies, stale partial reads and rollback after an unsupported later line. [Evidence](research/RUST_FILTER_LOAD.md).
|
| 1834 |
|
| 1835 |
RE-950: Eighty actual252213 fixtures compose integer/time/metadata children, preserving wrapping duration-plus-current-minute arithmetic, exact first-match metadata writes and save requests. Empty arguments emit exact console or client usage without clock/update calls; client formatting advances and wraps the ring. Negative/overflowing duration prefixes are retained. Native clock/calendar semantics, sends and export execution remain explicit boundaries. [Evidence](research/QVM_FILTER_DURATION.md).
|
|
|
|
|
|
|
|
|
| 1833 |
RE-949: Rust matches32 original loader compositions, preserving active count, first-prior-match metadata routing, short-read buffer provenance, NUL/line handling and unconditional read/close effects. The staged API accepts explicit declared length, delivered bytes, initial buffer and raw handle. It rejects unsafe negative lengths, overlong lines/tokens and insufficient table storage atomically. Unit tests cover duplicates, missing-file replies, stale partial reads and rollback after an unsupported later line. [Evidence](research/RUST_FILTER_LOAD.md).
|
| 1834 |
|
| 1835 |
RE-950: Eighty actual252213 fixtures compose integer/time/metadata children, preserving wrapping duration-plus-current-minute arithmetic, exact first-match metadata writes and save requests. Empty arguments emit exact console or client usage without clock/update calls; client formatting advances and wraps the ring. Negative/overflowing duration prefixes are retained. Native clock/calendar semantics, sends and export execution remain explicit boundaries. [Evidence](research/QVM_FILTER_DURATION.md).
|
| 1836 |
+
|
| 1837 |
+
RE-951: Rust matches80 original duration/usage command fixtures, preserving wrapping numeric prefixes, first-match metadata save requests, exact client/console text and formatter-counter wrap. Typed effects preserve time-before-update order; usage skips both. Unit tests cover client-only ring changes, negative duration producing permanent-1 metadata, duplicate preservation and atomic unsupported-address rejection. Native replies remain supplied or captured. [Evidence](research/RUST_FILTER_DURATION.md).
|
docs/PROVISIONAL_TODO.md
CHANGED
|
@@ -1881,3 +1881,5 @@ RE-934–943 completed bounded filter export/removal/metadata and insertion/remo
|
|
| 1881 |
- [x] RE-949 Rust bounded loading with explicit read replies ([evidence](research/RUST_FILTER_LOAD.md)).
|
| 1882 |
|
| 1883 |
- [x] RE-950 Original expiration-duration command and usage paths ([evidence](research/QVM_FILTER_DURATION.md)).
|
|
|
|
|
|
|
|
|
| 1881 |
- [x] RE-949 Rust bounded loading with explicit read replies ([evidence](research/RUST_FILTER_LOAD.md)).
|
| 1882 |
|
| 1883 |
- [x] RE-950 Original expiration-duration command and usage paths ([evidence](research/QVM_FILTER_DURATION.md)).
|
| 1884 |
+
|
| 1885 |
+
- [x] RE-951 Rust expiration command with typed time and usage effects ([evidence](research/RUST_FILTER_DURATION.md)).
|
docs/RESEARCH_LEDGER.md
CHANGED
|
@@ -2467,3 +2467,7 @@ Rust matches32 original loader compositions, preserving active count, first-prio
|
|
| 2467 |
## 2026-10-28 — RE-950 Original expiration-duration command and usage paths
|
| 2468 |
|
| 2469 |
Eighty actual252213 fixtures compose integer/time/metadata children, preserving wrapping duration-plus-current-minute arithmetic, exact first-match metadata writes and save requests. Empty arguments emit exact console or client usage without clock/update calls; client formatting advances and wraps the ring. Negative/overflowing duration prefixes are retained. Native clock/calendar semantics, sends and export execution remain explicit boundaries. Focused `cargo xtask test-group qvm-filter-duration` passes with deterministic report regeneration, relevant regressions and storage/documentation/whitespace checks. Full suite deferred to the tenth-commit boundary. Original assets unchanged; native IO effects remain captured.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2467 |
## 2026-10-28 — RE-950 Original expiration-duration command and usage paths
|
| 2468 |
|
| 2469 |
Eighty actual252213 fixtures compose integer/time/metadata children, preserving wrapping duration-plus-current-minute arithmetic, exact first-match metadata writes and save requests. Empty arguments emit exact console or client usage without clock/update calls; client formatting advances and wraps the ring. Negative/overflowing duration prefixes are retained. Native clock/calendar semantics, sends and export execution remain explicit boundaries. Focused `cargo xtask test-group qvm-filter-duration` passes with deterministic report regeneration, relevant regressions and storage/documentation/whitespace checks. Full suite deferred to the tenth-commit boundary. Original assets unchanged; native IO effects remain captured.
|
| 2470 |
+
|
| 2471 |
+
## 2026-10-28 — RE-951 Rust expiration command with typed time and usage effects
|
| 2472 |
+
|
| 2473 |
+
Rust matches80 original duration/usage command fixtures, preserving wrapping numeric prefixes, first-match metadata save requests, exact client/console text and formatter-counter wrap. Typed effects preserve time-before-update order; usage skips both. Unit tests cover client-only ring changes, negative duration producing permanent-1 metadata, duplicate preservation and atomic unsupported-address rejection. Native replies remain supplied or captured. Focused `cargo xtask test-group rust-filter-duration` passes with deterministic report regeneration, relevant regressions and storage/documentation/whitespace checks. Full suite deferred to the tenth-commit boundary. Original assets unchanged; native IO effects remain captured.
|
docs/REVERSE_ENGINEERING_TODO.md
CHANGED
|
@@ -1918,3 +1918,5 @@ RE-934–943 continue the prior batch with filter export/removal/metadata and co
|
|
| 1918 |
- [x] RE-949 Rust bounded loading with explicit read replies ([evidence](research/RUST_FILTER_LOAD.md)).
|
| 1919 |
|
| 1920 |
- [x] RE-950 Original expiration-duration command and usage paths ([evidence](research/QVM_FILTER_DURATION.md)).
|
|
|
|
|
|
|
|
|
| 1918 |
- [x] RE-949 Rust bounded loading with explicit read replies ([evidence](research/RUST_FILTER_LOAD.md)).
|
| 1919 |
|
| 1920 |
- [x] RE-950 Original expiration-duration command and usage paths ([evidence](research/QVM_FILTER_DURATION.md)).
|
| 1921 |
+
|
| 1922 |
+
- [x] RE-951 Rust expiration command with typed time and usage effects ([evidence](research/RUST_FILTER_DURATION.md)).
|
docs/research/README.md
CHANGED
|
@@ -1453,3 +1453,5 @@ The extension collector adds five focused tests for native tail calls, command-r
|
|
| 1453 |
- [RE-949: Rust bounded loading with explicit read replies](RUST_FILTER_LOAD.md).
|
| 1454 |
|
| 1455 |
- [RE-950: Original expiration-duration command and usage paths](QVM_FILTER_DURATION.md).
|
|
|
|
|
|
|
|
|
| 1453 |
- [RE-949: Rust bounded loading with explicit read replies](RUST_FILTER_LOAD.md).
|
| 1454 |
|
| 1455 |
- [RE-950: Original expiration-duration command and usage paths](QVM_FILTER_DURATION.md).
|
| 1456 |
+
|
| 1457 |
+
- [RE-951: Rust expiration command with typed time and usage effects](RUST_FILTER_DURATION.md).
|
docs/research/RUST_FILTER_DURATION.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Rust expiration command with typed time and usage effects
|
| 2 |
+
|
| 3 |
+
RE-951. Focused validation: `cargo xtask test-group rust-filter-duration`. [Machine report](rust-filter-duration.json).
|
| 4 |
+
|
| 5 |
+
Rust matches80 original duration/usage command fixtures, preserving wrapping numeric prefixes, first-match metadata save requests, exact client/console text and formatter-counter wrap. Typed effects preserve time-before-update order; usage skips both. Unit tests cover client-only ring changes, negative duration producing permanent-1 metadata, duplicate preservation and atomic unsupported-address rejection. Native replies remain supplied or captured.
|
docs/research/rust-filter-duration.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
docs/research/rust-health-impulse.json
CHANGED
|
@@ -2985,7 +2985,7 @@
|
|
| 2985 |
"exit_code": 2
|
| 2986 |
}
|
| 2987 |
],
|
| 2988 |
-
"rust_unit_tests":
|
| 2989 |
"source_report": "docs/research/qvm-health-impulse.json",
|
| 2990 |
"source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
|
| 2991 |
}
|
|
|
|
| 2985 |
"exit_code": 2
|
| 2986 |
}
|
| 2987 |
],
|
| 2988 |
+
"rust_unit_tests": 206,
|
| 2989 |
"source_report": "docs/research/qvm-health-impulse.json",
|
| 2990 |
"source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
|
| 2991 |
}
|
scripts/analyze_rust_filter_duration.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Compare recovered Rust policy with original QVM fixtures."""
|
| 2 |
+
import hashlib,json,os,subprocess
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from analyze_qvm import write_json
|
| 5 |
+
from rust_lifecycle_support import result
|
| 6 |
+
|
| 7 |
+
def main():
|
| 8 |
+
subprocess.run(['cargo','build','--offline','--package','urt-gameplay','--bin','urt-filter-loading'],check=True,capture_output=True)
|
| 9 |
+
binary=Path(os.environ.get('CARGO_TARGET_DIR','target'))/'debug'/'urt-filter-loading'
|
| 10 |
+
path=Path('docs/research/qvm-filter-duration.json');data=path.read_bytes();probes=[]
|
| 11 |
+
for p in json.loads(data)['probes']:
|
| 12 |
+
probes.append(dict(source={k:p[k] for k in ['events', 'updates', 'ring', 'rows']},rust=result(binary,'duration',encode(p['input']))))
|
| 13 |
+
write_json(Path('docs/research/rust-filter-duration.json'),dict(source_report=str(path),source_report_sha256=hashlib.sha256(data).hexdigest(),probes=probes,limits='Finite source/Rust contract parity; native time/file/network replies remain supplied or captured.'))
|
| 14 |
+
|
| 15 |
+
def encode(a):
|
| 16 |
+
ip=bytes.fromhex(a['address']);d=bytes.fromhex(a['duration']);return [a['now'],a['client'],a['ring'],len(ip),*ip,len(d),*d]
|
| 17 |
+
if __name__=='__main__':main()
|
scripts/test_rust_filter_duration.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import hashlib,json,unittest
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
class PortTests(unittest.TestCase):
|
| 4 |
+
def test_original_parity(self):
|
| 5 |
+
r=json.loads(Path('docs/research/rust-filter-duration.json').read_text());self.assertGreaterEqual(len(r['probes']),12)
|
| 6 |
+
for p in r['probes']:self.assertEqual(p['source'],p['rust'])
|
| 7 |
+
def test_identity(self):
|
| 8 |
+
r=json.loads(Path('docs/research/rust-filter-duration.json').read_text());self.assertEqual(hashlib.sha256(Path(r['source_report']).read_bytes()).hexdigest(),r['source_report_sha256'])
|
| 9 |
+
if __name__=='__main__':unittest.main()
|
xtask/checks.tsv
CHANGED
|
@@ -3268,3 +3268,19 @@ report scripts/analyze_qvm_filter_duration.py docs/research/qvm-filter-duration.
|
|
| 3268 |
edge docs/research/qvm-filter-duration.json scripts/test_qvm_filter_duration.py
|
| 3269 |
group qvm-filter-duration scripts/analyze_qvm_filter_duration.py
|
| 3270 |
edge scripts/analyze_qvm_filter_duration.py scripts/analyze_qvm_resumer_audit.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3268 |
edge docs/research/qvm-filter-duration.json scripts/test_qvm_filter_duration.py
|
| 3269 |
group qvm-filter-duration scripts/analyze_qvm_filter_duration.py
|
| 3270 |
edge scripts/analyze_qvm_filter_duration.py scripts/analyze_qvm_resumer_audit.py
|
| 3271 |
+
report scripts/analyze_rust_filter_duration.py docs/research/rust-filter-duration.json
|
| 3272 |
+
edge docs/research/rust-filter-duration.json scripts/test_rust_filter_duration.py
|
| 3273 |
+
group rust-filter-duration scripts/analyze_rust_filter_duration.py
|
| 3274 |
+
edge scripts/analyze_rust_filter_duration.py scripts/analyze_qvm_resumer_audit.py
|
| 3275 |
+
edge Cargo.toml scripts/analyze_rust_filter_duration.py
|
| 3276 |
+
edge Cargo.lock scripts/analyze_rust_filter_duration.py
|
| 3277 |
+
edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_filter_duration.py
|
| 3278 |
+
edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_filter_duration.py
|
| 3279 |
+
edge crates/urt-gameplay/src/filter_loading.rs scripts/analyze_rust_filter_duration.py
|
| 3280 |
+
edge crates/urt-gameplay/src/filter_lifecycle.rs scripts/analyze_rust_filter_duration.py
|
| 3281 |
+
edge crates/urt-gameplay/src/native_policy.rs scripts/analyze_rust_filter_duration.py
|
| 3282 |
+
edge crates/urt-gameplay/src/bin/urt-filter-loading.rs scripts/analyze_rust_filter_duration.py
|
| 3283 |
+
edge crates/urt-gameplay/src/qvm_format.rs scripts/analyze_rust_filter_duration.py
|
| 3284 |
+
edge docs/research/qvm-filter-duration.json scripts/analyze_rust_filter_duration.py
|
| 3285 |
+
group rust-filter-duration scripts/analyze_rust_health_impulse.py
|
| 3286 |
+
group gameplay scripts/analyze_rust_filter_duration.py
|