Scenarios

Mirage provides a simple way to modify the behaviour of a complex module. Indeed, some modules (such as ble_mitm, ble_master or ble_slave ) provides some complex behaviours and can be easily extended thanks to the scenarios.

If a module provides a SCENARIO input parameter, it allows to modify its behaviour using a scenario. The module associates some signals to some specific methods, allowing the user to catch this signal and execute an alternative code when it is triggered.

Writing a scenario

Mirage provides a command allowing to quickly generates a basic scenario : create_scenario This command can be called from the Command Line Interface :

$ mirage
 ~~> create_scenario
[QUESTION] Scenario's name : test
[SUCCESS] Scenario test successfully generated : /home/user/.mirage/scenarios/test.py

Or it can be used with the direct execution mode :

$ mirage --create_scenario
[QUESTION] Scenario's name : test
[SUCCESS] Scenario test successfully generated : /home/user/.mirage/scenarios/test.py

It generates the following code :

from mirage.core import scenario
from mirage.libs import io,ble,bt,utils

class test(scenario.Scenario):

        def onStart(self):
                return True

        def onEnd(self):
                return True

        def onKey(self,key):
                return True

As you can see, three methods are provided :

  • onStart is called when the module execution starts

  • onEnd is called when the module execution stops

  • onKey is called when a key is pressed, the provided parameter is a string indicating the key

These three methods are directly managed by Mirage, they are common to any modules allowing to use scenarios. However, some specific signals can be generated by the module. These signals and the associated parameters are described in the Signals section of the module’s documentation.

Every signal is associated to a module’s method executing the default behaviour. In the following module source code, the signal helloworld is associated to the method sayHello :

@module.scenarioSignal("helloworld")
def sayHello(self,name):
        io.info("Default behaviour : hello, "+name)

When this method will be called, the associated signal will be triggered, and can be caught in a scenario. If you want to customize the default behaviour of this sayHello method, you can add the following method to the scenario :

def helloworld(self,name):
        io.info("Custom behaviour : goodbye, "+name)

If this method returns nothing or returns True, the default behaviour will be executed right after the scenario’s method execution. If the method returns False, only the scenario’s method helloworld is executed.

_images/scenarios.png

You can get the module instance in a scenario by using the attribute module :

def helloworld(self,name):
        emitter = self.module.emitter
        receiver = self.module.receiver
        io.info("Custom behaviour : goodbye, "+name)

If you want to use a scenario with the module named test_module, just provide your scenario’s name as the SCENARIO input parameter of the module :

sudo mirage test_module SCENARIO=test

Integrating scenarios in a module

If you develop your own module, it’s quite easy to allow users to use scenarios in order to customize your module’s behaviour. First of all, you need to add an input parameter SCENARIO, that will be used to provide a scenario name :

self.args = {   [...]
                "SCENARIO":""}

If this input parameter contains an empty string, it means that your module should use its default behaviour, but if a name is provided the corresponding scenario should be loaded. Write the following piece of code at the beginning of your run method :

if self.loadScenario():
        io.info("Scenario loaded !")
        self.startScenario()
[...]

The loadScenario method checks if a scenario has been provided in the SCENARIO input parameter. It sets the boolean attribute scenarioEnabled to True if needed. This boolean allows you to easily check if a scenario is enabled, it can be really useful for ending the scenario execution. At the end of the code of run, you can use it in combination with the method endScenario :

[...]
if self.scenarioEnabled:
        self.endScenario()

By default, your module provides three main signals :

  • onStart, triggered when the method startScenario is called.

  • onEnd, triggered when the method endScenario is called.

  • onKey, triggered when a key is pressed (there is a single parameter, key, which is a string indicating what key has been pressed

However, you probably want to generate your own signals in order to allow your user to modify your module’s behavour. In mirage, it can be done by adding the decorator @module.scenarioSignal(signalName) above one of your methods. For example, I have the following module :

from mirage.core import module
from mirage.libs import utils,ble

class test_module(module.WirelessModule):
        def init(self):
                self.technology = "ble"
                self.type = "test"
                self.description = "Test module"
                self.args = {
                                'INTERFACE':'hci0',
                                'PARAM1':'value1',
                                'SCENARIO':''
                        }
                self.dependencies = ["ble_sniff","ble_scan"]

        @module.scenarioSignal("onHelloWorld")
        def hello(self,name):
                io.info("Hello, "+name)

        def run(self):
                if self.loadScenario():
                        io.info("Scenario loaded !")
                        self.startScenario()

                for _ in range(5):
                        utils.wait(seconds=1)
                self.hello("Romain")

                if self.scenarioEnabled:
                        self.endScenario()
                return self.ok({})

As you can see, the hello method (using one parameter, name) has been associated to the signal onHelloWorld. When the method hello is called, the signal onHelloWorld is triggered. If a scenario has been provided, Mirage tries to find a method called onHelloWorld, and executes it if it is found (the parameters are also passed to the callback method). If this method exists and returns False, the normal behaviour implemented in the hello method is ignored, while it is executed normally if the scenario method returns True.