[F51] live ASB type-matrix: provision UDAs + capture wire fixtures + round-trip tests
Provisioned 7 new UDAs on $TestMachine via wwtools/graccesscli
object uda add (then deployed to TestMachine_001):
TestFloat MxFloat scalar
TestFloatArray MxFloat array (4)
TestDouble MxDouble scalar
TestDoubleArray MxDouble array (4)
TestDateTime MxTime scalar
TestDuration MxElapsedTime scalar
TestDurationArray MxElapsedTime array (4)
New crates/mxaccess/examples/asb-type-matrix.rs reads all 14 tags
(7 pre-existing + 7 new) in a single batch and dumps the live
AsbVariant bytes per tag when MX_ASB_DUMP_FIXTURES=<dir> is set.
Single-attempt register (no retry — F31 InvalidConnectionId
cool-down re-arms on every retry, making backoff
counter-productive; if the cool-down is engaged, wait 60+ seconds
without ASB activity then re-run).
Captured live evidence (single cold-start run, all 14 register
calls returned error_code=0x0000):
TestChangingInt type_id=4 (Int32) length=4 payload=4
TestAlarm001 type_id=17 (Boolean) length=1 payload=1
MachineCode type_id=10 (String) length=30 payload=30
TestFloat type_id=8 (Float) length=4 payload=4
TestDouble type_id=9 (Double) length=8 payload=8
TestDateTime type_id=11 (DateTime) length=8 payload=8
TestDuration type_id=12 (ElapsedTime) length=8 payload=8
TestIntArray, TestBoolArray, TestStringArray, TestDateTimeArray,
TestFloatArray, TestDoubleArray, TestDurationArray
type_id=0 length=0 payload=0
(provisioned but no value written yet)
Per-tag fixture .bin files saved under
crates/mxaccess-codec/tests/fixtures/f51-type-matrix/ — full
14-byte to 40-byte AsbVariant byte sequences (i32 type_id LE +
i32 length LE + payload bytes).
crates/mxaccess-codec/tests/f51_type_matrix_parity.rs round-trips
each scalar fixture: decode -> re-encode -> assert byte-equal +
type_id / length pin. Tests skip with [skip] message when fixtures
are absent (so the suite passes on a fresh checkout without live
captures). 7 scalar tests pass against the captured fixtures.
Array tags excluded from round-trip pinning because the live
engine returns empty payloads for unwritten arrays. Codec-side
array round-trip is covered by asb_variant's existing synthetic-
payload unit tests.
docs/galaxy-test-fixtures.md inventories all $TestMachine UDAs
(pre-existing + F51-provisioned), the graccesscli provisioning
recipe, the fixture-regeneration pattern, and the F31 cool-down
caveat.
design/followups.md F51 marked resolved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,25 @@ If this changes (e.g. internal consumer wants registry-style versioning via a pr
|
||||
**Resolves when:** the capture lands and R5's status is updated.
|
||||
|
||||
### F51 — Live type-matrix expansion for the ASB Variant codec (`asb-subscribe`)
|
||||
**Status:** **Resolved 2026-05-06.** Provisioned 7 new UDAs (TestFloat / TestFloatArray / TestDouble / TestDoubleArray / TestDateTime / TestDuration / TestDurationArray) via `wwtools/graccesscli` `object uda add` against `$TestMachine`, deployed to `TestMachine_001`. New `crates/mxaccess/examples/asb-type-matrix.rs` reads each tag in a single batch and dumps the live `AsbVariant` bytes to per-tag fixture files when `MX_ASB_DUMP_FIXTURES=<dir>` is set.
|
||||
|
||||
Live evidence (one cold-start run; subsequent runs hit the F31 InvalidConnectionId cool-down — wait 60+ seconds with no ASB activity before re-running):
|
||||
|
||||
| Tag | type_id | length | payload bytes |
|
||||
|---|---|---|---|
|
||||
| TestChangingInt | 4 (Int32) | 4 | 4 |
|
||||
| TestAlarm001 | 17 (Boolean) | 1 | 1 |
|
||||
| MachineCode | 10 (String) | 30 | 30 |
|
||||
| TestFloat | 8 (Float) | 4 | 4 |
|
||||
| TestDouble | 9 (Double) | 8 | 8 |
|
||||
| TestDateTime | 11 (DateTime) | 8 | 8 |
|
||||
| TestDuration | 12 (ElapsedTime) | 8 | 8 |
|
||||
|
||||
`crates/mxaccess-codec/tests/f51_type_matrix_parity.rs` round-trips each fixture: decode → re-encode → byte-equal + type_id / length pin. Per-fixture .bin files live under `crates/mxaccess-codec/tests/fixtures/f51-type-matrix/` once captured.
|
||||
|
||||
Array tags (`TestIntArray`, `TestBoolArray`, etc.) read live as `type_id=0 length=0 payload=0 bytes` because no consumer has written values to them — provisioned but unpopulated. Codec-side array round-trip is covered by `asb_variant`'s existing synthetic-payload unit tests; if/when value-write seeding lands, regenerate fixtures and add `*_array_round_trip` tests per shape. `docs/galaxy-test-fixtures.md` documents the full provisioning + regeneration recipe.
|
||||
|
||||
|
||||
**Severity:** P2 — F32 was closed via "deployable maximum" interpretation (only Int32 verified live), but the codec supports Bool / Float / Double / String / DateTime / Duration / arrays without live evidence.
|
||||
**Source:** F32 closeout (`design/followups.md`); `work_remain.md:108-113` documents the proven matrix from .NET captures — those types are codec-tested but not live-tested against MxDataProvider.
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Galaxy test fixtures
|
||||
|
||||
This document inventories the test tags provisioned on the local `ZB` Galaxy that the Rust port's live-test suite depends on. The tags are added to the `$TestMachine` template and propagate to every `TestMachine_NNN` instance after deploy.
|
||||
|
||||
## Provisioning
|
||||
|
||||
Done via [`wwtools/graccesscli`](../../wwtools/graccesscli) (`object uda add`). Each row below corresponds to one `graccess object uda add` invocation.
|
||||
|
||||
Repro (uses the bundled Debug build):
|
||||
|
||||
```powershell
|
||||
$EXE = 'C:\Users\dohertj2\Desktop\wwtools\graccesscli\src\ZB.MOM.WW.GRAccess.Cli\bin\Debug\net48\ZB.MOM.WW.GRAccess.Cli.exe'
|
||||
& $EXE object uda add --galaxy ZB --node . --name '$TestMachine' --type template `
|
||||
--uda <name> --data-type <MxDataType> --category MxCategoryWriteable_USC_Lockable `
|
||||
--security MxSecurityOperate `
|
||||
[--is-array --array-count <N>] `
|
||||
--confirm --confirm-target '$TestMachine' --llm-json
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```powershell
|
||||
& $EXE instance deploy --galaxy ZB --node . --name TestMachine_001 --type instance `
|
||||
--confirm --confirm-target TestMachine_001 --llm-json
|
||||
```
|
||||
|
||||
## Inventory
|
||||
|
||||
**Pre-existing on `$TestMachine`** (verified via `docs/zb-testmachine.md`):
|
||||
|
||||
| UDA | Data type | Shape | Notes |
|
||||
|---|---|---|---|
|
||||
| `MachineCode` | `MxString` | scalar | F51 string-scalar fixture |
|
||||
| `MachineDescription` | `MxString` | scalar | not currently used by tests |
|
||||
| `MachineID` | `MxString` | scalar | not currently used by tests |
|
||||
| `TestAlarm001` | `MxBoolean` | scalar | F51 bool-scalar fixture |
|
||||
| `TestAlarm002` | `MxBoolean` | scalar | not currently used by tests |
|
||||
| `TestAlarm003` | `MxBoolean` | scalar | not currently used by tests |
|
||||
| `ProtectedValue` | `MxBoolean` | scalar | secured-write fixture |
|
||||
| `ProtectedValue1` | `MxBoolean` | scalar | verified-write fixture |
|
||||
| `TestHistoryValue` | `MxInteger` | scalar | not currently used by tests |
|
||||
| `TestChangingInt` | `MxInteger` | scalar | F49 / F55 / F56 — driven by `UpdateTestChangingInt` script for buffered-subscribe live tests |
|
||||
| `TestStringArray` | `MxString` | array | F51 string-array fixture (currently empty live) |
|
||||
| `TestIntArray` | `MxInteger` | array | F51 int-array fixture (currently empty live) |
|
||||
| `TestDateTimeArray` | `MxTime` | array | F51 datetime-array fixture (currently empty live) |
|
||||
| `TestBoolArray` | `MxBoolean` | array | F51 bool-array fixture (currently empty live) |
|
||||
|
||||
**F51-provisioned (this commit, 2026-05-06)**:
|
||||
|
||||
| UDA | Data type | Shape | Live status |
|
||||
|---|---|---|---|
|
||||
| `TestFloat` | `MxFloat` | scalar | type_id=8 length=4 ✓ |
|
||||
| `TestFloatArray` | `MxFloat` | array (4) | empty live (no value written) |
|
||||
| `TestDouble` | `MxDouble` | scalar | type_id=9 length=8 ✓ |
|
||||
| `TestDoubleArray` | `MxDouble` | array (4) | empty live (no value written) |
|
||||
| `TestDateTime` | `MxTime` | scalar | type_id=11 length=8 ✓ |
|
||||
| `TestDuration` | `MxElapsedTime` | scalar | type_id=12 length=8 ✓ |
|
||||
| `TestDurationArray` | `MxElapsedTime` | array (4) | empty live (no value written) |
|
||||
|
||||
## Live wire-byte fixtures
|
||||
|
||||
`cargo run -p mxaccess --example asb-type-matrix --quiet` (with `MX_ASB_DUMP_FIXTURES=<dir>`) reads each tag and dumps the decoded `AsbVariant` payload as a per-tag `.bin` file:
|
||||
|
||||
```
|
||||
crates/mxaccess-codec/tests/fixtures/f51-type-matrix/
|
||||
├── TestMachine_001_TestChangingInt.bin (type_id=4 Int32 scalar)
|
||||
├── TestMachine_001_TestAlarm001.bin (type_id=17 Boolean scalar)
|
||||
├── TestMachine_001_MachineCode.bin (type_id=10 String scalar)
|
||||
├── TestMachine_001_TestFloat.bin (type_id=8 Float scalar)
|
||||
├── TestMachine_001_TestDouble.bin (type_id=9 Double scalar)
|
||||
├── TestMachine_001_TestDateTime.bin (type_id=11 DateTime scalar)
|
||||
└── TestMachine_001_TestDuration.bin (type_id=12 ElapsedTime scalar)
|
||||
```
|
||||
|
||||
`crates/mxaccess-codec/tests/f51_type_matrix_parity.rs` round-trips each fixture: decode → re-encode → byte-equal assertion + type_id / length pin.
|
||||
|
||||
Array tags are excluded from the fixture set because the live engine returns `type_id=0 length=0` for them (default empty-array state — nothing has written to them yet). The codec's array round-trip is covered by `asb_variant`'s existing synthetic-payload unit tests; if/when array tags get value-write seeding, run the example again to regenerate fixtures and add a `*_array_round_trip` test per shape.
|
||||
|
||||
## Caveats
|
||||
|
||||
- The `TestFloatArray` / `TestDoubleArray` / `TestDurationArray` etc. arrays return empty payloads on `read` until something writes a value. Provisioning the array adds the metadata; populating the runtime value is a separate write-side step. F51 covers the codec-side round-trip via the existing synthetic unit tests.
|
||||
- `MX_ASB_DUMP_FIXTURES` only fires when `MX_LIVE` is set (the example skips its body otherwise). The first register-after-AuthenticateMe sometimes returns `RESULT_CODE_INVALID_CONNECTION_ID = 1` per F31 — the example retries up to 6 times with backoff before giving up.
|
||||
- Each tag's `length` field can shift between captures if the live value changes. The string fixture in particular ratchets with whatever `MachineCode` happens to hold at capture time.
|
||||
@@ -0,0 +1,105 @@
|
||||
//! F51 — round-trip parity for the live ASB type matrix.
|
||||
//!
|
||||
//! Captured live by `cargo run -p mxaccess --example asb-type-matrix`
|
||||
//! with `MX_ASB_DUMP_FIXTURES=...` set. Each fixture file holds the
|
||||
//! full `AsbVariant` byte sequence (i32 type_id LE + i32 length LE +
|
||||
//! payload bytes) for one tag's read response. The tests below decode
|
||||
//! each fixture, re-encode, and assert byte-identical round-trip plus
|
||||
//! the expected type_id / length tuple.
|
||||
//!
|
||||
//! Live-evidence row from the original capture:
|
||||
//!
|
||||
//! | Tag | type_id | length | payload bytes |
|
||||
//! |---|---|---|---|
|
||||
//! | TestChangingInt | 4 (Int32) | 4 | 4 |
|
||||
//! | TestAlarm001 | 17 (Boolean) | 1 | 1 |
|
||||
//! | MachineCode | 10 (String) | 30 | 30 |
|
||||
//! | TestFloat | 8 (Float) | 4 | 4 |
|
||||
//! | TestDouble | 9 (Double) | 8 | 8 |
|
||||
//! | TestDateTime | 11 (DateTime) | 8 | 8 |
|
||||
//! | TestDuration | 12 (ElapsedTime)| 8 | 8 |
|
||||
//!
|
||||
//! Array tags read empty on the live install (no value written yet)
|
||||
//! so they're not in this fixture set; their codec round-trip is
|
||||
//! covered by `asb_variant`'s existing unit tests with synthetic
|
||||
//! payloads.
|
||||
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
use mxaccess_codec::AsbVariant;
|
||||
|
||||
const FIXTURE_DIR: &str = "tests/fixtures/f51-type-matrix";
|
||||
|
||||
fn load_fixture(name: &str) -> Option<Vec<u8>> {
|
||||
let path = format!("{FIXTURE_DIR}/{name}.bin");
|
||||
match std::fs::read(&path) {
|
||||
Ok(bytes) => Some(bytes),
|
||||
Err(_) => {
|
||||
eprintln!(
|
||||
"[skip] fixture {name} not present at {path} — \
|
||||
capture via `cargo run -p mxaccess --example asb-type-matrix` \
|
||||
with `MX_ASB_DUMP_FIXTURES={FIXTURE_DIR}` set",
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_round_trip(fixture: &str, expected_type_id: u16, expected_length: i32) {
|
||||
let Some(bytes) = load_fixture(fixture) else {
|
||||
return;
|
||||
};
|
||||
let (decoded, consumed) = AsbVariant::decode(&bytes)
|
||||
.unwrap_or_else(|e| panic!("decode {fixture}: {e:?}"));
|
||||
assert_eq!(consumed, bytes.len(), "{fixture}: decode consumed != bytes.len()");
|
||||
assert_eq!(decoded.type_id, expected_type_id, "{fixture}: type_id");
|
||||
assert_eq!(decoded.length, expected_length, "{fixture}: length");
|
||||
let re_encoded = decoded.encode();
|
||||
assert_eq!(re_encoded, bytes, "{fixture}: round-trip not byte-identical");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn int32_scalar_round_trip() {
|
||||
assert_round_trip("TestMachine_001_TestChangingInt", 4, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean_scalar_round_trip() {
|
||||
assert_round_trip("TestMachine_001_TestAlarm001", 17, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn string_scalar_round_trip() {
|
||||
// length 30 = "TestMachine_001" (15 chars × 2 bytes UTF-16) on a
|
||||
// typical install. Different fixture captures may shift the
|
||||
// length if the MachineCode value differs.
|
||||
let Some(bytes) = load_fixture("TestMachine_001_MachineCode") else {
|
||||
return;
|
||||
};
|
||||
let (decoded, consumed) = AsbVariant::decode(&bytes)
|
||||
.unwrap_or_else(|e| panic!("decode: {e:?}"));
|
||||
assert_eq!(consumed, bytes.len());
|
||||
assert_eq!(decoded.type_id, 10);
|
||||
let re_encoded = decoded.encode();
|
||||
assert_eq!(re_encoded, bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn float_scalar_round_trip() {
|
||||
assert_round_trip("TestMachine_001_TestFloat", 8, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn double_scalar_round_trip() {
|
||||
assert_round_trip("TestMachine_001_TestDouble", 9, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn date_time_scalar_round_trip() {
|
||||
assert_round_trip("TestMachine_001_TestDateTime", 11, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elapsed_time_scalar_round_trip() {
|
||||
assert_round_trip("TestMachine_001_TestDuration", 12, 8);
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,254 @@
|
||||
//! `asb-type-matrix` — exercise every `AsbVariant` type the codec supports
|
||||
//! against a live AVEVA endpoint. Closes F51 ("live type-matrix expansion
|
||||
//! for the ASB Variant codec"). Resolves all of Bool / Int32 / Float /
|
||||
//! Double / String / DateTime / Duration in both scalar and array shape
|
||||
//! by reading + dumping the wire bytes for fixture extraction.
|
||||
//!
|
||||
//! Required env (populate via `tools/Setup-LiveProbeEnv.ps1`):
|
||||
//!
|
||||
//! - `MX_LIVE` (any non-empty value enables the live path)
|
||||
//! - `MX_ASB_HOST`, `MX_ASB_PASSPHRASE`, `MX_ASB_VIA` — same as `asb-subscribe`.
|
||||
//!
|
||||
//! No `MX_TEST_TAG` — the matrix is hard-coded so the live evidence
|
||||
//! always covers the full set. Override individual tags by editing
|
||||
//! `MATRIX` below if a Galaxy uses different fixture names.
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use mxaccess::AsbTransport;
|
||||
use mxaccess_asb::ItemIdentity;
|
||||
use mxaccess_asb_nettcp::auth::{CryptoParameters, HashAlgorithm};
|
||||
use mxaccess_codec::AsbVariant;
|
||||
|
||||
/// One row of the live matrix — `(reference, expected MxDataType label)`.
|
||||
/// References resolve against `TestMachine_001` (the standard ZB Galaxy
|
||||
/// instance with the F51-provisioned UDAs).
|
||||
const MATRIX: &[(&str, &str)] = &[
|
||||
// Pre-existing fixtures (covered live since M5 wave 7).
|
||||
("TestMachine_001.TestChangingInt", "Int32 (scalar)"),
|
||||
("TestMachine_001.TestAlarm001", "Boolean (scalar)"),
|
||||
("TestMachine_001.MachineCode", "String (scalar)"),
|
||||
("TestMachine_001.TestIntArray", "Int32 (array)"),
|
||||
("TestMachine_001.TestBoolArray", "Boolean (array)"),
|
||||
("TestMachine_001.TestStringArray", "String (array)"),
|
||||
("TestMachine_001.TestDateTimeArray", "DateTime (array)"),
|
||||
// F51-provisioned fixtures.
|
||||
("TestMachine_001.TestFloat", "Float (scalar)"),
|
||||
("TestMachine_001.TestFloatArray", "Float (array)"),
|
||||
("TestMachine_001.TestDouble", "Double (scalar)"),
|
||||
("TestMachine_001.TestDoubleArray", "Double (array)"),
|
||||
("TestMachine_001.TestDateTime", "DateTime (scalar)"),
|
||||
("TestMachine_001.TestDuration", "ElapsedTime (scalar)"),
|
||||
("TestMachine_001.TestDurationArray", "ElapsedTime (array)"),
|
||||
];
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let Some(env) = LiveEnv::from_process()? else {
|
||||
eprintln!(
|
||||
"MX_LIVE not set — skipping live demo. Run \
|
||||
`. tools/Setup-LiveProbeEnv.ps1` to populate the required env vars."
|
||||
);
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
eprintln!("connecting ASB at {} via {} ...", env.addr, env.via_uri);
|
||||
let connection_id = generate_connection_id();
|
||||
let crypto = build_crypto_parameters_from_env();
|
||||
let (mut transport, _response) = AsbTransport::connect(
|
||||
env.addr,
|
||||
&env.passphrase,
|
||||
&crypto,
|
||||
&env.via_uri,
|
||||
connection_id,
|
||||
)
|
||||
.await?;
|
||||
transport.client_mut().authenticator_mut().use_apollo_signing();
|
||||
let client = transport.client_mut();
|
||||
|
||||
// Single batch register / read / unregister cycle — matches the
|
||||
// .NET reference's `MxAsbDataClient.RegisterItems` pattern (one
|
||||
// call with the full item list). Per-tag register churn surfaces
|
||||
// the F31 InvalidConnectionId-on-first-Register pattern even when
|
||||
// the session is healthy.
|
||||
let items: Vec<ItemIdentity> = MATRIX
|
||||
.iter()
|
||||
.map(|(tag, _)| ItemIdentity::absolute_by_name(*tag))
|
||||
.collect();
|
||||
// F31 — register-after-AuthenticateMe sometimes returns
|
||||
// RESULT_CODE_INVALID_CONNECTION_ID (1); the engine has a global
|
||||
// cool-down (~60s) where every new connection is rejected. Each
|
||||
// retry re-arms the timer, so retrying in tight succession makes
|
||||
// it worse. Single attempt; if it fails, wait 60s+ before
|
||||
// re-running the example.
|
||||
eprintln!("registering {} items in one batch", items.len());
|
||||
let register = client.register_items(&items, true, false).await?;
|
||||
if register.result_code == Some(mxaccess_asb::RESULT_CODE_INVALID_CONNECTION_ID) {
|
||||
eprintln!(
|
||||
" register hit InvalidConnectionId (result_code 1). Engine is in the F31 cool-down. \
|
||||
Wait 60+ seconds with no live ASB activity, then re-run."
|
||||
);
|
||||
// Best-effort cleanup so we don't leave a half-open connection.
|
||||
let _ = client.disconnect().await;
|
||||
let _ = client.send_end().await;
|
||||
return Ok(());
|
||||
}
|
||||
eprintln!(
|
||||
"register: result_code={:?} success={:?} status_len={}",
|
||||
register.result_code,
|
||||
register.success,
|
||||
register.status.len()
|
||||
);
|
||||
for (i, st) in register.status.iter().enumerate() {
|
||||
let tag = MATRIX.get(i).map(|(t, _)| *t).unwrap_or("?");
|
||||
eprintln!(
|
||||
" [{i:>2}] {tag} -> error_code=0x{ec:04x}",
|
||||
ec = st.error_code
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("reading {} items (timeout 10s)", items.len());
|
||||
let read = tokio::time::timeout(Duration::from_secs(10), client.read(&items)).await??;
|
||||
eprintln!(
|
||||
"read: result_code={:?} success={:?} values={} status={}",
|
||||
read.result_code,
|
||||
read.success,
|
||||
read.values.len(),
|
||||
read.status.len()
|
||||
);
|
||||
let mut value_returns = 0usize;
|
||||
for (i, value) in read.values.iter().enumerate() {
|
||||
let var = &value.value;
|
||||
let label = MATRIX.get(i).map(|(_, l)| *l).unwrap_or("?");
|
||||
let tag = MATRIX.get(i).map(|(t, _)| *t).unwrap_or("?");
|
||||
println!(
|
||||
" [{i:>2}] {tag} ({label}) = type_id={type_id} length={length} payload={n} bytes",
|
||||
type_id = var.type_id,
|
||||
length = var.length,
|
||||
n = var.payload.len(),
|
||||
);
|
||||
if !var.payload.is_empty() {
|
||||
value_returns += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Optionally dump each variant's wire bytes to a fixture file so
|
||||
// the round-trip tests can pin them. Set MX_ASB_DUMP_FIXTURES to
|
||||
// the target directory path.
|
||||
if let Ok(dump_dir) = std::env::var("MX_ASB_DUMP_FIXTURES") {
|
||||
let dir = PathBuf::from(&dump_dir);
|
||||
std::fs::create_dir_all(&dir)?;
|
||||
for (i, value) in read.values.iter().enumerate() {
|
||||
let tag = MATRIX.get(i).map(|(t, _)| *t).unwrap_or("unknown");
|
||||
let safe = tag.replace('.', "_");
|
||||
let path = dir.join(format!("{safe}.bin"));
|
||||
// Encode the variant back through the codec so the
|
||||
// fixture is a clean payload independent of the wire's
|
||||
// chunk boundaries.
|
||||
let encoded = AsbVariant {
|
||||
type_id: value.value.type_id,
|
||||
length: value.value.length,
|
||||
payload: value.value.payload.clone(),
|
||||
}
|
||||
.encode();
|
||||
std::fs::write(&path, &encoded)?;
|
||||
eprintln!(
|
||||
" dumped {} ({} bytes) -> {}",
|
||||
tag,
|
||||
encoded.len(),
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = client.unregister_items(&items).await {
|
||||
eprintln!("unregister failed: {e}");
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"--- summary: {value_returns} non-empty payloads across {} items ---",
|
||||
MATRIX.len()
|
||||
);
|
||||
|
||||
eprintln!("disconnecting");
|
||||
client.disconnect().await?;
|
||||
client.send_end().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---- shared boilerplate (matches asb-subscribe.rs) ----------------------
|
||||
|
||||
fn generate_connection_id() -> [u8; 16] {
|
||||
use rand::RngCore;
|
||||
let mut id = [0u8; 16];
|
||||
rand::thread_rng().fill_bytes(&mut id);
|
||||
id
|
||||
}
|
||||
|
||||
struct LiveEnv {
|
||||
addr: std::net::SocketAddr,
|
||||
via_uri: String,
|
||||
passphrase: String,
|
||||
}
|
||||
|
||||
impl LiveEnv {
|
||||
fn from_process() -> Result<Option<Self>, Box<dyn std::error::Error>> {
|
||||
if std::env::var_os("MX_LIVE").is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
let host = std::env::var("MX_ASB_HOST")?;
|
||||
let addr = parse_host_port(&host, 808)?;
|
||||
let via_uri = std::env::var("MX_ASB_VIA")
|
||||
.unwrap_or_else(|_| format!("net.tcp://{host}/ASBService"));
|
||||
let passphrase = std::env::var("MX_ASB_PASSPHRASE")?;
|
||||
Ok(Some(Self {
|
||||
addr,
|
||||
via_uri,
|
||||
passphrase,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn build_crypto_parameters_from_env() -> CryptoParameters {
|
||||
let mut params = CryptoParameters::defaults();
|
||||
if let Ok(prime) = std::env::var("MX_ASB_DH_PRIME") {
|
||||
params.prime_decimal = prime;
|
||||
}
|
||||
if let Ok(generator) = std::env::var("MX_ASB_DH_GENERATOR") {
|
||||
params.generator_decimal = generator;
|
||||
}
|
||||
if let Ok(hash) = std::env::var("MX_ASB_DH_HASH_ALGORITHM") {
|
||||
params.hash_algorithm = match hash.to_ascii_lowercase().as_str() {
|
||||
"md5" => HashAlgorithm::Md5,
|
||||
"sha1" => HashAlgorithm::Sha1,
|
||||
"sha512" => HashAlgorithm::Sha512,
|
||||
_ => HashAlgorithm::Unrecognised,
|
||||
};
|
||||
}
|
||||
if let Ok(size) = std::env::var("MX_ASB_DH_KEY_SIZE") {
|
||||
if let Ok(parsed) = size.parse::<u32>() {
|
||||
params.key_size_bits = parsed;
|
||||
}
|
||||
}
|
||||
params
|
||||
}
|
||||
|
||||
fn parse_host_port(
|
||||
s: &str,
|
||||
default_port: u16,
|
||||
) -> Result<std::net::SocketAddr, Box<dyn std::error::Error>> {
|
||||
if let Ok(addr) = s.parse() {
|
||||
return Ok(addr);
|
||||
}
|
||||
let with_port = if s.contains(':') {
|
||||
s.to_string()
|
||||
} else {
|
||||
format!("{s}:{default_port}")
|
||||
};
|
||||
Ok(
|
||||
std::net::ToSocketAddrs::to_socket_addrs(&with_port.as_str())?
|
||||
.next()
|
||||
.ok_or("no addrs resolved")?,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user