Files
Joseph Doherty fe2a6db786
rust / build / test / clippy / fmt (push) Has been cancelled
Initial project state: .NET reference, design, Rust port (M0+M1), evidence
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>
2026-05-05 06:21:00 -04:00

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;
}
}