internal bool DispatchMethod()

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