Sunday, December 17, 2006

DeviceMock Considerations, part 1

Before starting to develop the DeviceMock I tried to recall and systematize what I know about collaboration between the Win32 API subsystem and a kernel mode driver.
There are five main API functions are intended to interoperate with drivers. These are CreateFile, ReadFile, WriteFile, DeviceIoControl and CloseHandle. All of them except the DeviceIoControl are also used to work with files. Point is that the collaboration between an user mode application and a device is accomplished using the corresponding file object.
The mentioned functions are used by the application as described below:
  • To start working with a device the user mode application should open the corresponding file object first. That is done with the CreateFile function. When the application invokes the CreateFile function the Win32 I/O manager opens the file object and send an IRP with the IRP_MJ_CREATE major function code.
  • To read data from the device the application uses the ReadFile function using the file object handled returned by the CreateFile function. Invoking of the ReadFile function causes sending an IRP with the IRP_MJ_READ major function code to the device.
  • To write data from the device the application invokes the WriteFile function. As a result, the driver receives an IRP with the IRP_MJ_WRITE major function code.
  • When the application needs to sent a custom request to the device, i.e. queries the device for its status or changes the device work mode, it invokes the DeviceIoControl function. An IRP with the IRP_MJ_DEVICE_CONTROL is sent to the device in this case.
  • When the application finishes its work with the device it should close the file object handle using the CloseHandle function. If the closed handle was the last one of the file object, the I/O manager sends an IRP with the IRP_MJ_CLEANUP major function code. On receiving this IRP the driver should complete all pending IRPs correspond to the specified file object and release all resources associated with those requests. When driver completes all IRPs the manager destroys the file object and sends an IRP with the IRP_MJ_CLOSE major function code.
The IRPs are designed to be handled by a stack of devices. In my practice I have never worked with such kind of IRPs. The main scenario I used to is when an user-mode application sends requests to a device and the device handles these requests on its own. In other words, the IRP sent to the device is allocated only with one subrequest. If there were a filter driver that attached another device on the top of mine, the IRP would be allocated with two subrequests and so on. The number of devices attached down to this own is specified in the StackSize field of the DEVICE_OBJECT structure.
Thus, there are two scenarios that the DeviceMock may implement:
  • The first scenario assumes no real devices presented on the system. The following steps are performed in this case:
    1. A unit test requests the DeviceMock to create a mock device, as result the DeviceMock creates a mock device to substitute the real obe and the corresponding file object.
    2. Next, the unit test provides the mock device with IRPs data are expected to be sent to the device by the SUT when the test is being executed. Along with the expected IRPs the corresponding device reaction is to be specified. The possible kinds of device reactions will be described later.
    3. Then, when the SUT is being exercised it invokes the API functions to collaborate with the device. These functions causes sending IRPs with the corresponding function codes to the device. The device matches the received IRPs with the expected ones using either IRPs arrival order or their parameters and reacts accordingly.
    4. On the verification phase the test checks whether all of expected IRPs and only expected IRPs are received by the device.
  • The second scenario differs from the first one in way that the mock devices are filter ones attached above the real devices. So, it is not necessary to specify device reaction for each of the expected IRPs on the step 2 of the previous scenario. Specifying of the device reaction on the IRPs is not mandatory is this case. If no reaction is specified for an expected IRP the IRP will be forwarded to the real device. The same approach may be applied to IRPs are not match to the expected ones.

No comments: