Skip to main content

Driver Extension Development

Drivers empower Engines to communication over different transport channels with the outside world. One driver may allow GUARDARA to communicate over the network, another one, to communicate over the air and so on.

Unlike Fields and Transforms, Drivers only have a backend component. The frontend is dynamically generated by the user interface based on the parameters expected by the Driver’s backend component.

Driver Manifest

The manifest.json files contain basic information about the driver extension. An example manifest file can be seen below.

{
"component": "driver",
"description": "GUARDARA TCP Network Driver",
"display_name": "Network / TCP",
"version": "1.0.0",
"license": "GUARDARA",
"url": "https://guardara.com",
"author": {
"name": "Zsolt Imre",
"email": "zsolt.imre@guardara.com"
}
}

The display_name optional attribute can be used to define the text to represent the Driver when the driver list is displayed on the Project Configuration page.

There may be two, identical manifest files in your project. When making changes to the manifest, make sure you update both files so they are identical.

Driver Properties

The properties.json file describe the configurable properties of the Driver. A Driver for example needs to know where to connect to in client mode, or what port to listen on in server mode. Drivers that operate by storing mutations on the file system would need to know where the files should be stored. Using the properties file developers can define what parameters the Driver expects and, also how those should be rendered on the user interface.

The properties file of a Driver however is a bit different than of other extensions’. This is mainly due to the fact that Drivers have no skeleton file, thus that information is derived from the properties file.

The properties file is a dictionary, where each key represents the name of a property. The value of each property is an object that describe the characteristics of the property.

The most basic properties file that define only the mandatory name property is shown below.

{
"name": {
"type": "str",
"mandatory": true,
“display”: {
"display_name": "Driver Name",
"description": "The name of the driver",
"visible": false
}
}
}

The name property is a special one and Drivers must define it in the properties file. The value is not rendered on the user interface. However, the Engine must know which Driver to use for a given test run, thus the name of the Driver is defined using this property. The user interface populates the value of this property automatically at the time a Driver is selected during Project configuration.

Another example: If a Driver would like to allow users to enable debug mode, the properties file could be extended as shown below.

{
"name": {
"type": "str",
"mandatory": true,
“display”: {
"display_name": "Driver Name",
"description": "The name of the driver",
"visible": false
}
},
"debug": {
"type": "bool",
"default": false,
“display”: {
"display_name": "Debug Mode",
"description": "Enable to see debug messages on console"
}
}
}

The supported configuration options of the property definitions are summarized by the table below.

PropertyDescription
typeThe type of the property. The value of the type property can be either a string representing a Python type or a list of strings representing multiple Python types. The supported Python types are: str, int, bool.
mandatoryDefines whether it is required to define the property.
defaultThe default value for the property. Non-mandatory properties must have a default value set.
valuesA whitelist of acceptable values.
display.conditionTo be used to define condition(s) to be evaluated in order to determine whether the property should be rendered or not. Please see the relevant section of this page for mode details.
display.display_nameThe short, human-friendly name of the property as shown on the user interface.
display.descriptionThe description of the property as shown on the user interface.
display.multilineApplicable to text fields only. Setting it to true will result in the render of a multiline text area.
display.visibleThe value of this property only has an impact on how the user interface renders the property. In case its value is false, no form control will be rendered for the property.
display.hideThe hide property, if set true, will prevent the property to be displayed on the test details page.
display.fileIn case of string fields (where the type property is set to str) setting this attribute to true will result in the rendering of a button, that once clicked, allows a user to select a file. The value of the selected file will be set as value for the property.

Display Conditions

One or more conditions can be defined for properties. If a condition evaluates to true, the property will be rendered.

Conditions can be defined either as a string, or a list. A single condition can be defined as a string as shown below.

    ...,
"example": {
"type": "int",
"default": 0,
"display": {
"display_name": "Example Integer",
"description": "Just an example to demonstrate single condition.",
"condition": "protocol == 'TCP'"
}
},
...

As can be seen, conditions can refer to the actual value of properties and compare it to a specific value, for example: protocol == 'tls’ and value <= 1. In the example above, when the Project Configuration page renders the driver properties, it will query the current value of the protocol field and, if its value is TCP, only then the example property will be rendered.

Multiple conditions can be set by providing a list as the value of the condition key. This is shown below.

    ...,
"example": {
"type": "int",
"default": 0,
"display": {
"display_name": "Example Integer",
"description": "Just an example to demonstrate single condition.",
"condition": [
"protocol == 'TCP'",
"mode == 'client'"
]
}
},
...

In the above example, the example property is only rendered if at least one of the conditions defined evaluate to true.

Action Properties

An Action in GUARDARA terminology is basically an activity to be performed by the Driver. The action properties are configurable via the user interface (Flow Designer) and define certain constraints such as the action timeout value and what to do in case of specific events such as timeout and connection error. This information is passed to the Driver methods in the form of an action parameter.

All actions share the same properties; however, their value may differ. An example action properties for the Send action can be seen below.

{
"action": "send",
"timeout": 3,
"on": {
"monitor": "",
"timeout": None
},
"retry": {
"retry": True,
"count": 0,
"interval": 1
},
"meta": {
"id": "055bd123-83fc-4287-8628-582fe2c00ea4",
"open": True
}
}

The table below summarizes the properties with their descriptions.

PropertyDescription
actionThe activity (e.g.: send, receive) the action belongs to. This allows the Engines to pass the action object to the right Driver method. In the above example, the action object will be passed to the Driver’s send method.
timeoutThe timeout in seconds for the given action. As per the above example, the Engine will wait 3 seconds to establish the connection. If it cannot establish a connection in 3 seconds, appropriate action will be taken. Please note, the type of the value is float.
on.timeoutDefines how the engine should handle an action timeout. Valid values are discussed under the Event Values section.
on.monitorDefines how the Engine should handle when a Monitor (if configured) reports an issue. Valid values are discussed under the Event Values section.
retry.retry(Optional) In case of an error or timeout, one may want to retry the operation again just in case the event was generated due to a temporary glitch (e.g., packet loss). This property defines whether a failed action should be performed again or not.
retry.countThis property defines how many times the Driver should try to perform the action again should the action fail.
retry.intervalThis property defines how much time the Driver should wait between each retry.
metaMeta data set by the GUARDARA Manager. It can be ignored by the Driver.

All Drivers should be prepared to handle the above action properties (such as retry) as required by the implementation. Worth noting that these action properties are not only used by the Driver but also by the Engine itself in order to control the flow.

Please note, the interpretation of retry.retry and retry.count is up to the Driver. A Driver, similar to the network drivers, may decide to ignore the retry.retry property and decide whether to repeat an action or not solely based on retry.count. The network drivers, for example, attempts to retry an action if retry.count is greater than 0.

Event Values

The on.timeout and on.monitor action properties support the following values.

  • report: The Engine will include the event as a finding in the report and will continue testing.
  • skip: The Engine will skip all remaining actions and continue from the next Flow iteration.
  • skip_and_report: Same as above, but the event will be included as a finding in the report.
  • next: The Engine will stop testing the current field and start mutating the next one starting from the beginning of the next Flow iteration.
  • next_and_report: Same as above, but the event will be included as a finding in the report.
  • pause: The Engine pauses the test execution immediately.
  • pause_and_report: Same as above, but the event will be included as a finding in the report.
  • terminate: The Engine terminates the test immediately.
  • terminate_and_report: Same as above, but the event will be included as a finding in the report.

Please note, that the on.monitor property is completely irrelevant to a Driver. It is only used by the Engine.

Even though the value of the on.timeout property is not directed to the Driver either, it may be useful for the Driver to know what will happen if it signals an action timeout to the engine. This allows the Driver to take appropriate action if needed. For example, if the value is terminate or terminate_and_report and the Driver signals a timeout, the Engine will most definitely terminate the test. The Driver knowing this could (optional!) terminate the connection to the target before throwing the TimeoutException.

Please note, the Driver should report timeout and connection error events via the standard exceptions discussed under the Signalling and Exceptions section of this document.

Driver Methods

Other than the send method below, all methods are documented in the Driver source files generated by the SDK.

The Send Method

The send method is responsible for transmitting data to the target.

The Engine passes the data to be sent as either a list of bytes or a single bytes. The reason for this is to support Drivers that, for example, would like to test a particular function of a shared object library and pass mutated values via one or more arguments. To be more specific:

It is possible to implement a Driver that could test individual methods of a software library. If a method accepted three arguments, we would have to create a Message Template with three Group Fields at the root, each representing the value of an argument. When generating test case based on this Message Template, the Engine renders the value of each block and passes those to the Driver’s send method as a list. The Driver then can distinguish which value belongs to which argument based on their index within the list.

For tests where the above is not relevant, Drivers can simply concatenate (join) the list, for example:

data = b"".join(data)

Signalling and Exceptions

Drivers are responsible for signaling some key events to the GUARDARA Engine using exceptions. In response to an exception, based on the action properties, the Engine will take appropriate action. Drivers can use the following exceptions to signal the Engine.

ExceptionDescription
TimeoutExceptionThis exception should be thrown in case an operation has timed out. Please note, if configured, the Driver should perform retries as defined by the action object before throwing the exception. The exception class should be imported as: from guardara.sdk.exceptions import TimeoutException
ConnectionErrorExceptionThis exception should be thrown in case an operation has failed due to connection error. Such errors can be if it was not at all possible to establish a connection to the target or the send or receive operation failed immediately. The exception class should be imported as: from guardara.sdk.exceptions import ConnectionErrorException
NextIterationExceptionDrivers should raise this exception to ask the engine to move to the next iteration. This can be useful, for example, in case of a connection error (e.g., connection reset) when the connection was dropped due to a mutation, but the event is not of interest. The exception class should be imported as: from guardara.sdk.exceptions import NextIterationException
ExceptionA generic exception should be thrown in case of unexpected errors.

In case the Engine receives:

  • Exception: The test is terminated, and the error is reported in both the activity log of the test and the Engine console.
  • ConnectionErrorException: Handled according to the "Connection Error" action property configured for the action within the Test Flow Template.
  • TimeoutException: Handled according to the "Action on Timeout" action property configured for the action within the Test Flow Template.

In the case a Monitor is configured for the test, even if the Driver raises a ConnectionErrorException or TimeoutException exception, The Engine queries the Monitor(s) first to see if any monitors picked up an issue. If none of the configured monitors reported an issue with the target, only then the Engine will process the "Connection Error" and "Action on Timeout" event handling as configured by the action properties.

External Resources

The SDK generates Driver templates so that any resource included under the directory of the Driver module (g_driver_<name>) will be included in the Driver package.

Drivers can get the path to their resources via self.context.get('path'). For example, referring to a shared object library named test.so included with the Driver extension would look like:

mylib = self.context.get('path') + '/test.so'

Example

The example below demonstrates how a shared object library can be bundled and used within a Driver extension.

Shared Object Library

Create the header file test.h:

#include <stdio.h>
extern void echo(char *text);

Implement the actual method in test.c:

#include "test.h"
void echo(char text[]) {
printf("GOT: %s\n", text);
}

Compile the code into a shared object library:

$ gcc -c -Wall -Werror -fPIC test.c
$ gcc -shared -o test.so ./test.o

Create a Driver

Issue the following command to create a Driver.

guardara extension -e driver -o create

Assume the name of the Driver is test and the SDK stored the Driver under the test directory, we use the command below to include the shared object library in the Driver:

cp test.so ./test/backend/g-driver-test/g_driver_test/

Finally, the actual Driver implementation. The below example will simply pass all generated mutations to the echo method of the shared library.

import os
from ctypes import *
from guardara.sdk.driver.DriverInterface import DriverInterface

class Driver(DriverInterface):

def __init__(self, context, properties, session=None):
DriverInterface.__init__(self, context, properties, session)
# Load the shared library bundled with the driver.
mod_path = os.sep.join([self.context.get('path'), "test.so"])
self.shared_lib = cdll.LoadLibrary(mod_path)

def connect(self, action):
pass # Not relevant

def disconnect(self, action):
pass # Not relevant

def send(self, action, data):
# Pass the data generated by the engine to the `echo` method of the
# shared library.
self.shared_lib.echo(b"".join(data))

def receive(self, action):
pass # Not relevant

def cleanup(self):
pass # Not relevant