using System;
|
using System.Collections.Generic;
|
using System.Diagnostics;
|
using System.Globalization;
|
using System.IO;
|
using System.Linq;
|
using System.Reflection;
|
using System.Runtime.InteropServices;
|
using System.Security.Cryptography;
|
using System.Text;
|
using System.Threading.Tasks;
|
|
[assembly: AssemblyVersion("1.0.0.0")]
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
namespace AnaSemi
|
{
|
public sealed class NativeProcessResultV006
|
{
|
public string schema_id { get; internal set; }
|
public string process_kind { get; internal set; }
|
public bool process_started { get; internal set; }
|
public UInt32? process_id { get; internal set; }
|
public UInt16 argv_count { get; internal set; }
|
public string argv_fingerprint { get; internal set; }
|
public string command_line_sha256 { get; internal set; }
|
public string roundtrip_argv_fingerprint { get; internal set; }
|
public DateTimeOffset? started_at { get; internal set; }
|
public DateTimeOffset finished_at { get; internal set; }
|
public Int32? exit_code { get; internal set; }
|
public bool timed_out { get; internal set; }
|
public string copy_state { get; internal set; }
|
public string child_liveness { get; internal set; }
|
public string stdout_path { get; internal set; }
|
public UInt64 stdout_bytes { get; internal set; }
|
public string stdout_sha256 { get; internal set; }
|
public string stderr_path { get; internal set; }
|
public UInt64 stderr_bytes { get; internal set; }
|
public string stderr_sha256 { get; internal set; }
|
public string result_contract_version { get; internal set; }
|
public string wrapper_status { get; internal set; }
|
|
internal NativeProcessResultV006() { }
|
}
|
|
public sealed class NativeProcessContractExceptionV006 : Exception
|
{
|
public string StopCode { get; private set; }
|
|
internal NativeProcessContractExceptionV006(string stopCode)
|
: base(stopCode)
|
{
|
StopCode = stopCode;
|
}
|
|
internal NativeProcessContractExceptionV006(string stopCode, Exception inner)
|
: base(stopCode, inner)
|
{
|
StopCode = stopCode;
|
}
|
}
|
|
public static class WindowsCommandLineV006
|
{
|
[DllImport("shell32.dll", SetLastError = true)]
|
private static extern IntPtr CommandLineToArgvW(string commandLine, out int argumentCount);
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
private static extern IntPtr LocalFree(IntPtr memory);
|
|
public static string QuoteAndJoin(string[] logicalArgv)
|
{
|
if (logicalArgv == null)
|
{
|
throw new ArgumentNullException("logicalArgv");
|
}
|
|
StringBuilder joined = new StringBuilder();
|
for (int i = 0; i < logicalArgv.Length; i++)
|
{
|
if (logicalArgv[i] == null)
|
{
|
throw new ArgumentException("Logical argv contains null.", "logicalArgv");
|
}
|
if (i != 0)
|
{
|
joined.Append(' ');
|
}
|
joined.Append(QuoteToken(logicalArgv[i]));
|
}
|
return joined.ToString();
|
}
|
|
public static string Fingerprint(string[] logicalArgv)
|
{
|
if (logicalArgv == null)
|
{
|
throw new ArgumentNullException("logicalArgv");
|
}
|
|
using (MemoryStream payload = new MemoryStream())
|
{
|
WriteUtf8(payload, "ANA-SEMI-ARGV-V001");
|
for (int i = 0; i < logicalArgv.Length; i++)
|
{
|
if (logicalArgv[i] == null)
|
{
|
throw new ArgumentException("Logical argv contains null.", "logicalArgv");
|
}
|
payload.WriteByte(0);
|
WriteUtf8(payload, logicalArgv[i]);
|
}
|
return Sha256Hex(payload.ToArray());
|
}
|
}
|
|
internal static string[] RoundTrip(string commandLine)
|
{
|
int count;
|
IntPtr pointer = CommandLineToArgvW(commandLine, out count);
|
if (pointer == IntPtr.Zero)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_ACTUAL_CHILD_ARGV_ROUNDTRIP_MISMATCH");
|
}
|
|
try
|
{
|
string[] result = new string[count];
|
for (int i = 0; i < count; i++)
|
{
|
IntPtr item = Marshal.ReadIntPtr(pointer, i * IntPtr.Size);
|
result[i] = Marshal.PtrToStringUni(item);
|
}
|
return result;
|
}
|
finally
|
{
|
LocalFree(pointer);
|
}
|
}
|
|
internal static string Sha256Utf16Le(string text)
|
{
|
return Sha256Hex(Encoding.Unicode.GetBytes(text));
|
}
|
|
internal static string Sha256File(string path, out ulong bytes)
|
{
|
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None))
|
using (SHA256 sha = SHA256.Create())
|
{
|
bytes = checked((ulong)stream.Length);
|
return ToHex(sha.ComputeHash(stream));
|
}
|
}
|
|
internal static string Sha256Hex(byte[] bytes)
|
{
|
using (SHA256 sha = SHA256.Create())
|
{
|
return ToHex(sha.ComputeHash(bytes));
|
}
|
}
|
|
private static string QuoteToken(string token)
|
{
|
if (token.Length == 0)
|
{
|
return "\"\"";
|
}
|
|
bool needsQuotes = token.Any(ch => ch == ' ' || ch == '\t' || ch == '\n' || ch == '\v' || ch == '"');
|
if (!needsQuotes)
|
{
|
return token;
|
}
|
|
StringBuilder quoted = new StringBuilder();
|
quoted.Append('"');
|
int slashCount = 0;
|
foreach (char ch in token)
|
{
|
if (ch == '\\')
|
{
|
slashCount++;
|
continue;
|
}
|
|
if (ch == '"')
|
{
|
quoted.Append('\\', slashCount * 2 + 1);
|
quoted.Append('"');
|
slashCount = 0;
|
continue;
|
}
|
|
quoted.Append('\\', slashCount);
|
slashCount = 0;
|
quoted.Append(ch);
|
}
|
quoted.Append('\\', slashCount * 2);
|
quoted.Append('"');
|
return quoted.ToString();
|
}
|
|
private static void WriteUtf8(Stream stream, string text)
|
{
|
byte[] bytes = Encoding.UTF8.GetBytes(text);
|
stream.Write(bytes, 0, bytes.Length);
|
}
|
|
private static string ToHex(byte[] bytes)
|
{
|
StringBuilder text = new StringBuilder(bytes.Length * 2);
|
for (int i = 0; i < bytes.Length; i++)
|
{
|
text.Append(bytes[i].ToString("x2", CultureInfo.InvariantCulture));
|
}
|
return text.ToString();
|
}
|
}
|
|
public static class NativeProcessRunnerV006
|
{
|
public static NativeProcessResultV006 Run(
|
string processKind,
|
string[] logicalArgv,
|
string stdoutPath,
|
string stderrPath,
|
int timeoutMilliseconds,
|
int killWaitMilliseconds,
|
string syntheticFaultMode)
|
{
|
ValidateInputs(processKind, logicalArgv, stdoutPath, stderrPath, timeoutMilliseconds, killWaitMilliseconds, syntheticFaultMode);
|
|
string fullCommandLine = WindowsCommandLineV006.QuoteAndJoin(logicalArgv);
|
string[] roundTrip = WindowsCommandLineV006.RoundTrip(fullCommandLine);
|
if (!OrdinalArrayEquals(logicalArgv, roundTrip))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_ACTUAL_CHILD_ARGV_ROUNDTRIP_MISMATCH");
|
}
|
|
string argvFingerprint = WindowsCommandLineV006.Fingerprint(logicalArgv);
|
string roundTripFingerprint = WindowsCommandLineV006.Fingerprint(roundTrip);
|
if (!StringComparer.Ordinal.Equals(argvFingerprint, roundTripFingerprint))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_ACTUAL_CHILD_ARGV_ROUNDTRIP_MISMATCH");
|
}
|
|
string stdoutAbsolute = ResolveRelativePath(stdoutPath);
|
string stderrAbsolute = ResolveRelativePath(stderrPath);
|
if (StringComparer.OrdinalIgnoreCase.Equals(stdoutAbsolute, stderrAbsolute))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_TARGET_ALREADY_EXISTS");
|
}
|
|
FileStream stdoutWriter = null;
|
FileStream stderrWriter = null;
|
Process process = null;
|
Task stdoutCopy = null;
|
Task stderrCopy = null;
|
bool processStarted = false;
|
bool timedOut = false;
|
bool copyFailed = false;
|
uint? processId = null;
|
DateTimeOffset? startedAt = null;
|
int? exitCode = null;
|
|
try
|
{
|
stdoutWriter = new FileStream(stdoutAbsolute, FileMode.CreateNew, FileAccess.Write, FileShare.Read);
|
stderrWriter = new FileStream(stderrAbsolute, FileMode.CreateNew, FileAccess.Write, FileShare.Read);
|
|
ProcessStartInfo startInfo = new ProcessStartInfo();
|
startInfo.FileName = logicalArgv[0];
|
startInfo.Arguments = WindowsCommandLineV006.QuoteAndJoin(logicalArgv.Skip(1).ToArray());
|
startInfo.UseShellExecute = false;
|
startInfo.CreateNoWindow = true;
|
startInfo.RedirectStandardOutput = true;
|
startInfo.RedirectStandardError = true;
|
|
process = new Process();
|
process.StartInfo = startInfo;
|
try
|
{
|
if (!process.Start())
|
{
|
return BuildStartFailed(processKind, logicalArgv, argvFingerprint, fullCommandLine, roundTripFingerprint,
|
stdoutPath, stderrPath, ref stdoutWriter, ref stderrWriter);
|
}
|
}
|
catch (Exception)
|
{
|
return BuildStartFailed(processKind, logicalArgv, argvFingerprint, fullCommandLine, roundTripFingerprint,
|
stdoutPath, stderrPath, ref stdoutWriter, ref stderrWriter);
|
}
|
|
processStarted = true;
|
processId = checked((uint)process.Id);
|
startedAt = DateTimeOffset.UtcNow;
|
|
bool injectCopyFailure = StringComparer.Ordinal.Equals(syntheticFaultMode, "FAIL_STDOUT_AFTER_86");
|
stdoutCopy = CopyStreamAsync(process.StandardOutput.BaseStream, stdoutWriter, injectCopyFailure, 86);
|
stderrCopy = CopyStreamAsync(process.StandardError.BaseStream, stderrWriter, false, 0);
|
|
if (!process.WaitForExit(timeoutMilliseconds))
|
{
|
timedOut = true;
|
try
|
{
|
process.Kill();
|
}
|
catch (Exception ex)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_CHILD_LIVENESS_UNCERTAIN", ex);
|
}
|
if (!process.WaitForExit(killWaitMilliseconds) || !process.HasExited)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_CHILD_LIVENESS_UNCERTAIN");
|
}
|
}
|
|
if (!process.HasExited)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_CHILD_LIVENESS_UNCERTAIN");
|
}
|
exitCode = process.ExitCode;
|
|
copyFailed = !WaitCopy(stdoutCopy, killWaitMilliseconds) | !WaitCopy(stderrCopy, killWaitMilliseconds);
|
CloseWriter(ref stdoutWriter, ref copyFailed);
|
CloseWriter(ref stderrWriter, ref copyFailed);
|
|
ulong stdoutBytes;
|
ulong stderrBytes;
|
string stdoutHash;
|
string stderrHash;
|
try
|
{
|
stdoutHash = WindowsCommandLineV006.Sha256File(stdoutAbsolute, out stdoutBytes);
|
stderrHash = WindowsCommandLineV006.Sha256File(stderrAbsolute, out stderrBytes);
|
}
|
catch (Exception ex)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_RAW_FINALIZATION_UNCERTAIN", ex);
|
}
|
|
string wrapperStatus = timedOut ? "TIMED_OUT" : (copyFailed ? "COPY_FAILED" : "RETURNED");
|
NativeProcessResultV006 result = NewResult(processKind, logicalArgv, argvFingerprint, fullCommandLine,
|
roundTripFingerprint, true, processId, startedAt, exitCode, timedOut,
|
copyFailed ? "FAIL" : "PASS", "EXITED", stdoutPath, stdoutBytes, stdoutHash,
|
stderrPath, stderrBytes, stderrHash, wrapperStatus);
|
return ProcessResultEnvelopeValidatorV006.ValidateSingle(new object[] { result });
|
}
|
catch (NativeProcessContractExceptionV006)
|
{
|
throw;
|
}
|
catch (Exception ex)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_RAW_FINALIZATION_UNCERTAIN", ex);
|
}
|
finally
|
{
|
if (processStarted && process != null)
|
{
|
try
|
{
|
if (!process.HasExited)
|
{
|
process.Kill();
|
process.WaitForExit(killWaitMilliseconds);
|
}
|
}
|
catch (Exception) { }
|
}
|
if (stdoutWriter != null)
|
{
|
try { stdoutWriter.Dispose(); } catch (Exception) { }
|
}
|
if (stderrWriter != null)
|
{
|
try { stderrWriter.Dispose(); } catch (Exception) { }
|
}
|
if (process != null)
|
{
|
process.Dispose();
|
}
|
}
|
}
|
|
private static NativeProcessResultV006 BuildStartFailed(
|
string processKind,
|
string[] logicalArgv,
|
string argvFingerprint,
|
string fullCommandLine,
|
string roundTripFingerprint,
|
string stdoutPath,
|
string stderrPath,
|
ref FileStream stdoutWriter,
|
ref FileStream stderrWriter)
|
{
|
bool closeFailed = false;
|
CloseWriter(ref stdoutWriter, ref closeFailed);
|
CloseWriter(ref stderrWriter, ref closeFailed);
|
if (closeFailed)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_RAW_FINALIZATION_UNCERTAIN");
|
}
|
|
ulong stdoutBytes;
|
ulong stderrBytes;
|
string stdoutHash;
|
string stderrHash;
|
try
|
{
|
stdoutHash = WindowsCommandLineV006.Sha256File(ResolveRelativePath(stdoutPath), out stdoutBytes);
|
stderrHash = WindowsCommandLineV006.Sha256File(ResolveRelativePath(stderrPath), out stderrBytes);
|
}
|
catch (Exception ex)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_RAW_FINALIZATION_UNCERTAIN", ex);
|
}
|
|
string emptyHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
if (stdoutBytes != 0 || stderrBytes != 0 || !StringComparer.Ordinal.Equals(stdoutHash, emptyHash) ||
|
!StringComparer.Ordinal.Equals(stderrHash, emptyHash))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_RAW_FINALIZATION_UNCERTAIN");
|
}
|
|
NativeProcessResultV006 result = NewResult(processKind, logicalArgv, argvFingerprint, fullCommandLine,
|
roundTripFingerprint, false, null, null, null, false, "NOT_STARTED", "NOT_STARTED",
|
stdoutPath, stdoutBytes, stdoutHash, stderrPath, stderrBytes, stderrHash, "PROCESS_START_FAILED");
|
return ProcessResultEnvelopeValidatorV006.ValidateSingle(new object[] { result });
|
}
|
|
private static NativeProcessResultV006 NewResult(
|
string processKind,
|
string[] logicalArgv,
|
string argvFingerprint,
|
string fullCommandLine,
|
string roundTripFingerprint,
|
bool processStarted,
|
uint? processId,
|
DateTimeOffset? startedAt,
|
int? exitCode,
|
bool timedOut,
|
string copyState,
|
string childLiveness,
|
string stdoutPath,
|
ulong stdoutBytes,
|
string stdoutHash,
|
string stderrPath,
|
ulong stderrBytes,
|
string stderrHash,
|
string wrapperStatus)
|
{
|
return new NativeProcessResultV006
|
{
|
schema_id = "ANA-SEMI-NATIVE-PROCESS-RESULT-V005",
|
process_kind = processKind,
|
process_started = processStarted,
|
process_id = processId,
|
argv_count = checked((ushort)logicalArgv.Length),
|
argv_fingerprint = argvFingerprint,
|
command_line_sha256 = WindowsCommandLineV006.Sha256Utf16Le(fullCommandLine),
|
roundtrip_argv_fingerprint = roundTripFingerprint,
|
started_at = startedAt,
|
finished_at = DateTimeOffset.UtcNow,
|
exit_code = exitCode,
|
timed_out = timedOut,
|
copy_state = copyState,
|
child_liveness = childLiveness,
|
stdout_path = stdoutPath,
|
stdout_bytes = stdoutBytes,
|
stdout_sha256 = stdoutHash,
|
stderr_path = stderrPath,
|
stderr_bytes = stderrBytes,
|
stderr_sha256 = stderrHash,
|
result_contract_version = "V005-001",
|
wrapper_status = wrapperStatus
|
};
|
}
|
|
private static async Task CopyStreamAsync(Stream source, Stream destination, bool failAfterBytes, int byteLimit)
|
{
|
byte[] buffer = new byte[4096];
|
int written = 0;
|
while (true)
|
{
|
int read = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
|
if (read == 0)
|
{
|
break;
|
}
|
|
int offset = 0;
|
while (offset < read)
|
{
|
if (failAfterBytes && written >= byteLimit)
|
{
|
throw new IOException("Synthetic copy failure.");
|
}
|
int allowed = failAfterBytes ? Math.Min(read - offset, byteLimit - written) : read - offset;
|
if (allowed <= 0)
|
{
|
throw new IOException("Synthetic copy failure.");
|
}
|
await destination.WriteAsync(buffer, offset, allowed).ConfigureAwait(false);
|
offset += allowed;
|
written += allowed;
|
}
|
}
|
}
|
|
private static bool WaitCopy(Task task, int timeoutMilliseconds)
|
{
|
try
|
{
|
if (!task.Wait(timeoutMilliseconds))
|
{
|
return false;
|
}
|
return task.Status == TaskStatus.RanToCompletion;
|
}
|
catch (Exception)
|
{
|
return false;
|
}
|
}
|
|
private static void CloseWriter(ref FileStream writer, ref bool failed)
|
{
|
if (writer == null)
|
{
|
return;
|
}
|
try
|
{
|
writer.Flush(true);
|
}
|
catch (Exception)
|
{
|
failed = true;
|
}
|
try
|
{
|
writer.Dispose();
|
}
|
catch (Exception)
|
{
|
failed = true;
|
}
|
writer = null;
|
}
|
|
private static void ValidateInputs(string processKind, string[] logicalArgv, string stdoutPath, string stderrPath,
|
int timeoutMilliseconds, int killWaitMilliseconds, string syntheticFaultMode)
|
{
|
if (String.IsNullOrWhiteSpace(processKind) || logicalArgv == null || logicalArgv.Length == 0 ||
|
logicalArgv.Length > UInt16.MaxValue || String.IsNullOrWhiteSpace(logicalArgv[0]))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
if (timeoutMilliseconds <= 0 || killWaitMilliseconds <= 0)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_CHILD_LIVENESS_UNCERTAIN");
|
}
|
if (!StringComparer.Ordinal.Equals(syntheticFaultMode, "NONE") &&
|
!StringComparer.Ordinal.Equals(syntheticFaultMode, "FAIL_STDOUT_AFTER_86"))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
if (StringComparer.Ordinal.Equals(syntheticFaultMode, "FAIL_STDOUT_AFTER_86") &&
|
!StringComparer.Ordinal.Equals(processKind, "SYNTHETIC_COPYFAIL"))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
ResolveRelativePath(stdoutPath);
|
ResolveRelativePath(stderrPath);
|
}
|
|
private static string ResolveRelativePath(string path)
|
{
|
if (String.IsNullOrWhiteSpace(path) || Path.IsPathRooted(path) || path.IndexOf(':') >= 0)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_TARGET_ALREADY_EXISTS");
|
}
|
string full = Path.GetFullPath(path);
|
string current = Path.GetFullPath(Environment.CurrentDirectory).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
if (!full.StartsWith(current, StringComparison.OrdinalIgnoreCase))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_TARGET_ALREADY_EXISTS");
|
}
|
return full;
|
}
|
|
private static bool OrdinalArrayEquals(string[] left, string[] right)
|
{
|
if (left.Length != right.Length)
|
{
|
return false;
|
}
|
for (int i = 0; i < left.Length; i++)
|
{
|
if (!StringComparer.Ordinal.Equals(left[i], right[i]))
|
{
|
return false;
|
}
|
}
|
return true;
|
}
|
}
|
|
public static class ProcessResultEnvelopeValidatorV006
|
{
|
private static readonly string[] PropertyNames = new[]
|
{
|
"schema_id", "process_kind", "process_started", "process_id", "argv_count", "argv_fingerprint",
|
"command_line_sha256", "roundtrip_argv_fingerprint", "started_at", "finished_at", "exit_code",
|
"timed_out", "copy_state", "child_liveness", "stdout_path", "stdout_bytes", "stdout_sha256",
|
"stderr_path", "stderr_bytes", "stderr_sha256", "result_contract_version", "wrapper_status"
|
};
|
|
private static readonly Type[] PropertyTypes = new[]
|
{
|
typeof(string), typeof(string), typeof(bool), typeof(UInt32?), typeof(UInt16), typeof(string),
|
typeof(string), typeof(string), typeof(DateTimeOffset?), typeof(DateTimeOffset), typeof(Int32?),
|
typeof(bool), typeof(string), typeof(string), typeof(string), typeof(UInt64), typeof(string),
|
typeof(string), typeof(UInt64), typeof(string), typeof(string), typeof(string)
|
};
|
|
public static NativeProcessResultV006 ValidateSingle(object[] items)
|
{
|
if (items == null || items.Length != 1 || items[0] == null ||
|
!StringComparer.Ordinal.Equals(items[0].GetType().FullName, "AnaSemi.NativeProcessResultV006"))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
|
PropertyInfo[] properties = items[0].GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
.OrderBy(property => property.MetadataToken).ToArray();
|
if (properties.Length != PropertyNames.Length)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
for (int i = 0; i < PropertyNames.Length; i++)
|
{
|
if (!StringComparer.Ordinal.Equals(properties[i].Name, PropertyNames[i]) || properties[i].PropertyType != PropertyTypes[i])
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
}
|
|
NativeProcessResultV006 result = (NativeProcessResultV006)items[0];
|
ValidateFields(result);
|
return result;
|
}
|
|
private static void ValidateFields(NativeProcessResultV006 result)
|
{
|
if (!StringComparer.Ordinal.Equals(result.schema_id, "ANA-SEMI-NATIVE-PROCESS-RESULT-V005") ||
|
!StringComparer.Ordinal.Equals(result.result_contract_version, "V005-001") ||
|
String.IsNullOrWhiteSpace(result.process_kind) || result.argv_count == 0 ||
|
!IsHex64(result.argv_fingerprint) || !IsHex64(result.command_line_sha256) ||
|
!IsHex64(result.roundtrip_argv_fingerprint) ||
|
!StringComparer.Ordinal.Equals(result.argv_fingerprint, result.roundtrip_argv_fingerprint) ||
|
String.IsNullOrWhiteSpace(result.stdout_path) || String.IsNullOrWhiteSpace(result.stderr_path) ||
|
!IsHex64(result.stdout_sha256) || !IsHex64(result.stderr_sha256))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
|
bool returned = StringComparer.Ordinal.Equals(result.wrapper_status, "RETURNED");
|
bool startFailed = StringComparer.Ordinal.Equals(result.wrapper_status, "PROCESS_START_FAILED");
|
bool timedOut = StringComparer.Ordinal.Equals(result.wrapper_status, "TIMED_OUT");
|
bool copyFailed = StringComparer.Ordinal.Equals(result.wrapper_status, "COPY_FAILED");
|
if (!returned && !startFailed && !timedOut && !copyFailed)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
|
if (startFailed)
|
{
|
if (result.process_started || result.process_id.HasValue || result.started_at.HasValue || result.exit_code.HasValue ||
|
result.timed_out || !StringComparer.Ordinal.Equals(result.copy_state, "NOT_STARTED") ||
|
!StringComparer.Ordinal.Equals(result.child_liveness, "NOT_STARTED"))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
}
|
else
|
{
|
if (!result.process_started || !result.process_id.HasValue || !result.started_at.HasValue || !result.exit_code.HasValue ||
|
!StringComparer.Ordinal.Equals(result.child_liveness, "EXITED"))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
if (timedOut != result.timed_out)
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
if (copyFailed && !StringComparer.Ordinal.Equals(result.copy_state, "FAIL"))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
if (returned && !StringComparer.Ordinal.Equals(result.copy_state, "PASS"))
|
{
|
throw new NativeProcessContractExceptionV006("STOP_PROCESS_RESULT_COUNT_TYPE_OR_PROPERTY_DRIFT");
|
}
|
}
|
}
|
|
private static bool IsHex64(string value)
|
{
|
if (value == null || value.Length != 64)
|
{
|
return false;
|
}
|
for (int i = 0; i < value.Length; i++)
|
{
|
char ch = value[i];
|
if (!((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')))
|
{
|
return false;
|
}
|
}
|
return true;
|
}
|
}
|
|
public static class ProcessResultSidecarCsvV006
|
{
|
public static void WriteCreateNew(NativeProcessResultV006 result, string sidecarPath)
|
{
|
NativeProcessResultV006 validated = ProcessResultEnvelopeValidatorV006.ValidateSingle(new object[] { result });
|
string[] header = new[]
|
{
|
"schema_id", "process_kind", "process_started", "process_id", "argv_count", "argv_fingerprint",
|
"command_line_sha256", "roundtrip_argv_fingerprint", "started_at", "finished_at", "exit_code",
|
"timed_out", "copy_state", "child_liveness", "stdout_path", "stdout_bytes", "stdout_sha256",
|
"stderr_path", "stderr_bytes", "stderr_sha256", "result_contract_version", "wrapper_status"
|
};
|
string[] values = new[]
|
{
|
validated.schema_id,
|
validated.process_kind,
|
validated.process_started ? "TRUE" : "FALSE",
|
validated.process_id.HasValue ? validated.process_id.Value.ToString(CultureInfo.InvariantCulture) : String.Empty,
|
validated.argv_count.ToString(CultureInfo.InvariantCulture),
|
validated.argv_fingerprint,
|
validated.command_line_sha256,
|
validated.roundtrip_argv_fingerprint,
|
validated.started_at.HasValue ? validated.started_at.Value.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture) : String.Empty,
|
validated.finished_at.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture),
|
validated.exit_code.HasValue ? validated.exit_code.Value.ToString(CultureInfo.InvariantCulture) : String.Empty,
|
validated.timed_out ? "TRUE" : "FALSE",
|
validated.copy_state,
|
validated.child_liveness,
|
validated.stdout_path,
|
validated.stdout_bytes.ToString(CultureInfo.InvariantCulture),
|
validated.stdout_sha256,
|
validated.stderr_path,
|
validated.stderr_bytes.ToString(CultureInfo.InvariantCulture),
|
validated.stderr_sha256,
|
validated.result_contract_version,
|
validated.wrapper_status
|
};
|
|
string text = String.Join(",", header.Select(CsvEscape).ToArray()) + "\n" +
|
String.Join(",", values.Select(CsvEscape).ToArray()) + "\n";
|
byte[] bytes = new UTF8Encoding(false).GetBytes(text);
|
using (FileStream stream = new FileStream(sidecarPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
|
{
|
stream.Write(bytes, 0, bytes.Length);
|
stream.Flush(true);
|
}
|
}
|
|
private static string CsvEscape(string value)
|
{
|
string safe = value ?? String.Empty;
|
if (safe.IndexOfAny(new[] { ',', '"', '\r', '\n' }) >= 0)
|
{
|
return "\"" + safe.Replace("\"", "\"\"") + "\"";
|
}
|
return safe;
|
}
|
}
|
}
|