fe2a6db786
rust / build / test / clippy / fmt (push) Has been cancelled
Layout:
- src/ .NET 10 x64 reference: MxNativeCodec, MxNativeClient,
MxAsbClient, probes, tests, harnesses. Executable spec.
- design/ Architectural plan for the Rust port (M0–M6), error
model, protocol invariants, risks (R1–R16), adversarial
review log (review.md).
- rust/ Rust workspace. M0 skeleton + M1 codec parity.
mxaccess-codec: 215 unit tests + 2 cross-implementation
parity tests (byte-identical against .NET reference).
Other crates are M0 stubs awaiting M2+.
- captures/ Frida + netsh + pcap evidence per CLAUDE.md
("captures are evidence, not throwaway logs").
- analysis/ Decompiled C# (frida/proxy/decompiled-*),
Ghidra exports for native DLLs (`exports/` only —
working state at `projects/` and AVEVA's input
binaries at `input/` are gitignored).
- docs/ Reverse-engineering reference docs.
- tools/ Setup-LiveProbeEnv.ps1 (Infisical credential fetcher),
Compute-Crc.ps1 (.NET parity helper).
- .github/workflows/ Rust CI: fmt + build + test + clippy on Windows.
- LICENSE MIT (Joseph Doherty, 2026).
Verified:
- cargo test --workspace → 217 passed (215 unit + 2 .NET parity), 0 failed
- cargo clippy --workspace -- -D warnings → clean
- cargo fmt --all -- --check → clean
- cargo publish --dry-run -p mxaccess-codec → packages cleanly
Excluded from history (see .gitignore):
- **/bin, **/obj, **/target — build artifacts
- analysis/ghidra/projects/ — Ghidra working state (regenerable)
- analysis/ghidra/input/ — AVEVA proprietary DLLs (vendor IP)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
174 lines
7.2 KiB
C#
174 lines
7.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Reflection;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.ServiceModel;
|
|
using System.ServiceModel.Channels;
|
|
using System.ServiceModel.Discovery;
|
|
using System.ServiceModel.Security;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Xml;
|
|
using ArchestrAServices.Common;
|
|
|
|
namespace Asb.Base.V2;
|
|
|
|
internal class ClientManagement : IClientManagement
|
|
{
|
|
public ConnectContext<T> CreateConnectContext<T>(EndpointAddress serviceEndpointAddress, Binding binding, string application, string user, CancellationToken cancellationToken) where T : class, IAuthenticateAsb
|
|
{
|
|
ConnectContext<T> connectContext = new ConnectContext<T>
|
|
{
|
|
Success = false,
|
|
ErrorMessage = string.Empty,
|
|
ServiceChannelFactory = null,
|
|
ServiceClient = null,
|
|
ServiceChannel = null,
|
|
ConnectionId = Guid.Empty,
|
|
ConnectionUser = user,
|
|
ConnectionApplication = application
|
|
};
|
|
ServiceTrace.LogVerbose("Try connecting with endpoint {0}", serviceEndpointAddress.Uri.AbsoluteUri);
|
|
connectContext.ServiceChannelFactory = new ChannelFactory<T>(binding, serviceEndpointAddress);
|
|
if (binding is NetTcpBinding netTcpBinding && netTcpBinding.Security.Mode == SecurityMode.Transport && connectContext.ServiceChannelFactory.Credentials != null)
|
|
{
|
|
connectContext.ServiceChannelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.ChainTrust;
|
|
connectContext.ServiceChannelFactory.Credentials.ServiceCertificate.Authentication.RevocationMode = X509RevocationMode.NoCheck;
|
|
}
|
|
InjectCustomBehavior(connectContext.ServiceChannelFactory);
|
|
if (!OpenOrAbort(connectContext.ServiceChannelFactory, cancellationToken))
|
|
{
|
|
ServiceTrace.LogVerbose("BaseV2Client CreateConnectContext is cancelled when create channel for {0}", serviceEndpointAddress.Uri.AbsoluteUri);
|
|
return connectContext;
|
|
}
|
|
connectContext.ServiceClient = connectContext.ServiceChannelFactory.CreateChannel();
|
|
if (connectContext.ServiceClient == null)
|
|
{
|
|
ServiceTrace.LogWarning("BaseV2Client not able to create a client for the endpoint {0}", serviceEndpointAddress.Uri.AbsoluteUri);
|
|
return null;
|
|
}
|
|
connectContext.CastClientToChannel();
|
|
if (connectContext.ServiceChannel == null)
|
|
{
|
|
ServiceTrace.LogWarning("BaseV2Client not able to create a channel for the endpoint {0}", serviceEndpointAddress.Uri.AbsoluteUri);
|
|
return null;
|
|
}
|
|
if (!OpenOrAbort(connectContext.ServiceChannel, cancellationToken))
|
|
{
|
|
ServiceTrace.LogVerbose("BaseV2Client CreateConnectContext is cancelled when open channel for {0}", serviceEndpointAddress.Uri.AbsoluteUri);
|
|
}
|
|
return connectContext;
|
|
}
|
|
|
|
public virtual void InjectCustomBehavior<T>(ChannelFactory<T> channelFactory) where T : class, IAuthenticateAsb
|
|
{
|
|
}
|
|
|
|
public bool EstablishSecureSession<T>(ConnectContext<T> context, string solutionName, ClientAccess access) where T : class, IAuthenticateAsb
|
|
{
|
|
if (context?.ServiceChannel == null)
|
|
{
|
|
return false;
|
|
}
|
|
if (context.ServiceChannel.State != CommunicationState.Faulted)
|
|
{
|
|
ServiceTrace.LogVerbose("Channel with endpoint {0} is open, establishing secure session using solution {1}", context.ServiceChannel.RemoteAddress.Uri.AbsoluteUri, solutionName);
|
|
return context.EstablishSecureSession(solutionName, access);
|
|
}
|
|
ServiceTrace.LogVerbose("Secure connection with endpoint {0} is NOT established using solution {1}", context.ServiceChannel.RemoteAddress.Uri.AbsoluteUri, solutionName);
|
|
context.ServiceChannel.Abort();
|
|
context.Dispose();
|
|
if (string.IsNullOrEmpty(context.ErrorMessage))
|
|
{
|
|
context.ErrorMessage = "BaseV2Client could not connect with service: EstablishSecureSession failed";
|
|
}
|
|
ServiceTrace.LogWarning(context.ErrorMessage);
|
|
return false;
|
|
}
|
|
|
|
public void DisconnectSecureSession<T>(ConnectContext<T> context) where T : class, IAuthenticateAsb
|
|
{
|
|
context?.DisconnectSecureSession();
|
|
}
|
|
|
|
public IEnumerable<EndpointDiscoveryMetadata> DiscoverEndpointsForContract(XmlQualifiedName contract, string scopeRule)
|
|
{
|
|
if (contract == null)
|
|
{
|
|
throw new ArgumentNullException("contract");
|
|
}
|
|
ServiceTrace.LogCsv("DiscoverEndpointsForContract entry [Contract scopeRule]", contract, string.IsNullOrEmpty(scopeRule) ? "<empty>" : scopeRule);
|
|
ServiceTrace.LogResume("DiscoverEndpointsForContract entry, contract {0}, scopeRule {1}", contract, string.IsNullOrEmpty(scopeRule) ? "<empty>" : scopeRule);
|
|
FindResponse findResponse;
|
|
try
|
|
{
|
|
Uri uri = RegistryHandler.MakeLDSProbeEndpointAddress(Environment.MachineName);
|
|
ServiceTrace.LogVerbose("DiscoverEndpointsForContract({0}) generated LDS endpoint for localhost: {1}", contract, uri.AbsoluteUri);
|
|
FindCriteria findCriteria = new FindCriteria();
|
|
findCriteria.ContractTypeNames.Add(contract);
|
|
List<string> list = new List<string>();
|
|
if (!string.IsNullOrEmpty(scopeRule))
|
|
{
|
|
list.Add(scopeRule);
|
|
}
|
|
Collection<Uri> collection = SvcUtilities.CreateFindScopes(string.Empty, string.Empty, string.Empty, list);
|
|
ServiceTrace.LogVerbose("DiscoverEndpointsForContract({0}) generated {1} scope Uris:", contract, collection.Count);
|
|
findCriteria.Scopes.Clear();
|
|
foreach (Uri item in collection)
|
|
{
|
|
ServiceTrace.LogVerbose(" {0}", item.AbsoluteUri);
|
|
findCriteria.Scopes.Add(item);
|
|
}
|
|
using DiscoveryClient discoveryClient = new DiscoveryClient(new DiscoveryEndpoint(SvcUtilities.GetBinding(uri.AbsoluteUri), new EndpointAddress(uri)));
|
|
findResponse = discoveryClient.Find(findCriteria);
|
|
ServiceTrace.LogVerbose("DiscoverEndpointsForContract({0}) found {1} endpoints", contract, findResponse.Endpoints.Count);
|
|
}
|
|
catch (TargetInvocationException ex)
|
|
{
|
|
ServiceTrace.LogError("DiscoverEndpointsForContract({0}, {1}) TargetInvocationException: {2}", contract, scopeRule, ex.Message);
|
|
if (ex.InnerException != null)
|
|
{
|
|
ServiceTrace.LogError(" {0}", ex.InnerException.Message);
|
|
}
|
|
findResponse = null;
|
|
}
|
|
catch (ObjectDisposedException ex2)
|
|
{
|
|
ServiceTrace.LogError("DiscoverEndpointsForContract({0}, {1}) ObjectDisposedException: {2}", contract, scopeRule, ex2.Message);
|
|
if (ex2.InnerException != null)
|
|
{
|
|
ServiceTrace.LogError(" {0}", ex2.InnerException.Message);
|
|
}
|
|
findResponse = null;
|
|
}
|
|
catch (Exception ex3)
|
|
{
|
|
ServiceTrace.LogError("DiscoverEndpointsForContract({0}, {1}) Exception: {2}", contract, scopeRule, ex3.Message);
|
|
if (ex3.InnerException != null)
|
|
{
|
|
ServiceTrace.LogError(" {0}", ex3.InnerException.Message);
|
|
}
|
|
findResponse = null;
|
|
}
|
|
ServiceTrace.LogSuspend("DiscoverEndpointsForContract exit, contract {0}, scopeRule {1}", contract, string.IsNullOrEmpty(scopeRule) ? "<empty>" : scopeRule);
|
|
ServiceTrace.LogCsv("DiscoverEndpointsForContract exit [Contract scopeRule]", contract, string.IsNullOrEmpty(scopeRule) ? "<empty>" : scopeRule);
|
|
return findResponse?.Endpoints;
|
|
}
|
|
|
|
private static bool OpenOrAbort(ICommunicationObject communicationObject, CancellationToken cancellationToken)
|
|
{
|
|
Task task = Task.Factory.StartNew(communicationObject.Open, cancellationToken, TaskCreationOptions.None, TaskScheduler.Default);
|
|
try
|
|
{
|
|
task.Wait(cancellationToken);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
communicationObject.Abort();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|