Initial project state: .NET reference, design, Rust port (M0+M1), evidence
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>
This commit is contained in:
Joseph Doherty
2026-05-05 06:21:00 -04:00
parent 43733699b0
commit fe2a6db786
3849 changed files with 352975 additions and 0 deletions
@@ -0,0 +1,149 @@
using Microsoft.Data.SqlClient;
namespace MxNativeClient;
public sealed record GalaxyUserProfile(
int UserProfileId,
string UserProfileName,
Guid UserGuid,
string DefaultSecurityGroup,
int? InTouchAccessLevel,
IReadOnlyList<string> Roles);
public sealed class GalaxyRepositoryUserResolver
{
private const string DefaultConnectionString =
"Server=localhost;Database=ZB;Integrated Security=True;Encrypt=False;TrustServerCertificate=True";
private readonly string _connectionString;
public GalaxyRepositoryUserResolver(string connectionString = DefaultConnectionString)
{
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
_connectionString = connectionString;
}
public async Task<int> ResolveUserProfileIdByGuidAsync(
Guid userGuid,
CancellationToken cancellationToken = default)
{
GalaxyUserProfile profile = await ResolveByGuidAsync(userGuid, cancellationToken).ConfigureAwait(false);
return profile.UserProfileId;
}
public async Task<GalaxyUserProfile> ResolveByGuidAsync(
Guid userGuid,
CancellationToken cancellationToken = default)
{
await using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
await using var command = connection.CreateCommand();
command.CommandText = UserByGuidSql;
command.Parameters.AddWithValue("@userGuid", userGuid);
await using SqlDataReader reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
throw new KeyNotFoundException($"Galaxy user GUID {userGuid} was not found in dbo.user_profile.");
}
return ReadProfile(reader);
}
public async Task<GalaxyUserProfile> ResolveByNameAsync(
string userName,
CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(userName);
await using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync(cancellationToken).ConfigureAwait(false);
await using var command = connection.CreateCommand();
command.CommandText = UserByNameSql;
command.Parameters.AddWithValue("@userName", userName);
await using SqlDataReader reader = await command.ExecuteReaderAsync(cancellationToken).ConfigureAwait(false);
if (!await reader.ReadAsync(cancellationToken).ConfigureAwait(false))
{
throw new KeyNotFoundException($"Galaxy user {userName} was not found in dbo.user_profile.");
}
return ReadProfile(reader);
}
private static GalaxyUserProfile ReadProfile(SqlDataReader reader)
{
return new GalaxyUserProfile(
reader.GetInt32(0),
reader.GetString(1),
reader.GetGuid(2),
reader.GetString(3),
reader.IsDBNull(4) ? null : reader.GetInt32(4),
reader.IsDBNull(5) ? [] : ParseRoleBlob(reader.GetString(5)));
}
private static IReadOnlyList<string> ParseRoleBlob(string rolesText)
{
if (!rolesText.StartsWith("0x", StringComparison.OrdinalIgnoreCase) || rolesText.Length <= 2)
{
return [];
}
byte[] bytes = Convert.FromHexString(rolesText[2..]);
List<string> roles = [];
for (int offset = 0; offset + 3 < bytes.Length; offset++)
{
List<char> chars = [];
int cursor = offset;
while (cursor + 1 < bytes.Length)
{
char c = (char)(bytes[cursor] | (bytes[cursor + 1] << 8));
if (c == '\0')
{
break;
}
if (c < 0x20 || c > 0x7e)
{
chars.Clear();
break;
}
chars.Add(c);
cursor += 2;
}
if (chars.Count < 2 || cursor + 1 >= bytes.Length || bytes[cursor] != 0 || bytes[cursor + 1] != 0)
{
continue;
}
string role = new(chars.ToArray());
if (!roles.Contains(role, StringComparer.OrdinalIgnoreCase))
{
roles.Add(role);
}
offset = cursor;
}
return roles;
}
private const string UserSelectSql = """
SELECT TOP (1)
user_profile_id,
user_profile_name,
user_guid,
default_security_group,
intouch_access_level,
CONVERT(nvarchar(max), roles) AS roles_text
FROM dbo.user_profile
""";
private const string UserByGuidSql = UserSelectSql + "\nWHERE user_guid = @userGuid\nORDER BY user_profile_id";
private const string UserByNameSql = UserSelectSql + "\nWHERE user_profile_name = @userName\nORDER BY user_profile_id";
}