Devices and connectors

In WHAD, devices and connectors are two different components that are used together to allow users and applications to perform various actions on wireless networks, using compatible hardware.

A device defines a compatible hardware device that is plugged to the host computer running WHAD, could it be an internal device present on a motherboard or an external device connected through a USB port. Such a device is represented in WHAD by a class that derives from the Device class acting as an interface between the framework and the real hardware used to communicate over the air with other networks and peripherals.

WHAD support the following hardware devices: - Internal bluetooth adapters (installed on motherboard), supporting at least Bluetooth version >= 4.0 - USB Bluetooth dongles, supporting at least Bluetooth version >= 4.0 - [Great Scott Gadgets’ Ubertooth One](https://greatscottgadgets.com/ubertoothone/) - [Great Scott Gadgets’ Yard Stick One](https://greatscottgadgets.com/yardstickone/) - [River Loop Security’s ApiMote](http://apimote.com/) - [Nordic nRF52840 USB dongle](https://www.nordicsemi.com/Products/Development-hardware/nRF52840-Dongle) - [Maker Diary’s nRF52840 MDK USB dongle](https://makerdiary.com/products/nrf52840-mdk-usb-dongle)

Some devices are supported natively (running a custom firmware implementing our WHAD protocol) while others are supported through a dedicated adaptation layer in a dedicated class inheriting from VirtualDevice.

Device classes are strongly tied to a specific hardware (and firmware, if required) and basically take WHAD messages in input and output WHAD messages too. WHAD protocol includes a discovery feature allowing each device to tell the host computer what domains it support (think of domains as wireless protocols) and its capabilities for each of them, as well as the set of commands supported for each domain.

Connectors on the other hand are not tied to any specific hardware but rather to a specific domain and a set of capabilities. A connector acts as a specialized role applied to a compatible hardware device, and exposes a set of actions that can be used by any application, no matter the hardware since it at least supports the required domain and capabilities. This way, any high-level interaction implemented in a connector can be used with any hardware that provides the required capabilities for a given domain, making it easier to create custom firmwares for new hardware without having to care about how some wireless attack or procedure is implemented.

Devices

WHAD provides various classes to interact with WHAD-enabled hardware:

Class whad.device.device.Device is the default class that allows WHAD devices enumeration and access. It is the main class to use to open any device, through its whad.device.Device.create() method as shown below:

from whad.device import Device

dev = Device.create("uart0")

The whad.device.device.VirtualDevice shall not be directly used. This class is used to add support for incompatible WHAD devices like the Ubertooth or the ApiMote and acts as an adaptation layer between the underlying WHAD protocol and the specific protocol used by the target hardware.

Important

The whad.device.device.WhadDevice class that is still defined in WHAD (and used in some old example scripts or documentation) is an alias for the new whad.device.device.Device class, and is meant to be deprecated in the future. This old class has been renamed to Device for clarity, and the same happened with the old default connector class whad.device.connector.WhadConnector that has been renamed to whad.device.connector.Connector.

These old classes will be marked as deprecated in a future release, with a specific EOL date announced. A warning message will be issued in case one of these classes is used in a script or a tool to give time to users to migrate to the new ones (renaming classes is enough to switch to the new implementation, APIs stay the same).

class whad.device.device.Device(index: int | None = None)[source]

WHAD hardware interface

busy() bool[source]

Check if the interface is busy.

We consider an interface as busy if there is at least one message in its output messages or in its input messages.

change_transport_speed(speed)[source]

Set device transport speed.

Optional.

classmethod check_interface(interface)[source]

Checks dynamically if the device can be instantiated.

close()[source]

Close device

property connector

Connector bound to the interface

classmethod create(interface_string)[source]

Create a specific device according to the provided interface string, formed as follows:

<device_type>[device_index][:device_identifier]

Examples:
  • uart or uart0: defines the first compatible UART device available

  • uart1: defines the second compatible UART device available

  • uart:/dev/ttyACMO: defines a compatible UART device identified by /dev/tty/ACMO

  • ubertooth or ubertooth0: defines the first available Ubertooth device

  • ubertooth:11223344556677881122334455667788: defines a Ubertooth device with serial number 11223344556677881122334455667788

classmethod create_inst(interface_string) Type[Device][source]

Helper allowing to get a device according to the interface string provided.

To make it work, every device class must implement:
  • a class attribute INTERFACE_NAME, matching the interface name

  • a class method list, returning the available devices

  • a property identifier, allowing to identify the device in a unique way

This method should NOT be used outside of this class. Use Device.create instead.

property device_id: str | None

Return device ID

discover()[source]

Performs device discovery (synchronously).

Discovery process asks the device to provide its description, including its supported domains and associated capabilities. For each domain we then query the device and get the list of supported commands.

static find(interface: str) Type[Device][source]

Find a device based on its interface string with lazy loading of available interface implementations.

Parameters:

interface (str) – Interface name following WHAD’s standard interface pattern

Returns:

Found interface object

Return type:

Device

Raise:

WhadDeviceNotFound

static get_all_ifaces()[source]

Load all known interfaces.

get_domain_capability(domain) int[source]

Get a device domain capabilities.

Parameters:

domain (Domain) – Target domain

Returns:

Domain capabilities

Return type:

DeviceDomainInfoResp

get_domain_commands(domain)[source]

Get a device supported domain commands.

Parameters:

domain (Domain) – Target domain

Returns:

Bitmask of supported commands

Return type:

int

get_domains()[source]

Get device’ supported domains.

Returns:

list of supported domains

Return type:

list

get_pending_message(timeout: float = None) Generator[HubMessage, None, None][source]

Get message waiting to be sent to the interface.

has_domain(domain) bool[source]

Checks if device supports a specific domain.

Parameters:

domain (Domain) – Domain

Returns:

True if domain is supported, False otherwise.

Return type:

bool

property hub

Retrieve the device protocol hub (parser/factory)

classmethod inc_dev_index()[source]

Inject and maintain device index.

property index: int

Get the interface index

Returns:

Interface index

Return type:

int

property info: DeviceInfo | None

Get device info object

Returns:

Device information object

Return type:

DeviceInfo

property interface

Returns the current interface of the device.

is_open() bool[source]

Determine if interface is opened.

classmethod list() list | dict[source]

Returns every available compatible devices.

lock()[source]

Lock interface for read/write operation.

on_discovery_msg(message)[source]

Method called when a discovery message is received. If a connector has been associated with the device, forward this message to this connector.

open()[source]

Handle device open

property opened: bool

Device is open ?

put_message(message: HubMessage | DeviceEvt)[source]

Process incoming message.

read() bytes[source]

Read bytes from interface (blocking).

reset()[source]

Reset device

classmethod reset_dev_index() None[source]

Reset the device index of the specified class.

Parameters:

cls (Device) – Device class

send_command(command: HubMessage, keep: Callable[[...], bool] = None)[source]

Sends a command and awaits a specific response from the device. WHAD commands usualy expect a CmdResult message, if keep is not provided then this method will by default wait for a CmdResult.

Parameters:
  • command (Message) – Command message to send to the device

  • keep – Message queue filter function (optional)

Returns:

Response message from the device

Return type:

Message

send_discover_domain_query(domain)[source]

Sends a DeviceDomainQuery message and awaits for a DeviceDomainResp answer.

send_discover_info_query(proto_version=256)[source]

Sends a DeviceInfoQuery message and awaits for a DeviceInfoResp answer.

send_message(message: HubMessage, keep: Callable[[...], bool] = None)[source]

Serializes a message and sends it to the interface, without waiting for an answer. Optionally, you can update the message queue filter if you need to wait for specific messages after the message is sent.

Parameters:
  • message (Message) – Message to send

  • keep – Message queue filter function

set_connector(connector)[source]

Set interface connector.

set_queue_filter(keep: Callable[[...], bool] = None)[source]

Set message queue filter.

property type

Returns the name of the class linked to the current device.

unlock()[source]

Unlock interface for read/write operation.

wait_for_message(timeout: float = None, keep: Callable[[...], bool] = None, command: bool = False)[source]

Configures the device message queue filter to automatically move messages that matches the filter into the queue, and then waits for the first message that matches this filter and process it.

This method is blocking until a matching message is received.

Parameters:
  • timeout (int) – Timeout

  • filter – Message queue filtering function (optional)

wait_for_single_message(timeout: float = None, keep: Callable[[...], bool] = None)[source]

Configures the device message queue filter to automatically move messages that matches the filter into the queue, and then waits for the first message that matches this filter and returns it.

write(payload: bytes) int[source]

Write payload to interface.

class whad.device.device.VirtualDevice(index: int | None = None)[source]

Virtual interface implementation.

This variant of the base Interface class provides a way to emulate an interface compatible with WHAD. This emulated compatible interface is used as an adaptation layer between WHAD’s core and third-party hardware that does not run a WHAD-enabled firmware.

send_message(message, keep=None)[source]

Send message to host.

Connectors

Connectors shall ensure the device they are linked to does support the target domain and a mimimal set of commands, and can tailor its behavior depending on the capabilities of the hardware. If a connector is linked to a device that either does not support the domain this connector is supposed to operate or lacks specific commands, a :py:whad.exceptions.UnsupportedDomain exception or a whad.exceptions.UnsupportedCapability may be raised.

WHAD provides a default connector class, whad.device.connector.Connector, that implements a set of features out-of-the-box:

  • Packet and message sniffing and processing

  • Event notification mechanism

  • Synchronous mode

Sniffing packet and messages could be useful to implement packet sniffers or intercept some specific events like disconnection of the linked hardware device. Most of the time this feature is used to sniff packets related to a target domain. The whad.device.connector.Connector.sniff() method is specifically tailored for this use. When not sniffing, packets received from the hardware device are forwarded to the connector’s packet processing methods than can be overriden by inheriting classes.

By default, the default connector class provides methods to add and remove custom event listeners (whad.device.connector.Connector.add_listener() and whad.device.connector.Connector.remove_listener()), and an additional method to send an event to the registered listeners (whad.device.connector.Connector.notify()).

Last but not least, the provided synchronous mode will disable packet forwarding and save all received packets in a reception queue, waiting for the application to retrieve and process them. Service messages will still be processed by the connector, in order to handle any device disconnection or other unexpected event that may occur. When this synchronous mode is disabled, every unprocessed packet stored in the reception queue are automatically forwarded to the connector’s packet processing methods, and will be then dispatched to the corresponding handlers.

class whad.device.connector.Connector(device: Device | None = None)[source]

Interface connector.

A connector creates a link between a device and a protocol controller.

__init__(device: Device | None = None)[source]

Constructor.

Link the device with this connector, and this connector with the provided device.

Parameters:

device (Device) – Device to be used with this connector.

add_listener(listener: Callable[[...], None], event_cls: List[Event] | Event = None)[source]

Add a connector notification listener with optional event filter.

Parameters:
  • listener (callable) – callable to handle events

  • event_cls (list, ConnectorEvent, optional) – List of event classes or single event class to match

add_locked_pdu(pdu)[source]

Add a pending Protocol Data Unit (PDU) to our locked pdus queue.

Parameters:

pdu (scapy.packet.Packet) – Packet to add to locked packets queue

add_sync_event(event: DeviceEvt)[source]

Add an event to the synchronous event queue when synchronous mode is enabled.

Parameters:

event (whad.device.DeviceEvt) – Device event to add to our queue of received events

attach_callback(callback, on_reception=True, on_transmission=True, packet: ~typing.Callable[[~scapy.packet.Packet], bool] = <function Connector.<lambda>>)[source]

Attach a new packet callback to current connector.

Parameters:
  • callback – Processing function.

  • on_reception – Boolean indicating if the callback monitors reception.

  • on_transmission – Boolean indicating if the callback monitors transmission.

  • filter – Lambda function filtering packets matching the callback.

Returns:

Boolean indicating if the callback has been successfully attached.

attach_error_callback(callback, context=None)[source]

Attach an error callback to this connector.

Parameters:
  • callback – function handling errors.

  • context – context object to pass to the error handling function.

Returns:

Boolean indicating if the callback has been successfully attached.

busy() bool[source]

Determine if this connector is busy.

clear_listeners()[source]

Clear listeners.

detach_callback(callback, on_reception=True, on_transmission=True)[source]

Detach an existing packet callback from current connector.

Parameters:
  • callback – Processing function.

  • on_reception – Boolean indicating if the callback was monitoring reception.

  • on_transmission – Boolean indicating if the callback was monitoring transmission.

Returns:

Boolean indicating if the callback has been successfully detached.

property device

Get the connector associated device instance

enable_synchronous(enabled: bool, events: bool = False)[source]

Enable or disable synchronous mode

Synchronous mode is a mode in which the connector expects sone third-party code to retrieve the received packets instead of forwarding them to the on_packet() callback. It is then possible to wait for some packet to be received and avoid the automatic behavior triggered by a call to on_packet().

Parameters:
  • enabled (bool) – If set to True, enable synchronous mode. Otherwise disable it.

  • events (bool, optional) – If set to True, synchronous mode will also capture events sent by the associate device

get_event(timeout: float | None = None) Generator[DeviceEvt, None, None][source]

Retrieve event from connector’s event queue.

Parameters:

timeout (float) – Timeout in seconds

has_locked_pdus() bool[source]

Determine if connector has locked PDUs.

Returns:

True if connector has locked PDUs, False otherwise.

Return type:

bool

property hub: ProtocolHub

Get the connector protocol hub

Returns:

Instance of ProtocolHub

Return type:

ProtocolHub

is_locked() bool[source]

Determine if the connector is locked.

Returns:

True if lock mode is enabled, False otherwise.

is_stalled() bool[source]

Determine if the interface associated with this connector is stalled, i.e. has messages awaiting processing even if closed.

Returns:

True if interface is stalled, False otherwise.

Return type:

bool

is_synchronous()[source]

Determine if the conncetor is in synchronous mode.

Returns:

True if synchronous mode is enabled, False otherwise.

join()[source]

Wait for the interface to disconnect and messages to be processed.

lock()[source]

Lock connector. A locked connector will not dispatch packets/pdus like in synchronous mode and will keep them in a waiting queue, but will dispatch them all at once when unlocked.

mark_stalled()[source]

Mark connector as stalled (pending disconnection)

migrate_callbacks(connector)[source]

Migrate callbacks to another connector

monitor_packet_rx(packet)[source]

Signals the reception of a packet and triggers execution of matching reception callbacks.

Parameters:

packet – scapy packet being received by whad-client.

monitor_packet_tx(packet)[source]

Signals the transmission of a packet and triggers execution of matching transmission callbacks.

Parameters:

packet – scapy packet being transmitted from whad-client.

notify(event)[source]

Notify listeners of a specific event.

on_any_msg(message)[source]

Callback function to process any incoming messages.

This method MAY be overriden by inherited classes.

Parameters:

message – WHAD message

on_device_event(event: DeviceEvt)[source]

Dispatch message to the connector’s handlers.

This method may trigger specific message processing in inherited connector’s classes as well as attached protocol stacks. Since it is only called by the connector’s I/O thread, that’s pretty safe.

Parameters:

event (whad.device.DeviceEvt) – Device event to process

on_disconnection()[source]

Device has disconnected or been closed.

on_discovery_msg(message)[source]

Callback function to process incoming discovery messages.

This method MUST be overriden by inherited classes.

Parameters:

message – Discovery message

on_domain_msg(domain, message)[source]

Callback function to process incoming domain-related messages.

This method MUST be overriden by inherited classes.

Parameters:

message – Domain message

on_error(error)[source]

Triggers a call to the device connector error handling registered callback(s).

on_event(event)[source]

Callback function to process incoming events.

This method MUST be overriden by inherited classes.

Parameters:

event (whad.hub.events.AbstractEvent) – Event to process

on_generic_msg(message)[source]

Callback function to process incoming generic messages.

This method MUST be overriden by inherited classes.

Parameters:

message – Generic message

on_packet(packet)[source]

Callback function to process incoming packets.

This method MUST be overriden by inherited classes.

Parameters:

packet (scapy.packet.Packet) – Packet

process_message(message: HubMessage)[source]

Process received message.

remove_listener(listener: Callable[[...], None]) bool[source]

Remove listener from registered listeners.

reset_callbacks(reception=True, transmission=True)[source]

Detach any packet callback attached to the current connector.

Parameters:
  • on_reception – Boolean indicating if the callbacks monitoring reception are detached.

  • on_transmission – Boolean indicating if the callbacks monitoring transmission are detached.

send_command(message, keep=None)[source]

Sends a command message to the underlying device and waits for an answer.

By default, this method will wait for a CmdResult message, but you can provide any other filtering function/lambda if you are expecting another message as a reply from the device.

Parameters:
  • message (Message) – WHAD message to send to the device

  • filter – Filtering function used to match the expected response from the device.

send_event(event: DeviceEvt)[source]

Send an event into the connector event queue.

Parameters:

event (DeviceEvt) – Event to add to the connector’s event queue

send_message(message, keep=None)[source]

Sends a message to the underlying device without waiting for an answer.

Parameters:
  • message (Message) – WHAD message to send to the device.

  • filter – optional filter function for incoming message queue.

send_packet(packet)[source]

Send packet to our device.

set_device(device=None)[source]

Set device linked to this connector.

Parameters:

device (WhadDevice) – Device to be used with this connector.

sniff(messages: List = None, timeout: float = None) Generator[HubMessage, None, None][source]

Enable sniffing mode and report any received messages, optionally filtered by their type/classes if messages is provided.

Parameters:
  • messages – If specified, sniff only messages that match the given types.

  • messages – List, optional

  • timeout (float, optional) – If specified, set a sniffing timeout in seconds

unlock(dispatch_callback=None)[source]

Unlock connector and dispatch pending PDUs.

Parameters:

dispatch_callback (callable) – PDU dispatch callback that overrides the internal dispatch routine

wait_for_message(timeout=None, keep=None, command=False)[source]

Waits for a specific message to be received.

This method reads the message queue and return the first message that matches the provided filter. A timeout can be specified and will cause this method to return None if this timeout is reached.

wait_packet(timeout: float = None)[source]

Wait for a packet when in synchronous mode. This method should be only used with SYNC_MODE_PKT to avoid discarding any device event.

Parameters:

timeout (float, optional) – If specified, defines a timeout when querying the PDU queue

Returns:

Received packet if any, None if empty or when timeout is reached

Return type:

scapy.packet.Packet

class whad.device.connector.LockedConnector(device)[source]

Provides a lockable connector.

__init__(device)[source]

Constructor.

Link the device with this connector, and this connector with the provided device.

Parameters:

device (Device) – Device to be used with this connector.

Deprecated classes

In early versions of WHAD, device and connector base classes had different names that were too long and badly chosen. We took the decision to rename them while keeping the old classes as aliases of the new ones. This way, old code and documentation and even tools keep working as expected, until we decide to definitely deprecate these old classes. For now, they are still defined and available, but we will mark them as deprecated starting from version 1.3.

A warning will be displayed each time such a class is used, inciting users and maintainers to update their code to use the new classes, which are strictly compatible as they expose the same methods and properties. This warning will include a deadline after which these classes will be definitely removed.

class whad.device.device.WhadDevice(index: int | None = None)[source]

Bases: Device

This class is an alias for whad.device.device.Device, and will be deprecated in a near future. This class has been introduced in a previous version of WHAD and has been renamed for clarity purpose.

class whad.device.device.WhadVirtualDevice(index: int | None = None)[source]

Bases: VirtualDevice

This class is an alias for whad.device.device.VirtualDevice, and will be deprecated in a near future. This class has been introduced in a previous version of WHAD and has been renamed for clarity purpose.

class whad.device.connector.WhadDeviceConnector(device: Device | None = None)[source]

Bases: Connector

This class is an alias for whad.device.connector.Connector, and will be deprecated in a near future. This class has been introduced in a previous version of WHAD and has been renamed for clarity purpose.