Transport Templates
A transport template is a prepackaged .vhb that gives you everything above
the device-specific behavior for one communication mode — the intercept targets,
the syscall vocabulary, the decode schemas, and a dump-and-default TCL stub for
every call — so you only fill in the logic that makes your device your device.
Importing a template is one action: File → New from Template. You pick a
transport, EMU opens a fresh unsaved session pre-wired with that transport’s
targets, embedded schemas, and stubs. Run your unmodified target application
against it and every intercepted call prints its decoded fields; turn the mock
into a working device by editing only the marked USER-LOGIC sections.
Bundled transports
| Template | Intercept target | Class |
|---|---|---|
usb | /dev/bus/usb/*/* (usbfs ioctls incl. async URB submit/reap) | device-file |
i2c | /dev/i2c-* (I2C_RDWR, I2C_SMBUS, slave/funcs) | device-file |
spi | /dev/spidev* (mode/speed/bits, SPI_IOC_MESSAGE) | device-file |
tty | /dev/ttyS*, ttyUSB*, ttyACM* (termios, modem lines) | device-file |
hidraw | /dev/hidraw* (report descriptor + report streams) | device-file |
socket-inet | AF_INET / AF_INET6 TCP/UDP | socket-family |
wifi-nl80211 | AF_NETLINK/NETLINK_GENERIC → nl80211 | socket-family |
bluetooth-hci | AF_BLUETOOTH/BTPROTO_HCI | socket-family |
socketcan | AF_CAN/CAN_RAW | socket-family |
Intercept target syntax
Device-file transports target glob patterns (e.g. /dev/bus/usb/*/*,
/dev/ttyUSB*). Socket-family transports use the socket: prefix:
socket:AF_FAMILY or socket:AF_FAMILY/PROTOCOL, for example
socket:AF_CAN/CAN_RAW or socket:AF_NETLINK/NETLINK_GENERIC. A family-only
spec matches any protocol of that family.
Anatomy of a template
A template is authored as a source directory and compiled with
certo emu package-template:
templates/usb/
manifest.json # targets, headers, and the syscall list
tcl/
_dict_printer.tcl # shared decode-and-print helper (inlined)
open_template.tcl # one dump-and-default stub per syscall
USBDEVFS_SUBMITURB_template.tcl
...
manifest.json names the intercept targets, the kernel headers to scan for
decode schemas, and each syscall (its type, the ioctl/ schema it decodes, and
the stub file that handles it). The packager scans the headers, embeds the
resulting schemas directly in the .vhb, and inlines each stub as the syscall’s
TCL — so the produced file is fully self-contained and loads like any project.
Async transports and the waker
Asynchronous transports (USB URBs, netlink, HCI events) split one operation
across a submit syscall and a later collect syscall. The stubs hold the
completion in device-model state and hand it back on collect. When the target
blocks in poll/epoll between the two calls, the stub announces readiness
with certo_set_ready_in; see
Asynchronous Readiness.
Extending a template into a real device
Each stub prints the decoded call and returns a permissive default, with the spot you edit clearly marked:
# i2c :: I2C_SMBUS — SMBus byte/word/block transfer.
set req_addr [lindex $certo::arguments 2]
set smbus [::certo::dump_struct i2c_smbus_ioctl_data $req_addr "smbus"]
# --- USER-LOGIC: implement your register map -----------------------------------
# On a read, write the register value back through the data union pointer.
# -------------------------------------------------------------------------------
certo_set_return_value 0
Device state lives in your own model variables (the stubs use globals like
::usb_pending_urbs as a starting point). See the
TCL API Reference for the decode, memory, and return-value helpers
available inside a stub.