diff --git a/contrib/ci/fedora.sh b/contrib/ci/fedora.sh
index b2a51a0ba..37a515f27 100755
--- a/contrib/ci/fedora.sh
+++ b/contrib/ci/fedora.sh
@@ -65,6 +65,7 @@ cp $HOME/rpmbuild/RPMS/*/*.rpm dist
if [ "$CI" = "true" ]; then
sed "s,^DisabledPlugins=.*,DisabledPlugins=," -i /etc/fwupd/daemon.conf
+ sed "s,^AllowEmulation=false,AllowEmulation=true," -i /etc/fwupd/daemon.conf
# set up enough PolicyKit and D-Bus to run the daemon
mkdir -p /run/dbus
diff --git a/data/bash-completion/fwupdmgr b/data/bash-completion/fwupdmgr
index 34001e0dd..572408e7e 100644
--- a/data/bash-completion/fwupdmgr
+++ b/data/bash-completion/fwupdmgr
@@ -4,9 +4,14 @@ _fwupdmgr_cmd_list=(
'clear-results'
'disable-remote'
'device-test'
+ 'device-emulate'
'downgrade'
'download'
'enable-remote'
+ 'emulation-tag'
+ 'emulation-untag'
+ 'emulation-load'
+ 'emulation-save'
'get-approved-firmware'
'get-bios-setting'
'get-blocked-firmware'
diff --git a/data/daemon.conf b/data/daemon.conf
index 4dbb02077..92a9fb459 100644
--- a/data/daemon.conf
+++ b/data/daemon.conf
@@ -52,6 +52,9 @@ OnlyTrusted=true
# Show private data like device serial numbers and instance IDs to clients
ShowDevicePrivate=true
+# Allow capturing and loading device emulation
+AllowEmulation=false
+
# UIDs that should marked as trusted
TrustedUids=
diff --git a/data/device-tests/analogix-anx7518.json b/data/device-tests/analogix-anx7518.json
index 296c6da3a..4cdb29f5a 100644
--- a/data/device-tests/analogix-anx7518.json
+++ b/data/device-tests/analogix-anx7518.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/5d860747c1378ef8921f95e41bbb7055dc9b470043655a65b00157ab74dd12e8-Analogix-ANX7518-fw-1.5.01-rx-1206.cab",
+ "emulation-url": "https://fwupd.org/downloads/63698f1d36491d795f36335a28e301e7484b1735c63aa763c007c149cc74569c-Analogix-ANX7518-fw-1.5.01-rx-1206.zip",
"components": [
{
"version": "0001.1501",
@@ -15,6 +16,7 @@
},
{
"url": "https://fwupd.org/downloads/2e4b5747ea2659ddae893a7b8a238d6d9c3166b426c0d0e5feb308a443662c38-Analogix-ANX7518-fw-1.5.08.cab",
+ "emulation-url": "https://fwupd.org/downloads/ff75670536d3de6def5cb4e6e1377dc1ef132882288dd6ceee37c0aecc3fbda3-Analogix-ANX7518-fw-1.5.08.zip",
"components": [
{
"version": "0001.1508",
diff --git a/data/device-tests/caldigit-ts4.json b/data/device-tests/caldigit-ts4.json
index 1d227eeb0..f31f62cde 100644
--- a/data/device-tests/caldigit-ts4.json
+++ b/data/device-tests/caldigit-ts4.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/dbeeba12332296ae68eaa104ed3b4b437659bfe027e347010a87c75bcea87292-CalDigit-TS4-combind-050322-secure-Cus-22.cab",
+ "emulation-url": "https://fwupd.org/downloads/e755191ff3c7d26777273262eff0be266758ebe5b79685cd44d14d491ab6107a-CalDigit-TS4-combind-050322-secure-Cus-22.zip",
"components": [
{
"version": "F907.14.10",
@@ -15,6 +16,7 @@
},
{
"url": "https://fwupd.org/downloads/161f0243e4f1222114b941b7dce057b962e38f9f9a1e799437531afcd5a68677-CalDigit-TS4-combind-121022-secure-Cus-37.cab",
+ "emulation-url": "https://fwupd.org/downloads/21c81548a6126f806966497613bc6e33ddd5b3dc045e7cc83b27abd64ba6afd8-CalDigit-TS4-combind-121022-secure-Cus-37.zip",
"components": [
{
"version": "F907.14.13",
diff --git a/data/device-tests/fwupd-a3bu-xplained.json b/data/device-tests/fwupd-a3bu-xplained.json
index 9ffb0cae1..00cd1ab3b 100644
--- a/data/device-tests/fwupd-a3bu-xplained.json
+++ b/data/device-tests/fwupd-a3bu-xplained.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/f5bbeaba1037dce31dd12f349e8148ae35f98b61-a3bu-xplained123.cab",
+ "emulation-url": "https://fwupd.org/downloads/01b95b0206f1a42a2bf95a432d162ef1f9f1f71edb5696127c923ceffadfdf68-a3bu-xplained123.zip",
"components": [
{
"version": "1.23",
@@ -15,6 +16,7 @@
},
{
"url": "https://fwupd.org/downloads/24d838541efe0340bf67e1cc5a9b95526e4d3702-a3bu-xplained124.cab",
+ "emulation-url": "https://fwupd.org/downloads/357483755bbc4f3a33c0b5bc05a0ef49654be0a15c14cbde2bdf0cd9c203d632-a3bu-xplained124.zip",
"components": [
{
"version": "1.24",
diff --git a/data/device-tests/fwupd-at90usbkey.json b/data/device-tests/fwupd-at90usbkey.json
index f0363b5d2..69635eadf 100644
--- a/data/device-tests/fwupd-at90usbkey.json
+++ b/data/device-tests/fwupd-at90usbkey.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/b6bef375597e848971f230cf992c9740f7bf5b92-at90usbkey123.cab",
+ "emulation-url": "https://fwupd.org/downloads/57c8acd0de45ff01d91da5ecec1f826fbb438cc3f7b11ca732a8fbfdc2ce24e1-at90usbkey123.zip",
"components": [
{
"version": "1.23",
@@ -15,6 +16,7 @@
},
{
"url": "https://fwupd.org/downloads/47807fd4a94a4d5514ac6bf7a73038e00ed63225-at90usbkey124.cab",
+ "emulation-url": "https://fwupd.org/downloads/e65432a2dd63c3ce666cc13a0f5ae13eb6d15cebbc1d022cafcff46b892cdcc4-at90usbkey124.zip",
"components": [
{
"version": "1.24",
diff --git a/data/device-tests/google-servo-micro.json b/data/device-tests/google-servo-micro.json
index adc77aafd..f629b542d 100644
--- a/data/device-tests/google-servo-micro.json
+++ b/data/device-tests/google-servo-micro.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/3c3123b6eaa89d5469553b301210a9e4cb06efa98a1cb7c6847a1d10bb0a0c4b-servo_micro_v2.4.0.cab",
+ "emulation-url": "https://fwupd.org/downloads/72f84e212c5c5976bd46ea0fa4f1dda26b4c41f35ac01aaa212867e672690726-servo_micro_v2.4.0.zip",
"components": [
{
"version": "2.4.0",
@@ -15,6 +16,7 @@
},
{
"url": "https://fwupd.org/downloads/1dc362734f138e71fa838ac5503491d297715d7e9bccc0424a62c4a5c68526cc-servo_micro_v2.4.17.cab",
+ "emulation-url": "https://fwupd.org/downloads/b5f152b8e1f814375ae586bdac9536b70b3a9cd1a2a12bf685e8da50eef78f2a-servo_micro_v2.4.17.zip",
"components": [
{
"version": "2.4.17",
diff --git a/data/device-tests/hughski-colorhug-plus.json b/data/device-tests/hughski-colorhug-plus.json
index e5313a398..6249442ed 100644
--- a/data/device-tests/hughski-colorhug-plus.json
+++ b/data/device-tests/hughski-colorhug-plus.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/5cbff92158331aeb10008ca36fa918a9637dde7bfe31de3e0523d14090be8977-fakedevice01_dfu.cab",
+ "emulation-url": "https://fwupd.org/downloads/8be5c7f3fe5c399a8699768da3f4ddd2582df19c70e197b852efaa027f42688b-fakedevice01_dfu.zip",
"components": [
{
"version": "0.1",
@@ -16,6 +17,7 @@
},
{
"url": "https://fwupd.org/downloads/8bc3afd07a0af3baaab8b19893791dd3972e8305-fakedevice02_dfu.cab",
+ "emulation-url": "https://fwupd.org/downloads/72c580400020f22929510e9fec43d6781d56af697e72e8d560b45daa57e1817c-fakedevice02_dfu.zip",
"components": [
{
"version": "0.2",
diff --git a/data/device-tests/hughski-colorhug.json b/data/device-tests/hughski-colorhug.json
index 96fef5e0e..0faba9ca0 100644
--- a/data/device-tests/hughski-colorhug.json
+++ b/data/device-tests/hughski-colorhug.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/9a4e77009da7d3b5f15a1388afeb9e5d41a5a8ae-hughski-colorhug2-1.2.5.cab",
+ "emulation-url": "https://fwupd.org/downloads/08ddc01a50fb866398d01cc4829531640e651f0233306e10a54117f88474b153-hughski-colorhug-1.2.5.zip",
"components": [
{
"version": "1.2.5",
@@ -15,6 +16,7 @@
},
{
"url": "https://fwupd.org/downloads/2a066c8a1bfbd99f161c867b4dbe7e51ac36fc2b16ef37b11d18419874fbcb6c-hughski-colorhug-1.2.6.cab",
+ "emulation-url": "https://fwupd.org/downloads/b25122f17912467c1488aaadb59483efc3fe773cb7d002f64289177ca2f3285c-hughski-colorhug-1.2.6.zip",
"components": [
{
"version": "1.2.6",
diff --git a/data/device-tests/hughski-colorhug2.json b/data/device-tests/hughski-colorhug2.json
index 7c0e1db06..19d0a6921 100644
--- a/data/device-tests/hughski-colorhug2.json
+++ b/data/device-tests/hughski-colorhug2.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab",
+ "emulation-url": "https://fwupd.org/downloads/5ddb5d8e9ac85ea05b8dafcad638befdf3e70e5e1db0ca47489fa43d93f33a57-hughski-colorhug2-2.0.6.zip",
"components": [
{
"version": "2.0.6",
@@ -15,6 +16,7 @@
},
{
"url": "https://fwupd.org/downloads/e5ad222bdbd3d3d48d8613e67c7e0a0e194f8cd828e33c554d9f05d933e482c7-hughski-colorhug2-2.0.7.cab",
+ "emulation-url": "https://fwupd.org/downloads/29ee025e65b6a469556bbdd819cba665b043244c90c65b9f659933a347f8cf2e-hughski-colorhug2-2.0.7.zip",
"components": [
{
"version": "2.0.7",
diff --git a/data/device-tests/lenovo-03x7609-cxaudio.json b/data/device-tests/lenovo-03x7609-cxaudio.json
index 6d6828fc0..5217dfe6a 100644
--- a/data/device-tests/lenovo-03x7609-cxaudio.json
+++ b/data/device-tests/lenovo-03x7609-cxaudio.json
@@ -5,6 +5,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/2e0bf8aaf9c63ca11cfe3444d032277c21ec0d678e5963123a8b33e5dcd37d99-Lenovo-ThinkPad-USBCGen2Dock-Firmware-49-0E-14.cab",
+ "emulation-url": "https://fwupd.org/downloads/9b6a1401bbd5ab3304a50a00dfc6d17d853bfbfd63f80c0360774d0e9e46e772-Lenovo-ThinkPad-USBCGen2Dock-Firmware-49-0E-14.zip",
"components": [
{
"name": "cxaudio",
diff --git a/data/device-tests/lenovo-GX90T33021-vli.json b/data/device-tests/lenovo-GX90T33021-vli.json
index 7e7f43600..eeb2db3ac 100644
--- a/data/device-tests/lenovo-GX90T33021-vli.json
+++ b/data/device-tests/lenovo-GX90T33021-vli.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/2004603b40bd529d85c2fcfcf9d50d77b47229e6564ef0ea9c03633bdccff94d-Lenovo-Travel_Hub_1in3_New.cab",
+ "emulation-url": "https://fwupd.org/downloads/de48f2281354c4479b8b2302cf85b9643b0ca248fcf4beb19a71f5aba7927278-Lenovo-Travel_Hub_1in3_New.zip",
"components": [
{
"name": "tier1",
@@ -16,10 +17,11 @@
},
{
"url": "https://fwupd.org/downloads/5fb4f4dce233626558806c2b7474d3b5ed4f500f6013aa3b376a54322642d449-Lenovo-Travel_Hub_1in3_Old.cab",
+ "emulation-url": "https://fwupd.org/downloads/58221c63de8cdec9b4db3cc72b1e0303b6cfee80b7e7965edad9816c124b0cd7-Lenovo-Travel_Hub_1in3_Old.zip",
"components": [
{
"name": "tier1",
- "version": "04.33",
+ "version": "4.33",
"guids": [
"7636b85e-d79f-5d30-a329-458957958b88"
]
diff --git a/data/device-tests/realtek-rts5423.json b/data/device-tests/realtek-rts5423.json
index 8a753d6dc..e45a12582 100644
--- a/data/device-tests/realtek-rts5423.json
+++ b/data/device-tests/realtek-rts5423.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/460a18d93f5d6118908d8437009ebbd6eceb3c6a0cdfbaf31f8a96df008564da-Realtek-RTS5423-1.56.cab",
+ "emulation-url": "https://fwupd.org/downloads/eaa82f84d31f524f48369c9b46698ff00bf6e398dbb66fb7fed3e6808c69789c-Realtek-RTS5423-1.56.zip",
"components": [
{
"version": "1.56",
@@ -15,6 +16,7 @@
},
{
"url": "https://fwupd.org/downloads/659e721b0efbe2dfc003d3d30ea5b771c00113cc9a162333ee5a8918c4515f69-Realtek-RTS5423-1.57.cab",
+ "emulation-url": "https://fwupd.org/downloads/962e175bf7809183620ecfad90583e912fb0e16a98c44b0ab539468fd340fc8a-Realtek-RTS5423-1.57.zip",
"components": [
{
"version": "1.57",
diff --git a/data/device-tests/realtek-rts5855.json b/data/device-tests/realtek-rts5855.json
index c19ff22b1..77e4e4405 100644
--- a/data/device-tests/realtek-rts5855.json
+++ b/data/device-tests/realtek-rts5855.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/ff989c4b71c92a4a217dfb2f82c1c87691b8eb31-rts5855_v0.4.cab",
+ "emulation-url": "https://fwupd.org/downloads/75d6c26e3842bbd00991a585078e872cae66c7a81e15c5736fc478ba5ec0f77d-rts5855_v0.4.zip",
"components": [
{
"version": "0.4",
diff --git a/data/device-tests/synaptics-prometheus.json b/data/device-tests/synaptics-prometheus.json
index d0573d151..d60181da3 100644
--- a/data/device-tests/synaptics-prometheus.json
+++ b/data/device-tests/synaptics-prometheus.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/7c260a13ea6df444f7a1fa8fa2bf431876a8c7203b6aeac371564aad30756ff1-Synaptics-Prometheus-10.01.3121519.cab",
+ "emulation-url": "https://fwupd.org/downloads/73877ed7c8c15dac6adf04db32bd5e9f1e702229cfdb266fe98855ee53929cd1-Synaptics-Prometheus-10.01.3121519.zip",
"components": [
{
"version": "10.01.3121519",
diff --git a/data/device-tests/ugreen-cm260.json b/data/device-tests/ugreen-cm260.json
index 28e35ad9e..ed135dfaf 100644
--- a/data/device-tests/ugreen-cm260.json
+++ b/data/device-tests/ugreen-cm260.json
@@ -3,7 +3,7 @@
"interactive": false,
"steps": [
{
- "url": "https://fwupd.org/downloads/53cd390673287a99e5d4cc4cf30b5fade0f5e708fe0b1bd424bf36467642d76c-Ugreen-CM260-7.2.1.0.cab",
+ "url": "https://fwupd.org/downloads/de152591660080ed5de8550b1b2dfcd738d1f5a68bd30e5f4c684b39b9ebeeb2-Ugreen-CM260-7.2.1.0.cab",
"components": [
{
"version": "7.2.1.0",
@@ -14,10 +14,10 @@
]
},
{
- "url": "https://fwupd.org/downloads/eec2c6216fa86077504587633fc8d018a366a1cfe3df8dd9c575c2e8ef0ef423-Ugreen-CM260-7.2.2.0.cab",
+ "url": "https://fwupd.org/downloads/9480d06d1a3e524609660653fadc337d14d72f362bd2e80b15c5c3c1733f629b-Ugreen-CM260-7.2.2.0.cab",
"components": [
{
- "version": "7.2.1.0",
+ "version": "7.2.2.0",
"guids": [
"7afc5bff-be55-5e95-81ca-584f13207b1d"
]
diff --git a/data/device-tests/wacom-intuos-bt-m.json b/data/device-tests/wacom-intuos-bt-m.json
index bcf88e653..febdd4707 100644
--- a/data/device-tests/wacom-intuos-bt-m.json
+++ b/data/device-tests/wacom-intuos-bt-m.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/d16b682de56c42b134f1e5af7f9926c62dc246df851648e49aa1d1d0e5b38532-Wacom-Intuos_BT-M_MainFW-1.66.cab",
+ "emulation-url": "https://fwupd.org/downloads/3cfdf76725fe4f58a2f0b2e41e402e6e76af37cde526f8be72b3382b230da9e1-Wacom-Intuos_BT-M_MainFW-1.66.zip",
"components": [
{
"name": "main",
@@ -16,6 +17,7 @@
},
{
"url": "https://fwupd.org/downloads/1ee4f3dc9fd08acd4c6bc833b25e7061f85ddd40122148d66940cd3ddd748920-Wacom-Intuos_BT-M_BluetoothFW-1.12.cab",
+ "emulation-url": "https://fwupd.org/downloads/2579550908be1d3e81f0776f2b26e68f6b95e485e58c35f8b02ffdb5dec1ee13-Wacom-Intuos_BT-M_BluetoothFW-1.12.zip",
"components": [
{
"name": "bluetooth",
diff --git a/data/device-tests/wistron-dock-40b7.json b/data/device-tests/wistron-dock-40b7.json
index 48dc52c37..3b687ddf7 100644
--- a/data/device-tests/wistron-dock-40b7.json
+++ b/data/device-tests/wistron-dock-40b7.json
@@ -4,6 +4,7 @@
"steps": [
{
"url": "https://fwupd.org/downloads/f00838f10d6faf58ff57cbfcfca390ed63bd804e4c654f19ff89423b286dc5d2-lda_viking_r_composite.cab",
+ "emulation-url": "https://fwupd.org/downloads/08c477c340e51990fc5df84907149f513aef65ae7f828b469e0a14ac01579a34-lda_viking_r_composite.zip",
"components": [
{
"version": "1.0.1.4",
@@ -15,6 +16,7 @@
},
{
"url": "https://fwupd.org/downloads/43bcfb3278ad2baf918af6abc972e0de9a0de66a25b012177b00d96664819010-lda_viking_r_composite.cab",
+ "emulation-url": "https://fwupd.org/downloads/330411c3eaaba8b7e8b51108fddc614963af5b5fcc0fd328683197491584f387-lda_viking_r_composite2.zip",
"components": [
{
"version": "1.0.1.6",
diff --git a/data/fish-completion/fwupdmgr.fish b/data/fish-completion/fwupdmgr.fish
index 85fe77e25..8bd9b3ca0 100644
--- a/data/fish-completion/fwupdmgr.fish
+++ b/data/fish-completion/fwupdmgr.fish
@@ -67,6 +67,10 @@ complete -c fwupdmgr -n '__fish_use_subcommand' -x -a verify-update -d 'Update t
complete -c fwupdmgr -n '__fish_use_subcommand' -x -a inhibit -d 'Inhibit the system to prevent upgrades'
complete -c fwupdmgr -n '__fish_use_subcommand' -x -a uninhibit -d 'Uninhibit the system to allow upgrades'
complete -c fwupdmgr -n '__fish_use_subcommand' -x -a quit -d 'Asks the daemon to quit'
+complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-load -d 'Load device emulation data'
+complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-save -d 'Save device emulation data'
+complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-tag -d 'Adds devices to watch for future emulation'
+complete -c fwupdmgr -n '__fish_use_subcommand' -x -a emulation-untag -d 'Removes devices to watch for future emulation'
# commands exclusively consuming device IDs
set -l deviceid_consumers activate clear-results downgrade get-releases get-results get-updates reinstall switch-branch unlock update verify verify-update
diff --git a/data/installed-tests/fwupd.sh b/data/installed-tests/fwupd.sh
index 320ded0d5..ae553c5c0 100755
--- a/data/installed-tests/fwupd.sh
+++ b/data/installed-tests/fwupd.sh
@@ -1,12 +1,11 @@
#!/bin/sh
exec 2>&1
-dirname=`dirname $0`
run_test()
{
- if [ -f $dirname/$1 ]; then
- $dirname/$1
+ if [ -f @installedtestsbindir@/$1 ]; then
+ @installedtestsbindir@/$1
rc=$?; if [ $rc != 0 ]; then exit $rc; fi
fi
}
@@ -27,5 +26,13 @@ run_test synaptics-prometheus-self-test
run_test dfu-self-test
run_test mtd-self-test
+# grab device tests from the CDN to avoid incrementing the download counter
+export FWUPD_DEVICE_TESTS_BASE_URI=http://cdn.fwupd.org/downloads
+for f in `grep --files-with-matches -r emulation-url @devicetestdir@`; do
+ echo "Emulating for $f"
+ fwupdmgr device-emulate --no-unreported-check --no-remote-check --no-metadata-check "$f"
+ rc=$?; if [ $rc != 0 ]; then exit $rc; fi
+done
+
# success!
exit 0
diff --git a/data/installed-tests/meson.build b/data/installed-tests/meson.build
index be3d5c6d9..dfce86b1c 100644
--- a/data/installed-tests/meson.build
+++ b/data/installed-tests/meson.build
@@ -2,6 +2,7 @@ con2 = configuration_data()
con2.set('installedtestsdir', installed_test_datadir)
con2.set('installedtestsbindir', installed_test_bindir)
con2.set('installedtestsdatadir', installed_test_datadir)
+con2.set('devicetestdir', join_paths(datadir, 'fwupd', 'device-tests'))
con2.set('bindir', bindir)
con2.set('libexecdir', libexecdir)
@@ -44,9 +45,11 @@ install_data([
install_dir: installed_test_datadir,
)
-install_data([
- 'fwupd.sh',
- ],
+configure_file(
+ input: 'fwupd.sh',
+ output: 'fwupd.sh',
+ configuration: con2,
+ install: true,
install_dir: installed_test_bindir,
)
diff --git a/debian/fwupd-tests.postinst b/debian/fwupd-tests.postinst
index 81a462c45..d8e1f3801 100644
--- a/debian/fwupd-tests.postinst
+++ b/debian/fwupd-tests.postinst
@@ -8,6 +8,7 @@ if [ "$1" = configure ] && [ -z "$2" ]; then
if [ -f /etc/fwupd/daemon.conf ]; then
if [ "$CI" = "true" ]; then
sed "s,^DisabledPlugins=.*,DisabledPlugins=," -i /etc/fwupd/daemon.conf
+ sed "s,^AllowEmulation=false,AllowEmulation=true," -i /etc/fwupd/daemon.conf
else
echo "To enable test suite, modify /etc/fwupd/daemon.conf"
fi
diff --git a/debian/fwupd-tests.postrm b/debian/fwupd-tests.postrm
index c43835526..d5bae7d3e 100644
--- a/debian/fwupd-tests.postrm
+++ b/debian/fwupd-tests.postrm
@@ -7,6 +7,7 @@ if [ "$1" = remove -o "$1" = purge ]; then
if [ -f /etc/fwupd/daemon.conf ]; then
if [ "$CI" = "true" ]; then
sed "s,^DisabledPlugins=,DisabledPlugins=test," -i /etc/fwupd/daemon.conf
+ sed "s,^AllowEmulation=true,AllowEmulation=false," -i /etc/fwupd/daemon.conf
else
echo "To disable test suite, modify /etc/fwupd/daemon.conf"
fi
diff --git a/debian/tests/ci b/debian/tests/ci
index 2ecab73f2..4b71089d7 100755
--- a/debian/tests/ci
+++ b/debian/tests/ci
@@ -4,6 +4,7 @@ set -e
modprobe mtdram 2>&1 || true
sed "s,^DisabledPlugins=.*,DisabledPlugins=," -i /etc/fwupd/daemon.conf
sed "s,^VerboseDomains=.*,VerboseDomains=*," -i /etc/fwupd/daemon.conf
+sed "s,^AllowEmulation=false,AllowEmulation=true," -i /etc/fwupd/daemon.conf
sed "s,ConditionVirtualization=.*,," \
/lib/systemd/system/fwupd.service > \
/etc/systemd/system/fwupd.service
diff --git a/docs/device-emulation.md b/docs/device-emulation.md
new file mode 100644
index 000000000..d3061c5ee
--- /dev/null
+++ b/docs/device-emulation.md
@@ -0,0 +1,71 @@
+---
+title: Device Emulation
+---
+
+## Introduction
+
+Using device-tests, fwupd can prevent regressions by updating and downgrading firmware on real
+hardware. However, much past a few dozen devices this does not scale, either by time, or because
+real devices need plugging in and out. We can unit test the plugin internals, but this does not
+actually test devices being attached, removed, and being updated.
+
+By recording the backend devices we can build a "history" in GUsb of what control, interrupt and
+bulk transfers were sent to, and received from the device. By dumping these we can "replay" the
+update without the physical hardware connected.
+
+There are some problems that make this emulation slightly harder than the naive implementation:
+
+* Devices are sometimes detached into a different "bootloader" device with a new VID:PID
+* Devices might be "composite" and actually be multiple logical devices in one physical device
+* Devices might not be 100% deterimistic, e.g. queries might be processed out-of-order
+
+For people to generate and consume emulated devices, we do need to make the process easy to
+understand, and also easy to use. Some key points that we think are important, is the ability to:
+
+* Dump the device of an unmodified running daemon.
+* Filter to multiple or single devices, to avoid storing data for unrelated parts of the system.
+* Load an emulated device into an unmodified running daemon.
+
+Because we do not want to modify the daemon, we think it makes sense to *load* and *save* emulation
+state over D-Bus. Each phase can be controlled, which makes it easy to view, and edit, the recorded
+emulation data.
+
+For instance, calling `fwupdmgr emulation-tag` would ask the end user to choose a device to start
+*recording* so that subsequent re-plugs are available to save.
+As the device state may not be persistent we save the device-should-be-recorded metadata in the
+pending database like we would do for a successful firmware update.
+
+To demo this, something like this could be done:
+
+ # connect ColorHug2
+ fwupdmgr modify-config AllowEmulation true
+ fwupdmgr emulation-tag b0a78eb71f4eeea7df8fb114522556ba8ce22074
+ # or, using the GUID
+ # fwupdmgr emulation-tag 2082b5e0-7a64-478a-b1b2-e3404fab6dad
+ # remove and re-insert ColorHug2
+ fwupdmgr get-devices --filter emulation-tag
+ fwupdmgr download https://fwupd.org/downloads/170f2c19f17b7819644d3fcc7617621cc3350a04-hughski-colorhug2-2.0.6.cab
+ fwupdmgr install e5* --allow-reinstall
+ fwupdmgr emulation-save colorhug.zip
+ # remove ColorHug2
+ fwupdmgr emulation-load colorhug.zip
+ fwupdmgr get-devices --filter emulated
+ fwupdmgr install e5* --allow-reinstall
+ fwupdmgr modify-config AllowEmulation false
+
+## Device Tests
+
+The `emulation-url` string parameter can be specified in the `steps` section of a specific device
+test. This causes the front end to load the emulation data before running the specific step.
+
+Device tests without emulation data will be skipped.
+
+For example:
+
+ fwupdmgr device-emulate ../data/device-tests/hughski-colorhug2.json
+ Decompressing… [***************************************]
+ Waiting… [***************************************]
+ Hughski ColorHug2: OK!
+ Decompressing… [***************************************]
+ Waiting… [***************************************]
+ Hughski ColorHug2: OK!
diff --git a/docs/index.html b/docs/index.html
index 13086a6b4..2de205113 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -48,6 +48,9 @@
+
diff --git a/libfwupd/fwupd-client-sync.c b/libfwupd/fwupd-client-sync.c
index a52d89909..c2383b33d 100644
--- a/libfwupd/fwupd-client-sync.c
+++ b/libfwupd/fwupd-client-sync.c
@@ -2566,3 +2566,113 @@ fwupd_client_upload_bytes(FwupdClient *self,
}
return g_steal_pointer(&helper->bytes);
}
+
+static void
+fwupd_client_emulation_load_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+ FwupdClientHelper *helper = (FwupdClientHelper *)user_data;
+ helper->ret = fwupd_client_emulation_load_finish(FWUPD_CLIENT(source), res, &helper->error);
+ g_main_loop_quit(helper->loop);
+}
+
+/**
+ * fwupd_client_emulation_load
+ * @self: a #FwupdClient
+ * @data: archive data of JSON files
+ * @cancellable: (nullable): optional #GCancellable
+ * @error: (nullable): optional return location for an error
+ *
+ * Loads an emulated device into the daemon backend that has the phases set by the JSON data,
+ * for instance, having one USB device emulated for the bootloader and another emulated for the
+ * runtime interface.
+ *
+ * Returns: %TRUE for success
+ *
+ * Since: 1.8.11
+ **/
+gboolean
+fwupd_client_emulation_load(FwupdClient *self,
+ GBytes *data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(FwupdClientHelper) helper = NULL;
+
+ g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE);
+ g_return_val_if_fail(data != NULL, FALSE);
+ g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), FALSE);
+ g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+
+ /* connect */
+ if (!fwupd_client_connect(self, cancellable, error))
+ return FALSE;
+
+ /* call async version and run loop until complete */
+ helper = fwupd_client_helper_new(self);
+ fwupd_client_emulation_load_async(self,
+ data,
+ cancellable,
+ fwupd_client_emulation_load_cb,
+ helper);
+ g_main_loop_run(helper->loop);
+ if (!helper->ret) {
+ g_propagate_error(error, g_steal_pointer(&helper->error));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void
+fwupd_client_emulation_save_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+ FwupdClientHelper *helper = (FwupdClientHelper *)user_data;
+ helper->bytes =
+ fwupd_client_emulation_save_finish(FWUPD_CLIENT(source), res, &helper->error);
+ g_main_loop_quit(helper->loop);
+}
+
+/**
+ * fwupd_client_emulation_save:
+ * @self: a #FwupdClient
+ * @cancellable: (nullable): optional #GCancellable
+ * @error: (nullable): optional return location for an error
+ *
+ * Gets the captured data from all filtered devices for all recorded phases. The data is returned
+ * in a ZIP archive of JSON output.
+ *
+ * NOTE: Device events are not automatically recorded for all devices. You must call something
+ * like `ModifyDevice(device_id, 'flags','emulation-tag')` to start the recording the backend.
+ *
+ * Once the device has been re-inserted then the emulation data will be available using
+ * this API call.
+ *
+ * Returns: (transfer full): archive data
+ *
+ * Since: 1.8.11
+ **/
+GBytes *
+fwupd_client_emulation_save(FwupdClient *self, GCancellable *cancellable, GError **error)
+{
+ g_autoptr(FwupdClientHelper) helper = NULL;
+
+ g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL);
+ g_return_val_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable), NULL);
+ g_return_val_if_fail(error == NULL || *error == NULL, NULL);
+
+ /* connect */
+ if (!fwupd_client_connect(self, cancellable, error))
+ return NULL;
+
+ /* call async version and run loop until complete */
+ helper = fwupd_client_helper_new(self);
+ fwupd_client_emulation_save_async(self,
+ cancellable,
+ fwupd_client_emulation_save_cb,
+ helper);
+ g_main_loop_run(helper->loop);
+ if (helper->bytes == NULL) {
+ g_propagate_error(error, g_steal_pointer(&helper->error));
+ return NULL;
+ }
+ return g_steal_pointer(&helper->bytes);
+}
diff --git a/libfwupd/fwupd-client-sync.h b/libfwupd/fwupd-client-sync.h
index d1ad3ba35..8dd82f331 100644
--- a/libfwupd/fwupd-client-sync.h
+++ b/libfwupd/fwupd-client-sync.h
@@ -254,5 +254,14 @@ fwupd_client_upload_bytes(FwupdClient *self,
FwupdClientUploadFlags flags,
GCancellable *cancellable,
GError **error) G_GNUC_WARN_UNUSED_RESULT;
+gboolean
+fwupd_client_emulation_load(FwupdClient *self,
+ GBytes *data,
+ GCancellable *cancellable,
+ GError **error);
+GBytes *
+fwupd_client_emulation_save(FwupdClient *self,
+ GCancellable *cancellable,
+ GError **error) G_GNUC_WARN_UNUSED_RESULT;
G_END_DECLS
diff --git a/libfwupd/fwupd-client.c b/libfwupd/fwupd-client.c
index c18b8acf5..20b4de649 100644
--- a/libfwupd/fwupd-client.c
+++ b/libfwupd/fwupd-client.c
@@ -5650,6 +5650,175 @@ fwupd_client_add_hint(FwupdClient *self, const gchar *key, const gchar *value)
g_hash_table_insert(priv->hints, g_strdup(key), g_strdup(value));
}
+static void
+fwupd_client_emulation_load_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+ g_autoptr(GTask) task = G_TASK(user_data);
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) val = NULL;
+
+ val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
+ if (val == NULL) {
+ fwupd_client_fixup_dbus_error(error);
+ g_task_return_error(task, g_steal_pointer(&error));
+ return;
+ }
+
+ /* success */
+ g_task_return_boolean(task, TRUE);
+}
+
+/**
+ * fwupd_client_emulation_load_async:
+ * @self: a #FwupdClient
+ * @data: archive data of JSON files
+ * @cancellable: (nullable): optional #GCancellable
+ * @callback: the function to run on completion
+ * @callback_data: the data to pass to @callback
+ *
+ * Loads an emulated device into the daemon backend that has the phases set by the JSON data,
+ * for instance, having one USB device emulated for the bootloader and another emulated for the
+ * runtime interface.
+ *
+ * Since: 1.8.11
+ **/
+void
+fwupd_client_emulation_load_async(FwupdClient *self,
+ GBytes *data,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer callback_data)
+{
+ FwupdClientPrivate *priv = GET_PRIVATE(self);
+ g_autoptr(GTask) task = NULL;
+ GVariant *variant;
+
+ g_return_if_fail(FWUPD_IS_CLIENT(self));
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable));
+ g_return_if_fail(priv->proxy != NULL);
+
+ /* call into daemon */
+ task = g_task_new(self, cancellable, callback, callback_data);
+ variant = g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, data, FALSE);
+ g_dbus_proxy_call(priv->proxy,
+ "EmulationLoad",
+ g_variant_new_tuple(&variant, 1),
+ G_DBUS_CALL_FLAGS_NONE,
+ FWUPD_CLIENT_DBUS_PROXY_TIMEOUT,
+ cancellable,
+ fwupd_client_emulation_load_cb,
+ g_steal_pointer(&task));
+}
+
+/**
+ * fwupd_client_emulation_load_finish:
+ * @self: a #FwupdClient
+ * @res: (not nullable): the asynchronous result
+ * @error: (nullable): optional return location for an error
+ *
+ * Gets the result of [method@FwupdClient.emulation_load_async].
+ *
+ * Returns: %TRUE for success
+ *
+ * Since: 1.8.11
+ **/
+gboolean
+fwupd_client_emulation_load_finish(FwupdClient *self, GAsyncResult *res, GError **error)
+{
+ g_return_val_if_fail(FWUPD_IS_CLIENT(self), FALSE);
+ g_return_val_if_fail(g_task_is_valid(res, self), FALSE);
+ g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+ return g_task_propagate_boolean(G_TASK(res), error);
+}
+
+static void
+fwupd_client_emulation_save_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+ g_autoptr(GTask) task = G_TASK(user_data);
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GVariant) val = NULL;
+
+ val = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
+ if (val == NULL) {
+ fwupd_client_fixup_dbus_error(error);
+ g_task_return_error(task, g_steal_pointer(&error));
+ return;
+ }
+
+ /* success */
+ g_task_return_pointer(task,
+ g_variant_get_data_as_bytes(val),
+ (GDestroyNotify)g_bytes_unref);
+}
+
+/**
+ * fwupd_client_emulation_save_async:
+ * @self: a #FwupdClient
+ * @cancellable: (nullable): optional #GCancellable
+ * @callback: the function to run on completion
+ * @callback_data: the data to pass to @callback
+ *
+ * Gets the captured data from all filtered devices for all recorded phases. The data is returned
+ * in a ZIP archive of JSON output.
+ *
+ * NOTE: Device events are not automatically recorded for all devices. You must call something
+ * like `ModifyDevice(device_id, 'flags','emulation-tag')` to start the recording the backend.
+ *
+ * Once the device has been re-inserted then the emulation data will be available using
+ * this API call.
+ *
+ * You must have called [method@Client.connect_async] on @self before using
+ * this method.
+ *
+ * Since: 1.8.11
+ **/
+void
+fwupd_client_emulation_save_async(FwupdClient *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer callback_data)
+{
+ FwupdClientPrivate *priv = GET_PRIVATE(self);
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail(FWUPD_IS_CLIENT(self));
+ g_return_if_fail(cancellable == NULL || G_IS_CANCELLABLE(cancellable));
+ g_return_if_fail(priv->proxy != NULL);
+
+ /* call into daemon */
+ task = g_task_new(self, cancellable, callback, callback_data);
+ g_dbus_proxy_call(priv->proxy,
+ "EmulationSave",
+ NULL,
+ G_DBUS_CALL_FLAGS_NONE,
+ FWUPD_CLIENT_DBUS_PROXY_TIMEOUT,
+ cancellable,
+ fwupd_client_emulation_save_cb,
+ g_steal_pointer(&task));
+}
+
+/**
+ * fwupd_client_emulation_save_finish:
+ * @self: a #FwupdClient
+ * @res: (not nullable): the asynchronous result
+ * @error: (nullable): optional return location for an error
+ *
+ * Gets the result of [method@FwupdClient.emulation_save_async].
+ *
+ * Returns: (transfer full): archive data
+ *
+ * Since: 1.8.11
+ **/
+GBytes *
+fwupd_client_emulation_save_finish(FwupdClient *self, GAsyncResult *res, GError **error)
+{
+ g_return_val_if_fail(FWUPD_IS_CLIENT(self), NULL);
+ g_return_val_if_fail(g_task_is_valid(res, self), NULL);
+ g_return_val_if_fail(error == NULL || *error == NULL, NULL);
+ return g_task_propagate_pointer(G_TASK(res), error);
+}
+
#ifdef SOUP_SESSION_COMPAT
/* this is bad; we dlopen libsoup-2.4.so.1 and get the gtype manually
* to avoid deps on both libcurl and libsoup whilst preserving ABI */
diff --git a/libfwupd/fwupd-client.h b/libfwupd/fwupd-client.h
index 1cddf1db5..f4952c458 100644
--- a/libfwupd/fwupd-client.h
+++ b/libfwupd/fwupd-client.h
@@ -405,6 +405,23 @@ gboolean
fwupd_client_uninhibit_finish(FwupdClient *self,
GAsyncResult *res,
GError **error) G_GNUC_WARN_UNUSED_RESULT;
+void
+fwupd_client_emulation_load_async(FwupdClient *self,
+ GBytes *data,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer callback_data);
+gboolean
+fwupd_client_emulation_load_finish(FwupdClient *self, GAsyncResult *res, GError **error);
+void
+fwupd_client_emulation_save_async(FwupdClient *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer callback_data);
+GBytes *
+fwupd_client_emulation_save_finish(FwupdClient *self,
+ GAsyncResult *res,
+ GError **error) G_GNUC_WARN_UNUSED_RESULT;
FwupdStatus
fwupd_client_get_status(FwupdClient *self);
diff --git a/libfwupd/fwupd-enums.c b/libfwupd/fwupd-enums.c
index d6f871e7d..417c5e330 100644
--- a/libfwupd/fwupd-enums.c
+++ b/libfwupd/fwupd-enums.c
@@ -211,6 +211,8 @@ fwupd_device_flag_to_string(FwupdDeviceFlags device_flag)
return "unsigned-payload";
if (device_flag == FWUPD_DEVICE_FLAG_EMULATED)
return "emulated";
+ if (device_flag == FWUPD_DEVICE_FLAG_EMULATION_TAG)
+ return "emulation-tag";
if (device_flag == FWUPD_DEVICE_FLAG_UNKNOWN)
return "unknown";
return NULL;
@@ -332,6 +334,8 @@ fwupd_device_flag_from_string(const gchar *device_flag)
return FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD;
if (g_strcmp0(device_flag, "emulated") == 0)
return FWUPD_DEVICE_FLAG_EMULATED;
+ if (g_strcmp0(device_flag, "emulation-tag") == 0)
+ return FWUPD_DEVICE_FLAG_EMULATION_TAG;
return FWUPD_DEVICE_FLAG_UNKNOWN;
}
diff --git a/libfwupd/fwupd-enums.h b/libfwupd/fwupd-enums.h
index dc0165e1b..dea3e8f5f 100644
--- a/libfwupd/fwupd-enums.h
+++ b/libfwupd/fwupd-enums.h
@@ -535,6 +535,14 @@ typedef enum {
* Since: 1.8.11
*/
#define FWUPD_DEVICE_FLAG_EMULATED (1llu << 49)
+/**
+ * FWUPD_DEVICE_FLAG_EMULATION_TAG:
+ *
+ * The device should be recorded by the backend, allowing emulation.
+ *
+ * Since: 1.8.11
+ */
+#define FWUPD_DEVICE_FLAG_EMULATION_TAG (1llu << 50)
/**
* FWUPD_DEVICE_FLAG_UNKNOWN:
*
diff --git a/libfwupd/fwupd.map b/libfwupd/fwupd.map
index 4ece215ee..1e9fb2ff5 100644
--- a/libfwupd/fwupd.map
+++ b/libfwupd/fwupd.map
@@ -904,6 +904,12 @@ LIBFWUPD_1.8.8 {
LIBFWUPD_1.8.11 {
global:
+ fwupd_client_emulation_load;
+ fwupd_client_emulation_load_async;
+ fwupd_client_emulation_load_finish;
+ fwupd_client_emulation_save;
+ fwupd_client_emulation_save_async;
+ fwupd_client_emulation_save_finish;
fwupd_client_inhibit;
fwupd_client_inhibit_async;
fwupd_client_inhibit_finish;
diff --git a/libfwupdplugin/fu-usb-device-private.h b/libfwupdplugin/fu-usb-device-private.h
index 0050c45c9..cd0f7bd9f 100644
--- a/libfwupdplugin/fu-usb-device-private.h
+++ b/libfwupdplugin/fu-usb-device-private.h
@@ -8,5 +8,7 @@
#include "fu-usb-device.h"
+#define FU_USB_DEVICE_EMULATION_TAG "org.freedesktop.fwupd.emulation.v1"
+
const gchar *
fu_usb_device_get_platform_id(FuUsbDevice *self);
diff --git a/libfwupdplugin/fu-usb-device.c b/libfwupdplugin/fu-usb-device.c
index 9f9de5f3a..e639f0b30 100644
--- a/libfwupdplugin/fu-usb-device.c
+++ b/libfwupdplugin/fu-usb-device.c
@@ -86,6 +86,18 @@ fu_usb_device_finalize(GObject *object)
G_OBJECT_CLASS(fu_usb_device_parent_class)->finalize(object);
}
+#if G_USB_CHECK_VERSION(0, 4, 5)
+static void
+fu_usb_device_flags_notify_cb(FuDevice *device, GParamSpec *pspec, gpointer user_data)
+{
+ GUsbDevice *usb_device = fu_usb_device_get_dev(FU_USB_DEVICE(device));
+ if (usb_device == NULL)
+ return;
+ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG))
+ g_usb_device_add_tag(usb_device, FU_USB_DEVICE_EMULATION_TAG);
+}
+#endif
+
static void
fu_usb_device_init(FuUsbDevice *device)
{
@@ -104,6 +116,19 @@ fu_usb_device_init(FuUsbDevice *device)
#endif
}
+static void
+fu_usb_device_constructed(GObject *obj)
+{
+ FuUsbDevice *self = FU_USB_DEVICE(obj);
+#if G_USB_CHECK_VERSION(0, 4, 5)
+ /* copy this to the GUsbDevice */
+ g_signal_connect(FU_DEVICE(self),
+ "notify::flags",
+ G_CALLBACK(fu_usb_device_flags_notify_cb),
+ NULL);
+#endif
+}
+
/**
* fu_usb_device_is_open:
* @device: a #FuUsbDevice
@@ -675,7 +700,7 @@ fu_usb_device_set_dev(FuUsbDevice *device, GUsbDevice *usb_device)
}
#ifdef HAVE_GUSB
-#if G_USB_CHECK_VERSION(0, 4, 4)
+#if G_USB_CHECK_VERSION(0, 4, 5)
/* propagate emulated flag */
if (usb_device != NULL && g_usb_device_is_emulated(usb_device))
fu_device_add_flag(FU_DEVICE(device), FWUPD_DEVICE_FLAG_EMULATED);
@@ -913,6 +938,7 @@ fu_usb_device_class_init(FuUsbDeviceClass *klass)
object_class->finalize = fu_usb_device_finalize;
object_class->get_property = fu_usb_device_get_property;
object_class->set_property = fu_usb_device_set_property;
+ object_class->constructed = fu_usb_device_constructed;
device_class->open = fu_usb_device_open;
device_class->setup = fu_usb_device_setup;
device_class->ready = fu_usb_device_ready;
diff --git a/plugins/corsair/fu-corsair-bp.c b/plugins/corsair/fu-corsair-bp.c
index 5b4e91f10..0a6cf10d4 100644
--- a/plugins/corsair/fu-corsair-bp.c
+++ b/plugins/corsair/fu-corsair-bp.c
@@ -220,6 +220,9 @@ fu_corsair_bp_incorporate(FuDevice *self, FuDevice *donor)
FuCorsairBp *bp_self = FU_CORSAIR_BP(self);
FuCorsairBp *bp_donor = FU_CORSAIR_BP(donor);
+ /* FuUsbDevice */
+ FU_DEVICE_CLASS(fu_corsair_bp_parent_class)->incorporate(self, donor);
+
bp_self->epin = bp_donor->epin;
bp_self->epout = bp_donor->epout;
bp_self->cmd_write_size = bp_donor->cmd_write_size;
diff --git a/src/fu-config.c b/src/fu-config.c
index 6b93925ea..5b75836e3 100644
--- a/src/fu-config.c
+++ b/src/fu-config.c
@@ -38,6 +38,7 @@ struct _FuConfig {
gboolean ignore_power;
gboolean only_trusted;
gboolean show_device_private;
+ gboolean allow_emulation;
};
G_DEFINE_TYPE(FuConfig, fu_config, G_TYPE_OBJECT)
@@ -70,6 +71,7 @@ fu_config_reload(FuConfig *self, GError **error)
g_autoptr(GError) error_ignore_power = NULL;
g_autoptr(GError) error_only_trusted = NULL;
g_autoptr(GError) error_show_device_private = NULL;
+ g_autoptr(GError) error_allow_emulation = NULL;
g_autoptr(GError) error_enumerate_all = NULL;
g_autoptr(GByteArray) buf = g_byte_array_new();
@@ -247,6 +249,14 @@ fu_config_reload(FuConfig *self, GError **error)
self->show_device_private = TRUE;
}
+ /* whether to allow emulation to work */
+ self->allow_emulation =
+ g_key_file_get_boolean(keyfile, "fwupd", "AllowEmulation", &error_allow_emulation);
+ if (!self->allow_emulation && error_allow_emulation != NULL) {
+ g_debug("failed to read AllowEmulation key: %s", error_allow_emulation->message);
+ self->allow_emulation = FALSE;
+ }
+
/* fetch host best known configuration */
host_bkc = g_key_file_get_string(keyfile, "fwupd", "HostBkc", NULL);
if (host_bkc != NULL && host_bkc[0] != '\0')
@@ -446,6 +456,13 @@ fu_config_get_show_device_private(FuConfig *self)
return self->show_device_private;
}
+gboolean
+fu_config_get_allow_emulation(FuConfig *self)
+{
+ g_return_val_if_fail(FU_IS_CONFIG(self), FALSE);
+ return self->allow_emulation;
+}
+
gboolean
fu_config_get_enumerate_all_devices(FuConfig *self)
{
diff --git a/src/fu-config.h b/src/fu-config.h
index 1d5fcad2b..6c9abc692 100644
--- a/src/fu-config.h
+++ b/src/fu-config.h
@@ -44,6 +44,8 @@ gboolean
fu_config_get_only_trusted(FuConfig *self);
gboolean
fu_config_get_show_device_private(FuConfig *self);
+gboolean
+fu_config_get_allow_emulation(FuConfig *self);
const gchar *
fu_config_get_host_bkc(FuConfig *self);
const gchar *
diff --git a/src/fu-daemon.c b/src/fu-daemon.c
index c9ff21bdb..39dd83e05 100644
--- a/src/fu-daemon.c
+++ b/src/fu-daemon.c
@@ -1399,6 +1399,45 @@ fu_daemon_daemon_method_call(GDBusConnection *connection,
g_dbus_method_invocation_return_value(invocation, NULL);
return;
}
+ if (g_strcmp0(method_name, "EmulationLoad") == 0) {
+ g_autoptr(GBytes) data = NULL;
+
+ g_debug("Called %s()", method_name);
+
+ /* load data into engine */
+ data = g_variant_get_data_as_bytes(g_variant_get_child_value(parameters, 0));
+ if (!fu_engine_emulation_load(self->engine, data, &error)) {
+ g_dbus_method_invocation_return_error(invocation,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "failed to load emulation data: %s",
+ error->message);
+ return;
+ }
+
+ /* success */
+ g_dbus_method_invocation_return_value(invocation, NULL);
+ return;
+ }
+ if (g_strcmp0(method_name, "EmulationSave") == 0) {
+ g_autoptr(GBytes) data = NULL;
+
+ g_debug("Called %s()", method_name);
+
+ /* save data from engine */
+ data = fu_engine_emulation_save(self->engine, &error);
+ if (data == NULL) {
+ g_dbus_method_invocation_return_error(invocation,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "failed to save emulation data: %s",
+ error->message);
+ return;
+ }
+ val = g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, data, FALSE);
+ g_dbus_method_invocation_return_value(invocation, g_variant_new_tuple(&val, 1));
+ return;
+ }
if (g_strcmp0(method_name, "ModifyDevice") == 0) {
const gchar *device_id;
const gchar *key = NULL;
diff --git a/src/fu-device-list.c b/src/fu-device-list.c
index adef8900a..eb3d61a36 100644
--- a/src/fu-device-list.c
+++ b/src/fu-device-list.c
@@ -688,6 +688,8 @@ fu_device_list_replace(FuDeviceList *self, FuDeviceItem *item, FuDevice *device)
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD);
if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD))
fu_device_add_flag(device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
+ if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_EMULATION_TAG))
+ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG);
/* device won't come back in right mode */
if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR)) {
@@ -866,7 +868,8 @@ fu_device_list_get_wait_for_replug(FuDeviceList *self)
GPtrArray *devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
for (guint i = 0; i < self->devices->len; i++) {
FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i);
- if (fu_device_has_flag(item_tmp->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG))
+ if (fu_device_has_flag(item_tmp->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) &&
+ !fu_device_has_flag(item_tmp->device, FWUPD_DEVICE_FLAG_EMULATED))
g_ptr_array_add(devices, g_object_ref(item_tmp->device));
}
return devices;
diff --git a/src/fu-engine.c b/src/fu-engine.c
index 441812089..15a7a9d1d 100644
--- a/src/fu-engine.c
+++ b/src/fu-engine.c
@@ -96,7 +96,7 @@ static void
fu_engine_ensure_security_attrs(FuEngine *self);
typedef enum {
- FU_ENGINE_INSTALL_PHASE_UNKNOWN,
+ FU_ENGINE_INSTALL_PHASE_SETUP,
FU_ENGINE_INSTALL_PHASE_WRITE,
FU_ENGINE_INSTALL_PHASE_ATTACH,
FU_ENGINE_INSTALL_PHASE_DETACH,
@@ -132,6 +132,8 @@ struct _FuEngine {
GHashTable *compile_versions;
GHashTable *approved_firmware; /* (nullable) */
GHashTable *blocked_firmware; /* (nullable) */
+ GHashTable *emulation_phases; /* (element-type int utf8) */
+ GHashTable *emulation_backend_ids; /* (element-type str int) */
gchar *host_machine_id;
JcatContext *jcat_context;
gboolean loaded;
@@ -370,6 +372,8 @@ fu_engine_device_request_cb(FuDevice *device, FwupdRequest *request, FuEngine *s
static const gchar *
fu_engine_install_phase_to_string(FuEngineInstallPhase phase)
{
+ if (phase == FU_ENGINE_INSTALL_PHASE_SETUP)
+ return "setup";
if (phase == FU_ENGINE_INSTALL_PHASE_WRITE)
return "install";
if (phase == FU_ENGINE_INSTALL_PHASE_ATTACH)
@@ -763,6 +767,7 @@ gboolean
fu_engine_modify_config(FuEngine *self, const gchar *key, const gchar *value, GError **error)
{
const gchar *keys[] = {"ArchiveSizeMax",
+ "AllowEmulation",
"ApprovedFirmware",
"BlockedFirmware",
"DisabledDevices",
@@ -1056,6 +1061,118 @@ fu_engine_modify_bios_settings(FuEngine *self,
return TRUE;
}
+static void
+fu_engine_check_context_flag_save_events(FuEngine *self)
+{
+ if (g_hash_table_size(self->emulation_backend_ids) > 0 &&
+ fu_config_get_allow_emulation(self->config)) {
+ fu_context_add_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS);
+ } else {
+ fu_context_remove_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS);
+ }
+}
+
+static gboolean
+fu_engine_remove_device_flag(FuEngine *self,
+ const gchar *device_id,
+ FwupdDeviceFlags flag,
+ GError **error)
+{
+ FuDevice *proxy;
+ g_autoptr(FuDevice) device = NULL;
+
+ if (flag == FWUPD_DEVICE_FLAG_NOTIFIED) {
+ device = fu_history_get_device_by_id(self->history, device_id, error);
+ if (device == NULL)
+ return FALSE;
+ fu_device_remove_flag(device, flag);
+ return fu_history_modify_device(self->history, device, error);
+ }
+ if (flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) {
+ device = fu_device_list_get_by_id(self->device_list, device_id, error);
+ if (device == NULL)
+ return FALSE;
+ proxy = fu_device_get_proxy(device);
+ if (proxy != NULL) {
+ g_set_error(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "device %s uses a proxy, remove the flag on %s instead",
+ fu_device_get_id(device),
+ fu_device_get_id(proxy));
+ return FALSE;
+ }
+ g_hash_table_remove(self->emulation_backend_ids, fu_device_get_backend_id(device));
+ return TRUE;
+ }
+ g_set_error_literal(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "flag cannot be removed from client");
+ return FALSE;
+}
+
+static gboolean
+fu_engine_add_device_flag(FuEngine *self,
+ const gchar *device_id,
+ FwupdDeviceFlags flag,
+ GError **error)
+{
+ FuDevice *proxy;
+ g_autoptr(FuDevice) device = NULL;
+
+ if (flag == FWUPD_DEVICE_FLAG_REPORTED || flag == FWUPD_DEVICE_FLAG_NOTIFIED) {
+ device = fu_history_get_device_by_id(self->history, device_id, error);
+ if (device == NULL)
+ return FALSE;
+ fu_device_add_flag(device, flag);
+ return fu_history_modify_device(self->history, device, error);
+ }
+ if (flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) {
+ device = fu_device_list_get_by_id(self->device_list, device_id, error);
+ if (device == NULL)
+ return FALSE;
+ proxy = fu_device_get_proxy(device);
+ if (proxy != NULL) {
+ g_set_error(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "device %s uses a proxy, set the flag on %s instead",
+ fu_device_get_id(device),
+ fu_device_get_id(proxy));
+ return FALSE;
+ }
+ g_hash_table_insert(self->emulation_backend_ids,
+ g_strdup(fu_device_get_backend_id(device)),
+ GUINT_TO_POINTER(1));
+ return TRUE;
+ }
+ g_set_error_literal(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "flag cannot be added from client");
+ return FALSE;
+}
+
+static gboolean
+fu_engine_modify_device_flags(FuEngine *self,
+ const gchar *device_id,
+ const gchar *value,
+ GError **error)
+{
+ /* add or remove a subset of device flags */
+ if (g_str_has_prefix(value, "~")) {
+ return fu_engine_remove_device_flag(self,
+ device_id,
+ fwupd_device_flag_from_string(value + 1),
+ error);
+ }
+ return fu_engine_add_device_flag(self,
+ device_id,
+ fwupd_device_flag_from_string(value),
+ error);
+}
+
/**
* fu_engine_modify_device:
* @self: a #FuEngine
@@ -1076,45 +1193,8 @@ fu_engine_modify_device(FuEngine *self,
const gchar *value,
GError **error)
{
- g_autoptr(FuDevice) device = NULL;
-
- /* find the correct device */
- device = fu_history_get_device_by_id(self->history, device_id, error);
- if (device == NULL)
- return FALSE;
-
- /* support adding and removing a subset of device flags */
- if (g_strcmp0(key, "Flags") == 0) {
- if (g_str_has_prefix(value, "~")) {
- FwupdDeviceFlags flag = fwupd_device_flag_from_string(value + 1);
- if (flag == FWUPD_DEVICE_FLAG_NOTIFIED) {
- fu_device_remove_flag(device, flag);
- } else {
- g_set_error(error,
- FWUPD_ERROR,
- FWUPD_ERROR_NOT_SUPPORTED,
- "flag %s cannot be unset from client",
- key);
- return FALSE;
- }
- } else {
- FwupdDeviceFlags flag = fwupd_device_flag_from_string(value);
- if (flag == FWUPD_DEVICE_FLAG_REPORTED ||
- flag == FWUPD_DEVICE_FLAG_NOTIFIED) {
- fu_device_add_flag(device, flag);
- } else {
- g_set_error(error,
- FWUPD_ERROR,
- FWUPD_ERROR_NOT_SUPPORTED,
- "flag %s cannot be set from client",
- key);
- return FALSE;
- }
- }
- return fu_history_modify_device(self->history, device, error);
- }
-
- /* others invalid */
+ if (g_strcmp0(key, "Flags") == 0)
+ return fu_engine_modify_device_flags(self, device_id, value, error);
g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "key %s not supported", key);
return FALSE;
}
@@ -3032,6 +3112,9 @@ fu_engine_install_release(FuEngine *self,
fu_engine_wait_for_acquiesce(self, fu_device_get_acquiesce_delay(device_orig));
}
+ /* allow capturing setup again */
+ fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_SETUP);
+
/* make the UI update */
fu_engine_emit_changed(self);
@@ -3055,13 +3138,220 @@ fu_engine_get_plugins(FuEngine *self)
return fu_plugin_list_get_all(self->plugin_list);
}
+static gboolean
+fu_engine_emulation_load_json(FuEngine *self, const gchar *json, GError **error)
+{
+ JsonNode *root;
+ g_autoptr(JsonParser) parser = json_parser_new();
+
+ /* parse */
+ if (!json_parser_load_from_data(parser, json, -1, error))
+ return FALSE;
+ root = json_parser_get_root(parser);
+
+ /* load into all backends */
+ for (guint i = 0; i < self->backends->len; i++) {
+ FuBackend *backend = g_ptr_array_index(self->backends, i);
+ if (!fu_backend_load(backend,
+ json_node_get_object(root),
+ FU_USB_DEVICE_EMULATION_TAG,
+ FU_BACKEND_LOAD_FLAG_NONE,
+ error))
+ return FALSE;
+ }
+
+ /* success */
+ return TRUE;
+}
+
+static gboolean
+fu_engine_emulation_load_phase(FuEngine *self, FuEngineInstallPhase phase, GError **error)
+{
+ const gchar *json = g_hash_table_lookup(self->emulation_phases, GINT_TO_POINTER(phase));
+ if (json == NULL)
+ return TRUE;
+ if (g_getenv("FWUPD_BACKEND_VERBOSE") != NULL)
+ g_debug("loading phase %s: %s", fu_engine_install_phase_to_string(phase), json);
+ return fu_engine_emulation_load_json(self, json, error);
+}
+
+gboolean
+fu_engine_emulation_load(FuEngine *self, GBytes *data, GError **error)
+{
+ gboolean got_json = FALSE;
+ g_autoptr(FuArchive) archive = NULL;
+
+ g_return_val_if_fail(FU_IS_ENGINE(self), FALSE);
+ g_return_val_if_fail(data != NULL, FALSE);
+ g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
+
+ /* not supported */
+ if (!fu_config_get_allow_emulation(self->config)) {
+ g_set_error_literal(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "emulation is not allowed from config");
+ return FALSE;
+ }
+
+ /* unload any existing devices */
+ if (!fu_engine_emulation_load_json(self, "{\"UsbDevices\":[]}", error))
+ return FALSE;
+
+ /* load archive */
+ archive = fu_archive_new(data, FU_ARCHIVE_FLAG_NONE, error);
+ if (archive == NULL)
+ return FALSE;
+
+ /* load JSON files from archive */
+ g_hash_table_remove_all(self->emulation_phases);
+ for (guint phase = FU_ENGINE_INSTALL_PHASE_SETUP; phase < FU_ENGINE_INSTALL_PHASE_LAST;
+ phase++) {
+ g_autofree gchar *fn =
+ g_strdup_printf("%s.json", fu_engine_install_phase_to_string(phase));
+ g_autofree gchar *json_safe = NULL;
+ GBytes *blob = fu_archive_lookup_by_fn(archive, fn, NULL);
+
+ /* not found */
+ if (blob == NULL)
+ continue;
+ json_safe = g_strndup(g_bytes_get_data(blob, NULL), g_bytes_get_size(blob));
+ got_json = TRUE;
+ g_debug("got emulation for phase %s", fu_engine_install_phase_to_string(phase));
+ if (phase == FU_ENGINE_INSTALL_PHASE_SETUP) {
+ if (!fu_engine_emulation_load_json(self, json_safe, error))
+ return FALSE;
+ } else {
+ g_hash_table_insert(self->emulation_phases,
+ GINT_TO_POINTER(phase),
+ g_steal_pointer(&json_safe));
+ }
+ }
+ if (!got_json) {
+ g_set_error_literal(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "no emulation data found in archive");
+ return FALSE;
+ }
+
+ /* success */
+ return TRUE;
+}
+
+GBytes *
+fu_engine_emulation_save(FuEngine *self, GError **error)
+{
+ gboolean got_json = FALSE;
+ g_autoptr(GBytes) bytes = NULL;
+ g_autoptr(FuArchive) archive = fu_archive_new(NULL, FU_ARCHIVE_FLAG_NONE, NULL);
+
+ g_return_val_if_fail(FU_IS_ENGINE(self), NULL);
+ g_return_val_if_fail(error == NULL || *error == NULL, NULL);
+
+ /* not supported */
+ if (!fu_config_get_allow_emulation(self->config)) {
+ g_set_error_literal(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "emulation is not allowed from config");
+ return NULL;
+ }
+
+ /* sanity check */
+ for (guint phase = FU_ENGINE_INSTALL_PHASE_SETUP; phase < FU_ENGINE_INSTALL_PHASE_LAST;
+ phase++) {
+ const gchar *json =
+ g_hash_table_lookup(self->emulation_phases, GINT_TO_POINTER(phase));
+ g_autofree gchar *fn =
+ g_strdup_printf("%s.json", fu_engine_install_phase_to_string(phase));
+ g_autoptr(GBytes) blob = NULL;
+
+ /* nothing set */
+ if (json == NULL)
+ continue;
+ got_json = TRUE;
+ blob = g_bytes_new_static(json, strlen(json));
+ fu_archive_add_entry(archive, fn, blob);
+ }
+ if (!got_json) {
+ g_set_error_literal(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_NOT_SUPPORTED,
+ "no emulation data, perhaps no devices have been added?");
+ return NULL;
+ }
+
+ /* write */
+ bytes =
+ fu_archive_write(archive, FU_ARCHIVE_FORMAT_ZIP, FU_ARCHIVE_COMPRESSION_GZIP, error);
+ if (bytes == NULL)
+ return NULL;
+
+ /* success */
+ g_hash_table_remove_all(self->emulation_phases);
+ return g_steal_pointer(&bytes);
+}
+
+static gboolean
+fu_engine_backends_save_phase(FuEngine *self, GError **error)
+{
+ const gchar *data_old;
+ g_autofree gchar *data_new = NULL;
+ g_autoptr(JsonBuilder) json_builder = json_builder_new();
+ g_autoptr(JsonGenerator) json_generator = NULL;
+ g_autoptr(JsonNode) json_root = NULL;
+
+ /* all devices in all backends */
+ for (guint i = 0; i < self->backends->len; i++) {
+ FuBackend *backend = g_ptr_array_index(self->backends, i);
+ if (!fu_backend_save(backend,
+ json_builder,
+ FU_USB_DEVICE_EMULATION_TAG,
+ FU_BACKEND_SAVE_FLAG_NONE,
+ error))
+ return FALSE;
+ }
+ json_root = json_builder_get_root(json_builder);
+ json_generator = json_generator_new();
+ json_generator_set_pretty(json_generator, TRUE);
+ json_generator_set_root(json_generator, json_root);
+
+ data_old =
+ g_hash_table_lookup(self->emulation_phases, GINT_TO_POINTER(self->install_phase));
+ data_new = json_generator_to_data(json_generator, NULL);
+ if (g_strcmp0(data_new, "") == 0) {
+ g_debug("no data for phase %s",
+ fu_engine_install_phase_to_string(self->install_phase));
+ return TRUE;
+ }
+ if (g_strcmp0(data_old, data_new) == 0) {
+ g_debug("JSON unchanged for phase %s",
+ fu_engine_install_phase_to_string(self->install_phase));
+ return TRUE;
+ }
+ if (g_getenv("FWUPD_BACKEND_VERBOSE") != NULL) {
+ g_autofree gchar *data_new_safe = data_new_safe = g_strndup(data_new, 8000);
+ g_debug("JSON %s for phase %s: %s...",
+ data_old == NULL ? "added" : "changed",
+ fu_engine_install_phase_to_string(self->install_phase),
+ data_new_safe);
+ }
+ g_hash_table_insert(self->emulation_phases,
+ GINT_TO_POINTER(self->install_phase),
+ g_steal_pointer(&data_new));
+
+ /* success */
+ return TRUE;
+}
+
/**
* fu_engine_get_device:
* @self: a #FuEngine
* @device_id: a device ID
* @error: (nullable): optional return location for an error
*
- * Gets a specific device.
+ * Gets a specific device, optionally loading an emulated phase.
*
* Returns: (transfer full): a device, or %NULL if not found
**/
@@ -3070,6 +3360,17 @@ fu_engine_get_device(FuEngine *self, const gchar *device_id, GError **error)
{
g_autoptr(FuDevice) device = NULL;
+ /* we are emulating a device */
+ if (self->install_phase != FU_ENGINE_INSTALL_PHASE_SETUP) {
+ g_autoptr(FuDevice) device_old = NULL;
+ device_old = fu_device_list_get_by_id(self->device_list, device_id, NULL);
+ if (device_old != NULL &&
+ fu_device_has_flag(device_old, FWUPD_DEVICE_FLAG_EMULATED)) {
+ if (!fu_engine_emulation_load_phase(self, self->install_phase, error))
+ return NULL;
+ }
+ }
+
/* wait for any device to disconnect and reconnect */
if (!fu_device_list_wait_for_replug(self->device_list, error)) {
g_prefix_error(error, "failed to wait for detach replug: ");
@@ -3214,6 +3515,12 @@ fu_engine_prepare(FuEngine *self,
return FALSE;
}
+ /* save to emulated phase */
+ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
+ !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
+ if (!fu_engine_backends_save_phase(self, error))
+ return FALSE;
+ }
return TRUE;
}
@@ -3245,6 +3552,12 @@ fu_engine_cleanup(FuEngine *self,
return FALSE;
}
+ /* save to emulated phase */
+ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
+ !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
+ if (!fu_engine_backends_save_phase(self, error))
+ return FALSE;
+ }
return TRUE;
}
@@ -3301,6 +3614,13 @@ fu_engine_detach(FuEngine *self,
return FALSE;
}
+ /* save to emulated phase */
+ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
+ !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
+ if (!fu_engine_backends_save_phase(self, error))
+ return FALSE;
+ }
+
/* success */
return TRUE;
}
@@ -3333,6 +3653,14 @@ fu_engine_attach(FuEngine *self, const gchar *device_id, FuProgress *progress, G
if (!fu_plugin_runner_attach(plugin, device, progress, error))
return FALSE;
+
+ /* save to emulated phase */
+ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
+ !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
+ if (!fu_engine_backends_save_phase(self, error))
+ return FALSE;
+ }
+
return TRUE;
}
@@ -3412,6 +3740,15 @@ fu_engine_reload(FuEngine *self, const gchar *device_id, GError **error)
g_prefix_error(error, "failed to reload device: ");
return FALSE;
}
+
+ /* save to emulated phase */
+ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
+ !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
+ if (!fu_engine_backends_save_phase(self, error))
+ return FALSE;
+ }
+
+ /* success */
return TRUE;
}
@@ -3457,11 +3794,13 @@ fu_engine_write_firmware(FuEngine *self,
g_autoptr(GError) error_cleanup = NULL;
/* attack back into runtime then cleanup */
+ fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_ATTACH);
fu_progress_reset(progress);
if (!fu_plugin_runner_attach(plugin, device, progress, &error_attach)) {
g_warning("failed to attach device after failed update: %s",
error_attach->message);
}
+ fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_CLEANUP);
fu_progress_reset(progress);
if (!fu_engine_cleanup(self, device_id, progress, flags, &error_cleanup)) {
g_warning("failed to update-cleanup after failed update: %s",
@@ -3498,6 +3837,15 @@ fu_engine_write_firmware(FuEngine *self,
}
}
}
+
+ /* save to emulated phase */
+ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
+ !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
+ if (!fu_engine_backends_save_phase(self, error))
+ return FALSE;
+ }
+
+ /* success */
return TRUE;
}
@@ -3659,7 +4007,6 @@ fu_engine_install_blob(FuEngine *self,
fu_progress_step_done(progress_local);
/* the device and plugin both may have changed */
- fu_engine_set_install_phase(self, FU_ENGINE_INSTALL_PHASE_UNKNOWN);
device_tmp = fu_engine_get_device(self, device_id, error);
if (device_tmp == NULL) {
g_prefix_error(error, "failed to get device after install blob: ");
@@ -6353,6 +6700,25 @@ fu_engine_device_inherit_history(FuEngine *self, FuDevice *device)
}
}
+static void
+fu_engine_ensure_device_emulation_tag(FuEngine *self, FuDevice *device)
+{
+ /* already done */
+ if (fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG))
+ return;
+
+ /* we matched this physical ID */
+ if (fu_device_get_backend_id(device) == NULL)
+ return;
+ if (!g_hash_table_contains(self->emulation_backend_ids, fu_device_get_backend_id(device)))
+ return;
+
+ /* success */
+ g_debug("adding emulation-tag to %s", fu_device_get_backend_id(device));
+ fu_device_add_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG);
+ fu_engine_check_context_flag_save_events(self);
+}
+
void
fu_engine_add_device(FuEngine *self, FuDevice *device)
{
@@ -6432,6 +6798,9 @@ fu_engine_add_device(FuEngine *self, FuDevice *device)
}
}
+ /* check if the device needs emulation-tag */
+ fu_engine_ensure_device_emulation_tag(self, device);
+
/* set or clear the SUPPORTED flag */
fu_engine_ensure_device_supported(self, device);
@@ -6482,6 +6851,16 @@ fu_engine_add_device(FuEngine *self, FuDevice *device)
if (component != NULL)
fu_engine_md_refresh_device_from_component(self, device, component);
+ /* save to emulated phase, but avoid overwriting reload */
+ if (fu_context_has_flag(self->ctx, FU_CONTEXT_FLAG_SAVE_EVENTS) &&
+ self->install_phase == FU_ENGINE_INSTALL_PHASE_SETUP &&
+ fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG) &&
+ !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_EMULATED)) {
+ g_autoptr(GError) error_local = NULL;
+ if (!fu_engine_backends_save_phase(self, &error_local))
+ g_warning("failed to save phase: %s", error_local->message);
+ }
+
fu_engine_emit_changed(self);
}
@@ -7438,6 +7817,9 @@ fu_engine_backend_device_added(FuEngine *self, FuDevice *device, FuProgress *pro
}
fu_progress_step_done(progress);
+ /* check if the device needs emulation-tag */
+ fu_engine_ensure_device_emulation_tag(self, device);
+
/* super useful for plugin development */
if (g_getenv("FWUPD_PROBE_VERBOSE") != NULL) {
g_autofree gchar *str = fu_device_to_string(FU_DEVICE(device));
@@ -8438,22 +8820,6 @@ fu_engine_idle_status_notify_cb(FuIdle *idle, GParamSpec *pspec, FuEngine *self)
fu_engine_set_status(self, status);
}
-gboolean
-fu_engine_backends_save(FuEngine *self, JsonBuilder *json_builder, GError **error)
-{
- json_builder_begin_object(json_builder);
- json_builder_set_member_name(json_builder, "Backends");
- json_builder_begin_array(json_builder);
- for (guint i = 0; i < self->backends->len; i++) {
- FuBackend *backend = g_ptr_array_index(self->backends, i);
- if (!fu_backend_save(backend, json_builder, NULL, FU_BACKEND_SAVE_FLAG_NONE, error))
- return FALSE;
- }
- json_builder_end_array(json_builder);
- json_builder_end_object(json_builder);
- return TRUE;
-}
-
static void
fu_engine_init(FuEngine *self)
{
@@ -8479,6 +8845,8 @@ fu_engine_init(FuEngine *self)
self->runtime_versions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
self->compile_versions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
self->acquiesce_loop = g_main_loop_new(NULL, FALSE);
+ self->emulation_phases = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
+ self->emulation_backend_ids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
fu_context_set_runtime_versions(self->ctx, self->runtime_versions);
fu_context_set_compile_versions(self->ctx, self->compile_versions);
@@ -8639,6 +9007,8 @@ fu_engine_finalize(GObject *obj)
g_ptr_array_unref(self->local_monitors);
g_hash_table_unref(self->runtime_versions);
g_hash_table_unref(self->compile_versions);
+ g_hash_table_unref(self->emulation_phases);
+ g_hash_table_unref(self->emulation_backend_ids);
g_object_unref(self->plugin_list);
G_OBJECT_CLASS(fu_engine_parent_class)->finalize(obj);
diff --git a/src/fu-engine.h b/src/fu-engine.h
index abd0b979e..e90f0ac2b 100644
--- a/src/fu-engine.h
+++ b/src/fu-engine.h
@@ -250,4 +250,6 @@ fu_engine_modify_bios_settings(FuEngine *self,
gboolean force_ro,
GError **error);
gboolean
-fu_engine_backends_save(FuEngine *self, JsonBuilder *json_builder, GError **error);
+fu_engine_emulation_load(FuEngine *self, GBytes *data, GError **error);
+GBytes *
+fu_engine_emulation_save(FuEngine *self, GError **error);
diff --git a/src/fu-self-test.c b/src/fu-self-test.c
index ac4400079..94b20051c 100644
--- a/src/fu-self-test.c
+++ b/src/fu-self-test.c
@@ -3243,7 +3243,7 @@ fu_backend_usb_func(gconstpointer user_data)
g_autoptr(GPtrArray) devices = NULL;
g_autoptr(GPtrArray) possible_plugins = NULL;
-#if !G_USB_CHECK_VERSION(0, 4, 4)
+#if !G_USB_CHECK_VERSION(0, 4, 5)
g_test_skip("GUsb version too old");
return;
#endif
@@ -3332,7 +3332,7 @@ fu_backend_usb_invalid_func(gconstpointer user_data)
g_autoptr(GPtrArray) devices = NULL;
g_autoptr(JsonParser) parser = json_parser_new();
-#if !G_USB_CHECK_VERSION(0, 4, 2)
+#if !G_USB_CHECK_VERSION(0, 4, 5)
g_test_skip("GUsb version too old");
return;
#endif
diff --git a/src/fu-tool.c b/src/fu-tool.c
index 721201222..da145d5cd 100644
--- a/src/fu-tool.c
+++ b/src/fu-tool.c
@@ -3441,34 +3441,6 @@ fu_util_setup_interactive(FuUtilPrivate *priv, GError **error)
return fu_util_setup_interactive_console(error);
}
-static gboolean
-fu_util_backends_save(FuUtilPrivate *priv, const gchar *fn, GError **error)
-{
- g_autofree gchar *data = NULL;
- g_autoptr(JsonBuilder) json_builder = json_builder_new();
- g_autoptr(JsonGenerator) json_generator = NULL;
- g_autoptr(JsonNode) json_root = NULL;
-
- /* export as a string */
- if (!fu_engine_backends_save(priv->engine, json_builder, error))
- return FALSE;
- json_root = json_builder_get_root(json_builder);
- json_generator = json_generator_new();
- json_generator_set_pretty(json_generator, TRUE);
- json_generator_set_root(json_generator, json_root);
- data = json_generator_to_data(json_generator, NULL);
- if (data == NULL) {
- g_set_error_literal(error,
- G_IO_ERROR,
- G_IO_ERROR_INVALID_DATA,
- "Failed to convert to JSON string");
- return FALSE;
- }
-
- /* save to file */
- return g_file_set_contents(fn, data, -1, error);
-}
-
static void
fu_util_print_error(FuUtilPrivate *priv, const GError *error)
{
@@ -3498,7 +3470,6 @@ main(int argc, char *argv[])
g_autoptr(GPtrArray) cmd_array = fu_util_cmd_array_new();
g_autofree gchar *cmd_descriptions = NULL;
g_autofree gchar *filter = NULL;
- g_autofree gchar *save_backends_fn = NULL;
const GOptionEntry options[] = {
{"version",
'\0',
@@ -3653,15 +3624,6 @@ main(int argc, char *argv[])
N_("Filter with a set of device flags using a ~ prefix to "
"exclude, e.g. 'internal,~needs-reboot'"),
NULL},
- {"save-backends",
- '\0',
- 0,
- G_OPTION_ARG_STRING,
- &save_backends_fn,
- /* TRANSLATORS: command line option */
- N_("Specify a filename to use to save backend events"),
- /* TRANSLATORS: filename argument with path */
- N_("FILENAME")},
{"json",
'\0',
0,
@@ -4103,10 +4065,6 @@ main(int argc, char *argv[])
/* load engine */
priv->engine = fu_engine_new();
- if (save_backends_fn != NULL) {
- fu_context_add_flag(fu_engine_get_context(priv->engine),
- FU_CONTEXT_FLAG_SAVE_EVENTS);
- }
g_signal_connect(FU_ENGINE(priv->engine),
"device-request",
G_CALLBACK(fu_util_update_device_request_cb),
@@ -4165,12 +4123,6 @@ main(int argc, char *argv[])
return EXIT_FAILURE;
}
- /* dump devices */
- if (save_backends_fn != NULL && !fu_util_backends_save(priv, save_backends_fn, &error)) {
- g_printerr("%s\n", error->message);
- return EXIT_FAILURE;
- }
-
/* a good place to do the traceback */
if (fu_progress_get_profile(priv->progress)) {
g_autofree gchar *str = fu_progress_traceback(priv->progress);
diff --git a/src/fu-usb-backend.c b/src/fu-usb-backend.c
index 2a6a42f29..39bfb1e08 100644
--- a/src/fu-usb-backend.c
+++ b/src/fu-usb-backend.c
@@ -50,52 +50,18 @@ fu_usb_backend_device_notify_flags_cb(FuDevice *device, GParamSpec *pspec, FuBac
static void
fu_usb_backend_device_added_cb(GUsbContext *ctx, GUsbDevice *usb_device, FuBackend *backend)
{
- FuDevice *device_tmp;
- g_autoptr(FuUsbDevice) device = NULL;
-
- /* is emulated? */
- device_tmp = fu_backend_lookup_by_id(backend, g_usb_device_get_platform_id(usb_device));
- if (device_tmp != NULL && fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_EMULATED)) {
- GUsbDevice *usb_device_tmp = fu_usb_device_get_dev(FU_USB_DEVICE(device_tmp));
-#if G_USB_CHECK_VERSION(0, 4, 5)
- if (g_date_time_equal(g_usb_device_get_created(usb_device),
- g_usb_device_get_created(usb_device_tmp))) {
-#else
- if (g_usb_device_get_vid(usb_device) == g_usb_device_get_vid(usb_device_tmp) &&
- g_usb_device_get_pid(usb_device) == g_usb_device_get_pid(usb_device_tmp)) {
-#endif
- g_debug("replacing GUsbDevice of emulated device %s",
- fu_usb_device_get_platform_id(FU_USB_DEVICE(device_tmp)));
- fu_usb_device_set_dev(FU_USB_DEVICE(device_tmp), usb_device);
- fu_backend_device_changed(backend, device_tmp);
- return;
- }
- g_debug("delayed removal as emulated device changed");
- fu_backend_device_removed(backend, device_tmp);
- }
-
- /* success */
- device = fu_usb_device_new(fu_backend_get_context(backend), usb_device);
+ g_autoptr(FuUsbDevice) device =
+ fu_usb_device_new(fu_backend_get_context(backend), usb_device);
fu_backend_device_added(backend, FU_DEVICE(device));
}
static void
fu_usb_backend_device_removed_cb(GUsbContext *ctx, GUsbDevice *usb_device, FuBackend *backend)
{
- FuUsbBackend *self = FU_USB_BACKEND(backend);
- FuDevice *device_tmp;
-
- /* find the device we enumerated */
- device_tmp =
- fu_backend_lookup_by_id(FU_BACKEND(self), g_usb_device_get_platform_id(usb_device));
- if (device_tmp != NULL) {
- if (fu_device_has_flag(device_tmp, FWUPD_DEVICE_FLAG_EMULATED)) {
- g_debug("ignoring removal of emulated device %s",
- fu_device_get_id(device_tmp));
- return;
- }
- fu_backend_device_removed(backend, device_tmp);
- }
+ FuDevice *device =
+ fu_backend_lookup_by_id(backend, g_usb_device_get_platform_id(usb_device));
+ if (device != NULL)
+ fu_backend_device_removed(backend, device);
}
static void
@@ -107,13 +73,11 @@ fu_usb_backend_context_finalized_cb(gpointer data, GObject *where_the_object_was
static void
fu_usb_backend_context_flags_check(FuUsbBackend *self)
{
-#if G_USB_CHECK_VERSION(0, 4, 1)
+#if G_USB_CHECK_VERSION(0, 4, 5)
FuContext *ctx = fu_backend_get_context(FU_BACKEND(self));
GUsbContextFlags usb_flags = G_USB_CONTEXT_FLAGS_NONE;
-#if G_USB_CHECK_VERSION(0, 4, 4)
if (g_getenv("FWUPD_BACKEND_VERBOSE") != NULL)
usb_flags |= G_USB_CONTEXT_FLAGS_DEBUG;
-#endif
if (fu_context_has_flag(ctx, FU_CONTEXT_FLAG_SAVE_EVENTS)) {
g_debug("saving FuUsbBackend events");
usb_flags |= G_USB_CONTEXT_FLAGS_SAVE_EVENTS;
@@ -213,7 +177,7 @@ fu_usb_backend_load(FuBackend *backend,
FuBackendLoadFlags flags,
GError **error)
{
-#if G_USB_CHECK_VERSION(0, 4, 1)
+#if G_USB_CHECK_VERSION(0, 4, 5)
FuUsbBackend *self = FU_USB_BACKEND(backend);
return g_usb_context_load_with_tag(self->usb_ctx, json_object, tag, error);
#else
@@ -232,9 +196,30 @@ fu_usb_backend_save(FuBackend *backend,
FuBackendSaveFlags flags,
GError **error)
{
-#if G_USB_CHECK_VERSION(0, 4, 1)
+#if G_USB_CHECK_VERSION(0, 4, 5)
FuUsbBackend *self = FU_USB_BACKEND(backend);
- return g_usb_context_save_with_tag(self->usb_ctx, json_builder, tag, error);
+ guint usb_events_cnt = 0;
+ g_autoptr(GPtrArray) devices = g_usb_context_get_devices(self->usb_ctx);
+
+ for (guint i = 0; i < devices->len; i++) {
+ GUsbDevice *usb_device = g_ptr_array_index(devices, i);
+ g_autoptr(GPtrArray) usb_events = g_usb_device_get_events(usb_device);
+ if (usb_events->len > 0 || g_usb_device_has_tag(usb_device, tag)) {
+ g_debug("%u USB events to save for %s",
+ usb_events->len,
+ g_usb_device_get_platform_id(usb_device));
+ }
+ usb_events_cnt += usb_events->len;
+ }
+ if (usb_events_cnt == 0)
+ return TRUE;
+ if (!g_usb_context_save_with_tag(self->usb_ctx, json_builder, tag, error))
+ return FALSE;
+ for (guint i = 0; i < devices->len; i++) {
+ GUsbDevice *usb_device = g_ptr_array_index(devices, i);
+ g_usb_device_clear_events(usb_device);
+ }
+ return TRUE;
#else
g_set_error_literal(error,
G_IO_ERROR,
diff --git a/src/fu-util-common.c b/src/fu-util-common.c
index 106bf0ab0..0cdcd68ab 100644
--- a/src/fu-util-common.c
+++ b/src/fu-util-common.c
@@ -1316,6 +1316,10 @@ fu_util_device_flag_to_string(guint64 device_flag)
/* TRANSLATORS: this device is not actually real */
return _("Emulated");
}
+ if (device_flag == FWUPD_DEVICE_FLAG_EMULATION_TAG) {
+ /* TRANSLATORS: we're saving all USB events for emulation */
+ return _("Tagged for emulation");
+ }
if (device_flag == FWUPD_DEVICE_FLAG_SKIPS_RESTART) {
/* skip */
return NULL;
diff --git a/src/fu-util.c b/src/fu-util.c
index 65c09bcbe..06d36ce9a 100644
--- a/src/fu-util.c
+++ b/src/fu-util.c
@@ -689,6 +689,7 @@ typedef struct {
guint nr_missing;
JsonBuilder *builder;
const gchar *name;
+ gboolean use_emulation;
} FuUtilDeviceTestHelper;
static gboolean
@@ -814,12 +815,45 @@ fu_util_device_test_step(FuUtilPrivate *priv,
{
JsonArray *json_array;
const gchar *url;
+ const gchar *emulation_url = NULL;
const gchar *baseuri = g_getenv("FWUPD_DEVICE_TESTS_BASE_URI");
g_autofree gchar *filename = NULL;
g_autofree gchar *url_safe = NULL;
g_autoptr(GBytes) fw = NULL;
g_autoptr(GError) error_local = NULL;
+ /* send this data to the daemon */
+ if (helper->use_emulation) {
+ g_autofree gchar *emulation_filename = NULL;
+ g_autofree gchar *emulation_safe = NULL;
+ g_autoptr(GBytes) emulation_data = NULL;
+
+ /* just ignore anything without emulation data */
+ if (!json_object_has_member(json_obj, "emulation-url"))
+ return TRUE;
+
+ emulation_url = json_object_get_string_member(json_obj, "emulation-url");
+ if (baseuri != NULL) {
+ g_autofree gchar *basename = g_path_get_basename(emulation_url);
+ emulation_safe = g_build_filename(baseuri, basename, NULL);
+ } else {
+ emulation_safe = g_strdup(emulation_url);
+ }
+ emulation_filename = fu_util_download_if_required(priv, emulation_safe, error);
+ if (emulation_filename == NULL) {
+ g_prefix_error(error, "failed to download %s: ", emulation_safe);
+ return FALSE;
+ }
+ emulation_data = fu_bytes_get_contents(emulation_filename, error);
+ if (emulation_data == NULL)
+ return FALSE;
+ if (!fwupd_client_emulation_load(priv->client,
+ emulation_data,
+ priv->cancellable,
+ error))
+ return FALSE;
+ }
+
/* download file if required */
if (!json_object_has_member(json_obj, "url")) {
g_set_error_literal(error,
@@ -1028,13 +1062,13 @@ fu_util_quit(FuUtilPrivate *priv, gchar **values, GError **error)
}
static gboolean
-fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error)
+fu_util_device_test_full(FuUtilPrivate *priv,
+ gchar **values,
+ FuUtilDeviceTestHelper *helper,
+ GError **error)
{
g_autoptr(JsonBuilder) builder = json_builder_new();
- FuUtilDeviceTestHelper helper = {.nr_failed = 0,
- .nr_success = 0,
- .builder = builder,
- .name = "Unknown"};
+ helper->builder = builder;
/* required for interactive devices */
priv->current_operation = FU_UTIL_OPERATION_UPDATE;
@@ -1056,7 +1090,7 @@ fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error)
json_builder_begin_array(builder);
for (guint i = 0; values[i] != NULL; i++) {
json_builder_begin_object(builder);
- if (!fu_util_device_test_filename(priv, &helper, values[i], error))
+ if (!fu_util_device_test_filename(priv, helper, values[i], error))
return FALSE;
json_builder_end_object(builder);
}
@@ -1070,23 +1104,23 @@ fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error)
}
/* we need all to pass for a zero return code */
- if (helper.nr_failed > 0) {
+ if (helper->nr_failed > 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"Some of the tests failed");
return FALSE;
}
- if (helper.nr_missing > 0) {
+ if (helper->nr_missing > 0) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"%u devices required for %u tests were not found",
- helper.nr_missing,
+ helper->nr_missing,
g_strv_length(values));
return FALSE;
}
- if (helper.nr_success == 0) {
+ if (helper->nr_success == 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
@@ -1102,6 +1136,20 @@ fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error)
return TRUE;
}
+static gboolean
+fu_util_device_emulate(FuUtilPrivate *priv, gchar **values, GError **error)
+{
+ FuUtilDeviceTestHelper helper = {.use_emulation = TRUE};
+ return fu_util_device_test_full(priv, values, &helper, error);
+}
+
+static gboolean
+fu_util_device_test(FuUtilPrivate *priv, gchar **values, GError **error)
+{
+ FuUtilDeviceTestHelper helper = {.use_emulation = FALSE};
+ return fu_util_device_test_full(priv, values, &helper, error);
+}
+
static gboolean
fu_util_download(FuUtilPrivate *priv, gchar **values, GError **error)
{
@@ -4025,6 +4073,89 @@ fu_util_get_bios_setting(FuUtilPrivate *priv, gchar **values, GError **error)
return TRUE;
}
+static gboolean
+fu_util_emulation_tag(FuUtilPrivate *priv, gchar **values, GError **error)
+{
+ g_autoptr(FwupdDevice) dev = NULL;
+ g_autofree gchar *cmd = g_strdup_printf("%s emulation-save", g_get_prgname());
+
+ /* set the flag */
+ dev = fu_util_get_device_or_prompt(priv, values, error);
+ if (dev == NULL)
+ return FALSE;
+ if (!fwupd_client_modify_device(priv->client,
+ fwupd_device_get_id(dev),
+ "Flags",
+ "emulation-tag",
+ priv->cancellable,
+ error))
+ return FALSE;
+
+ /* TRANSLATORS: these are instructions on how to generate the data, and the %1 is a command
+ * like 'fwupdmgr emulation-save' */
+ g_print(_("Now unplug and replug the device, install the firmware, and then run %s"), cmd);
+ g_print("\n");
+ return TRUE;
+}
+
+static gboolean
+fu_util_emulation_untag(FuUtilPrivate *priv, gchar **values, GError **error)
+{
+ g_autoptr(FwupdDevice) dev = NULL;
+
+ /* set the flag */
+ priv->filter_include |= FWUPD_DEVICE_FLAG_EMULATION_TAG;
+ dev = fu_util_get_device_or_prompt(priv, values, error);
+ if (dev == NULL)
+ return FALSE;
+ return fwupd_client_modify_device(priv->client,
+ fwupd_device_get_id(dev),
+ "Flags",
+ "~emulation-tag",
+ priv->cancellable,
+ error);
+}
+
+static gboolean
+fu_util_emulation_save(FuUtilPrivate *priv, gchar **values, GError **error)
+{
+ g_autoptr(GBytes) data = NULL;
+
+ /* check args */
+ if (g_strv_length(values) != 1) {
+ g_set_error_literal(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_INVALID_ARGS,
+ "Invalid arguments, expected FILENAME");
+ return FALSE;
+ }
+
+ /* save */
+ data = fwupd_client_emulation_save(priv->client, priv->cancellable, error);
+ if (data == NULL)
+ return FALSE;
+ return fu_bytes_set_contents(values[0], data, error);
+}
+
+static gboolean
+fu_util_emulation_load(FuUtilPrivate *priv, gchar **values, GError **error)
+{
+ g_autoptr(GBytes) data = NULL;
+
+ /* check args */
+ if (g_strv_length(values) != 1) {
+ g_set_error_literal(error,
+ FWUPD_ERROR,
+ FWUPD_ERROR_INVALID_ARGS,
+ "Invalid arguments, expected FILENAME");
+ return FALSE;
+ }
+ data = fu_bytes_get_contents(values[0], error);
+ if (data == NULL)
+ return FALSE;
+ return fwupd_client_emulation_load(priv->client, data, priv->cancellable, error);
+}
+
static gboolean
fu_util_version(FuUtilPrivate *priv, GError **error)
{
@@ -4555,6 +4686,13 @@ main(int argc, char *argv[])
/* TRANSLATORS: command description */
_("Test a device using a JSON manifest"),
fu_util_device_test);
+ fu_util_cmd_array_add(cmd_array,
+ "device-emulate",
+ /* TRANSLATORS: command argument: uppercase, spaces->dashes */
+ _("[FILENAME1] [FILENAME2]"),
+ /* TRANSLATORS: command description */
+ _("Emulate a device using a JSON manifest"),
+ fu_util_device_emulate);
fu_util_cmd_array_add(cmd_array,
"inhibit",
/* TRANSLATORS: command argument: uppercase, spaces->dashes */
@@ -4590,6 +4728,34 @@ main(int argc, char *argv[])
/* TRANSLATORS: command description */
_("Sets one or more BIOS settings"),
fu_util_set_bios_setting);
+ fu_util_cmd_array_add(cmd_array,
+ "emulation-load",
+ /* TRANSLATORS: command argument: uppercase, spaces->dashes */
+ _("FILENAME"),
+ /* TRANSLATORS: command description */
+ _("Load device emulation data"),
+ fu_util_emulation_load);
+ fu_util_cmd_array_add(cmd_array,
+ "emulation-save",
+ /* TRANSLATORS: command argument: uppercase, spaces->dashes */
+ _("FILENAME"),
+ /* TRANSLATORS: command description */
+ _("Save device emulation data"),
+ fu_util_emulation_save);
+ fu_util_cmd_array_add(cmd_array,
+ "emulation-tag",
+ /* TRANSLATORS: command argument: uppercase, spaces->dashes */
+ _("[DEVICE-ID|GUID]"),
+ /* TRANSLATORS: command description */
+ _("Adds devices to watch for future emulation"),
+ fu_util_emulation_tag);
+ fu_util_cmd_array_add(cmd_array,
+ "emulation-untag",
+ /* TRANSLATORS: command argument: uppercase, spaces->dashes */
+ _("[DEVICE-ID|GUID]"),
+ /* TRANSLATORS: command description */
+ _("Removes devices to watch for future emulation"),
+ fu_util_emulation_untag);
/* do stuff on ctrl+c */
priv->cancellable = g_cancellable_new();
diff --git a/src/org.freedesktop.fwupd.xml b/src/org.freedesktop.fwupd.xml
index f9008260e..33aced03a 100644
--- a/src/org.freedesktop.fwupd.xml
+++ b/src/org.freedesktop.fwupd.xml
@@ -957,6 +957,48 @@
+
+
+
+
+
+ Load emulation data.
+
+
+
+
+
+
+
+ JSON data of each phase packaged as a ZIP archive
+ (e.g. setup.json, install.json, reload.json).
+
+
+
+
+
+
+
+
+
+
+
+ Return emulation data.
+
+
+
+
+
+
+
+ JSON data of each phase packaged as a compressed ZIP archive
+ (e.g. setup.json, install.json, reload.json).
+
+
+
+
+
+
diff --git a/subprojects/gusb.wrap b/subprojects/gusb.wrap
index 10558da8c..4b2d38aa7 100644
--- a/subprojects/gusb.wrap
+++ b/subprojects/gusb.wrap
@@ -1,4 +1,4 @@
[wrap-git]
directory = gusb
url = https://github.com/hughsie/libgusb.git
-revision = 0.4.0
+revision = 0.4.5