terminal/src/terminal/adapter/IInteractDispatch.hpp
James Holderness 55151a4a04
Refactor VT parameter handling (#7799)
This PR introduces a pair of classes for managing VT parameters that
automatically handle range checking and default fallback values, so the
individual operations don't have to do that validation themselves. In
addition to simplifying the code, this fixes a few cases where we were
mishandling missing or extraneous parameters, and adds support for
parameter sequences on commands that couldn't previously handle them.
This PR also sets a limit on the number of parameters allowed, to help
thwart DoS memory consumption attacks.

## References

* The new parameter class also introduces the concept of an
  omitted/default parameter which is not necessarily zero, which is a
  prerequisite for addressing issue #4417.

## Detailed Description of the Pull Request / Additional comments

There are two new classes provide by this PR: a `VTParameter` class,
similar in function to a `std::optional<size_t>`, which holds an
individual parameter (which may be an omitted/default value); and a
`VTParameters` class, similar in function to `gsl:span<VTParameter>`,
which holds a sequence of those parameters.

Where `VTParameter` differs from `std::optional` is with the inclusion
of two cast operators. There is a `size_t` cast that interprets omitted
and zero values as 1 (the expected behaviour for most numeric
parameters). And there is a generic cast, for use with the enum
parameter types, which interprets omitted values as 0 (the expected
behaviour for most selective parameters).

The advantage of `VTParameters` class is that it has an `at` method that
can never fail - out of range values simply return the a default
`VTParameter` instance (this is standard behaviour in VT terminals). It
also has a `size` method that will always return a minimum count of 1,
since an empty parameter list is typically the equivalent of a single
"default" parameter, so this guarantees you'll get at least one value
when iterating over the list with `size()`.

For cases where we just need to call the same dispatch method for every
parameter, there is a helper `for_each` method, which repeatedly calls a
given predicate function with each value in the sequence. It also
collates the returned success values to determine the overall result of
the sequence. As with the `size` method, this will always make at least
one call, so it correctly handles empty sequences.

With those two classes in place, we could get rid of all the parameter
validation and default handling code in the `OutputStateMachineEngine`.
We now just use the `VTParameters::at` method to grab a parameter and
typically pass it straight to the appropriate dispatch method, letting
the cast operators automatically handle the assignment of default
values. Occasionally we might need a `value_or` call to specify a
non-standard default value, but those cases are fairly rare.

In some case the `OutputStateMachineEngine` was also checking whether
parameters values were in range, but for the most part this shouldn't
have been necessary, since that is something the dispatch classes would
already have been doing themselves (in the few cases that they weren't,
I've now updated them to do so).

I've also updated the `InputStateMachineEngine` in a similar way to the
`OutputStateMachineEngine`, getting rid of a few of the parameter
extraction methods, and simplifying other parts of the implementation.
It's not as clean a replacement as the output engine, but there are
still benefits in using the new classes.

## Validation Steps Performed

For the most part I haven't had to alter existing tests other than
accounting for changes to the API. There were a couple of tests I needed
to drop because they were checking for failure cases which shouldn't
have been failing (unexpected parameters should never be an error), or
testing output engine validation that is no longer handled at that
level.

I've added a few new tests to cover operations that take sequences of
selective parameters (`ED`, `EL`, `TBC`, `SM`, and `RM`). And I've
extended the cursor movement tests to make sure those operations can
handle extraneous parameters that weren't expected. I've also added a
test to verify that the state machine will correctly ignore parameters
beyond the maximum 32 parameter count limit.

I've also manual confirmed that the various test cases given in issues
#2101 are now working as expected.

Closes #2101
2020-10-15 16:12:52 +00:00

47 lines
1.4 KiB
C++

/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- InteractDispatch.hpp
Abstract:
- Base class for Input State Machine callbacks. When actions occur, they will
be dispatched to the methods on this interface which must be implemented by
a child class and passed into the state machine on creation.
Author(s):
- Mike Griese (migrie) 11 Oct 2017
--*/
#pragma once
#include "DispatchTypes.hpp"
#include "../../types/inc/IInputEvent.hpp"
namespace Microsoft::Console::VirtualTerminal
{
class IInteractDispatch
{
public:
#pragma warning(push)
#pragma warning(disable : 26432) // suppress rule of 5 violation on interface because tampering with this is fraught with peril
virtual ~IInteractDispatch() = default;
#pragma warning(pop)
virtual bool WriteInput(std::deque<std::unique_ptr<IInputEvent>>& inputEvents) = 0;
virtual bool WriteCtrlKey(const KeyEvent& event) = 0;
virtual bool WriteString(const std::wstring_view string) = 0;
virtual bool WindowManipulation(const DispatchTypes::WindowManipulationType function,
const VTParameter parameter1,
const VTParameter parameter2) = 0;
virtual bool MoveCursor(const size_t row,
const size_t col) = 0;
virtual bool IsVtInputEnabled() const = 0;
};
}