[M5] mxaccess-asb: F28 wire-format fixes — AuthenticateMe accepted live
Three wire-level bugs surfaced by side-by-side relay capture against
the .NET probe routed via the new --via flag:
1. **Dynamic-dictionary id drift**. Our `encode_envelope` hardcoded
action_dict_id=1 / to_dict_id=3, which is correct for the FIRST
message in a session but wrong for every subsequent one. The
per-session dynamic dict accumulates across messages: Connect's
binary header pre-pops [action,to] at ids 1,3; AuthenticateMe must
reference the new action at id 5 (continuing the odd sequence) and
the To URL at id 3 (still in the dict from Connect). Fix uses
`DynamicDictionary::position_of` + `intern` to compute the right
wire id, only pre-popping strings that are NEW to the session.
Captured against .NET probe via asb-relay: AuthenticateMe binary
header has only one string (action) at offset 0x260 (`06 de 08 2f
2e ...`), and `<a:Action>` value `ab 05` references the new id 5.
2. **ConnectionValidator wire format depends on operation**. .NET's
`IAsbDataV2` declares `[XmlSerializerFormat]` on AuthenticateMe,
Disconnect, KeepAlive (one-way ops) — those use XmlSerializer for
the ENTIRE message including the [MessageHeader] ConnectionValid-
ator. Other ops use the default DataContractSerializer. The wire
shapes differ:
XmlSerializer: `<ConnectionId xmlns="http://asb.contracts.data/
20111111">guid</ConnectionId>` (PascalCase property name in
data namespace)
DataContract: `<connectionIdField xmlns="http://schemas.data
contract.org/2004/07/ArchestrAServices.ASBContract">guid</…>`
(private "fooField" name in datacontract namespace)
New `ValidatorWireFormat::for_action` picks the right shape per
action; `encode_validator` now branches on it. New helpers
`push_xml_text_field` / `push_xml_byte_array_field` for the
XmlSerializer form. The DataContract form is preserved verbatim
for Register/Read/Write/etc.
3. **Decoder missing 0x0A** (`ShortDictionaryXmlnsAttribute`). The
server's RegisterItemsResponse uses `0x0A {dict-id}` to set the
default namespace from the static dict; our decoder bailed out
with `UnknownRecord(10)`. New decode arm produces a
`DefaultNamespace` token with `DictionaryStatic` value.
**.NET probe gains a `--via` flag** (`AsbConnectionOptions.Via` →
`ChannelFactory.CreateChannel(addr, viaUri)`) so the probe can be
routed through asb-relay for byte-level capture without triggering
an `AddressFilterMismatch` fault. CoreWCF / .NET 10 dropped
`ClientViaBehavior`; the `CreateChannel(addr, via)` overload is the
modern equivalent.
Live status (this commit): Connect handshake works, AuthenticateMe
no longer faults (canonical XML + crypto + wire-format all match
.NET now), RegisterItemsResponse comes back from the server (a real
response, not a dispatcher fault). One remaining issue: our response
decoder hits `MissingField { field: "Status" }` — the server's
RegisterItemsResponse uses a slightly different element naming or
encoding than `collect_asbidata_payloads` expects. Next iteration
hunts that.
Workspace: 710 unit tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,11 @@ using System.Globalization;
|
||||
|
||||
string endpoint = GetArg(args, "--endpoint")
|
||||
?? "net.tcp://desktop-6jl3kko/ASBService/Default_ZB_MxDataProvider/IDataV2";
|
||||
// `--via` overrides the TCP destination without changing the `<a:To>`
|
||||
// SOAP header (so the registered service URL still matches inside
|
||||
// SMSvcHost). Use to route the probe through `asb-relay` for wire
|
||||
// byte capture, e.g. `--via net.tcp://127.0.0.1:8088/...`.
|
||||
string? clientVia = GetArg(args, "--via");
|
||||
string[] tags = GetArgs(args, "--tag");
|
||||
if (tags.Length == 0)
|
||||
{
|
||||
@@ -300,7 +305,14 @@ if (probeConnectFailure)
|
||||
{
|
||||
try
|
||||
{
|
||||
using MxAsbDataClient connectFailureClient = MxAsbDataClient.Connect(endpoint, solution, Console.WriteLine, dumpMessages);
|
||||
using MxAsbDataClient connectFailureClient = MxAsbDataClient.Connect(new AsbConnectionOptions
|
||||
{
|
||||
Endpoint = endpoint,
|
||||
SolutionName = solution,
|
||||
Trace = Console.WriteLine,
|
||||
DumpMessages = dumpMessages,
|
||||
Via = clientVia,
|
||||
});
|
||||
Console.WriteLine("connect_failure_observed=False");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -322,7 +334,14 @@ if (compatibilitySubscribe)
|
||||
return;
|
||||
}
|
||||
|
||||
using MxAsbDataClient client = MxAsbDataClient.Connect(endpoint, solution, Console.WriteLine, dumpMessages);
|
||||
using MxAsbDataClient client = MxAsbDataClient.Connect(new AsbConnectionOptions
|
||||
{
|
||||
Endpoint = endpoint,
|
||||
SolutionName = solution,
|
||||
Trace = Console.WriteLine,
|
||||
DumpMessages = dumpMessages,
|
||||
Via = clientVia,
|
||||
});
|
||||
int publishedEventCount = 0;
|
||||
client.PublishedValueReceived += (_, value) =>
|
||||
{
|
||||
|
||||
@@ -10,6 +10,16 @@ public sealed record AsbConnectionOptions
|
||||
|
||||
public bool DumpMessages { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional `ClientVia` URL — if set, WCF will TCP-connect to this
|
||||
/// URL but address messages with the `Endpoint` URL (so the
|
||||
/// `<a:To>` SOAP header still matches the registered service).
|
||||
/// Used to route the .NET probe through the `asb-relay` middleman
|
||||
/// without triggering an `AddressFilterMismatch` fault. Mirrors
|
||||
/// `System.ServiceModel.Description.ClientViaBehavior`.
|
||||
/// </summary>
|
||||
public string? Via { get; init; }
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Endpoint))
|
||||
|
||||
@@ -68,7 +68,19 @@ public sealed class MxAsbDataClient : IDisposable
|
||||
AsbSystemAuthenticator authenticator = new(passphrase, cryptoParameters, trace);
|
||||
trace?.Invoke("asb.stage=authenticator-ready");
|
||||
NetTcpBinding binding = CreateBinding();
|
||||
ChannelFactory<IAsbDataV2> factory = new(binding, new EndpointAddress(endpoint));
|
||||
// Optional `ClientVia`: when set, TCP-connect to that URL but
|
||||
// keep the `<a:To>` header pointing at the registered Endpoint.
|
||||
// Used to route through `asb-relay` for byte-level capture
|
||||
// without triggering an `AddressFilterMismatch` fault. CoreWCF /
|
||||
// .NET 10 dropped `ClientViaBehavior`; the equivalent is to
|
||||
// pass the Via URL through to `CreateChannel(addr, viaUri)`.
|
||||
EndpointAddress endpointAddress = new(endpoint);
|
||||
ChannelFactory<IAsbDataV2> factory = new(binding, endpointAddress);
|
||||
Uri? viaUri = string.IsNullOrWhiteSpace(options.Via) ? null : new Uri(options.Via);
|
||||
if (viaUri is not null)
|
||||
{
|
||||
trace?.Invoke($"asb.client_via={viaUri}");
|
||||
}
|
||||
AsbDataCustomSerializer.Trace = dumpMessages ? trace : null;
|
||||
int replacedSerializers = AsbCustomSerializerContractBehavior.ReplaceSerializer(factory.Endpoint.Contract);
|
||||
trace?.Invoke($"asb.serializer.behaviors-replaced={replacedSerializers}");
|
||||
@@ -83,7 +95,9 @@ public sealed class MxAsbDataClient : IDisposable
|
||||
{
|
||||
trace?.Invoke("asb.stage=open-factory");
|
||||
factory.Open();
|
||||
channel = factory.CreateChannel();
|
||||
channel = viaUri is not null
|
||||
? factory.CreateChannel(endpointAddress, viaUri)
|
||||
: factory.CreateChannel();
|
||||
|
||||
trace?.Invoke("asb.stage=open-channel");
|
||||
clientChannel = (IClientChannel)channel;
|
||||
|
||||
Reference in New Issue
Block a user