M3 R3.1 decode: AddNonStreamValues reaches server StoreNonStreamValues (storage-engine console pipe)
Empirically decoded the AddNonStreamValues btInput framing against the live 2023 R2 server (grpc-nonstream-decode command + ProbeNonStreamedBuffersAsync driver). Every transaction rolled back (bCommit=false) — no data written. Finding: the btInput is assembled native-C++-side (not in any decompile), so 6 evidence-based framings (44-54B, packed HISTORIAN_VALUE2 variants) were probed. All 6 returned the IDENTICAL server error while an empty buffer returned a different InvalidParameter — so non-empty buffers pass parameter validation into CHistStorageConnection::StoreNonStreamValues, which routes to the \.\pipe\aahStorageEngine\console pipe server-side. Identical-across-framings => the blocker is NOT the btInput layout but a missing storage-engine console session / tag-registration precondition for the connection. Next step (untested): StorageService.OpenStorageConnection + tag registration (RegisterTags/AddTagidPairs/AddShardTagids) before AddNonStreamValues, then commit + read-back on a sandbox tag. Documented in revision-write-path.md (R3.1 decode section); raw artifact gitignored. 272 unit tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B6mcaT2PjRFKcogzp9UkfC
This commit is contained in:
@@ -33,6 +33,106 @@ internal sealed class HistorianGrpcRevisionProbe
|
||||
public Task<HistorianGrpcRevisionProbeResult> ProbeBeginAsync(CancellationToken cancellationToken)
|
||||
=> Task.Run(() => ProbeBegin(cancellationToken), cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Empirical-decode driver for the <c>AddNonStreamValues</c> <c>btInput</c> buffer (R3.1). For
|
||||
/// each candidate buffer it opens a fresh transaction, sends the buffer, records the server's
|
||||
/// accept/reject, and ALWAYS ends with <c>bCommit=false</c> (rollback) so nothing persists.
|
||||
/// The candidate buffers are supplied by the caller (the RE tool) — this method does not invent
|
||||
/// wire bytes, it just reports what the live server says about each. Safe against a real tag key
|
||||
/// because every transaction is discarded.
|
||||
/// </summary>
|
||||
public Task<IReadOnlyList<HistorianGrpcNonStreamedCandidateResult>> ProbeNonStreamedBuffersAsync(
|
||||
IReadOnlyList<(string Label, byte[] Buffer)> candidates,
|
||||
CancellationToken cancellationToken)
|
||||
=> Task.Run<IReadOnlyList<HistorianGrpcNonStreamedCandidateResult>>(
|
||||
() => ProbeNonStreamedBuffers(candidates, cancellationToken), cancellationToken);
|
||||
|
||||
private List<HistorianGrpcNonStreamedCandidateResult> ProbeNonStreamedBuffers(
|
||||
IReadOnlyList<(string Label, byte[] Buffer)> candidates,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var results = new List<HistorianGrpcNonStreamedCandidateResult>();
|
||||
|
||||
using HistorianGrpcConnection connection = HistorianGrpcChannelFactory.Create(_options);
|
||||
HistorianGrpcHandshake.Session session = HistorianGrpcHandshake.OpenSession(
|
||||
connection, _options, cancellationToken,
|
||||
connectionMode: HistorianWcfAuthChainHelper.NativeIntegratedWriteEnabledConnectionMode);
|
||||
|
||||
var transactionClient = new GrpcTransaction.TransactionService.TransactionServiceClient(connection.Channel);
|
||||
string handle = session.StringHandle;
|
||||
DateTime Deadline() => DateTime.UtcNow.Add(_options.RequestTimeout);
|
||||
|
||||
// Prime the Transaction service session table.
|
||||
try
|
||||
{
|
||||
transactionClient.GetTransactionInterfaceVersion(
|
||||
new GrpcTransaction.GetTransactionInterfaceVersionRequest(), connection.Metadata, Deadline(), cancellationToken);
|
||||
}
|
||||
catch { /* version prime is best-effort */ }
|
||||
|
||||
foreach ((string label, byte[] buffer) in candidates)
|
||||
{
|
||||
var candidate = new HistorianGrpcNonStreamedCandidateResult { Label = label, BufferLength = buffer.Length };
|
||||
string? transactionId = null;
|
||||
try
|
||||
{
|
||||
GrpcTransaction.AddNonStreamValuesBeginResponse begin = transactionClient.AddNonStreamValuesBegin(
|
||||
new GrpcTransaction.AddNonStreamValuesBeginRequest { StrHandle = handle },
|
||||
connection.Metadata, Deadline(), cancellationToken);
|
||||
if (!(begin.Status?.BSuccess ?? false) || string.IsNullOrEmpty(begin.StrTransactionId))
|
||||
{
|
||||
candidate.BeginFailed = true;
|
||||
byte[] be = begin.Status?.BtError?.ToByteArray() ?? [];
|
||||
candidate.AddErrorHex = be.Length == 0 ? null : Convert.ToHexString(be);
|
||||
results.Add(candidate);
|
||||
continue;
|
||||
}
|
||||
|
||||
transactionId = begin.StrTransactionId;
|
||||
|
||||
GrpcTransaction.AddNonStreamValuesResponse add = transactionClient.AddNonStreamValues(
|
||||
new GrpcTransaction.AddNonStreamValuesRequest
|
||||
{
|
||||
StrHandle = handle,
|
||||
StrTransactionId = transactionId,
|
||||
BtInput = ByteString.CopyFrom(buffer),
|
||||
},
|
||||
connection.Metadata, Deadline(), cancellationToken);
|
||||
|
||||
candidate.AddSucceeded = add.Status?.BSuccess ?? false;
|
||||
byte[] ae = add.Status?.BtError?.ToByteArray() ?? [];
|
||||
candidate.AddErrorHex = ae.Length == 0 ? null : Convert.ToHexString(ae);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
candidate.Exception = $"{ex.GetType().Name}: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Always roll back — bCommit=false writes nothing.
|
||||
if (!string.IsNullOrEmpty(transactionId))
|
||||
{
|
||||
try
|
||||
{
|
||||
transactionClient.AddNonStreamValuesEnd(
|
||||
new GrpcTransaction.AddNonStreamValuesEndRequest
|
||||
{
|
||||
StrHandle = handle,
|
||||
StrTransactionId = transactionId,
|
||||
BCommit = false,
|
||||
},
|
||||
connection.Metadata, Deadline(), cancellationToken);
|
||||
}
|
||||
catch { /* rollback best-effort */ }
|
||||
}
|
||||
}
|
||||
|
||||
results.Add(candidate);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private HistorianGrpcRevisionProbeResult ProbeBegin(CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new HistorianGrpcRevisionProbeResult();
|
||||
@@ -155,3 +255,13 @@ internal sealed class HistorianGrpcRevisionBeginAttempt
|
||||
public string? ErrorHex { get; set; }
|
||||
public string? Exception { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class HistorianGrpcNonStreamedCandidateResult
|
||||
{
|
||||
public string Label { get; set; } = "";
|
||||
public int BufferLength { get; set; }
|
||||
public bool BeginFailed { get; set; }
|
||||
public bool AddSucceeded { get; set; }
|
||||
public string? AddErrorHex { get; set; }
|
||||
public string? Exception { get; set; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user