in TSS.NET/TSS.Net/Tpm2.cs [864:1211]
internal bool DispatchMethod(
TpmCc ordinal,
TpmStructureBase inParms,
TpmStructureBase outParms,
int numInHandlesNotUsed,
int numOutHandlesNotUsed)
{
// todo - ClearCommandContext should be moved to the front of this
// routine (and we should make local copies of the context-values we depend upon)
// There are uncaught exceptions that can be generated that would skip
// ClearCOmmandCOntext and leave the Tpm2 with some leftover state.
byte[] response;
if (CurrentCommand != TpmCc.None)
OuterCommand = CurrentCommand;
CurrentCommand = ordinal;
#if false
// Introduce random delays during TPM command calls
if (ordinal == TpmCc.Unseal)
initialized = true;
if (initialized && (ordinal == TpmCc.Unseal || ordinal == TpmCc.GetCapability)) Thread.Sleep(Globs.GetRandomInt(20) * 100);
else if (ordinal == TpmCc.FlushContext) Thread.Sleep(Globs.GetRandomInt(4) * 200);
else if (ordinal == TpmCc.HierarchyChangeAuth || ordinal == TpmCc.PolicySecret || ordinal == TpmCc.PolicySigned) Thread.Sleep(Globs.GetRandomInt(6) * 200);
#endif
// The AlternateActionCallback allows alternate processing (or filtering/data
// collection on the executing command stream.
if (TheAlternateActionCallback != null)
{
bool desiredSuccessCode;
bool alternate = TheAlternateActionCallback(ordinal,
inParms,
outParms.GetType(),
out TpmStructureBase outParmsAlt,
out desiredSuccessCode);
Debug.Assert (outParmsAlt == null);
if (alternate)
{
_ClearCommandContext();
return desiredSuccessCode;
}
}
CommandInfo commandInfo = CommandInfoFromCommandCode(ordinal);
byte[] parms;
TpmHandle[] inHandles;
try
{
// Get the handles and the parameters from the command input structure
CommandProcessor.Fragment(inParms, commandInfo.HandleCountIn, out inHandles, out parms);
// Start processing sessions
PrepareRequestSessions(commandInfo, inHandles);
}
catch (Exception e)
{
bool allowedToContinue = e is TpmException && IsErrorAllowed((e as TpmException).RawResponse);
_ClearCommandPrelaunchContext();
_ClearCommandContext();
if (allowedToContinue)
return false;
throw;
}
// The caller can install observer/modifier callbacks, and request repeated
// execution of the same command.
bool repeat = false;
byte[] parmsCopy = null;
if (TheCmdParamsCallback != null)
parmsCopy = Globs.CopyData(parms);
// Response atoms
TpmSt responseTag = TpmSt.None;
TpmRc resultCode = TpmRc.None;
uint responseParamSize = 0;
byte[] outParmsNoHandles = null,
outParmsWithHandles = null;
TpmHandle[] outHandles = null;
SessionOut[] outSessions = null;
// In normal processing there is just one pass through this do-while loop
// If command observation/modification callbacks are installed, then the
// caller repeats the command as long as necessary.
do try
{
bool invokeCallbacks = OuterCommand == TpmCc.None &&
!CpHashMode && !DoNotDispatchCommand;
if (invokeCallbacks && TheInjectCmdCallback != null)
{
InjectCmdCallbackInvoked = true;
TheInjectCmdCallback(this, CurrentCommand);
InjectCmdCallbackInvoked = false;
}
if (TheCmdParamsCallback != null && invokeCallbacks)
{
parms = Globs.CopyData(parmsCopy);
TheCmdParamsCallback(commandInfo, ref parms, inHandles);
}
// If there are any encrypting sessions then next we encrypt the data in place
parms = DoParmEncryption(parms, commandInfo, 0, Direction.Command);
// Now do the HMAC (note that the handles are needed for name-replacement)
SessionIn[] inSessions = CreateRequestSessions(parms, inHandles);
// CpHashMode is enabled for a single command through tpm.GetCpHash().TpmCommand(...)
if (OuterCommand == TpmCc.None && CpHashMode)
{
CommandParmHash.HashData = GetCommandHash(CommandParmHash.HashAlg, parms, inHandles);
CpHashMode = false;
_ClearCommandContext();
return true;
}
// Create the command buffer
byte[] command = CommandProcessor.CreateCommand(ordinal, inHandles, inSessions, parms);
// And dispatch the command
Log(ordinal, inParms, 0);
// Nested command cannot be skipped as the outer command execution depends on it
if (DoNotDispatchCommand && OuterCommand == TpmCc.None)
{
CommandBytes = command;
DoNotDispatchCommand = false;
_ClearCommandContext();
return true;
}
if (TheCmdBufCallback != null && invokeCallbacks)
{
TheCmdBufCallback(ref command);
if (command == null)
{
repeat = true;
continue; // retry
}
}
// And actually dispatch the command into the underlying device
DateTime commandSentTime, responseReceivedTime;
int nvRateRecoveryCount = 0;
// No more than 4 retries on NV_RATE error
for (;;)
{
responseReceivedTime = commandSentTime = DateTime.Now;
if (!TestCycleNv)
{
commandSentTime = DateTime.Now;
Device.DispatchCommand(ActiveModifiers, command, out response);
responseReceivedTime = DateTime.Now;
}
else
{
// In TestCycleNv we submit the command with NV not-available. If the TPM indicates that
// NV is not available we re-submit.
try
{
// Once with NV off
Device.SignalNvOff();
Device.DispatchCommand(ActiveModifiers, command, out response);
Device.SignalNvOn();
// And if it did not work, try again with NV on
TpmRc respCode = CommandProcessor.GetResponseCode(response);
if ((uint)respCode == 0x923U || respCode == TpmRc.Lockout)
{
Device.DispatchCommand(ActiveModifiers, command, out response);
}
}
catch (Exception)
{
Device.SignalNvOn();
throw;
}
}
// Convert the byte[] response into its constituent parts.
CommandProcessor.SplitResponse(response,
commandInfo.HandleCountOut,
out responseTag,
out responseParamSize,
out resultCode,
out outHandles,
out outSessions,
out outParmsNoHandles,
out outParmsWithHandles);
if (resultCode == TpmRc.Retry)
{
continue;
}
if (resultCode != TpmRc.NvRate || ++nvRateRecoveryCount > 4)
{
break;
}
//Console.WriteLine(">>>> NV_RATE: Retrying... Attempt {0}", nvRateRecoveryCount);
var delay = (int)Tpm2.GetProperty(this, Pt.NvWriteRecovery) + 100;
#if WINDOWS_UWP
Task.Delay(delay).Wait();
#else
Thread.Sleep(delay);
#endif
} // infinite loop
// Invoke the trace callback if installed
if (TheTraceCallback != null)
{
TheTraceCallback(command, response);
}
// Collect basic statistics on command execution
if (TheCmdStatsCallback != null && invokeCallbacks)
{
repeat = TheCmdStatsCallback(ordinal, GetBaseErrorCode(resultCode),
(responseReceivedTime - commandSentTime).TotalSeconds);
}
if (repeat && resultCode == TpmRc.Success)
{
// Update session state
ProcessResponseSessions(outSessions);
int offset = (int)commandInfo.HandleCountOut * 4;
outParmsWithHandles = DoParmEncryption(outParmsWithHandles, commandInfo, offset, Direction.Response);
var m = new Marshaller(outParmsWithHandles);
CommandHeader actualHeader;
TpmHandle[] actualHandles;
SessionIn[] actualSessions;
byte[] actualParmsBuf;
CommandProcessor.CrackCommand(command, out actualHeader, out actualHandles, out actualSessions, out actualParmsBuf);
m = new Marshaller();
foreach (TpmHandle h in actualHandles)
{
m.Put(h, "handle");
}
m.Put(actualParmsBuf, "parms");
var actualParms = (TpmStructureBase)Activator.CreateInstance(inParms.GetType());
actualParms.ToHost(m);
for (int i = 0; i < actualHandles.Length; ++i)
{
for (int j = 0; j < inHandles.Length; ++j)
{
if (actualHandles[i].handle == inHandles[j].handle)
actualHandles[i] = inHandles[j];
}
}
UpdateHandleData(actualHeader.CommandCode, actualParms, actualHandles, outParms);
//ValidateResponseSessions(outHandles, outSessions, ordinal, resultCode, outParmsNoHandles);
foreach (var h in outHandles)
{
CancelSafeFlushContext(h);
}
} // if (repeat && resultCode == TpmRc.Success)
}
catch (Exception e)
{
try
{
if (e is System.IO.IOException)
Device.Close();
_ClearCommandPrelaunchContext();
_ClearCommandContext();
}
catch (Exception) { }
throw;
}
while (repeat);
// Update the audit session if needed
if (AuditThisCommand && !InjectCmdCallbackInvoked)
{
if (OuterCommand == TpmCc.None)
AuditThisCommand = false;
if (CommandAuditHash == null)
{
Globs.Throw("No audit hash set for this command stream");
CommandAuditHash = TpmAlgId.None;
}
byte[] parmHash = GetCommandHash(CommandAuditHash.HashAlg, parms, inHandles);
byte[] expectedResponseHash = GetExpectedResponseHash(CommandAuditHash.HashAlg,
outParmsNoHandles,
ordinal,
resultCode);
CommandAuditHash.Extend(Globs.Concatenate(parmHash, expectedResponseHash));
}
// FlushContest that may be executed as part of _ClearCommandPrelaunchContext()
// must be executed before any command-related info is updated.
_ClearCommandPrelaunchContext();
// Process errors if there are any
bool commandSucceeded = ProcessError(responseTag, responseParamSize, resultCode, inParms);
try
{
if (commandSucceeded)
{
if (!InjectCmdCallbackInvoked)
{
ProcessResponseSessions(outSessions);
int offset = (int)commandInfo.HandleCountOut * 4;
outParmsWithHandles = DoParmEncryption(outParmsWithHandles, commandInfo, offset, Direction.Response);
}
var mt = new Marshaller(outParmsWithHandles);
outParms.ToHost(mt);
TheParamsTraceCallback?.Invoke(ordinal, inParms, outParms);
if (!InjectCmdCallbackInvoked)
{
UpdateHandleData(ordinal, inParms, inHandles, outParms);
ValidateResponseSessions(outHandles, outSessions, ordinal, resultCode, outParmsNoHandles);
foreach (var s in Sessions) if (s is AuthSession)
{
var sess = s as AuthSession;
if (sess.Attrs.HasFlag(SessionAttr.Audit) && !TpmHandle.IsNull(sess.BindObject))
{
sess.BindObject = TpmRh.Null;
break; // only one audit session is expected
}
}
}
}
}
finally
{
Log(ordinal, outParms, 1);
// Clear all per-invocation state (e.g. sessions, errors expected) ready for next command
_ClearCommandContext();
}
return commandSucceeded;
}